Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/crutchcorn/binarrow. An experimental browser runtime for AArch64 Linux ELF executables
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807//! 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<GuestAddress> { 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<Vec<u8>>, /// Segments sorted by virtual address. pub load_segments: Vec<LoadSegment>,}
struct ParsedProgramHeaders { table: ProgramHeaderTable, interpreter: Option<Vec<u8>>, load_segments: Vec<LoadSegment>,}
/// 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<ElfImage, ElfError> { 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<ParsedProgramHeaders, ElfError> { 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<Vec<u8>, 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<LoadSegment, ElfError> { 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<u16> { Some(u16::from_le_bytes( bytes.get(offset..offset + 2)?.try_into().ok()?, ))}
fn read_u32(bytes: &[u8], offset: usize) -> Option<u32> { Some(u32::from_le_bytes( bytes.get(offset..offset + 4)?.try_into().ok()?, ))}
fn read_u64(bytes: &[u8], offset: usize) -> Option<u64> { 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<u8> { 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()); }}