fix: harden against confirmed review findings - #2
Merged
Conversation
Fixes four confirmed findings from an automated multi-agent review, each reproduced against the current source and guarded by a new regression test (all 8 fail on the pre-fix source, pass after): 1. (security, headline) Bad-checksum "resync" no longer reinterprets a member's data as subsequent headers. A corrupt member is skipped as a whole unit (header + padded declared size); if the size is implausible the archive is rejected. Pending GNU long-name/long-link/pax state is cleared. This closes a content-smuggling / scanner-evasion differential where a file whose contents are a valid tar surfaced its inner members as top-level entries. 2. (security) Base-256 numeric decode raises when the value exceeds 64 bits instead of silently wrapping a 95-bit field into a signed Int. 3. (correctness) add_symlink emits pax path/linkpath records for names or targets over 100 bytes instead of silently truncating; _set_str now raises on any field overflow (e.g. uname/gname over 32 bytes). 4. (security/doc) GNU sparse members (typeflag 'S' or GNU.sparse.* pax records) are rejected rather than mis-read and desynced; documented in the module docstring and SECURITY.md. Build prerequisite: fixed the pixi mojo pin (>=1.0.0b3 -> >=1.0.0b3.dev0) so the dev nightly toolchain solves. Co-Authored-By: Claude <noreply@anthropic.com>
Owner
Author
|
🤖 Independent Claude review: Ready to mark for review. Second-look pass on all four fixes + the pixi pin. No blocking correctness/security issue found. Verified:
Non-blocking notes (polish only):
Draft is sound; the three notes are optional follow-ups, not merge blockers. |
conorbronsdon
marked this pull request as ready for review
July 6, 2026 07:58
Owner
Author
|
Code review (opus, static — no toolchain, verify CI): CHANGES-NEEDED Findings 2/3/4 are correct and well-executed (base-256 overflow guard,
Fix: on a non-first bad-checksum block, raise (mirror |
…d size The "anti-smuggling" resync was itself bypassable. On a bad-checksum member, the parser resynced with skip_end = pos + BLOCKSIZE + _padded(bad_size), where bad_size was read from the corrupt, attacker-controlled header. A crafted size of 0 passed the plausibility gate (0 >= 0, 0 <= n) and made skip_end = pos + BLOCKSIZE -- degrading to the old single-block skip, so the member's data was reinterpreted as top-level headers: the exact content-smuggling / scanner-evasion vector the PR claimed to close. Fix: a bad checksum on a non-first block now raises (mirroring CPython tarfile's ReadError), consistent with the existing first-block branch. There is no trustworthy way to learn where a corrupt member's data ends, so we reject rather than guess. - src/tar/tar.mojo: replace the size-based resync with a raise. - SECURITY.md / README.md: correct the now-false "skipped as a whole unit" claim to describe the abort-on-corruption behavior. - test/test_tar.mojo: add test_bad_checksum_zero_size_bypass_raises (the crafted size=0 case); update test_bad_checksum_member_raises and test_bad_checksum_data_not_reinterpreted to expect the raise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
From an automated multi-agent review (personal-context#62); implemented + verified by Claude Code.
Each fix was reproduced against the current source, then re-checked after the fix. Every finding has a new regression test in
test/test_tar.mojo; all 8 new tests fail on the pre-fix source and pass after (verified by stashing the src changes and re-running). Full suite: 37/37 pass;pixi run fuzzandpixi run demoboth clean.1. (security, headline) Bad-checksum "resync" reinterpreted member data as headers
_parse_archiveskipped only a single block on a checksum mismatch, so a file member's data was reparsed as top-level headers. A file whose contents are themselves a valid tar surfaced its inner members as top-level entries — a content-smuggling / scanner-evasion differential (a scanner sees one opaque corrupt file; mojo-tar surfaces the hidden member).Fix: skip the whole member (header + padded declared size). If the corrupt block's size field is implausible (
<0or> archive), resync is impossible → raise. Pending GNU long-name/long-link/pax overrides are cleared so a corrupt member can't leak them onto a later member.Before / after (repro: outer archive
readme.txt+innocent.dat, whereinnocent.dat's content is an inner tar carryingSMUGGLED.txt; corruptinnocent.dat's header name byte):Regression tests:
test_bad_checksum_data_not_reinterpreted,test_bad_checksum_implausible_size_raises.2. (security) Base-256 size decode wrapped a 95-bit field into 64 bits
_get_numshifted an up-to-95-bit base-256 field into a signed 64-bitInt, silently wrapping a huge size to a small/negative value. Fix: raise if the running value would overflow on the next<< 8(v > Int.MAX >> 8). Regression test:test_base256_oversized_size_raises.3. (correctness) add_symlink silently truncated long names/targets
add_symlinkpassed name/target straight into the 100-byte ustar fields, silently truncating anything longer; over-length uname/gname (32-byte field) were also truncated silently. Fix:add_symlinknow emits paxpath/linkpathrecords for >100-byte name/target (mirroringadd), so they round-trip;_set_strnow raises on any field overflow instead of truncating. Regression tests:test_writer_symlink_long_name_roundtrips,test_writer_symlink_long_target_roundtrips,test_writer_oversized_uname_raises.4. (security/doc) GNU sparse members desynced the parse
A GNU sparse member (typeflag
S, orGNU.sparse.*pax records) stores a hole map plus only non-hole bytes, so its archived length differs from its logicalsize— parsing it as a normal member desyncs the rest of the archive. Fix: detect and raise on both forms. Documented in the module docstring andSECURITY.md. Regression tests:test_gnu_sparse_typeflag_raises,test_gnu_sparse_pax_raises.Build prerequisite (bundled)
The
pixi.tomlmojo pin>=1.0.0b3sorts below dev nightlies, sopixi installfailed to solve on the nightly toolchain. Fixed to>=1.0.0b3.dev0,<2(same class of fix confirmed in mojo-redis). Verifiedpixi installnow solves.Notes on scope / parallel work
README.mdorCHANGELOG.md(a separate agent is editing those in parallel PRs). Overlap to flag: finding 4 asks for a "Sparse files" bullet in the README "What it deliberately does NOT do" list — I documented sparse in the module docstring +SECURITY.mdinstead; the README bullet should be added by the docs PR.SECURITY.md(findings 1 & 4) is the one file-doc change that belongs with these fixes.Draft — not for merge without Conor's review.