source2rosetta/fuzz/fuzz_targets/fuzz_concmd.rs
Kamal Tufekcic c458b4cb50
All checks were successful
CI / lint (push) Successful in 17s
CI / fuzz (push) Successful in 1m52s
CI / test (push) Successful in 24s
read what the binary says about itself: names, signatures, prototypes; gen v2
2026-07-29 20:09:21 +03:00

45 lines
2.2 KiB
Rust

#![no_main]
//! Console-command extraction decodes every function in the image and does arithmetic on values the
//! FILE controls at every step: `lea` displacements are added to tracked register contents, member
//! offsets are added to a symbolic base, and the accessor window walks `arg3 + k*8` looking for a stored
//! pointer. A crafted (or truncated) `.so` can make any of those wrap, point outside every section, or
//! nest arbitrarily deep — and the reader must answer with fewer commands, never a panic.
//!
//! It also exercises the two indirections that resolve a callback (a static object's first virtual, and
//! a constructor-stored member) against pointers the file chose, which is the same untrusted-chase shape
//! the Valve table readers had to be hardened for.
use libfuzzer_sys::fuzz_target;
use source2rosetta::concmd;
use source2rosetta::elf::CodeImage;
fuzz_target!(|data: &[u8]| {
let Ok(img) = CodeImage::from_bytes(data.to_vec()) else {
return;
};
let cmds = concmd::console_commands(&img);
for c in &cmds {
// A recovered handler is only ever accepted because it lands in executable code, so the reader
// must never hand back one that does not — a caller treats this as a locator.
assert!(
img.is_code(c.handler),
"a non-executable handler was recorded for {:?}",
c.name
);
// The name gate is what stops prose and format strings being read as commands.
assert!(
!c.name.is_empty() && c.name.len() <= 64,
"an implausible command name was recorded: {:?}",
c.name
);
// Flag decoding is a pure bit test and must stay within the bits it claims to know.
let named = concmd::flag_names(c.flags);
assert!(named.len() <= 12, "more flag names than there are flag bits");
let _ = (c.description.len(), c.form.describe(), c.flags);
}
// Commands are deduped on (name, address), so no pair may survive twice.
let mut seen: Vec<(&str, u64)> = cmds.iter().map(|c| (c.name.as_str(), c.handler)).collect();
let before = seen.len();
seen.sort_unstable();
seen.dedup();
assert_eq!(before, seen.len(), "a duplicate (name, handler) survived");
});