test(fuzz): fuzz B-tree v2 traversal, not just header parsing
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]>
This commit is contained in:
@@ -1 +1,4 @@
|
||||
target/
|
||||
corpus/
|
||||
artifacts/
|
||||
coverage/
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
#![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] {
|
||||
let _ = clawhdf5_format::btree_v2::BTreeV2Header::parse(
|
||||
data,
|
||||
0,
|
||||
offset_size,
|
||||
length_size,
|
||||
);
|
||||
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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user