Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
395 changes: 362 additions & 33 deletions senbei-elf/src/layout.rs

Large diffs are not rendered by default.

17 changes: 7 additions & 10 deletions senbei-elf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,23 @@ pub use layout::{
/// ELF machine identifier for AArch64.
pub const AARCH64_MACHINE: u16 = EM_AARCH64;

/// Dynamic sections required by the restored AArch64 loader image.
pub const DYNAMIC_SECTION_NAMES: [&str; 8] = [
/// Dynamic sections required by every restored AArch64 loader image.
pub const DYNAMIC_SECTION_NAMES: [&str; 7] = [
".dynsym",
".gnu.version",
".gnu.version_r",
".gnu.hash",
".dynstr",
".rela.dyn",
".rela.plt",
".dynamic",
];

/// Supported dynamic symbol-hash sections. At least one must be present.
pub const SYMBOL_HASH_SECTION_NAMES: [&str; 2] = [".gnu.hash", ".hash"];

/// Dynamic sections needed to identify a protected image before extraction.
pub const PROBE_SECTION_NAMES: [&str; 5] = [
".dynsym",
".dynstr",
".gnu.hash",
".gnu.version",
".gnu.version_r",
];
/// Hash tables are checked separately because either GNU or SysV hashing is valid.
pub const PROBE_SECTION_NAMES: [&str; 4] = [".dynsym", ".dynstr", ".gnu.version", ".gnu.version_r"];

/// ELF64 dynamic table record sizes.
pub const ELF64_SYMBOL_SIZE: usize = 0x18;
Expand Down
10 changes: 10 additions & 0 deletions senbei-engine/src/android/extract/stage1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ pub(crate) fn inspect(
return invalid(format!("protected ELF lacks required section {wanted}"));
}
}
let has_symbol_hash = senbei_elf::SYMBOL_HASH_SECTION_NAMES.iter().any(|wanted| {
elf.section_headers.iter().any(|section| {
elf.shdr_strtab
.get_at(section.sh_name)
.is_some_and(|name| name == *wanted)
})
});
if !has_symbol_hash {
return invalid("protected ELF lacks a .gnu.hash or .hash symbol-hash section");
}
let (section_index, section) = matches[0];
let section_offset = usize::try_from(section.sh_offset)
.map_err(|_| Error::Invalid("SHT_LOUSER offset exceeds usize".to_owned()))?;
Expand Down
126 changes: 94 additions & 32 deletions senbei-engine/src/android/restore/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,18 @@ fn dynamic_contains_tag(output: &[u8], dynamic: SectionHeader, wanted: u64) -> R
}

fn required_section_indices(names: &[String]) -> Result<HashMap<&'static str, usize>> {
let mut result = HashMap::with_capacity(senbei_elf::DYNAMIC_SECTION_NAMES.len());
let present = senbei_elf::SYMBOL_HASH_SECTION_NAMES
.iter()
.filter(|name| names.iter().any(|section| section == *name))
.count();
if present == 0 {
return invalid(
"ELF must carry at least one of the .gnu.hash / .hash symbol-hash sections",
);
}
let mut result = HashMap::with_capacity(
senbei_elf::DYNAMIC_SECTION_NAMES.len() + senbei_elf::SYMBOL_HASH_SECTION_NAMES.len(),
);
for required in senbei_elf::DYNAMIC_SECTION_NAMES {
let indices = names
.iter()
Expand All @@ -772,17 +783,19 @@ fn required_section_indices(names: &[String]) -> Result<HashMap<&'static str, us
_ => return invalid(format!("ELF contains duplicate section {required}")),
}
}
let sysv_hash = names
.iter()
.enumerate()
.filter_map(|(index, name)| (name == ".hash").then_some(index))
.collect::<Vec<_>>();
match sysv_hash.as_slice() {
[index] => {
result.insert(".hash", *index);
for hash_table in senbei_elf::SYMBOL_HASH_SECTION_NAMES {
let indices = names
.iter()
.enumerate()
.filter_map(|(index, name)| (name == hash_table).then_some(index))
.collect::<Vec<_>>();
match indices.as_slice() {
[index] => {
result.insert(hash_table, *index);
}
[] => {}
_ => return invalid(format!("ELF contains duplicate section {hash_table}")),
}
[] => {}
_ => return invalid("ELF contains duplicate section .hash"),
}
Ok(result)
}
Expand Down Expand Up @@ -862,17 +875,21 @@ fn metadata_mapping_length(
usize_from_u64(rela_plt.size / ELF64_RELA_SIZE as u64, ".rela.plt count")?
.checked_add(auxiliary.relocation2_count as usize)
.ok_or_else(|| Error::Invalid("merged .rela.plt count overflow".to_owned()))?;
let gnu_hash_size = 28_usize
.checked_add(
new_symbol_count
.checked_sub(1)
.ok_or_else(|| {
Error::Invalid("dynamic symbol table is unexpectedly empty".to_owned())
})?
.checked_mul(4)
.ok_or_else(|| Error::Invalid("GNU hash size overflow".to_owned()))?,
)
.ok_or_else(|| Error::Invalid("GNU hash size overflow".to_owned()))?;
let gnu_hash_size = if indices.contains_key(".gnu.hash") {
28_usize
.checked_add(
new_symbol_count
.checked_sub(1)
.ok_or_else(|| {
Error::Invalid("dynamic symbol table is unexpectedly empty".to_owned())
})?
.checked_mul(4)
.ok_or_else(|| Error::Invalid("GNU hash size overflow".to_owned()))?,
)
.ok_or_else(|| Error::Invalid("GNU hash size overflow".to_owned()))?
} else {
0
};
let sysv_hash_size = indices.contains_key(".hash").then(|| {
new_symbol_count
.checked_mul(2)
Expand All @@ -884,7 +901,9 @@ fn metadata_mapping_length(
Some(size) => size?,
None => 0,
};
let mut cursor = 0_u64;
let program_header_reservation = u64::try_from(layout.additional_program_header_reservation()?)
.map_err(|_| Error::Invalid("program-header reservation exceeds u64".to_owned()))?;
let mut cursor = program_header_reservation;
for (size, alignment) in [
(
new_symbol_count
Expand Down Expand Up @@ -1029,7 +1048,10 @@ fn materialize_static_elf_tables(
.contains_key(".hash")
.then(|| build_sysv_hash(&merged_names))
.transpose()?;
let gnu_hash_table = build_gnu_hash(&merged_names)?;
let gnu_hash_table = indices
.contains_key(".gnu.hash")
.then(|| build_gnu_hash(&merged_names))
.transpose()?;
let new_symbol_count = merged_names.len();
let new_dynstr_size = merged_strings.len();

Expand Down Expand Up @@ -1100,6 +1122,11 @@ fn materialize_static_elf_tables(
let rela_dyn_count = merged_rela_dyn.len() / ELF64_RELA_SIZE;
let rela_plt_count = merged_rela_plt.len() / ELF64_RELA_SIZE;

let gnu_hash = gnu_hash_table.map(|data| TablePayload {
name: ".gnu.hash",
alignment: 8,
data,
});
let mut tables = vec![
TablePayload {
name: ".dynsym",
Expand All @@ -1116,12 +1143,10 @@ fn materialize_static_elf_tables(
alignment: 4,
data: version_requirements,
},
TablePayload {
name: ".gnu.hash",
alignment: 8,
data: gnu_hash_table,
},
];
if let Some(payload) = gnu_hash {
tables.push(payload);
}
if let Some(data) = sysv_hash {
tables.push(TablePayload {
name: ".hash",
Expand Down Expand Up @@ -1152,8 +1177,12 @@ fn materialize_static_elf_tables(
let mut capacity_end = metadata_capacity_end(layout, &indices, metadata_start)?;
if cursor > capacity_end {
let alignment = layout.load_alignment()?;
let program_header_reservation = layout.additional_program_header_reservation()?;
let extension_start = align_up(layout.private_section()?.offset, alignment)?;
let extension_end = table_end(&tables, extension_start)?;
let extension_metadata_start = extension_start
.checked_add(program_header_reservation as u64)
.ok_or_else(|| Error::Invalid("dynamic-table extension start overflow".to_owned()))?;
let extension_end = table_end(&tables, extension_metadata_start)?;
let extension_size = extension_end
.checked_sub(extension_start)
.ok_or_else(|| Error::Invalid("dynamic-table extension underflow".to_owned()))?;
Expand All @@ -1168,8 +1197,9 @@ fn materialize_static_elf_tables(
flags: PF_R,
alignment,
},
program_header_reservation,
)?;
metadata_start = extension_start;
metadata_start = extension_metadata_start;
(placements, cursor) = table_placements(&tables, metadata_start)?;
capacity_end = cursor;
}
Expand Down Expand Up @@ -1224,10 +1254,12 @@ fn materialize_static_elf_tables(
(DT_RELASZ, (rela_dyn_count * ELF64_RELA_SIZE) as u64),
(DT_STRSZ, new_dynstr_size as u64),
(DT_JMPREL, section_address(".rela.plt")),
(DT_GNU_HASH, section_address(".gnu.hash")),
(DT_VERSYM, section_address(".gnu.version")),
(DT_VERNEED, section_address(".gnu.version_r")),
]);
if indices.contains_key(".gnu.hash") {
dynamic_values.insert(DT_GNU_HASH, section_address(".gnu.hash"));
}
if dynamic_contains_tag(output, dynamic, DT_RELACOUNT)? {
dynamic_values.insert(DT_RELACOUNT, relative_count as u64);
}
Expand Down Expand Up @@ -1720,4 +1752,34 @@ mod tests {
let error = AuxiliaryElfImage::parse(&auxiliary_image(0)).expect_err("missing null symbol");
assert!(error.to_string().contains("no null entry"));
}

fn dynamic_section_names(hash_sections: &[&str]) -> Vec<String> {
senbei_elf::DYNAMIC_SECTION_NAMES
.into_iter()
.chain(hash_sections.iter().copied())
.map(str::to_owned)
.collect()
}

#[test]
fn required_sections_accept_each_hash_layout() {
for hashes in [
[".gnu.hash"].as_slice(),
[".hash"].as_slice(),
[".gnu.hash", ".hash"].as_slice(),
] {
let indices = required_section_indices(&dynamic_section_names(hashes))
.expect("valid hash layout");
for hash in hashes {
assert!(indices.contains_key(hash));
}
}
}

#[test]
fn required_sections_reject_missing_symbol_hash() {
let error =
required_section_indices(&dynamic_section_names(&[])).expect_err("missing hash");
assert!(error.to_string().contains("at least one"));
}
}
Loading
Loading