Repository navigation
ntfs: bound the MFT; exfat: clamp the boot cluster count - #26
Merged
Merged
Conversation
When record 0's $DATA cannot be read, the fallback $MFT run was (4, 1 << 20) regardless of volume size, so record_count could claim over half a billion records and build_tree() (every listdir) read them all: the fuzzer's hang. Clamp the fallback run to cluster_count, clamp the computed record_count to source_size // record_size (with a finding when it fires), and floor the boot sector's record size at 128 bytes so a degenerate value falls back to the usual 1024. record() also cached the parsed record before checking rec.valid, so an unformatted record was None on first lookup and an invalid MftRecord (no .sequence) on every later one: the fuzzer's AttributeError in stat(). Only valid records enter the cache now. Flips test_record_count_bounded_by_volume and test_invalid_record_stays_invalid_on_second_lookup. Fixes part of switch-nz#15.
The switch-nz#17 heap-end clamp in chain() trusts cluster_count itself, so a boot sector claiming 0xFFFFFFFF clusters (offset 92) still let a NoFatChain stream expand by the billions: the fuzzer's stat loop hung on the mutated image even after that fix. Bound the boot sector's cluster count by what the image actually holds past the cluster heap (with a finding), which protects both the contiguous limit and the FAT-walk bound. Fixes part of switch-nz#15.
Adds the Unreleased entries CONTRIBUTING.md asks for, and brings main into the branch so they sit with the jbd2 entry above 0.1.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
switch-nz
added a commit
to deadp/strata
that referenced
this pull request
Sep 17, 2026
Resolves the CHANGELOG conflict with switch-nz#26 by keeping both sets of entries. The exFAT timestamp entry now says that an entry recording no valid offset is still shown unadjusted and labelled UTC, so examiners know to read those times as the device's local time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
|
Merged — thanks, Josh. Reviewed and verified:
Pushed before merging: One residual worth knowing, not a blocker. The exFAT bound is now the image's real size, which is right, but |
This was referenced Sep 17, 2026
Closed
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stage 3 of the #15 fuzzer findings, plus the maintainer follow-up on #17.
NTFS — item 3 (hang + AttributeError)
$DATAcannot be read, the fallback$MFTrun was(mft_cluster, 1 << 20)regardless of volume size — the fuzzer's hang input then claimed 536,870,912 records andbuild_tree()(every listdir) read them all. The fallback run is now clamped tocluster_count, and the computedrecord_countis clamped tosource_size // record_sizewith a finding when that fires.record()cached the parsed record before checkingrec.valid, so an unformatted record wasNoneon first lookup and an invalidMftRecord(no.sequence,stat()→AttributeErrorat ntfs.py:699) on every later one. Only valid records enter the cache now; cache hits need no re-check.test_record_count_bounded_by_volumeandtest_invalid_record_stays_invalid_on_second_lookup(both were@unittest.expectedFailurebug markers).exFAT — item 1 residual (#17 follow-up)
The #17 heap-end clamp in
chain()trustscluster_countitself, so a boot sector claiming 0xFFFFFFFF clusters (offset 92) still let a NoFatChain stream expand by the billions — the reviewer's mutation of the saved #17 input mounted and hung a stat loop. The boot sector's cluster count is now bounded by what the image actually holds past the cluster heap (with a finding), which protects both the contiguous limit and the FAT-walk bound.Verification
Ran 305, OK (expected failures=14)(baseline 304/16: −2 flips, +1 new test);tests/smoke.pyexit 0.ntfs-hang-3ebda510.binpreviously exit 124 undertimeout 30;ntfs-exception-b9407023.binpreviously AttributeError;exfat-memory-4c12571f.binunchanged).test_cluster_count_bounded_by_imagepins the exFAT clamp; post-fix probe of the hang image:record_size == 1024,record_count == 32 ≤ 128,record(30)→Noneon repeat lookups,stat({"mft": 30})→{}.Fixes part of #15.