#![no_main] //! Valve's in-binary name tables are read by walking every writable data section in 8-byte steps and //! treating each position as a candidate record — chasing a name pointer, two code pointers and, for the //! entity-IO table, an input-name pointer. Every one of those fields is whatever the file says it is, so a //! crafted (or truncated) `.so` can aim them anywhere; the readers must answer with fewer records, never a //! panic. Also exercises the accessors the derivation reads off each record, and the ambiguity rule that //! decides which names are safe to ship as locators. use libfuzzer_sys::fuzz_target; use source2rosetta::elf::CodeImage; use source2rosetta::valvetab; fuzz_target!(|data: &[u8]| { let Ok(img) = CodeImage::from_bytes(data.to_vec()) else { return; }; let pulse = valvetab::pulse_bindings(&img); for b in &pulse { let _ = ( b.name.len(), b.display.as_ref().map(String::len), b.description.as_ref().map(String::len), b.descriptor, b.arg_descriptor, b.flags.raw, ); } let inputs = valvetab::datadesc_inputs(&img); for i in &inputs { let _ = (i.handler.len(), i.io_name.len(), i.func); } // Array segmentation walks BACKWARD and forward from a confirmed input over addresses the file // controls, so every step is arithmetic on untrusted values — the same shape as the `r.base + 8` // overflow the Pulse reader had to be hardened against. It must yield fewer arrays, never a panic. let arrays = valvetab::datadesc_arrays(&img); for a in &arrays { // An array is only recorded because it holds an input, and a field descriptor's offset is read // as a u32 — so neither list may come back as something the caller has to re-validate. assert!(!a.inputs.is_empty(), "an array with no inputs was recorded"); for (n, o) in &a.fields { let _ = (n.len(), *o); } } // Every input the array walk finds must also be one the direct reader finds: the two disagree only // if one of them is reading a record the other rejects, which is a contradiction worth catching. assert!( arrays.iter().map(|a| a.inputs.len()).sum::() <= inputs.len(), "the array walk claimed more inputs than the record reader accepts" ); // The shipping gate: a name reaches gamedata only through here, so it is the part that must never // panic AND never over-claim — no name may survive with more than one address behind it. let (names, _dropped) = valvetab::names(&inputs); for n in &names { assert_eq!( inputs .iter() .filter(|i| i.handler == n.name) .map(|i| i.func) .collect::>() .len(), 1, "shipped an ambiguous handler name" ); } });