read what the binary says about itself: names, signatures, prototypes; gen v2
This commit is contained in:
parent
54ef572202
commit
c458b4cb50
34 changed files with 58363 additions and 192 deletions
45
fuzz/fuzz_targets/fuzz_concmd.rs
Normal file
45
fuzz/fuzz_targets/fuzz_concmd.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#![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");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue