//! Seed-corpus generator for the source2rosetta fuzz targets. //! //! The targets parse ELF64 bytes, so a good seed is a *valid* ELF that gets the fuzzer past the magic //! and into the section/symbol/reloc/schema/RTTI parsing where the real edge cases live. We build a //! minimal-but-structurally-valid ELF in-process (no committed Valve bytes), add a couple of malformed //! variants to seed the error paths, and — for richer real-world coverage — copy a small system ELF if //! one is present. libfuzzer mutates + minimises from there. //! //! Run: cd source2rosetta/fuzz && cargo +nightly run --bin gen_corpus use std::fs; use std::path::Path; const TARGETS: &[&str] = &[ "fuzz_elf", "fuzz_schema", "fuzz_rtti", "fuzz_sig_abi", "fuzz_xref", ]; fn w16(v: &mut [u8], o: usize, x: u16) { v[o..o + 2].copy_from_slice(&x.to_le_bytes()); } fn w32(v: &mut [u8], o: usize, x: u32) { v[o..o + 4].copy_from_slice(&x.to_le_bytes()); } fn w64(v: &mut [u8], o: usize, x: u64) { v[o..o + 8].copy_from_slice(&x.to_le_bytes()); } /// A 64-byte ELF64 section header, matching the fields `elf.rs` reads. fn sec_hdr(typ: u32, flags: u64, addr: u64, off: u64, size: u64, link: u32, entsize: u64) -> Vec { let mut h = vec![0u8; 64]; w32(&mut h, 4, typ); w64(&mut h, 8, flags); w64(&mut h, 16, addr); w64(&mut h, 24, off); w64(&mut h, 32, size); w32(&mut h, 40, link); w64(&mut h, 56, entsize); h } /// A minimal, valid ELF64 with an executable `.text` (one `mov eax,1; ret`), a `.rodata` carrying an /// Itanium type name + a distinctive string, a dynstr/dynsym pair, and two relocations (one pointing a /// slot at the code, one at the type name). Enough for the fuzzer to reach every reader's happy path. fn minimal_elf() -> Vec { const SHF_ALLOC: u64 = 0x2; const SHF_EXEC: u64 = 0x4; let text: &[u8] = &[0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3]; // mov eax,1 ; ret let rodata: &[u8] = b"11CBaseEntity\0a distinctive fuzz seed string\0"; let dynstr: &[u8] = b"\0_ZTV11CBaseEntity\0"; // one Elf64_Sym (24B): st_name=1, st_info=0x12, st_shndx=1(.text), st_value=0x1000, st_size=6 let mut sym = vec![0u8; 24]; w32(&mut sym, 0, 1); sym[4] = 0x12; w16(&mut sym, 6, 1); w64(&mut sym, 8, 0x1000); w64(&mut sym, 16, text.len() as u64); // two Elf64_Rela (24B each): R_X86_64_RELATIVE (type 8), addend = target vaddr let mut relas = vec![0u8; 48]; w64(&mut relas, 0, 0x4010); // slot w64(&mut relas, 8, 8); // r_info: type RELATIVE w64(&mut relas, 16, 0x1000); // -> .text (a code-pointer target) w64(&mut relas, 24, 0x4018); w64(&mut relas, 32, 8); w64(&mut relas, 40, 0x2000); // -> .rodata type name // Lay content out after the 64-byte header; section headers go at the end. let mut body: Vec = Vec::new(); let push = |data: &[u8], body: &mut Vec| -> u64 { let off = 64 + body.len() as u64; body.extend_from_slice(data); off }; let text_off = push(text, &mut body); let rodata_off = push(rodata, &mut body); let dynstr_off = push(dynstr, &mut body); let dynsym_off = push(&sym, &mut body); let rela_off = push(&relas, &mut body); let secs = [ sec_hdr(0, 0, 0, 0, 0, 0, 0), // [0] null sec_hdr(1, SHF_ALLOC | SHF_EXEC, 0x1000, text_off, text.len() as u64, 0, 0), // [1] .text sec_hdr(1, SHF_ALLOC, 0x2000, rodata_off, rodata.len() as u64, 0, 0), // [2] .rodata sec_hdr(3, SHF_ALLOC, 0x3000, dynstr_off, dynstr.len() as u64, 0, 0), // [3] .dynstr (STRTAB) sec_hdr(11, SHF_ALLOC, 0x4000, dynsym_off, sym.len() as u64, 3, 24), // [4] .dynsym -> link 3 sec_hdr(4, SHF_ALLOC, 0x5000, rela_off, relas.len() as u64, 4, 24), // [5] .rela.dyn ]; let shoff = 64 + body.len() as u64; let mut elf = vec![0u8; 64]; elf[0..4].copy_from_slice(b"\x7fELF"); elf[4] = 2; // ELF64 elf[5] = 1; // little-endian w16(&mut elf, 16, 3); // e_type = ET_DYN w16(&mut elf, 18, 0x3e); // e_machine = x86-64 w64(&mut elf, 40, shoff); // e_shoff w16(&mut elf, 58, 64); // e_shentsize w16(&mut elf, 60, secs.len() as u16); // e_shnum elf.extend_from_slice(&body); for s in &secs { elf.extend_from_slice(s); } elf } fn write(dir: &Path, name: &str, data: &[u8]) { fs::create_dir_all(dir).unwrap(); fs::write(dir.join(name), data).unwrap(); } fn main() { let minimal = minimal_elf(); // sanity: the minimal seed must actually parse (else it's a poor seed) match source2rosetta::elf::CodeImage::from_bytes(minimal.clone()) { Ok(_) => println!("minimal_elf() parses OK ({} bytes)", minimal.len()), Err(e) => println!("WARNING: minimal_elf() failed to parse: {e}"), } let mut magic_only = vec![0u8; 64]; magic_only[0..4].copy_from_slice(b"\x7fELF"); magic_only[4] = 2; // a small real ELF for richer coverage (real symtab/eh_frame/relocs), if one is around let real = ["/usr/bin/true", "/bin/true", "/usr/bin/head"] .iter() .find_map(|p| fs::read(p).ok()); for t in TARGETS { let dir = Path::new("corpus").join(t); write(&dir, "minimal.elf", &minimal); write(&dir, "magic_only.bin", &magic_only); if let Some(r) = &real { write(&dir, "real.elf", r); } println!( "seeded corpus/{t}/ ({} files)", 2 + real.is_some() as usize ); } }