Skip to content

lints: adopt canonical clippy block; bound the VHD BAT allocation by the file - #6

Merged
h4x0r merged 6 commits into
mainfrom
lints/canonical-lints
Aug 8, 2026
Merged

lints: adopt canonical clippy block; bound the VHD BAT allocation by the file#6
h4x0r merged 6 commits into
mainfrom
lints/canonical-lints

Conversation

@h4x0r

@h4x0r h4x0r commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

fix(vhd): bound the BAT by the file before allocating; adopt canonical lints

GREEN for the preceding RED commit.

parse_dynamic now checks the claimed BAT extent against the actual file
length before allocating, and reports the offending size and offset
verbatim rather than letting it surface as "failed to fill whole buffer":

VHD BAT claims 17179869180 bytes at offset 1536, past the end of
the 2560-byte file

The BAT cannot outgrow the file that holds it, so the file length is the
natural bound (ADR-0012: never trust a length field). The usize::try_from
on the byte count also removes the max_entries * 4 multiplication, which
overflows usize on a 32-bit target.

disk-forensic had [lints.rust] unsafe_code = "forbid" but NO
[lints.clippy] table at all. It now carries the canonical recipe
(CLAUDE.core.md "Rust Lint Posture") plus the ADR-0012 superset:
correctness/suspicious denied, unwrap_used/expect_used denied, all +
pedantic warned with the canonical cast_* allows.

Sixteen production findings surfaced; all are closed at the source:

clippy::unwrap_used 4 VHD footer and BAT integer reads
moved to safe-read bounded readers
(be_u64/be_u32), which is also what
retires the try_into().unwrap()
idiom in this file
clippy::doc_markdown 5 backticks added
clippy::cast_lossless 4 u32::from
clippy::missing_fields_in_debug 1 finish() -> finish_non_exhaustive()
on LogicalImage, whose Debug omits
the non-Debug backend
clippy::items_after_statements 1 ISO_PVD_OFFSET hoisted to module
scope beside the other magic consts

Two allows, each narrow and with its reason recorded:

too_many_lines container::open is a flat dispatch over every supported
container format; splitting it would scatter one
readable match across helpers. Allowed across the fleet.

redundant_else Two sites in disk4n6. Without serde the JSON arm ends
in return, so clippy reads the else as redundant;
with serde enabled it is required. Verified the lint
does NOT fire under --all-features, so removing the
else would break the feature-on build.

Gate: cargo build --all-targets --all-features, cargo test (21 suites) and
cargo test --all-features (21 suites), cargo clippy --all-targets -- -D
warnings under BOTH default and --all-features, cargo fmt --check - all
clean.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

🤖 Generated with Claude Code

h4x0r and others added 2 commits August 6, 2026 08:11
VhdReader::parse_dynamic reads max_entries from the dynamic-disk
"cxsparse" header and allocates from it directly:

    let max_entries = u32::from_be_bytes(dh[28..32]...) as usize;
    let mut bat_raw = vec![0u8; max_entries * 4];

max_entries is an untrusted u32 straight out of the image. block_size is
validated on the line above it; max_entries is not. A header claiming
0xFFFF_FFFF entries asks for 17_179_869_180 bytes - 16 GiB - before a
single BAT byte is known to exist. The fixture here is under 3 KiB.

RED: the new test fails against the current code with

    expected a BAT-bounds error naming the offending size,
    got: Vhd decode error: failed to fill whole buffer

Observed behaviour, stated precisely rather than assumed: on this host
(macOS, lazily-committed zero pages) the 16 GiB allocation SUCCEEDS, and
the failure only surfaces later as a generic read_exact error. So this is
not a guaranteed crash. It is an unbounded speculative allocation driven
by an unvalidated length field, which is a memory-pressure/DoS vector on
an allocator that commits eagerly or under a cgroup/ulimit, and which
reports an unhelpful error even when it survives. The second test pins
the happy path so the bound cannot be made too tight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l lints

GREEN for the preceding RED commit.

parse_dynamic now checks the claimed BAT extent against the actual file
length before allocating, and reports the offending size and offset
verbatim rather than letting it surface as "failed to fill whole buffer":

    VHD BAT claims 17179869180 bytes at offset 1536, past the end of
    the 2560-byte file

The BAT cannot outgrow the file that holds it, so the file length is the
natural bound (ADR-0012: never trust a length field). The usize::try_from
on the byte count also removes the `max_entries * 4` multiplication, which
overflows usize on a 32-bit target.

disk-forensic had `[lints.rust] unsafe_code = "forbid"` but NO
[lints.clippy] table at all. It now carries the canonical recipe
(CLAUDE.core.md "Rust Lint Posture") plus the ADR-0012 superset:
correctness/suspicious denied, unwrap_used/expect_used denied, all +
pedantic warned with the canonical cast_* allows.

Sixteen production findings surfaced; all are closed at the source:

  clippy::unwrap_used              4  VHD footer and BAT integer reads
                                      moved to safe-read bounded readers
                                      (be_u64/be_u32), which is also what
                                      retires the `try_into().unwrap()`
                                      idiom in this file
  clippy::doc_markdown             5  backticks added
  clippy::cast_lossless            4  u32::from
  clippy::missing_fields_in_debug  1  finish() -> finish_non_exhaustive()
                                      on LogicalImage, whose Debug omits
                                      the non-Debug `backend`
  clippy::items_after_statements   1  ISO_PVD_OFFSET hoisted to module
                                      scope beside the other magic consts

Two allows, each narrow and with its reason recorded:

  too_many_lines   container::open is a flat dispatch over every supported
                   container format; splitting it would scatter one
                   readable match across helpers. Allowed across the fleet.

  redundant_else   Two sites in disk4n6. Without `serde` the JSON arm ends
                   in `return`, so clippy reads the `else` as redundant;
                   with `serde` enabled it is required. Verified the lint
                   does NOT fire under --all-features, so removing the
                   `else` would break the feature-on build.

Gate: cargo build --all-targets --all-features, cargo test (21 suites) and
cargo test --all-features (21 suites), cargo clippy --all-targets -- -D
warnings under BOTH default and --all-features, cargo fmt --check - all
clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@h4x0r
h4x0r force-pushed the lints/canonical-lints branch from 6fd9f2e to ed59866 Compare August 6, 2026 00:11
h4x0r and others added 4 commits August 5, 2026 17:22
safe-read 0.2.1 is newly published and this store had no record for it, so
`cargo vet --locked` fails with:

    safe-read:0.2.1 missing ["safe-to-deploy"]

safe-read is ours, published to crates.io by h4x0r, so ADR-0018 mechanism (2)
applies — a trust entry keyed on the publisher, not an exemption pinned to
0.2.1 that would go stale again on its next release. This is the third time in
this sweep that publishing one of our own crates reddened consumers holding
version-pinned records; the trust entry is what stops the cycle.

Verified by control: with the entry removed `cargo vet --locked` reproduces the
failure above, and with it restored the run succeeds.
… target

Two failures, two unrelated causes.

Coverage. The gate is `--fail-under-functions 100`, and it was reporting
111 functions with 1 uncovered. The 133 uncovered LINES in the same output are
not the gate -- the job says so itself, because reader-generic code is
monomorphized per reader type and line % can never reach 100 there.

The single uncovered function was the `map_err` CLOSURE in vhd.rs's
parse_dynamic. It is invisible in the missing-lines list, because line 117
counts as covered: the `map_err` call executes, only the closure body never
does. And it never can -- `bat_bytes` is a u32 multiplied by 4, so
`usize::try_from` cannot fail on any 64-bit target.

Rewritten as `let ... else`. The guard survives untouched for a 32-bit build;
what goes is the closure, which llvm-cov counts as a function it can never see
executed. Deliberately NOT a lowered gate, and not a deleted defensive check.

Clippy. `map(|o| o.status.success()).unwrap_or(false)` in tests/live_linux.rs,
which is `#![cfg(target_os = "linux")]` and therefore invisible to clippy on a
macOS host -- the job failed on a target that cannot be linted locally without
help. Verified by lifting the cfg gate temporarily: the lint is
platform-independent even though the test is not.

Verified by control on both:

  coverage   with the fix     110 functions, 0 uncovered, 100.00%  PASS
             closure restored 111 functions, 1 uncovered,  99.10%  FAIL
  clippy     with the fix     0 errors
             map_or restored  2 errors

Both mutations were asserted applied before re-running, so neither control can
silently no-op. Full suite: 103 passed, 0 failed under --no-fail-fast.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main moved while this branch was open, conflicting on Cargo.lock,
supply-chain/audits.toml and supply-chain/imports.lock.

Resolved by taking main's copies of all three and re-deriving, rather than
hand-merging attestation records. The branch's only audits.toml addition was a
`[[trusted.safe-read]]` entry, and main had independently gained the same one,
so nothing was dropped -- checked rather than assumed, because silently losing
a trust record would quietly downgrade safe-read to an exemption and nobody
would notice.

Verified on the merged tree:

  coverage   110 functions, 0 uncovered, 100.00%   (the gated metric)
  clippy     0 errors, -D warnings
  tests      103 passed, 0 failed (--no-fail-fast)
  deny       advisories ok, bans ok, licenses ok, sources ok
  vet        Vetting Succeeded (46 fully audited, 1 partially, 103 exempted)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The merge commit staged Cargo.lock and only then refreshed it, so the refresh
never made it into the commit and a stale lock shipped. Two jobs failed on the
one cause:

  cargo-vet             cannot update the lock file ... --locked was passed
  Package completeness  cargo package regenerates the lock, so the tree reads
                        dirty and it refuses without --allow-dirty

Both are the same staleness wearing different error messages. Refreshed
minimally -- `cargo metadata`, not `cargo update`, so nothing is upgraded
beyond what the manifests already require.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@h4x0r
h4x0r marked this pull request as ready for review August 8, 2026 08:48
@h4x0r
h4x0r merged commit 4868793 into main Aug 8, 2026
20 checks passed
@h4x0r
h4x0r deleted the lints/canonical-lints branch August 8, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant