mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-09-20 06:18:01 -04:00
Merge PR #6: support SysV-only ELF hash tables
This commit is contained in:
+7
-10
@@ -15,26 +15,23 @@ pub use layout::{
|
|||||||
/// ELF machine identifier for AArch64.
|
/// ELF machine identifier for AArch64.
|
||||||
pub const AARCH64_MACHINE: u16 = EM_AARCH64;
|
pub const AARCH64_MACHINE: u16 = EM_AARCH64;
|
||||||
|
|
||||||
/// Dynamic sections required by the restored AArch64 loader image.
|
/// Dynamic sections required by every restored AArch64 loader image.
|
||||||
pub const DYNAMIC_SECTION_NAMES: [&str; 8] = [
|
pub const DYNAMIC_SECTION_NAMES: [&str; 7] = [
|
||||||
".dynsym",
|
".dynsym",
|
||||||
".gnu.version",
|
".gnu.version",
|
||||||
".gnu.version_r",
|
".gnu.version_r",
|
||||||
".gnu.hash",
|
|
||||||
".dynstr",
|
".dynstr",
|
||||||
".rela.dyn",
|
".rela.dyn",
|
||||||
".rela.plt",
|
".rela.plt",
|
||||||
".dynamic",
|
".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.
|
/// Dynamic sections needed to identify a protected image before extraction.
|
||||||
pub const PROBE_SECTION_NAMES: [&str; 5] = [
|
/// Hash tables are checked separately because either GNU or SysV hashing is valid.
|
||||||
".dynsym",
|
pub const PROBE_SECTION_NAMES: [&str; 4] = [".dynsym", ".dynstr", ".gnu.version", ".gnu.version_r"];
|
||||||
".dynstr",
|
|
||||||
".gnu.hash",
|
|
||||||
".gnu.version",
|
|
||||||
".gnu.version_r",
|
|
||||||
];
|
|
||||||
|
|
||||||
/// ELF64 dynamic table record sizes.
|
/// ELF64 dynamic table record sizes.
|
||||||
pub const ELF64_SYMBOL_SIZE: usize = 0x18;
|
pub const ELF64_SYMBOL_SIZE: usize = 0x18;
|
||||||
|
|||||||
@@ -70,6 +70,16 @@ pub(crate) fn inspect(
|
|||||||
return invalid(format!("protected ELF lacks required section {wanted}"));
|
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_index, section) = matches[0];
|
||||||
let section_offset = usize::try_from(section.sh_offset)
|
let section_offset = usize::try_from(section.sh_offset)
|
||||||
.map_err(|_| Error::Invalid("SHT_LOUSER offset exceeds usize".to_owned()))?;
|
.map_err(|_| Error::Invalid("SHT_LOUSER offset exceeds usize".to_owned()))?;
|
||||||
|
|||||||
@@ -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>> {
|
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 {
|
for required in senbei_elf::DYNAMIC_SECTION_NAMES {
|
||||||
let indices = names
|
let indices = names
|
||||||
.iter()
|
.iter()
|
||||||
@@ -772,17 +783,19 @@ fn required_section_indices(names: &[String]) -> Result<HashMap<&'static str, us
|
|||||||
_ => return invalid(format!("ELF contains duplicate section {required}")),
|
_ => return invalid(format!("ELF contains duplicate section {required}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let sysv_hash = names
|
for hash_table in senbei_elf::SYMBOL_HASH_SECTION_NAMES {
|
||||||
|
let indices = names
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(index, name)| (name == ".hash").then_some(index))
|
.filter_map(|(index, name)| (name == hash_table).then_some(index))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
match sysv_hash.as_slice() {
|
match indices.as_slice() {
|
||||||
[index] => {
|
[index] => {
|
||||||
result.insert(".hash", *index);
|
result.insert(hash_table, *index);
|
||||||
}
|
}
|
||||||
[] => {}
|
[] => {}
|
||||||
_ => return invalid("ELF contains duplicate section .hash"),
|
_ => return invalid(format!("ELF contains duplicate section {hash_table}")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@@ -862,7 +875,8 @@ fn metadata_mapping_length(
|
|||||||
usize_from_u64(rela_plt.size / ELF64_RELA_SIZE as u64, ".rela.plt count")?
|
usize_from_u64(rela_plt.size / ELF64_RELA_SIZE as u64, ".rela.plt count")?
|
||||||
.checked_add(auxiliary.relocation2_count as usize)
|
.checked_add(auxiliary.relocation2_count as usize)
|
||||||
.ok_or_else(|| Error::Invalid("merged .rela.plt count overflow".to_owned()))?;
|
.ok_or_else(|| Error::Invalid("merged .rela.plt count overflow".to_owned()))?;
|
||||||
let gnu_hash_size = 28_usize
|
let gnu_hash_size = if indices.contains_key(".gnu.hash") {
|
||||||
|
28_usize
|
||||||
.checked_add(
|
.checked_add(
|
||||||
new_symbol_count
|
new_symbol_count
|
||||||
.checked_sub(1)
|
.checked_sub(1)
|
||||||
@@ -872,7 +886,10 @@ fn metadata_mapping_length(
|
|||||||
.checked_mul(4)
|
.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()))?,
|
||||||
)
|
)
|
||||||
.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(|| {
|
let sysv_hash_size = indices.contains_key(".hash").then(|| {
|
||||||
new_symbol_count
|
new_symbol_count
|
||||||
.checked_mul(2)
|
.checked_mul(2)
|
||||||
@@ -1031,7 +1048,10 @@ fn materialize_static_elf_tables(
|
|||||||
.contains_key(".hash")
|
.contains_key(".hash")
|
||||||
.then(|| build_sysv_hash(&merged_names))
|
.then(|| build_sysv_hash(&merged_names))
|
||||||
.transpose()?;
|
.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_symbol_count = merged_names.len();
|
||||||
let new_dynstr_size = merged_strings.len();
|
let new_dynstr_size = merged_strings.len();
|
||||||
|
|
||||||
@@ -1102,6 +1122,11 @@ fn materialize_static_elf_tables(
|
|||||||
let rela_dyn_count = merged_rela_dyn.len() / ELF64_RELA_SIZE;
|
let rela_dyn_count = merged_rela_dyn.len() / ELF64_RELA_SIZE;
|
||||||
let rela_plt_count = merged_rela_plt.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![
|
let mut tables = vec![
|
||||||
TablePayload {
|
TablePayload {
|
||||||
name: ".dynsym",
|
name: ".dynsym",
|
||||||
@@ -1118,12 +1143,10 @@ fn materialize_static_elf_tables(
|
|||||||
alignment: 4,
|
alignment: 4,
|
||||||
data: version_requirements,
|
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 {
|
if let Some(data) = sysv_hash {
|
||||||
tables.push(TablePayload {
|
tables.push(TablePayload {
|
||||||
name: ".hash",
|
name: ".hash",
|
||||||
@@ -1231,10 +1254,12 @@ fn materialize_static_elf_tables(
|
|||||||
(DT_RELASZ, (rela_dyn_count * ELF64_RELA_SIZE) as u64),
|
(DT_RELASZ, (rela_dyn_count * ELF64_RELA_SIZE) as u64),
|
||||||
(DT_STRSZ, new_dynstr_size as u64),
|
(DT_STRSZ, new_dynstr_size as u64),
|
||||||
(DT_JMPREL, section_address(".rela.plt")),
|
(DT_JMPREL, section_address(".rela.plt")),
|
||||||
(DT_GNU_HASH, section_address(".gnu.hash")),
|
|
||||||
(DT_VERSYM, section_address(".gnu.version")),
|
(DT_VERSYM, section_address(".gnu.version")),
|
||||||
(DT_VERNEED, section_address(".gnu.version_r")),
|
(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)? {
|
if dynamic_contains_tag(output, dynamic, DT_RELACOUNT)? {
|
||||||
dynamic_values.insert(DT_RELACOUNT, relative_count as u64);
|
dynamic_values.insert(DT_RELACOUNT, relative_count as u64);
|
||||||
}
|
}
|
||||||
@@ -1727,4 +1752,34 @@ mod tests {
|
|||||||
let error = AuxiliaryElfImage::parse(&auxiliary_image(0)).expect_err("missing null symbol");
|
let error = AuxiliaryElfImage::parse(&auxiliary_image(0)).expect_err("missing null symbol");
|
||||||
assert!(error.to_string().contains("no null entry"));
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user