The existing target only called `BTreeV2Header::parse`, so the recursive walk behind it — where a node that is its own child overflowed the stack — was never fuzzed at all. Parsing also requires a valid Jenkins checksum, which random input essentially never produces, so almost every input stopped at the first branch. The target now walks the tree after a successful parse, and also builds a header straight from the input bytes so the traversal is reachable without forging a checksum. Checked both ways: against the unfixed traversal libFuzzer finds the stack overflow (ASan: stack-overflow), and against the fix that same input executes in 0 ms and 34.7 million further runs produce no crash, timeout or OOM. Corpora and crash artifacts stay out of the repository; the two crafted inputs are covered by unit tests instead. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
37 lines
1.5 KiB
Rust
37 lines
1.5 KiB
Rust
#![no_main]
|
|
use clawhdf5_format::btree_v2::{BTreeV2Header, collect_btree_v2_records};
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
for &offset_size in &[4u8, 8] {
|
|
for &length_size in &[4u8, 8] {
|
|
if let Ok(header) = BTreeV2Header::parse(data, 0, offset_size, length_size) {
|
|
let _ = collect_btree_v2_records(data, &header, offset_size, length_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parsing a header requires a valid checksum, which random input almost
|
|
// never has, so the traversal behind it went unfuzzed — and that is where
|
|
// a node listing itself as its own child overflowed the stack. Take the
|
|
// header fields straight from the input instead and walk the rest.
|
|
let Some((fields, file)) = data.split_first_chunk::<20>() else {
|
|
return;
|
|
};
|
|
let header = BTreeV2Header {
|
|
tree_type: fields[0],
|
|
node_size: u32::from_le_bytes([fields[1], fields[2], fields[3], fields[4]]),
|
|
record_size: u16::from_le_bytes([fields[5], fields[6]]),
|
|
depth: u16::from_le_bytes([fields[7], fields[8]]),
|
|
root_node_address: u64::from(u32::from_le_bytes([
|
|
fields[9], fields[10], fields[11], fields[12],
|
|
])),
|
|
num_records_in_root: u16::from_le_bytes([fields[13], fields[14]]),
|
|
total_records: u64::from(u32::from_le_bytes([
|
|
fields[15], fields[16], fields[17], fields[18],
|
|
])),
|
|
};
|
|
let offset_size = if fields[19] & 1 == 0 { 4 } else { 8 };
|
|
let _ = collect_btree_v2_records(file, &header, offset_size, 8);
|
|
});
|