Skip to content

Bound every size-driven read when opening EWF images - #14

Merged
switch-nz merged 7 commits into
switch-nz:mainfrom
deadp:ewf-size-limits
Sep 17, 2026
Merged

switch-nz merged 7 commits into
switch-nz:mainfrom
deadp:ewf-size-limits

Conversation

@deadp

@deadp deadp commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

This branches from #13. Only the last commit (Bound every size-driven read when opening EWF images) is new. Once #13 merges, this diff shrinks to that commit alone.

Problem

EwfImage used sizes and offsets from the segment file directly as read lengths and seek offsets:

  • the section descriptor's size (volume, header, hash and digest sections)
  • the table's base offset and entry count
  • the section next pointer
  • sectors_per_chunk × bytes_per_sector, which also sizes the zero-fill for a chunk that won't decompress

One damaged or hostile descriptor could therefore make Strata try to read gigabytes into memory. In other cases it raised OverflowError or OSError from seek, instead of recording a finding. tests/fuzz.py hit all of these within 3 minutes (ewf.py:178, 195, 211, 291, 305, 306, 317).

Fix

  • One bounded read path. Everything now reads through _read_bounded, which stops at the end of the segment and at a per-structure cap: metadata sections 1 MB, header sections 16 MB, table entries 2^20, chunks at the chunk size plus zlib's worst-case expansion.
  • Clips become findings. A section or chunk that declares more than can be read is recorded, not silently truncated. A chunk that merely runs into the end of the file is not flagged, since a final chunk sized from the nominal length does that legitimately.
  • Implausible geometry falls back to defaults. Bytes per sector outside 512–4096, or chunks over 64 MB, fall back to the defaults, with a finding.
  • A failed open closes its segment handles. Previously they leaked, which on Windows keeps the evidence file locked.

Readable images behave as before: the existing EWF tests pass unchanged, including the ones asserting no findings on clean images.

Tests

  • New HostileSizes class in tests/test_ewf.py with 8 tests. All 8 fail or error on the previous ewf.py and pass with this change.
  • Every EWF input the CI fuzz run saved (11 distinct) now opens, or fails with a clean EwfError.
  • Full suite: 297 tests, OK (17 expected failures, all unrelated to this change). Both files parse as Python 3.8.

🤖 Generated with Claude Code

deadp and others added 7 commits September 16, 2026 21:52
Locks in the Host, Origin and Sec-Fetch-Site checks that keep other
origins and DNS-rebinding pages from driving the local interface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Synthetic images are built in code (tests/imagebuild_*.py) so no evidence
or binary fixture is committed. Ten engine bugs found along the way are
recorded as expected failures, each naming the fault: inline data and
inline directories, sparse extents, extent-tree blow-up, the deleted-file
path without a journal, short and truncated hives, over-broad EWF segment
discovery, truncated zlib chunks, and split raw sets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Volumes are built in code (FAT32 sparsely, as it needs ~34 MB) and opened
the way the server opens them. Seven engine bugs are recorded as expected
failures: scrambled deleted long names and wrong slack for deleted files
on FAT; an unbounded contiguous run list, truncated contiguous directories
and unconverted UTC offsets on exFAT; invalid MFT records served from the
cache, and a record count that ignores volume size when $MFT is unreadable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests/fuzz.py mutates the synthetic images and drives the app's own entry
points in a child worker, so hangs and memory blow-ups are caught and
their inputs kept for replay. It runs in its own workflow: briefly and
non-blocking on PRs that touch the engine, and at length weekly or on
demand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
EwfImage._fh is a method, not a handle, so the harness's cleanup raised
on every EWF case and swamped the real findings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Section sizes, table base offsets, section pointers and chunk geometry all
come from the segment file, and were used directly as read lengths and seek
offsets. One corrupt descriptor could make Strata try to read gigabytes into
memory, or raise OverflowError/OSError instead of reporting the damage.

Reads now go through _read_bounded, capped by the end of the segment and by
what the format can hold. Metadata sections are capped at 1 MB, headers at
16 MB, table entries at 2^20, and chunks at the declared chunk size plus
zlib's worst-case expansion. Implausible sector or chunk sizes fall back to
the defaults. Each clip is recorded as a finding rather than dropped. A
failed open also closes its segment handles, which would otherwise stay
locked on Windows.

Found by tests/fuzz.py; the new HostileSizes tests fail on the previous
code and every recorded EWF fuzz input now opens or fails cleanly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@switch-nz
switch-nz merged commit eed8a24 into switch-nz:main Sep 17, 2026
25 of 26 checks passed
switch-nz added a commit to deadp/strata that referenced this pull request Sep 17, 2026
Known issues pointed only at switch-nz#15, which covers crashes and hangs. The
bugs that matter more to an examiner reading release notes are the ones
that return wrong or missing results with no finding, tracked in switch-nz#19:
exFAT timestamps labelled UTC without applying the offset, sparse ext4
files at the wrong offsets, misread ext4 inline data, scrambled deleted
FAT names and misplaced FAT slack, a truncated EWF chunk returned short,
and split raw sets loading as a partial disk. Each is still an expected
failure on main, so each is listed individually rather than behind a
link.

0.1.0 is not tagged yet, so what has merged since this changelog was
written ships in it: the EWF read bounds (switch-nz#14), the exFAT contiguous
run clamp (switch-nz#17) and the parser tests, fuzzing and CodeQL (switch-nz#13) are
added. Raw/dd is described as single-file and split raw sets move to
Not implemented, matching the README correction in switch-nz#20.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@deadp
deadp deleted the ewf-size-limits branch September 17, 2026 10:33
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.

2 participants