Skip to content

Validation

ewf-forensic analyses untrusted EWF/E01 forensic acquisition images and reports integrity anomalies. Correctness is therefore established the way forensic tooling must be: against independent oracles (a different tool, or a different code path, that already decodes the same bytes correctly) on real third-party corpora with known ground truth — never against fixtures we hand-encoded and then graded ourselves.

This page records exactly which oracle and which corpus back each capability, so the claim is independently re-checkable. Per-file provenance (source, download URL, hashes, tool) lives in tests/data/README.md; the fleet-wide machine index is issen/docs/corpus-catalog.md. This page cross-references both rather than duplicating them.

How to read the evidence tiers

Each validation below is tagged with the trustworthiness of its check, not whether the data is "synthetic":

  • Tier 1 — an independent third party authored the artifact and the answer key, or it is real-world data decoded by an independent tool. The strongest claim.
  • Tier 2 — real engine output whose ground truth is derivable from the documented construction, or confirmed by an independent code path on real data. Genuinely checked, but we chose the scenario.
  • Tier 3 — fixture and expected answer both authored here, nothing independent vouching. Used only for per-branch coverage, never as a correctness claim: a self-consistent round trip proves internal consistency, not correctness against real-world bytes.

Independent oracles

Oracle Independent of us? Validates Tier
ewfverify (libewf-tools 20231119) Yes — separate C codebase (Joachim Metz) Stored vs computed MD5/SHA-1/SHA-256 over decompressed media data; SUCCESS/FAILURE verdict on every committed real fixture 1
ewfacquire (libewf-tools 20231119) Yes — same C codebase, the writer Ground-truth hashes for the images it acquired (multiseg_v1, ewfacquire_clean) 1
blazehash (blazehash::algorithm::hash_bytes) Yes — independent hashing code path from the in-tree md-5/sha1/sha2 readers compute_hashes() MD5/SHA-1/SHA-256 match an independent hasher over the same recovered media bytes 2
Python zlib.compress (CPython C extension) Yes — independent RFC-1950 implementation from Rust's flate2::ZlibDecoder The EWF v2 zlib decompression path (compressed-chunk fixture authored with Python's encoder, decoded by ours) 1
The Sleuth Kit test corpus (bogus.E01/.E02, gpt_130_partitions.E01) Yes — third-party DFIR project Error-path rejection of invalid input and a clean-container baseline, cross-checked against ewfverify 1

ewfverify and ewf-forensic are also run side-by-side on the same input in tests/differential_tests.rs and tests/ctf_fixture_tests.rs, so neither tool's verdict is load-bearing alone; the differential harness classifies any divergence (false positive / false negative / characterisation / coverage). All ewfverify tests skip cleanly when the binary is absent (run_differential returns None, tests/differential_tests.rs:66).

Independent test corpora

All committed real fixtures are third-party or tool-acquired with independently established ground truth. Large CTF images are gitignored and fetched manually; the small fixtures are committed. Hashes and full provenance are in tests/data/README.md.

Corpus Source Used for License / redistribution
DFTT exfat1 / imageformat_mmls_1 Brian Carrier's Digital Forensics Tool Testing, via Digital Corpora (AWS Open Data) EnCase/FTK compressed-chunk MD5/SHA-1/SHA-256 vs ewfverify Public research corpus; gitignored, fetched per tests/data/README.md
NPS nps-2010-emails Naval Postgraduate School corpus, via Digital Corpora EnCase compressed-chunk hashes vs ewfverify Public research corpus; gitignored
ctf_file6.E01 github.com/mfput/CTF-Questions (Cal Poly CTF) Clean EWF v1 baseline, agreement with ewfverify CTF public distribution; committed
gpt_130_partitions.E01, bogus.E01/.E02 github.com/sleuthkit/sleuthkit Clean-container baseline + zero-byte error-path rejection sleuthkit test/data; committed
2011-10-19-Sample.E01 (not committed) oddin-forensic/autopsy-sample-case error2 bad-sector coverage difference vs ewfverify Sample case; gitignored, download in tests/data/README.md
CNC.E01 (not committed) HaxonicOfficial/CTF-Practice Volume/table mismatch coverage difference vs ewfverify CTF practice image; gitignored
multiseg_v1.E01..E08, ewfacquire_clean.E01, zeros_128s*.Ex01 Acquired here with ewfacquire / ewfacquirestream / Python zlib Multi-segment discovery, EWF v2 (un)compressed paths; ground truth from ewfverify/ewfacquire Tool-acquired; committed (generator commands in tests/data/README.md)

Per-capability validation

Stored-hash verification (MD5 / SHA-1 / SHA-256) — Tier 1

tests/real_image_tests.rs decompresses the full media stream of each committed real image and compares ewf-forensic's computed digest against the ground-truth value ewfverify derived independently, pinned per algorithm: e.g. exfat1_computed_md5_matches_ewfverify / exfat1_computed_sha256_matches_ewfverify, nps_emails_computed_*, mmls_computed_md5/sha1/sha256_matches_ewfverify. The multiseg_v1 set is pinned in multiseg_v1_md5_matches / multiseg_v1_sha1_matches against ewfacquire/ewfverify ground truth.

Verdict agreement with ewfverify (differential) — Tier 1

tests/differential_tests.rs runs ewfverify and ewf-forensic on the same input and asserts the verdicts agree (differential_exfat1_both_clean, _nps_emails_, _mmls_, _ewfacquire_clean_, _multiseg_v1_, _zeros_128s_, _zeros_compressed_, :136:209). Adversarial mutations are asserted to be caught by both tools (differential_tampered_compressed_chunk_both_detect, _tampered_uncompressed_chunk_, _truncated_file_, _invalid_signature_, _wrong_stored_md5_both_detect). The harness flags any true false positive / false negative; across the committed corpus none is found.

compute_hashes() independent-code-path check — Tier 2

tests/compute_hashes_tests.rs recomputes the recovered media bytes through blazehash::algorithm::hash_bytes — a hashing implementation independent of the in-tree md-5/sha1/sha2 readers — and asserts byte-equality: compute_hashes_md5_matches_blazehash_oracle (:56), compute_hashes_sha1_matches_blazehash_oracle (:76), compute_hashes_sha256_matches_blazehash_oracle (:96). A second code path (EwfIntegrityPath mmap vs in-memory EwfIntegrity) is cross-checked in ewf_integrity_path_compute_hashes_matches_ewf_integrity (:162).

EWF v2 zlib decompression — Tier 1

zeros_128s_compressed.Ex01 is authored with Python's zlib.compress(level=1) (an RFC-1950 encoder independent of Rust's flate2 decoder) and confirmed by ewfverify. tests/ewf2_compressed_chunk_tests.rs asserts ewf-forensic's compute_hashes() reproduces the ewfverify-confirmed MD5/SHA-1/SHA-256 and that flipping a byte inside the zlib stream produces ChunkDecompressionError. The uncompressed EWF v2 path is covered by zeros_128s.Ex01 (tests/ewf2_*_tests.rs).

Invalid-input rejection + diagnostic depth — Tier 1

tests/sleuthkit_fixture_tests.rs drives the sleuthkit zero-byte fixtures: bogus_e01_both_report_invalid (:114) and bogus_e02_both_report_invalid (:158) assert both tools reject the input (ewf-forensic emits a structured CRITICAL section-chain anomaly; ewfverify an open error). gpt_130_partitions_both_clean (:199) confirms a clean-container baseline agrees with ewfverify.

Coverage differences ewfverify misses (CTF, env/download-gated) — Tier 1

tests/ctf_fixture_tests.rs records two real images where ewf-forensic surfaces structure ewfverify does not check: the error2 acquisition-bad-sector section (2011-10-19-Sample.E01BadSectorsPresent) and a volume-vs-table chunk-count mismatch (CNC.E01TableChunkCountMismatch). These tests are #[ignore] and require the large images to be downloaded (instructions in tests/data/README.md).

Per-chunk Adler-32 (uncompressed chunks) — Tier 3

No real uncompressed-chunk fixture with a known-bad checksum exists in the public corpus, so ChunkChecksumMismatch detection is exercised with a builder-authored image (tests/builder.rs, tests/chunk_integrity_tests.rs: corrupt_chunk_checksum_detected, clean_chunk_checksums_no_anomaly). This is a self-authored round trip — internal consistency, not a correctness claim against real-world bytes. Gap: a real uncompressed-chunk acquisition with a corrupt Adler-32 would lift this to Tier 1/2; the clean side is partially covered by the real ewfacquire_clean.E01 / multiseg_v1 uncompressed fixtures.

Canonical reporting model — Tier 2

tests/canonical_finding_tests.rs (anomaly_converts_to_a_canonical_finding, :8) verifies EwfIntegrityAnomaly normalises onto forensicnomicon::report via the Observation producer trait (src/integrity.rs:2041), applying the 4-level→5-level severity re-grade.

Robustness — never panic, never over-read

analyse, analyse_with_progress, compute_hashes, and from_segments are property-tested to never panic on arbitrary input (tests/proptest_tests.rs: analyse_never_panics :18, analyse_with_progress_never_panics :24, compute_hashes_never_panics :32, from_segments_never_panics :38) and fuzzed by two cargo-fuzz targets (fuzz/fuzz_targets/fuzz_integrity.rs, fuzz_repair.rs; a fuzz.yml CI workflow builds and smoke-runs them). The crate contains no unsafe of its own except the read-only memmap2::Mmap::map of immutable evidence files: lints are set to unsafe_code = "deny" with a justified #[allow(unsafe_code)] on each mmap site (src/integrity_path.rs:84, :105, :144), and clippy::unwrap_used / clippy::expect_used are denied (Cargo.toml).

Reproducing the validation

The committed always-on tests run with cargo test. The CTF differentials need the large images (fetch per tests/data/README.md) and ewfverify (skips if absent).

# Real-image hash pinning + clean-verdict (committed fixtures, always run)
cargo test --test real_image_tests

# compute_hashes() vs the blazehash independent code path
cargo test --test compute_hashes_tests

# Per-chunk Adler-32 (synthetic builder)
cargo test --test chunk_integrity_tests

# sleuthkit baseline + zero-byte rejection
cargo test --test sleuthkit_fixture_tests

# Differential: ewf-forensic vs ewfverify (needs `brew install libewf`; skips if absent)
cargo test --test differential_tests
cargo test --test ctf_fixture_tests

# CTF coverage-difference tests (download the large images first, then):
python3 -c "
import urllib.request
urllib.request.urlretrieve(
    'https://raw.githubusercontent.com/oddin-forensic/autopsy-sample-case/master/2011-10-19-Sample.E01',
    'tests/data/2011-10-19-Sample.E01')
urllib.request.urlretrieve(
    'https://raw.githubusercontent.com/HaxonicOfficial/CTF-Practice/master/CNC.E01',
    'tests/data/CNC.E01')
"
cargo test --test ctf_fixture_tests -- --ignored

# Run ewfverify independently to reproduce the ground-truth values
ewfverify -q tests/data/ctf_file6.E01
ewfverify -q tests/data/gpt_130_partitions.E01
ewfverify -d sha256 -d sha1 tests/data/zeros_128s_compressed.Ex01

# Full suite (includes property tests)
cargo test

Coverage & fuzzing as backstops

Line coverage is enforced in CI (cargo llvm-cov, failing on any zero-hit line not annotated // cov:unreachable). Coverage is a regression backstop that proves behavior is exercised — it is not the correctness claim. The oracles above are.