use crate::{Error, Result, invalid}; pub const SHT_NOBITS: u32 = 8; pub const SHT_STRTAB: u32 = 3; pub const SHT_LOUSER: u32 = 0x8000_0000; pub const SHF_ALLOC: u64 = 2; const PT_LOAD: u32 = 1; const PT_NOTE: u32 = 4; const PT_PHDR: u32 = 6; pub const PF_R: u32 = 4; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LoadSegment { pub offset: u64, pub virtual_address: u64, pub file_size: u64, pub memory_size: u64, pub flags: u32, pub alignment: u64, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SectionHeader { pub name: u32, pub section_type: u32, pub flags: u64, pub address: u64, pub offset: u64, pub size: u64, pub link: u32, pub info: u32, pub alignment: u64, pub entry_size: u64, } impl SectionHeader { pub const SIZE: usize = 0x40; fn parse(data: &[u8], offset: usize) -> Result { Ok(Self { name: read_u32(data, offset)?, section_type: read_u32(data, offset + 4)?, flags: read_u64(data, offset + 8)?, address: read_u64(data, offset + 0x10)?, offset: read_u64(data, offset + 0x18)?, size: read_u64(data, offset + 0x20)?, link: read_u32(data, offset + 0x28)?, info: read_u32(data, offset + 0x2c)?, alignment: read_u64(data, offset + 0x30)?, entry_size: read_u64(data, offset + 0x38)?, }) } pub fn encode(self) -> [u8; Self::SIZE] { let mut output = [0_u8; Self::SIZE]; output[0..4].copy_from_slice(&self.name.to_le_bytes()); output[4..8].copy_from_slice(&self.section_type.to_le_bytes()); output[8..0x10].copy_from_slice(&self.flags.to_le_bytes()); output[0x10..0x18].copy_from_slice(&self.address.to_le_bytes()); output[0x18..0x20].copy_from_slice(&self.offset.to_le_bytes()); output[0x20..0x28].copy_from_slice(&self.size.to_le_bytes()); output[0x28..0x2c].copy_from_slice(&self.link.to_le_bytes()); output[0x2c..0x30].copy_from_slice(&self.info.to_le_bytes()); output[0x30..0x38].copy_from_slice(&self.alignment.to_le_bytes()); output[0x38..0x40].copy_from_slice(&self.entry_size.to_le_bytes()); output } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ElfLayout { pub entrypoint: u64, pub program_header_offset: usize, pub program_header_size: usize, pub program_header_count: usize, pub program_headers: Vec, pub section_headers: Vec, pub section_name_index: usize, pub private_section_index: usize, } impl ElfLayout { pub fn parse(data: &[u8], require_private: bool) -> Result { let ident = slice(data, 0, 6)?; if ident[..4] != *b"\x7fELF" || ident[4] != 2 || ident[5] != 1 { return invalid("input is not a little-endian ELF64 file"); } if read_u16(data, 0x12)? != crate::AARCH64_MACHINE { return invalid("input is not an AArch64 ELF"); } let entrypoint = read_u64(data, 0x18)?; let program_header_offset = usize_from_u64(read_u64(data, 0x20)?, "program header offset")?; let section_header_offset = usize_from_u64(read_u64(data, 0x28)?, "section header offset")?; let program_header_size = usize::from(read_u16(data, 0x36)?); let program_header_count = usize::from(read_u16(data, 0x38)?); let section_header_size = usize::from(read_u16(data, 0x3a)?); let section_header_count = usize::from(read_u16(data, 0x3c)?); let section_name_index = usize::from(read_u16(data, 0x3e)?); if program_header_size != 0x38 || section_header_size != SectionHeader::SIZE { return invalid("unexpected ELF program/section header size"); } let mut program_headers = Vec::new(); for index in 0..program_header_count { let offset = checked_index(program_header_offset, index, program_header_size)?; if read_u32(data, offset)? != PT_LOAD { continue; } let segment = LoadSegment { flags: read_u32(data, offset + 4)?, offset: read_u64(data, offset + 8)?, virtual_address: read_u64(data, offset + 0x10)?, file_size: read_u64(data, offset + 0x20)?, memory_size: read_u64(data, offset + 0x28)?, alignment: read_u64(data, offset + 0x30)?, }; let file_end = segment .offset .checked_add(segment.file_size) .ok_or_else(|| Error::Invalid(format!("PT_LOAD {index} file range overflow")))?; if file_end > data.len() as u64 { return invalid(format!("PT_LOAD {index} exceeds input file")); } program_headers.push(segment); } if program_headers.is_empty() { return invalid("input ELF contains no PT_LOAD segments"); } let mut section_headers = Vec::with_capacity(section_header_count); for index in 0..section_header_count { let offset = checked_index(section_header_offset, index, section_header_size)?; section_headers.push(SectionHeader::parse(data, offset)?); } if section_name_index >= section_headers.len() { return invalid("ELF section-name index is out of range"); } let private = section_headers .iter() .enumerate() .filter_map(|(index, section)| (section.section_type == SHT_LOUSER).then_some(index)) .collect::>(); let private_section_index = match private.as_slice() { [index] => *index, [] if !require_private => usize::MAX, _ => { return invalid(format!( "expected {} SHT_LOUSER section, found {}", if require_private { "one" } else { "at most one" }, private.len() )); } }; let layout = Self { entrypoint, program_header_offset, program_header_size, program_header_count, program_headers, section_headers, section_name_index, private_section_index, }; // Section roles are resolved from the ELF's own string table. Validate // it at the format boundary so callers cannot silently continue with // fabricated or lossy section names. layout.section_names(data)?; Ok(layout) } pub fn private_section(&self) -> Result { self.section_headers .get(self.private_section_index) .copied() .ok_or_else(|| Error::Invalid("ELF has no private section".to_owned())) } pub fn load_end(&self) -> Result { self.program_headers .iter() .map(|segment| { segment .virtual_address .checked_add(segment.memory_size) .ok_or_else(|| Error::Invalid("PT_LOAD memory end overflow".to_owned())) }) .collect::>>()? .into_iter() .max() .ok_or_else(|| Error::Invalid("ELF has no PT_LOAD memory range".to_owned())) } pub fn file_load_end(&self) -> Result { self.program_headers .iter() .map(|segment| { segment .offset .checked_add(segment.file_size) .ok_or_else(|| Error::Invalid("PT_LOAD file end overflow".to_owned())) }) .collect::>>()? .into_iter() .max() .ok_or_else(|| Error::Invalid("ELF has no PT_LOAD file range".to_owned())) } pub fn load_alignment(&self) -> Result { let alignment = self .program_headers .iter() .map(|segment| segment.alignment) .max() .ok_or_else(|| Error::Invalid("ELF has no PT_LOAD alignment".to_owned()))?; if alignment == 0 || !alignment.is_power_of_two() { return invalid(format!("invalid PT_LOAD alignment 0x{alignment:x}")); } Ok(alignment) } pub fn additional_program_header_reservation(&self) -> Result { if self.program_header_size != 0x38 { return invalid("unexpected ELF program header size"); } let new_count = self .program_header_count .checked_add(1) .ok_or_else(|| Error::Invalid("program header count overflow".to_owned()))?; let header_offset = checked_index( self.program_header_offset, self.program_header_count, self.program_header_size, )?; let header_end = header_offset .checked_add(self.program_header_size) .ok_or_else(|| Error::Invalid("new program header range overflow".to_owned()))?; let first_file_section = self .section_headers .iter() .filter(|section| section.section_type != SHT_NOBITS && section.size != 0) .map(|section| section.offset) .min(); if first_file_section.is_none_or(|offset| header_end as u64 <= offset) { return Ok(0); } new_count .checked_mul(self.program_header_size) .ok_or_else(|| Error::Invalid("relocated program header size overflow".to_owned())) } fn has_program_header_type(&self, data: &[u8], program_type: u32) -> Result { for index in 0..self.program_header_count { let offset = checked_index(self.program_header_offset, index, self.program_header_size)?; if read_u32(data, offset)? == program_type { return Ok(true); } } Ok(false) } fn reusable_note_program_header(&self, data: &[u8]) -> Result> { let mut note_count = 0_usize; let mut candidates = Vec::new(); for index in 0..self.program_header_count { let offset = checked_index(self.program_header_offset, index, self.program_header_size)?; if read_u32(data, offset)? != PT_NOTE { continue; } note_count += 1; let file_offset = read_u64(data, offset + 8)?; let file_size = read_u64(data, offset + 0x20)?; let file_end = file_offset .checked_add(file_size) .ok_or_else(|| Error::Invalid("PT_NOTE file range overflow".to_owned()))?; let contained = self.program_headers.iter().any(|segment| { segment .offset .checked_add(segment.file_size) .is_some_and(|segment_end| { segment.offset <= file_offset && file_end <= segment_end }) }); if contained { candidates.push((file_offset, index)); } } if note_count < 2 { return Ok(None); } Ok(candidates .into_iter() .max_by_key(|(file_offset, _)| *file_offset) .map(|(_, index)| index)) } pub fn append_load_segment( &self, output: &mut [u8], segment: LoadSegment, reserved_program_header_bytes: usize, ) -> Result { if self.program_header_size != 0x38 { return invalid("unexpected ELF program header size"); } if segment.file_size == 0 { return invalid("new PT_LOAD has no file contents"); } if segment.memory_size < segment.file_size { return invalid("new PT_LOAD memory size is smaller than file size"); } if segment.alignment == 0 || !segment.alignment.is_power_of_two() { return invalid(format!( "invalid new PT_LOAD alignment 0x{:x}", segment.alignment )); } if segment.offset % segment.alignment != segment.virtual_address % segment.alignment { return invalid("new PT_LOAD offset and address are misaligned"); } let segment_file_end = segment .offset .checked_add(segment.file_size) .ok_or_else(|| Error::Invalid("new PT_LOAD file range overflow".to_owned()))?; let segment_memory_end = segment .virtual_address .checked_add(segment.memory_size) .ok_or_else(|| Error::Invalid("new PT_LOAD memory range overflow".to_owned()))?; if segment_file_end > output.len() as u64 { return invalid("new PT_LOAD exceeds output mapping"); } for existing in &self.program_headers { let existing_file_end = existing .offset .checked_add(existing.file_size) .ok_or_else(|| Error::Invalid("PT_LOAD file range overflow".to_owned()))?; if segment.offset < existing_file_end && existing.offset < segment_file_end { return invalid("new PT_LOAD overlaps an existing file range"); } let existing_memory_end = existing .virtual_address .checked_add(existing.memory_size) .ok_or_else(|| Error::Invalid("PT_LOAD memory range overflow".to_owned()))?; if segment.virtual_address < existing_memory_end && existing.virtual_address < segment_memory_end { return invalid("new PT_LOAD overlaps an existing memory range"); } } let new_count = self .program_header_count .checked_add(1) .ok_or_else(|| Error::Invalid("program header count overflow".to_owned()))?; let new_count_u16 = u16::try_from(new_count) .map_err(|_| Error::Invalid("program header count exceeds u16".to_owned()))?; let reservation = self.additional_program_header_reservation()?; if reserved_program_header_bytes != reservation { return invalid(format!( "program-header reservation mismatch: expected 0x{reservation:x}, got 0x{reserved_program_header_bytes:x}" )); } let mut header = [0_u8; 0x38]; header[0..4].copy_from_slice(&PT_LOAD.to_le_bytes()); header[4..8].copy_from_slice(&segment.flags.to_le_bytes()); header[8..0x10].copy_from_slice(&segment.offset.to_le_bytes()); header[0x10..0x18].copy_from_slice(&segment.virtual_address.to_le_bytes()); header[0x18..0x20].copy_from_slice(&segment.virtual_address.to_le_bytes()); header[0x20..0x28].copy_from_slice(&segment.file_size.to_le_bytes()); header[0x28..0x30].copy_from_slice(&segment.memory_size.to_le_bytes()); header[0x30..0x38].copy_from_slice(&segment.alignment.to_le_bytes()); let old_table_size = self .program_header_count .checked_mul(self.program_header_size) .ok_or_else(|| Error::Invalid("program header table size overflow".to_owned()))?; let new_table_size = new_count .checked_mul(self.program_header_size) .ok_or_else(|| Error::Invalid("program header table size overflow".to_owned()))?; let mut updated_program_header_offset = self.program_header_offset; let mut updated_program_header_count = new_count; if reservation == 0 { let header_offset = checked_index( self.program_header_offset, self.program_header_count, self.program_header_size, )?; let header_end = header_offset .checked_add(self.program_header_size) .ok_or_else(|| Error::Invalid("new program header range overflow".to_owned()))?; output .get_mut(header_offset..header_end) .ok_or_else(|| Error::Invalid("new program header exceeds output".to_owned()))? .copy_from_slice(&header); update_pt_phdr( output, self.program_header_offset, self.program_header_count, self.program_header_size, None, new_table_size as u64, )?; } else if let Some(note_index) = self.reusable_note_program_header(output)? { let header_offset = checked_index( self.program_header_offset, note_index, self.program_header_size, )?; let header_end = header_offset .checked_add(self.program_header_size) .ok_or_else(|| Error::Invalid("reused program header range overflow".to_owned()))?; output .get_mut(header_offset..header_end) .ok_or_else(|| Error::Invalid("reused program header exceeds output".to_owned()))? .copy_from_slice(&header); updated_program_header_count = self.program_header_count; } else { if !self.has_program_header_type(output, PT_PHDR)? { return invalid( "cannot safely relocate program headers without PT_PHDR or a redundant PT_NOTE", ); } if reservation != new_table_size { return invalid("relocated program-header reservation has unexpected size"); } if segment.file_size < reservation as u64 { return invalid("new PT_LOAD is too small for relocated program headers"); } let relocated = usize_from_u64(segment.offset, "relocated program header offset")?; let relocated_end = relocated.checked_add(new_table_size).ok_or_else(|| { Error::Invalid("relocated program header range overflow".to_owned()) })?; slice(output, relocated, new_table_size)?; let old_table = slice(output, self.program_header_offset, old_table_size)?.to_vec(); output[relocated..relocated + old_table_size].copy_from_slice(&old_table); output[relocated + old_table_size..relocated_end].copy_from_slice(&header); output .get_mut(0x20..0x28) .ok_or_else(|| Error::Invalid("ELF header is truncated".to_owned()))? .copy_from_slice(&segment.offset.to_le_bytes()); update_pt_phdr( output, relocated, self.program_header_count, self.program_header_size, Some((segment.offset, segment.virtual_address)), new_table_size as u64, )?; updated_program_header_offset = relocated; } if updated_program_header_count == new_count { output .get_mut(0x38..0x3a) .ok_or_else(|| Error::Invalid("ELF header is truncated".to_owned()))? .copy_from_slice(&new_count_u16.to_le_bytes()); } let mut updated = self.clone(); updated.program_header_offset = updated_program_header_offset; updated.program_header_count = updated_program_header_count; updated.program_headers.push(segment); Ok(updated) } /// Resolve every section's name from the ELF `shstrtab` section. /// /// The returned names are source data, not role labels supplied by the /// caller. Any malformed string-table reference is an input error. pub fn section_names(&self, data: &[u8]) -> Result> { let table = self .section_headers .get(self.section_name_index) .copied() .ok_or_else(|| Error::Invalid("ELF section-name index is out of range".to_owned()))?; if table.section_type != SHT_STRTAB { return invalid(format!( "ELF section-name table has unexpected type 0x{:x}", table.section_type )); } let strings = slice_u64(data, table.offset, table.size)?; if strings.is_empty() || strings[0] != 0 { return invalid("ELF section-name table does not start with NUL"); } if strings.last().copied() != Some(0) { return invalid("ELF section-name table is not NUL terminated"); } self.section_headers .iter() .enumerate() .map(|(index, section)| { let offset = section.name as usize; if offset >= strings.len() { return invalid(format!( "ELF section {index} name offset 0x{offset:x} exceeds section-name table" )); } let end = strings[offset..] .iter() .position(|&byte| byte == 0) .map(|length| offset + length) .ok_or_else(|| { Error::Invalid(format!( "ELF section {index} name at 0x{offset:x} is unterminated" )) })?; let name = std::str::from_utf8(&strings[offset..end]).map_err(|error| { Error::Invalid(format!( "ELF section {index} name at 0x{offset:x} is not UTF-8: {error}" )) })?; if index == 0 && section.name != 0 { return invalid("ELF null section has a nonzero name offset"); } Ok(name.to_owned()) }) .collect() } pub fn file_offset_to_virtual_address(&self, offset: u64, size: u64) -> Result { let end = offset .checked_add(size) .ok_or_else(|| Error::Invalid("file range overflow".to_owned()))?; for segment in &self.program_headers { let segment_end = segment .offset .checked_add(segment.file_size) .ok_or_else(|| Error::Invalid("PT_LOAD file range overflow".to_owned()))?; if segment.offset <= offset && end <= segment_end { return segment .virtual_address .checked_add(offset - segment.offset) .ok_or_else(|| Error::Invalid("virtual address overflow".to_owned())); } } invalid(format!( "file range 0x{offset:x}..0x{end:x} is not in PT_LOAD" )) } } fn update_pt_phdr( output: &mut [u8], table_offset: usize, old_count: usize, entry_size: usize, relocated: Option<(u64, u64)>, table_size: u64, ) -> Result<()> { for index in 0..old_count { let offset = checked_index(table_offset, index, entry_size)?; if read_u32(output, offset)? != PT_PHDR { continue; } if let Some((file_offset, virtual_address)) = relocated { output .get_mut(offset + 8..offset + 0x10) .ok_or_else(|| Error::Invalid("PT_PHDR file offset exceeds output".to_owned()))? .copy_from_slice(&file_offset.to_le_bytes()); output .get_mut(offset + 0x10..offset + 0x18) .ok_or_else(|| Error::Invalid("PT_PHDR virtual address exceeds output".to_owned()))? .copy_from_slice(&virtual_address.to_le_bytes()); output .get_mut(offset + 0x18..offset + 0x20) .ok_or_else(|| { Error::Invalid("PT_PHDR physical address exceeds output".to_owned()) })? .copy_from_slice(&virtual_address.to_le_bytes()); } output .get_mut(offset + 0x20..offset + 0x28) .ok_or_else(|| Error::Invalid("PT_PHDR file size exceeds output".to_owned()))? .copy_from_slice(&table_size.to_le_bytes()); output .get_mut(offset + 0x28..offset + 0x30) .ok_or_else(|| Error::Invalid("PT_PHDR memory size exceeds output".to_owned()))? .copy_from_slice(&table_size.to_le_bytes()); } Ok(()) } pub fn slice(data: &[u8], offset: usize, size: usize) -> Result<&[u8]> { let end = offset .checked_add(size) .ok_or_else(|| Error::Invalid("byte range overflow".to_owned()))?; data.get(offset..end).ok_or_else(|| { Error::Invalid(format!( "byte range 0x{offset:x}..0x{end:x} is out of bounds" )) }) } pub fn slice_u64(data: &[u8], offset: u64, size: u64) -> Result<&[u8]> { slice( data, usize_from_u64(offset, "file offset")?, usize_from_u64(size, "file size")?, ) } pub fn read_u16(data: &[u8], offset: usize) -> Result { let bytes: [u8; 2] = slice(data, offset, 2)? .try_into() .map_err(|_| Error::Invalid("invalid u16 range".to_owned()))?; Ok(u16::from_le_bytes(bytes)) } pub fn read_u32(data: &[u8], offset: usize) -> Result { let bytes: [u8; 4] = slice(data, offset, 4)? .try_into() .map_err(|_| Error::Invalid("invalid u32 range".to_owned()))?; Ok(u32::from_le_bytes(bytes)) } pub fn read_u64(data: &[u8], offset: usize) -> Result { let bytes: [u8; 8] = slice(data, offset, 8)? .try_into() .map_err(|_| Error::Invalid("invalid u64 range".to_owned()))?; Ok(u64::from_le_bytes(bytes)) } pub fn read_i64(data: &[u8], offset: usize) -> Result { let bytes: [u8; 8] = slice(data, offset, 8)? .try_into() .map_err(|_| Error::Invalid("invalid i64 range".to_owned()))?; Ok(i64::from_le_bytes(bytes)) } pub fn usize_from_u64(value: u64, field: &str) -> Result { usize::try_from(value).map_err(|_| Error::Invalid(format!("{field} 0x{value:x} exceeds usize"))) } pub fn checked_index(base: usize, index: usize, stride: usize) -> Result { index .checked_mul(stride) .and_then(|value| base.checked_add(value)) .ok_or_else(|| Error::Invalid("table index overflow".to_owned())) } pub fn align_up(value: u64, alignment: u64) -> Result { if alignment == 0 || !alignment.is_power_of_two() { return invalid(format!("invalid alignment {alignment}")); } value .checked_add(alignment - 1) .map(|aligned| aligned & !(alignment - 1)) .ok_or_else(|| Error::Invalid("alignment overflow".to_owned())) } #[cfg(test)] mod tests { use super::*; fn layout(name_index: u32) -> ElfLayout { ElfLayout { entrypoint: 0, program_header_offset: 0, program_header_size: 0x38, program_header_count: 0, program_headers: Vec::new(), section_headers: vec![ SectionHeader { name: 0, section_type: 0, flags: 0, address: 0, offset: 0, size: 0, link: 0, info: 0, alignment: 0, entry_size: 0, }, SectionHeader { name: name_index, section_type: 1, flags: 0, address: 0, offset: 0, size: 0, link: 0, info: 0, alignment: 0, entry_size: 0, }, SectionHeader { name: 1, section_type: SHT_STRTAB, flags: 0, address: 0, offset: 0, size: 8, link: 0, info: 0, alignment: 1, entry_size: 0, }, ], section_name_index: 2, private_section_index: usize::MAX, } } #[test] fn section_names_resolve_from_elf_string_table() { let names = layout(1) .section_names(b"\0text\0\0\0") .expect("valid names"); assert_eq!(names, ["", "text", "text"]); } #[test] fn section_names_reject_out_of_range_name_offsets() { let error = layout(8) .section_names(b"\0text\0\0\0") .expect_err("invalid offset"); assert!(error.to_string().contains("exceeds section-name table")); } #[test] fn section_names_reject_invalid_utf8() { let mut elf_layout = layout(1); elf_layout.section_headers[1].name = 1; let error = elf_layout .section_names(b"\0\xff\0\0\0\0\0\0") .expect_err("invalid UTF-8"); assert!(error.to_string().contains("is not UTF-8")); } #[test] fn section_names_reject_non_string_table() { let mut elf_layout = layout(1); elf_layout.section_headers[2].section_type = 1; let error = elf_layout .section_names(b"\0text\0\0\0") .expect_err("wrong section type"); assert!(error.to_string().contains("unexpected type")); } #[test] fn section_names_reject_unterminated_table() { let elf_layout = layout(1); let error = elf_layout .section_names(b"\0text\0\x01\x01") .expect_err("unterminated table"); assert!(error.to_string().contains("not NUL terminated")); } #[test] fn append_load_segment_updates_program_headers() { let elf_layout = ElfLayout { entrypoint: 0, program_header_offset: 0, program_header_size: 0x38, program_header_count: 0, program_headers: Vec::new(), section_headers: Vec::new(), section_name_index: 0, private_section_index: usize::MAX, }; let mut output = vec![0_u8; 0x2000]; let updated = elf_layout .append_load_segment( &mut output, LoadSegment { offset: 0x1000, virtual_address: 0x2000, file_size: 0x20, memory_size: 0x20, flags: PF_R, alignment: 0x1000, }, 0, ) .expect("append segment"); assert_eq!(updated.program_header_count, 1); assert_eq!(updated.program_headers[0].virtual_address, 0x2000); assert_eq!(&output[0..4], &PT_LOAD.to_le_bytes()); assert_eq!(&output[0x38..0x3a], &1_u16.to_le_bytes()); } #[test] fn append_load_segment_fails_closed_when_no_safe_phdr_slot() { let mut elf_layout = ElfLayout { entrypoint: 0, program_header_offset: 0, program_header_size: 0x38, program_header_count: 0, program_headers: Vec::new(), section_headers: Vec::new(), section_name_index: 0, private_section_index: usize::MAX, }; elf_layout.section_headers.push(SectionHeader { name: 0, section_type: 1, flags: 0, address: 0, offset: 0x20, size: 1, link: 0, info: 0, alignment: 1, entry_size: 0, }); let reservation = elf_layout .additional_program_header_reservation() .expect("reservation"); assert_eq!(reservation, 0x38); let mut output = vec![0_u8; 0x200]; let error = elf_layout .append_load_segment( &mut output, LoadSegment { offset: 0x80, virtual_address: 0x1080, file_size: 0x80, memory_size: 0x80, flags: PF_R, alignment: 0x1000, }, reservation, ) .expect_err("unsafe relocation must fail"); assert!(error.to_string().contains("cannot safely relocate")); } #[test] fn append_load_segment_reuses_redundant_note_when_no_slack() { let mut elf_layout = ElfLayout { entrypoint: 0, program_header_offset: 0x40, program_header_size: 0x38, program_header_count: 3, program_headers: vec![LoadSegment { offset: 0, virtual_address: 0, file_size: 0x400, memory_size: 0x400, flags: PF_R, alignment: 0x1000, }], section_headers: Vec::new(), section_name_index: 0, private_section_index: usize::MAX, }; elf_layout.section_headers.push(SectionHeader { name: 0, section_type: 1, flags: 0, address: 0, offset: 0xe8, size: 1, link: 0, info: 0, alignment: 1, entry_size: 0, }); let mut output = vec![0_u8; 0x2000]; output[0x38..0x3a].copy_from_slice(&3_u16.to_le_bytes()); output[0x40..0x44].copy_from_slice(&PT_LOAD.to_le_bytes()); output[0x78..0x7c].copy_from_slice(&PT_NOTE.to_le_bytes()); output[0x80..0x88].copy_from_slice(&0x100_u64.to_le_bytes()); output[0x98..0xa0].copy_from_slice(&0x20_u64.to_le_bytes()); output[0xb0..0xb4].copy_from_slice(&PT_NOTE.to_le_bytes()); output[0xb8..0xc0].copy_from_slice(&0x200_u64.to_le_bytes()); output[0xd0..0xd8].copy_from_slice(&0x20_u64.to_le_bytes()); let reservation = elf_layout .additional_program_header_reservation() .expect("reservation"); assert_eq!(reservation, 0xe0); let updated = elf_layout .append_load_segment( &mut output, LoadSegment { offset: 0x1000, virtual_address: 0x2000, file_size: 0x100, memory_size: 0x100, flags: PF_R, alignment: 0x1000, }, reservation, ) .expect("reuse redundant PT_NOTE"); assert_eq!(updated.program_header_offset, 0x40); assert_eq!(updated.program_header_count, 3); assert_eq!(updated.program_headers.len(), 2); assert_eq!(&output[0x38..0x3a], &3_u16.to_le_bytes()); assert_eq!(&output[0x78..0x7c], &PT_NOTE.to_le_bytes()); assert_eq!(&output[0xb0..0xb4], &PT_LOAD.to_le_bytes()); assert_eq!(&output[0xb8..0xc0], &0x1000_u64.to_le_bytes()); assert_eq!(&output[0xc0..0xc8], &0x2000_u64.to_le_bytes()); } #[test] fn relocated_program_headers_update_pt_phdr() { let mut elf_layout = ElfLayout { entrypoint: 0, program_header_offset: 0x40, program_header_size: 0x38, program_header_count: 1, program_headers: Vec::new(), section_headers: Vec::new(), section_name_index: 0, private_section_index: usize::MAX, }; elf_layout.section_headers.push(SectionHeader { name: 0, section_type: 1, flags: 0, address: 0, offset: 0x78, size: 1, link: 0, info: 0, alignment: 1, entry_size: 0, }); let mut output = vec![0_u8; 0x2000]; output[0x20..0x28].copy_from_slice(&0x40_u64.to_le_bytes()); output[0x40..0x44].copy_from_slice(&PT_PHDR.to_le_bytes()); output[0x48..0x50].copy_from_slice(&0x40_u64.to_le_bytes()); output[0x50..0x58].copy_from_slice(&0x40_u64.to_le_bytes()); output[0x58..0x60].copy_from_slice(&0x40_u64.to_le_bytes()); output[0x60..0x68].copy_from_slice(&0x38_u64.to_le_bytes()); output[0x68..0x70].copy_from_slice(&0x38_u64.to_le_bytes()); let reservation = elf_layout .additional_program_header_reservation() .expect("reservation"); assert_eq!(reservation, 0x70); let updated = elf_layout .append_load_segment( &mut output, LoadSegment { offset: 0x1000, virtual_address: 0x2000, file_size: 0x100, memory_size: 0x100, flags: PF_R, alignment: 0x1000, }, reservation, ) .expect("relocate program headers"); assert_eq!(updated.program_header_offset, 0x1000); assert_eq!(&output[0x20..0x28], &0x1000_u64.to_le_bytes()); assert_eq!(&output[0x1000..0x1004], &PT_PHDR.to_le_bytes()); assert_eq!(&output[0x1008..0x1010], &0x1000_u64.to_le_bytes()); assert_eq!(&output[0x1010..0x1018], &0x2000_u64.to_le_bytes()); assert_eq!(&output[0x1020..0x1028], &0x70_u64.to_le_bytes()); assert_eq!(&output[0x1028..0x1030], &0x70_u64.to_le_bytes()); assert_eq!(&output[0x1038..0x103c], &PT_LOAD.to_le_bytes()); } }