45 lines
2.2 KiB
Rust
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");
|
|
});
|