Files
clawhdf5/crates/clawhdf5-format/fuzz
osobhandClaude Opus 5 0901fb1499 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]>
2026-09-20 17:03:21 -07:00
..

Fuzz Testing for clawhdf5-format

Uses cargo-fuzz (libFuzzer) to test parser robustness against malformed inputs.

Prerequisites

cargo install cargo-fuzz
rustup toolchain install nightly

Fuzz Targets

Target Parser Description
fuzz_superblock Superblock::parse Superblock parsing (v0-v3) with signature search
fuzz_object_header ObjectHeader::parse Object header v1/v2 with various offset/length sizes
fuzz_datatype Datatype::parse All 12 HDF5 datatype classes (recursive)
fuzz_dataspace Dataspace::parse Dataspace messages with various length sizes
fuzz_fractal_heap FractalHeapHeader::parse Fractal heap header parsing
fuzz_btree_v2 BTreeV2Header::parse B-tree v2 header parsing
fuzz_filter_pipeline FilterPipeline::parse Filter pipeline messages (v1/v2)
fuzz_full_file signature + superblock + root group End-to-end file parsing chain
fuzz_dataset_read Dataset::read_* (via clawhdf5) Walks every dataset in the parsed file and exercises the contiguous/chunked/compact raw-data read paths (chunked_read.rs, data_read.rs) that fuzz_full_file doesn't reach

Running

Run a single target (runs indefinitely until stopped or a crash is found):

cd crates/clawhdf5-format
cargo +nightly fuzz run fuzz_datatype

Run with a time limit (seconds):

cargo +nightly fuzz run fuzz_datatype -- -max_total_time=60

Run all targets for 30 seconds each:

for target in fuzz_superblock fuzz_object_header fuzz_datatype fuzz_dataspace \
              fuzz_fractal_heap fuzz_btree_v2 fuzz_filter_pipeline fuzz_full_file \
              fuzz_dataset_read; do
    echo "=== $target ==="
    cargo +nightly fuzz run "$target" -- -max_total_time=30 -max_len=4096
done

CI

These targets are not run in CI (.gitea/workflows/ci.yml) — cargo-fuzz requires nightly and each meaningful run takes minutes, which doesn't fit a per-PR gate. Run them manually on a schedule (e.g. before a release, or after touching parser code) instead.

Reproducing Crashes

If a crash is found, the input is saved to fuzz/artifacts/<target>/. Reproduce with:

cargo +nightly fuzz run fuzz_datatype fuzz/artifacts/fuzz_datatype/crash-<hash>

Minimize the crashing input:

cargo +nightly fuzz tmin fuzz_datatype fuzz/artifacts/fuzz_datatype/crash-<hash>

Design

Each fuzz target feeds arbitrary bytes directly to a parser entry point. The parsers must never panic on any input -- they should return Err(FormatError) for malformed data. Any panic found by fuzzing is a bug that should be fixed with proper bounds checks and error returns.