CI / test (push) Failing after 4s
- Add ensure_len(data, offset, needed) helper to chunked_read.rs, data_read.rs, and local_heap.rs (matching the existing btree_v1.rs/ object_header.rs convention) and use it at every plain-arithmetic offset+size bounds check found in these files, closing usize-overflow panics reachable from crafted near-usize::MAX offsets/addresses. - collect_chunk_info: add a depth-limited internal wrapper (collect_chunk_info_inner, MAX_CHUNK_BTREE_DEPTH=64) to reject a crafted self-referencing/cyclic B-tree v1 chunk index instead of recursing unboundedly (stack-overflow DoS). - read_compound_fields: validate byte_offset+field_size against the compound's declared element size before slicing, instead of an unguarded out-of-bounds panic on a crafted member offset. - read_chunked_data/_cached/_sweep/_indexed: guard `ndims - 1` against underflow for a degenerate zero-dimension chunked layout. - copy_chunk_to_output: rewrite all offset/stride arithmetic (both the 1-D fast path and the general N-D path) to use checked_add/checked_mul, skipping an out-of-range row/chunk instead of panicking on overflow. Add a new cargo-fuzz target, fuzz_dataset_read, that walks every dataset in a parsed file via the clawhdf5 facade and exercises the contiguous/ chunked/compact raw-data read paths that the existing fuzz_full_file target doesn't reach. Seeded with the chunked/VDS/compound-relevant test fixtures plus two crash regressions found during this pass (the copy_chunk_to_output overflow and the ndims-1 underflow, both fixed above — this target found real bugs within the first couple of runs). Not wired into CI (nightly-only, multi-minute runs); documented in fuzz/README.md as a manual/scheduled check instead. Also fixed the README's stale rustyhdf5-format naming while touching this file. Added regression tests for every fix (near-usize::MAX offsets, the self-referencing B-tree case, the compound byte_offset overrun, the zero-dim layout, and both copy_chunk_to_output overflow paths) so these are caught by `cargo test`, not just the fuzz corpus.
46 lines
1.5 KiB
Rust
46 lines
1.5 KiB
Rust
#![no_main]
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
const MAX_WALK_DEPTH: usize = 16;
|
|
|
|
/// Walk groups/datasets from `group`, exercising every dataset-reading code
|
|
/// path reachable through the public API (contiguous/chunked/compact raw
|
|
/// reads via `chunked_read.rs`/`data_read.rs`). Depth-limited independently
|
|
/// of any parser-level recursion guard, since this is fuzz-harness
|
|
/// bookkeeping, not something under test.
|
|
fn walk_group(group: &clawhdf5::Group, depth: usize) {
|
|
if depth > MAX_WALK_DEPTH {
|
|
return;
|
|
}
|
|
if let Ok(names) = group.datasets() {
|
|
for name in names {
|
|
if let Ok(dataset) = group.dataset(&name) {
|
|
let _ = dataset.shape();
|
|
let _ = dataset.max_dimensions();
|
|
let _ = dataset.dtype();
|
|
let _ = dataset.read_raw_ref();
|
|
let _ = dataset.read_f64();
|
|
let _ = dataset.read_f32();
|
|
let _ = dataset.read_i32();
|
|
let _ = dataset.read_i64();
|
|
let _ = dataset.read_u64();
|
|
let _ = dataset.read_string();
|
|
}
|
|
}
|
|
}
|
|
if let Ok(names) = group.groups() {
|
|
for name in names {
|
|
if let Ok(subgroup) = group.group(&name) {
|
|
walk_group(&subgroup, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
let Ok(file) = clawhdf5::File::from_bytes(data.to_vec()) else {
|
|
return;
|
|
};
|
|
walk_group(&file.root(), 0);
|
|
});
|