diff --git a/crates/molotov-core/src/elf.rs b/crates/molotov-core/src/elf.rs new file mode 100644 index 0000000..177c621 --- /dev/null +++ b/crates/molotov-core/src/elf.rs @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ElfError { + #[error("File is smaller than minimum ELF64 header (64 bytes)")] + TooSmall, + #[error("Invalid ELF magic header: expected 0x7F 'ELF'")] + InvalidMagic, + #[error("Unsupported ELF class: expected 64-bit")] + UnsupportedClass, + #[error("Unsupported endianness: expected Little Endian")] + UnsupportedEndianness, + #[error("Unsupported architecture: expected x86_64 (62), found {0}")] + UnsupportedMachine(u16), + #[error("Invalid ELF type: expected ET_DYN (3), found {0}")] + UnsupportedType(u16), +} + +#[derive(Debug, Clone)] +pub struct ProgramHeader { + pub p_type: u32, + pub p_flags: u32, + pub p_offset: u64, + pub p_vaddr: u64, + pub p_paddr: u64, + pub p_filesz: u64, + pub p_memsz: u64, + pub p_align: u64, +} + +pub const PT_LOAD: u32 = 1; +pub const PT_DYNAMIC: u32 = 2; +pub const PT_TLS: u32 = 7; + +#[derive(Debug, Clone)] +pub struct ElfBinary<'a> { + pub raw: &'a [u8], + pub entry_point: u64, + pub program_headers: Vec, +} + +impl<'a> ElfBinary<'a> { + pub fn parse(bytes: &'a [u8]) -> Result { + if bytes.len() < 64 { + return Err(ElfError::TooSmall); + } + + if &bytes[0..4] != b"\x7fELF" { + return Err(ElfError::InvalidMagic); + } + + if bytes[4] != 2 { + return Err(ElfError::UnsupportedClass); + } + + if bytes[5] != 1 { + return Err(ElfError::UnsupportedEndianness); + } + + let e_type = u16::from_le_bytes([bytes[16], bytes[17]]); + if e_type != 3 { + return Err(ElfError::UnsupportedType(e_type)); + } + + let e_machine = u16::from_le_bytes([bytes[18], bytes[19]]); + if e_machine != 62 { + return Err(ElfError::UnsupportedMachine(e_machine)); + } + + let entry_point = u64::from_le_bytes(bytes[24..32].try_into().unwrap()); + let phoff = u64::from_le_bytes(bytes[32..40].try_into().unwrap()) as usize; + let phentsize = u16::from_le_bytes([bytes[54], bytes[55]]) as usize; + let phnum = u16::from_le_bytes([bytes[56], bytes[57]]) as usize; + + let mut program_headers = Vec::with_capacity(phnum); + + if phnum > 0 && phentsize >= 56 { + for i in 0..phnum { + let offset = phoff + i * phentsize; + if offset + 56 <= bytes.len() { + let ph = ProgramHeader { + p_type: u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()), + p_flags: u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap()), + p_offset: u64::from_le_bytes(bytes[offset + 8..offset + 16].try_into().unwrap()), + p_vaddr: u64::from_le_bytes(bytes[offset + 16..offset + 24].try_into().unwrap()), + p_paddr: u64::from_le_bytes(bytes[offset + 24..offset + 32].try_into().unwrap()), + p_filesz: u64::from_le_bytes(bytes[offset + 32..offset + 40].try_into().unwrap()), + p_memsz: u64::from_le_bytes(bytes[offset + 40..offset + 48].try_into().unwrap()), + p_align: u64::from_le_bytes(bytes[offset + 48..offset + 56].try_into().unwrap()), + }; + program_headers.push(ph); + } + } + } + + Ok(Self { + raw: bytes, + entry_point, + program_headers, + }) + } + + pub fn load_segments(&self) -> impl Iterator { + self.program_headers.iter().filter(|ph| ph.p_type == PT_LOAD) + } +} diff --git a/crates/molotov-core/src/lib.rs b/crates/molotov-core/src/lib.rs index 864cee8..85c0939 100644 --- a/crates/molotov-core/src/lib.rs +++ b/crates/molotov-core/src/lib.rs @@ -1,2 +1,3 @@ // SPDX-License-Identifier: GPL-3.0-or-later +pub mod elf; pub mod symbols; diff --git a/crates/molotov-core/tests/elf_parser_test.rs b/crates/molotov-core/tests/elf_parser_test.rs new file mode 100644 index 0000000..f0bf0f7 --- /dev/null +++ b/crates/molotov-core/tests/elf_parser_test.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_core::elf::ElfBinary; + +#[test] +fn test_rejects_non_elf_magic() { + let dummy = b"NOT_AN_ELF_BINARY"; + let res = ElfBinary::parse(dummy); + assert!(res.is_err(), "Must reject files without 0x7F 'E' 'L' 'F' magic header"); +} + +#[test] +fn test_parses_valid_minimal_elf64() { + let mut header = vec![0u8; 64]; + // e_ident + header[0..4].copy_from_slice(b"\x7fELF"); + header[4] = 2; // ELFCLASS64 + header[5] = 1; // ELFDATA2LSB (little endian) + header[6] = 1; // EV_CURRENT + // e_type = ET_DYN (3) + header[16] = 3; + // e_machine = EM_X86_64 (62) + header[18] = 62; + // e_phentsize = 56 + header[54] = 56; + + let res = ElfBinary::parse(&header); + assert!(res.is_ok(), "Valid minimal ELF64 dynamic library header must be accepted"); +}