//! Strict, host-independent inspection of the initial binarrow ELF profile. //! //! This crate parses bytes and returns project-owned metadata. Mapping segments //! into a process image remains the loader's responsibility. use core::fmt; use binarrow_runtime_core::GuestAddress; use object::{Architecture, BinaryFormat, FileKind, Object, ObjectKind}; const ELF_HEADER_SIZE: usize = 64; const ELF64_PROGRAM_HEADER_SIZE: usize = 56; const ELF_CLASS_OFFSET: usize = 4; const ELF_DATA_OFFSET: usize = 5; const ELF_OS_ABI_OFFSET: usize = 7; const ELF_PROGRAM_HEADER_OFFSET: usize = 32; const ELF_PROGRAM_HEADER_ENTRY_SIZE_OFFSET: usize = 54; const ELF_PROGRAM_HEADER_COUNT_OFFSET: usize = 56; const ELF_CLASS_64: u8 = 2; const ELF_DATA_LITTLE_ENDIAN: u8 = 1; const ELF_OS_ABI_SYSTEM_V: u8 = 0; const ELF_OS_ABI_LINUX: u8 = 3; const ELF_EXTENDED_PROGRAM_HEADER_COUNT: u16 = 0xffff; const PT_LOAD: u32 = 1; const PT_INTERP: u32 = 3; const PF_EXECUTE: u32 = 1; const PF_WRITE: u32 = 2; const PF_READ: u32 = 4; /// The supported executable shape. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ImageKind { /// A fixed-address `ET_EXEC` image. Executable, /// An `ET_DYN` image. PositionIndependent, } impl fmt::Display for ImageKind { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Executable => formatter.write_str("executable"), Self::PositionIndependent => formatter.write_str("position-independent"), } } } /// Read, write, and execute permissions declared by a loadable segment. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct SegmentPermissions { pub read: bool, pub write: bool, pub execute: bool, } impl SegmentPermissions { fn from_elf_flags(flags: u32) -> Self { Self { read: flags & PF_READ != 0, write: flags & PF_WRITE != 0, execute: flags & PF_EXECUTE != 0, } } } impl fmt::Display for SegmentPermissions { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!( formatter, "{}{}{}", if self.read { 'R' } else { '-' }, if self.write { 'W' } else { '-' }, if self.execute { 'X' } else { '-' }, ) } } /// Checked metadata for one `PT_LOAD` program header. #[derive(Clone, Debug, Eq, PartialEq)] pub struct LoadSegment { /// Original program-header table index. pub index: usize, pub file_offset: u64, pub file_size: u64, pub virtual_address: GuestAddress, pub memory_size: u64, pub alignment: u64, pub permissions: SegmentPermissions, } /// Location and shape of the ELF program-header table. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ProgramHeaderTable { pub file_offset: u64, pub entry_size: u16, pub entry_count: u16, } impl ProgramHeaderTable { /// Total byte length of the table. #[must_use] pub const fn byte_size(self) -> u64 { self.entry_size as u64 * self.entry_count as u64 } } impl LoadSegment { /// The first address after the mapped segment, or `None` if caller-built /// metadata overflows the guest address space. #[must_use] pub const fn end_address(&self) -> Option { self.virtual_address.checked_add(self.memory_size) } /// Whether this segment contains an address. #[must_use] pub fn contains(&self, address: GuestAddress) -> bool { self.end_address() .is_some_and(|end| self.virtual_address <= address && address < end) } } /// Validated metadata for an `AArch64` ELF image. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ElfImage { pub kind: ImageKind, pub entry: GuestAddress, pub program_headers: ProgramHeaderTable, /// Absolute guest path requested by `PT_INTERP`, without its trailing NUL. pub interpreter: Option>, /// Segments sorted by virtual address. pub load_segments: Vec, } struct ParsedProgramHeaders { table: ProgramHeaderTable, interpreter: Option>, load_segments: Vec, } /// A precise reason an input cannot enter the initial loader pipeline. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ElfError { TruncatedIdentification, InvalidMagic, UnsupportedClass(u8), UnsupportedEndianness(u8), UnsupportedOsAbi(u8), UnsupportedFileKind, Parse(String), UnsupportedArchitecture(String), UnsupportedImageKind(String), DuplicateInterpreter, EmptyInterpreter, InterpreterFileRangeOutOfBounds, UnterminatedInterpreter, InterpreterContainsInteriorNul, InterpreterNotAbsolute, InvalidProgramHeaderSize(u16), ExtendedProgramHeaderCount, ProgramHeaderTableOutOfBounds, NoLoadSegments, EmptyLoadSegment { index: usize }, FileSizeExceedsMemorySize { index: usize }, SegmentFileRangeOutOfBounds { index: usize }, SegmentAddressOverflow { index: usize }, InvalidSegmentAlignment { index: usize, alignment: u64 }, MisalignedSegment { index: usize }, WritableExecutableSegment { index: usize }, OverlappingSegments { first: usize, second: usize }, EntryPointNotExecutable(GuestAddress), } impl fmt::Display for ElfError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::TruncatedIdentification => formatter.write_str("ELF identification is truncated"), Self::InvalidMagic => formatter.write_str("input does not have the ELF magic"), Self::UnsupportedClass(class) => { write!(formatter, "unsupported ELF class {class}; expected ELF64") } Self::UnsupportedEndianness(data) => write!( formatter, "unsupported ELF data encoding {data}; expected little-endian" ), Self::UnsupportedOsAbi(abi) => { write!( formatter, "unsupported ELF OS ABI {abi}; expected System V or Linux" ) } Self::UnsupportedFileKind => formatter.write_str("input is not an ELF64 object"), Self::Parse(message) => write!(formatter, "invalid ELF: {message}"), Self::UnsupportedArchitecture(architecture) => write!( formatter, "unsupported ELF architecture {architecture}; expected AArch64 LP64" ), Self::UnsupportedImageKind(kind) => write!( formatter, "unsupported ELF image kind {kind}; expected ET_EXEC or ET_DYN" ), Self::DuplicateInterpreter => { formatter.write_str("ELF contains more than one PT_INTERP program header") } Self::EmptyInterpreter => formatter.write_str("PT_INTERP contains an empty path"), Self::InterpreterFileRangeOutOfBounds => { formatter.write_str("PT_INTERP extends beyond the ELF file") } Self::UnterminatedInterpreter => { formatter.write_str("PT_INTERP path is not NUL-terminated") } Self::InterpreterContainsInteriorNul => { formatter.write_str("PT_INTERP path contains an interior NUL byte") } Self::InterpreterNotAbsolute => formatter.write_str("PT_INTERP path is not absolute"), Self::InvalidProgramHeaderSize(size) => write!( formatter, "invalid ELF64 program-header size {size}; expected {ELF64_PROGRAM_HEADER_SIZE}" ), Self::ExtendedProgramHeaderCount => { formatter.write_str("extended ELF program-header counts are not supported") } Self::ProgramHeaderTableOutOfBounds => { formatter.write_str("ELF program-header table is outside the file") } Self::NoLoadSegments => formatter.write_str("ELF has no PT_LOAD segments"), Self::EmptyLoadSegment { index } => { write!(formatter, "PT_LOAD segment {index} has zero memory size") } Self::FileSizeExceedsMemorySize { index } => write!( formatter, "PT_LOAD segment {index} has p_filesz greater than p_memsz" ), Self::SegmentFileRangeOutOfBounds { index } => { write!(formatter, "PT_LOAD segment {index} extends beyond the file") } Self::SegmentAddressOverflow { index } => { write!( formatter, "PT_LOAD segment {index} overflows the guest address space" ) } Self::InvalidSegmentAlignment { index, alignment } => write!( formatter, "PT_LOAD segment {index} has invalid alignment {alignment:#x}" ), Self::MisalignedSegment { index } => write!( formatter, "PT_LOAD segment {index} has incongruent file and virtual addresses" ), Self::WritableExecutableSegment { index } => write!( formatter, "PT_LOAD segment {index} is writable and executable" ), Self::OverlappingSegments { first, second } => write!( formatter, "PT_LOAD segments {first} and {second} overlap in guest memory" ), Self::EntryPointNotExecutable(entry) => { write!( formatter, "entry point {entry} is not in an executable PT_LOAD segment" ) } } } } impl std::error::Error for ElfError {} /// Parse and validate the supported `AArch64` ELF profile. /// /// # Errors /// /// Returns [`ElfError`] when the input is malformed or falls outside the /// deliberately narrow initial executable profile. pub fn inspect(bytes: &[u8]) -> Result { validate_identification(bytes)?; let file_kind = FileKind::parse(bytes).map_err(|_| ElfError::UnsupportedFileKind)?; if file_kind != FileKind::Elf64 { return Err(ElfError::UnsupportedFileKind); } let ParsedProgramHeaders { table: program_headers, interpreter, mut load_segments, } = parse_program_headers(bytes)?; let file = object::File::parse(bytes).map_err(|error| ElfError::Parse(error.to_string()))?; if file.format() != BinaryFormat::Elf || !file.is_64() || !file.is_little_endian() { return Err(ElfError::UnsupportedFileKind); } if file.architecture() != Architecture::Aarch64 { return Err(ElfError::UnsupportedArchitecture(format!( "{:?}", file.architecture() ))); } let kind = match file.kind() { ObjectKind::Executable => ImageKind::Executable, ObjectKind::Dynamic => ImageKind::PositionIndependent, other => return Err(ElfError::UnsupportedImageKind(format!("{other:?}"))), }; let entry = GuestAddress::new(file.entry()); validate_segment_relationships(&load_segments, entry)?; load_segments.sort_unstable_by_key(|segment| segment.virtual_address); Ok(ElfImage { kind, entry, program_headers, interpreter, load_segments, }) } fn validate_identification(bytes: &[u8]) -> Result<(), ElfError> { let ident = bytes.get(..16).ok_or(ElfError::TruncatedIdentification)?; if ident.get(..4) != Some(b"\x7fELF") { return Err(ElfError::InvalidMagic); } if ident[ELF_CLASS_OFFSET] != ELF_CLASS_64 { return Err(ElfError::UnsupportedClass(ident[ELF_CLASS_OFFSET])); } if ident[ELF_DATA_OFFSET] != ELF_DATA_LITTLE_ENDIAN { return Err(ElfError::UnsupportedEndianness(ident[ELF_DATA_OFFSET])); } if !matches!( ident[ELF_OS_ABI_OFFSET], ELF_OS_ABI_SYSTEM_V | ELF_OS_ABI_LINUX ) { return Err(ElfError::UnsupportedOsAbi(ident[ELF_OS_ABI_OFFSET])); } Ok(()) } fn parse_program_headers(bytes: &[u8]) -> Result { if bytes.len() < ELF_HEADER_SIZE { return Err(ElfError::ProgramHeaderTableOutOfBounds); } let table_offset = read_u64(bytes, ELF_PROGRAM_HEADER_OFFSET) .and_then(|offset| usize::try_from(offset).ok()) .ok_or(ElfError::ProgramHeaderTableOutOfBounds)?; let entry_size = read_u16(bytes, ELF_PROGRAM_HEADER_ENTRY_SIZE_OFFSET) .ok_or(ElfError::ProgramHeaderTableOutOfBounds)?; if usize::from(entry_size) != ELF64_PROGRAM_HEADER_SIZE { return Err(ElfError::InvalidProgramHeaderSize(entry_size)); } let entry_count = read_u16(bytes, ELF_PROGRAM_HEADER_COUNT_OFFSET) .ok_or(ElfError::ProgramHeaderTableOutOfBounds)?; if entry_count == ELF_EXTENDED_PROGRAM_HEADER_COUNT { return Err(ElfError::ExtendedProgramHeaderCount); } let table_size = ELF64_PROGRAM_HEADER_SIZE .checked_mul(usize::from(entry_count)) .ok_or(ElfError::ProgramHeaderTableOutOfBounds)?; let table_end = table_offset .checked_add(table_size) .ok_or(ElfError::ProgramHeaderTableOutOfBounds)?; let table = bytes .get(table_offset..table_end) .ok_or(ElfError::ProgramHeaderTableOutOfBounds)?; let mut load_segments = Vec::new(); let mut interpreter = None; for (index, header) in table.chunks_exact(ELF64_PROGRAM_HEADER_SIZE).enumerate() { match read_u32(header, 0).expect("program header type is in bounds") { PT_INTERP => { if interpreter.is_some() { return Err(ElfError::DuplicateInterpreter); } interpreter = Some(parse_interpreter(bytes, header)?); } PT_LOAD => load_segments.push(parse_load_segment(bytes, header, index)?), _ => {} } } if load_segments.is_empty() { return Err(ElfError::NoLoadSegments); } Ok(ParsedProgramHeaders { table: ProgramHeaderTable { file_offset: table_offset as u64, entry_size, entry_count, }, interpreter, load_segments, }) } fn parse_interpreter(bytes: &[u8], header: &[u8]) -> Result, ElfError> { let file_offset = read_u64(header, 8).expect("program header file offset is in bounds"); let file_size = read_u64(header, 32).expect("program header file size is in bounds"); let start = usize::try_from(file_offset).map_err(|_| ElfError::InterpreterFileRangeOutOfBounds)?; let size = usize::try_from(file_size).map_err(|_| ElfError::InterpreterFileRangeOutOfBounds)?; let end = start .checked_add(size) .ok_or(ElfError::InterpreterFileRangeOutOfBounds)?; let encoded = bytes .get(start..end) .ok_or(ElfError::InterpreterFileRangeOutOfBounds)?; let (&terminator, path) = encoded.split_last().ok_or(ElfError::EmptyInterpreter)?; if terminator != 0 { return Err(ElfError::UnterminatedInterpreter); } if path.is_empty() { return Err(ElfError::EmptyInterpreter); } if path.contains(&0) { return Err(ElfError::InterpreterContainsInteriorNul); } if path.first() != Some(&b'/') { return Err(ElfError::InterpreterNotAbsolute); } Ok(path.to_vec()) } fn parse_load_segment(bytes: &[u8], header: &[u8], index: usize) -> Result { let flags = read_u32(header, 4).expect("program header flags are in bounds"); let file_offset = read_u64(header, 8).expect("program header file offset is in bounds"); let virtual_address = read_u64(header, 16).expect("program header address is in bounds"); let file_size = read_u64(header, 32).expect("program header file size is in bounds"); let memory_size = read_u64(header, 40).expect("program header memory size is in bounds"); let alignment = read_u64(header, 48).expect("program header alignment is in bounds"); if memory_size == 0 { return Err(ElfError::EmptyLoadSegment { index }); } if file_size > memory_size { return Err(ElfError::FileSizeExceedsMemorySize { index }); } let file_end = file_offset .checked_add(file_size) .ok_or(ElfError::SegmentFileRangeOutOfBounds { index })?; if file_end > bytes.len() as u64 { return Err(ElfError::SegmentFileRangeOutOfBounds { index }); } virtual_address .checked_add(memory_size) .ok_or(ElfError::SegmentAddressOverflow { index })?; if alignment > 1 && !alignment.is_power_of_two() { return Err(ElfError::InvalidSegmentAlignment { index, alignment }); } if alignment > 1 && virtual_address % alignment != file_offset % alignment { return Err(ElfError::MisalignedSegment { index }); } let permissions = SegmentPermissions::from_elf_flags(flags); if permissions.write && permissions.execute { return Err(ElfError::WritableExecutableSegment { index }); } Ok(LoadSegment { index, file_offset, file_size, virtual_address: GuestAddress::new(virtual_address), memory_size, alignment, permissions, }) } fn validate_segment_relationships( segments: &[LoadSegment], entry: GuestAddress, ) -> Result<(), ElfError> { let mut by_address: Vec<_> = segments.iter().collect(); by_address.sort_unstable_by_key(|segment| segment.virtual_address); for pair in by_address.windows(2) { if pair[0] .end_address() .is_some_and(|end| pair[1].virtual_address < end) { return Err(ElfError::OverlappingSegments { first: pair[0].index, second: pair[1].index, }); } } if !segments .iter() .any(|segment| segment.permissions.execute && segment.contains(entry)) { return Err(ElfError::EntryPointNotExecutable(entry)); } Ok(()) } fn read_u16(bytes: &[u8], offset: usize) -> Option { Some(u16::from_le_bytes( bytes.get(offset..offset + 2)?.try_into().ok()?, )) } fn read_u32(bytes: &[u8], offset: usize) -> Option { Some(u32::from_le_bytes( bytes.get(offset..offset + 4)?.try_into().ok()?, )) } fn read_u64(bytes: &[u8], offset: usize) -> Option { Some(u64::from_le_bytes( bytes.get(offset..offset + 8)?.try_into().ok()?, )) } #[cfg(test)] mod tests { use super::{ ELF_CLASS_OFFSET, ELF_DATA_OFFSET, ELF_PROGRAM_HEADER_COUNT_OFFSET, ELF_PROGRAM_HEADER_ENTRY_SIZE_OFFSET, ElfError, ImageKind, PT_INTERP, inspect, }; const FIRST_HEADER: usize = 64; const SECOND_HEADER: usize = 120; #[test] fn inspects_static_aarch64_executable() { let bytes = valid_elf(); let image = inspect(&bytes).expect("fixture should be a supported ELF"); assert_eq!(image.kind, ImageKind::Executable); assert_eq!(image.entry.get(), 0x0040_0100); assert_eq!(image.program_headers.file_offset, 64); assert_eq!(image.program_headers.entry_size, 56); assert_eq!(image.program_headers.entry_count, 2); assert_eq!(image.interpreter, None); assert_eq!(image.load_segments.len(), 2); assert_eq!(image.load_segments[0].permissions.to_string(), "R-X"); assert_eq!(image.load_segments[1].permissions.to_string(), "RW-"); assert_eq!(image.load_segments[1].memory_size, 0x100); } #[test] fn accepts_static_position_independent_image() { let mut bytes = valid_elf(); write_u16(&mut bytes, 16, 3); assert_eq!( inspect(&bytes).expect("static PIE should be accepted").kind, ImageKind::PositionIndependent ); } #[test] fn rejects_invalid_identification() { assert_eq!(inspect(&[]), Err(ElfError::TruncatedIdentification)); let mut bytes = valid_elf(); bytes[0] = 0; assert_eq!(inspect(&bytes), Err(ElfError::InvalidMagic)); let mut bytes = valid_elf(); bytes[ELF_CLASS_OFFSET] = 1; assert_eq!(inspect(&bytes), Err(ElfError::UnsupportedClass(1))); let mut bytes = valid_elf(); bytes[ELF_DATA_OFFSET] = 2; assert_eq!(inspect(&bytes), Err(ElfError::UnsupportedEndianness(2))); } #[test] fn rejects_non_aarch64_and_non_executable_images() { let mut bytes = valid_elf(); write_u16(&mut bytes, 18, 62); assert!(matches!( inspect(&bytes), Err(ElfError::UnsupportedArchitecture(_)) )); let mut bytes = valid_elf(); write_u16(&mut bytes, 16, 1); assert!(matches!( inspect(&bytes), Err(ElfError::UnsupportedImageKind(_)) )); } #[test] fn exposes_dynamic_interpreter() { let mut bytes = valid_elf(); write_u16(&mut bytes, ELF_PROGRAM_HEADER_COUNT_OFFSET, 3); let third_header = 176; write_u32(&mut bytes, third_header, PT_INTERP); write_u64(&mut bytes, third_header + 8, 0x1e0); write_u64(&mut bytes, third_header + 32, 26); bytes[0x1e0..0x1fa].copy_from_slice(b"/lib/ld-musl-aarch64.so.1\0"); assert_eq!( inspect(&bytes).unwrap().interpreter, Some(b"/lib/ld-musl-aarch64.so.1".to_vec()) ); } #[test] fn rejects_malformed_dynamic_interpreters() { let mut bytes = valid_elf(); write_u16(&mut bytes, ELF_PROGRAM_HEADER_COUNT_OFFSET, 3); let third_header = 176; write_u32(&mut bytes, third_header, PT_INTERP); write_u64(&mut bytes, third_header + 8, 0x1e0); assert_eq!(inspect(&bytes), Err(ElfError::EmptyInterpreter)); write_u64(&mut bytes, third_header + 32, 5); bytes[0x1e0..0x1e5].copy_from_slice(b"/libx"); assert_eq!(inspect(&bytes), Err(ElfError::UnterminatedInterpreter)); bytes[0x1e0..0x1e5].copy_from_slice(b"ld-x\0"); assert_eq!(inspect(&bytes), Err(ElfError::InterpreterNotAbsolute)); bytes[0x1e0..0x1e5].copy_from_slice(b"/a\0b\0"); assert_eq!( inspect(&bytes), Err(ElfError::InterpreterContainsInteriorNul) ); write_u64(&mut bytes, third_header + 8, u64::MAX); assert_eq!( inspect(&bytes), Err(ElfError::InterpreterFileRangeOutOfBounds) ); } #[test] fn rejects_duplicate_dynamic_interpreters() { let mut bytes = valid_elf(); write_u16(&mut bytes, ELF_PROGRAM_HEADER_COUNT_OFFSET, 4); for header in [176, 232] { write_u32(&mut bytes, header, PT_INTERP); write_u64(&mut bytes, header + 8, 0x1e0); write_u64(&mut bytes, header + 32, 4); } bytes[0x1e0..0x1e4].copy_from_slice(b"/ld\0"); assert_eq!(inspect(&bytes), Err(ElfError::DuplicateInterpreter)); } #[test] fn rejects_invalid_program_header_table() { let mut bytes = valid_elf(); write_u16(&mut bytes, ELF_PROGRAM_HEADER_ENTRY_SIZE_OFFSET, 55); assert_eq!(inspect(&bytes), Err(ElfError::InvalidProgramHeaderSize(55))); let mut bytes = valid_elf(); write_u64(&mut bytes, 32, u64::MAX); assert_eq!( inspect(&bytes), Err(ElfError::ProgramHeaderTableOutOfBounds) ); let mut bytes = valid_elf(); write_u16( &mut bytes, ELF_PROGRAM_HEADER_COUNT_OFFSET, super::ELF_EXTENDED_PROGRAM_HEADER_COUNT, ); assert_eq!(inspect(&bytes), Err(ElfError::ExtendedProgramHeaderCount)); } #[test] fn rejects_invalid_segment_ranges() { let mut bytes = valid_elf(); write_u64(&mut bytes, FIRST_HEADER + 32, 0x200); write_u64(&mut bytes, FIRST_HEADER + 40, 0x100); assert_eq!( inspect(&bytes), Err(ElfError::FileSizeExceedsMemorySize { index: 0 }) ); let mut bytes = valid_elf(); write_u64(&mut bytes, SECOND_HEADER + 8, u64::MAX); assert_eq!( inspect(&bytes), Err(ElfError::SegmentFileRangeOutOfBounds { index: 1 }) ); let mut bytes = valid_elf(); write_u64(&mut bytes, SECOND_HEADER + 16, u64::MAX - 7); write_u64(&mut bytes, SECOND_HEADER + 40, 8); assert_eq!( inspect(&bytes), Err(ElfError::SegmentAddressOverflow { index: 1 }) ); } #[test] fn rejects_unsafe_or_overlapping_mappings() { let mut bytes = valid_elf(); write_u32(&mut bytes, FIRST_HEADER + 4, 7); assert_eq!( inspect(&bytes), Err(ElfError::WritableExecutableSegment { index: 0 }) ); let mut bytes = valid_elf(); write_u64(&mut bytes, SECOND_HEADER + 16, 0x0040_0100); write_u64(&mut bytes, SECOND_HEADER + 48, 1); assert_eq!( inspect(&bytes), Err(ElfError::OverlappingSegments { first: 0, second: 1, }) ); let mut bytes = valid_elf(); write_u64(&mut bytes, SECOND_HEADER + 48, 3); assert_eq!( inspect(&bytes), Err(ElfError::InvalidSegmentAlignment { index: 1, alignment: 3, }) ); let mut bytes = valid_elf(); write_u64(&mut bytes, SECOND_HEADER + 16, 0x0040_1201); assert_eq!( inspect(&bytes), Err(ElfError::MisalignedSegment { index: 1 }) ); } #[test] fn rejects_unmapped_entry_point() { let mut bytes = valid_elf(); write_u64(&mut bytes, 24, 0x0050_0000); assert_eq!( inspect(&bytes), Err(ElfError::EntryPointNotExecutable( binarrow_runtime_core::GuestAddress::new(0x0050_0000) )) ); } #[test] fn every_truncated_prefix_is_rejected_without_panicking() { let bytes = valid_elf(); for end in 0..bytes.len() { assert!(inspect(&bytes[..end]).is_err(), "prefix of length {end}"); } } fn valid_elf() -> Vec { let mut bytes = vec![0; 0x208]; bytes[..4].copy_from_slice(b"\x7fELF"); bytes[4] = 2; bytes[5] = 1; bytes[6] = 1; write_u16(&mut bytes, 16, 2); write_u16(&mut bytes, 18, 183); write_u32(&mut bytes, 20, 1); write_u64(&mut bytes, 24, 0x0040_0100); write_u64(&mut bytes, 32, 64); write_u16(&mut bytes, 52, 64); write_u16(&mut bytes, 54, 56); write_u16(&mut bytes, 56, 2); write_load_header(&mut bytes, FIRST_HEADER, 5, 0, 0x0040_0000, 0x180, 0x180); write_load_header(&mut bytes, SECOND_HEADER, 6, 0x200, 0x0040_1200, 8, 0x100); bytes[0x100..0x108].copy_from_slice(&[ 0x40, 0x05, 0x80, 0xd2, // mov x0, #42 0x00, 0x04, 0x00, 0x91, // add x0, x0, #1 ]); bytes } fn write_load_header( bytes: &mut [u8], offset: usize, flags: u32, file_offset: u64, virtual_address: u64, file_size: u64, memory_size: u64, ) { write_u32(bytes, offset, 1); write_u32(bytes, offset + 4, flags); write_u64(bytes, offset + 8, file_offset); write_u64(bytes, offset + 16, virtual_address); write_u64(bytes, offset + 24, virtual_address); write_u64(bytes, offset + 32, file_size); write_u64(bytes, offset + 40, memory_size); write_u64(bytes, offset + 48, 0x1000); } fn write_u16(bytes: &mut [u8], offset: usize, value: u16) { bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes()); } fn write_u32(bytes: &mut [u8], offset: usize, value: u32) { bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); } fn write_u64(bytes: &mut [u8], offset: usize, value: u64) { bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes()); } }