Nothing to see here, move along
at main 201 lines 5.7 kB view raw
1use crate::static_vec::StaticVec; 2use zerocopy::{FromBytes, Immutable, KnownLayout}; 3 4const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F']; 5const PT_LOAD: u32 = 1; 6const MAX_LOAD_SEGMENTS: usize = 8; 7 8#[derive(Debug, Clone, Copy, PartialEq, Eq)] 9pub enum ElfError { 10 Truncated, 11 BadMagic, 12 UnsupportedFormat, 13 InvalidPhdrSize, 14 TooManySegments, 15 MalformedSegment, 16} 17 18fn read_struct<T: FromBytes + KnownLayout + Immutable>(data: &[u8], offset: usize) -> Option<T> { 19 let size = core::mem::size_of::<T>(); 20 let end = offset.checked_add(size)?; 21 T::read_from_bytes(data.get(offset..end)?).ok() 22} 23 24#[repr(C)] 25#[derive(Clone, Copy, FromBytes, KnownLayout, Immutable)] 26pub struct Elf64Header { 27 pub e_ident: [u8; 16], 28 pub e_type: u16, 29 pub e_machine: u16, 30 pub e_version: u32, 31 pub e_entry: u64, 32 pub e_phoff: u64, 33 pub e_shoff: u64, 34 pub e_flags: u32, 35 pub e_ehsize: u16, 36 pub e_phentsize: u16, 37 pub e_phnum: u16, 38 pub e_shentsize: u16, 39 pub e_shnum: u16, 40 pub e_shstrndx: u16, 41} 42 43#[repr(C)] 44#[derive(Clone, Copy, FromBytes, KnownLayout, Immutable)] 45pub struct Elf64Phdr { 46 pub p_type: u32, 47 pub p_flags: u32, 48 pub p_offset: u64, 49 pub p_vaddr: u64, 50 pub p_paddr: u64, 51 pub p_filesz: u64, 52 pub p_memsz: u64, 53 pub p_align: u64, 54} 55 56#[derive(Debug)] 57pub struct ElfInfo { 58 pub entry: u64, 59 pub segments: StaticVec<LoadSegment, MAX_LOAD_SEGMENTS>, 60} 61 62#[derive(Debug, Clone, Copy)] 63pub struct LoadSegment { 64 pub vaddr: u64, 65 pub file_offset: u64, 66 pub filesz: u64, 67 pub memsz: u64, 68 pub writable: bool, 69 pub executable: bool, 70} 71 72pub fn parse(data: &[u8]) -> Result<ElfInfo, ElfError> { 73 let header: Elf64Header = read_struct(data, 0).ok_or(ElfError::Truncated)?; 74 75 if header.e_ident[0..4] != ELF_MAGIC { 76 return Err(ElfError::BadMagic); 77 } 78 79 if header.e_ident[4] != 2 || header.e_ident[5] != 1 || header.e_machine != 0x3E { 80 return Err(ElfError::UnsupportedFormat); 81 } 82 83 if (header.e_phentsize as usize) < core::mem::size_of::<Elf64Phdr>() { 84 return Err(ElfError::InvalidPhdrSize); 85 } 86 87 let mut segments: StaticVec<LoadSegment, MAX_LOAD_SEGMENTS> = StaticVec::new(); 88 89 let phdr_base = header.e_phoff as usize; 90 let phdr_size = header.e_phentsize as usize; 91 92 (0..header.e_phnum as usize) 93 .filter_map(|i| { 94 let offset = phdr_base.checked_add(i.checked_mul(phdr_size)?)?; 95 let phdr: Elf64Phdr = read_struct(data, offset)?; 96 let valid = phdr.p_type == PT_LOAD 97 && phdr.p_memsz > 0 98 && phdr.p_memsz >= phdr.p_filesz 99 && phdr 100 .p_offset 101 .checked_add(phdr.p_filesz) 102 .is_some_and(|end| end <= data.len() as u64); 103 if valid { 104 Some(LoadSegment { 105 vaddr: phdr.p_vaddr, 106 file_offset: phdr.p_offset, 107 filesz: phdr.p_filesz, 108 memsz: phdr.p_memsz, 109 writable: (phdr.p_flags & 2) != 0, 110 executable: (phdr.p_flags & 1) != 0, 111 }) 112 } else { 113 None 114 } 115 }) 116 .try_fold(&mut segments, |segs, seg| match segs.push(seg) { 117 Ok(()) => Ok(segs), 118 Err(_) => Err(ElfError::TooManySegments), 119 })?; 120 121 Ok(ElfInfo { 122 entry: header.e_entry, 123 segments, 124 }) 125} 126 127#[cfg(kani)] 128mod kani_proofs { 129 use super::*; 130 131 #[kani::proof] 132 fn parse_rejects_empty() { 133 assert!(parse(&[]).is_err()); 134 } 135 136 #[kani::proof] 137 fn read_struct_rejects_truncated_header() { 138 let len: usize = kani::any(); 139 kani::assume(len < core::mem::size_of::<Elf64Header>()); 140 let data = [0u8; 63]; 141 let result: Option<Elf64Header> = read_struct(&data[..len], 0); 142 assert!(result.is_none()); 143 } 144 145 #[kani::proof] 146 fn read_struct_bounds_check() { 147 let data: [u8; 16] = kani::any(); 148 let offset: usize = kani::any(); 149 kani::assume(offset <= 20); 150 let result: Option<u64> = read_struct(&data, offset); 151 if offset.checked_add(8).is_none_or(|end| end > 16) { 152 assert!(result.is_none()); 153 } 154 } 155 156 #[kani::proof] 157 #[kani::unwind(10)] 158 fn phdr_offset_arithmetic_no_overflow() { 159 let base: usize = kani::any(); 160 let entry_size: usize = kani::any(); 161 let count: usize = kani::any(); 162 kani::assume(count <= 8); 163 kani::assume(entry_size >= core::mem::size_of::<Elf64Phdr>()); 164 kani::assume(entry_size <= 256); 165 166 (0..count).for_each(|i| { 167 let product = i.checked_mul(entry_size); 168 let offset = product.and_then(|p| base.checked_add(p)); 169 assert!(product.is_none() || offset.is_some() || base > usize::MAX - product.unwrap()); 170 }); 171 } 172 173 #[kani::proof] 174 fn segment_file_bounds_check() { 175 let data_len: u64 = kani::any(); 176 let p_offset: u64 = kani::any(); 177 let p_filesz: u64 = kani::any(); 178 179 kani::assume(data_len <= 0x1_0000); 180 181 let in_bounds = p_offset 182 .checked_add(p_filesz) 183 .is_some_and(|end| end <= data_len); 184 185 if in_bounds { 186 assert!(p_offset <= data_len); 187 assert!(p_filesz <= data_len); 188 } 189 } 190 191 #[kani::proof] 192 fn segment_memsz_ge_filesz_check() { 193 let p_memsz: u64 = kani::any(); 194 let p_filesz: u64 = kani::any(); 195 196 let valid = p_memsz > 0 && p_memsz >= p_filesz; 197 if valid { 198 assert!(p_memsz >= p_filesz); 199 } 200 } 201}