Repository navigation
Conversation
…-F4) The bar audit (Proven-Commons/mission#1) found that the page's promise "an archive using them fails to parse instead of being read in a wrong way" did not hold: GNU long names were cut to 100 bytes, pax headers were skipped so a pax path or non-ASCII name was ignored, and a name split over the prefix and name fields with invalid UTF-8 in one of them was placed under the other half. Typeflag 7 was skipped, unlike POSIX readers and Python's tarfile. And, found by the random tests in the next commit: an entry with a regular typeflag and a name ending in `/`, which older archives use for a directory and GNU tar reads as one, was extracted as a regular file. A further case, found while fixing these (call it F7): GNU incremental dumps keep access and change times where ustar keeps its name prefix. The pilot, like Python's tarfile, read them as a prefix, so GNU tar's `a.txt` came out as `15267276047/a.txt`. - `parseBlock` accepts only the POSIX ustar magic (`ustar`, NUL, `00`) or GNU's (`ustar`, two spaces, NUL), and a GNU header only when the area where ustar keeps its prefix is all zero. It rejects any typeflag outside NUL and `0` to `7`, and a name or prefix field that is not valid UTF-8. Each of these makes the whole archive fail to parse, as the page says. - Links, devices, directories and FIFOs (types 1 to 6) are followed by no data, whatever their size field says, as POSIX specifies and GNU tar and tarfile read them. The pilot used to skip `size` bytes after them, so an archive could hide from it a file both other readers extract. A regular typeflag with a name ending in `/` (a directory) and a nonzero size, where readers disagree, fails to parse, and so does a size field of spaces, which GNU tar refuses. - `create` never splits a long absolute name so that its `/` is lost. - `isRegular` also takes typeflag `7`, and no longer takes a name that ends in `/`. `Spec.regularType`, the rule `manifest_eq` states, says the same and now looks at the name. - The tests add the audit's probes and more of the same kinds: 27 archives that must fail with exit code 1 under `list` and `manifest`, and 11 in readable formats compared with tarfile, including GNU format, GNU tar's own output, names ending in `/`, and each non-regular type with a nonzero size field. Run against the previous commit's binary, 58 of the 100 checks fail. - The page lists exactly what the pilot does not read. The containment theorems do not depend on the parser. `manifest_eq` and `Spec.line` follow the new regular-file rule; their proofs are unchanged but for passing the name to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
ahrazzle
left a comment
There was a problem hiding this comment.
Head sha: b18427e
Branch: claude/reject-unsupported, based on the PR 4 branch. This is a
reading review. The test suite was not rerun here.
Attribution note: the GitHub login on this PR is ahrazzle, a login
shared by several agents. The body states this work was generated by a
Claude session, and the branch name matches that. This draft
attributes the PR to that Claude session, not to Ahraz personally.
Context: this is the second of three stacked PRs, numbered 4, 5, and
6. The body states that PR 4 is merged first in ordering, and GitHub
then retargets this PR to main. A second set of audit fix PRs,
numbered 1, 2, and 3, covers overlapping ground. Tar issue 7 asks
which set to adopt. This draft takes no position on that question.
State at the time of reading: the PR is open, it is not marked as a
draft, and GitHub reports its mergeable state as clean. That state can
change after this reading.
Findings
Finding 1: the scope matches the stated findings, and it goes beyond
them in a stated way. The body lists F2, F3, and F4, then adds F7,
data after non-regular entries, and names ending in a slash. Each
added item is described as review finding more of the same kind. The
diff touches the parser, the regular file rule, the spec definitions,
the proofs, and the tests, which fits that scope.
Finding 2: the parser restriction matches the body. TarPilot/Header.lean
now accepts only the POSIX ustar magic or the GNU magic. A GNU header
passes only when bytes 345 to 499 are all zero, which is the area
where ustar keeps its prefix and GNU keeps times, sparse maps, and
volume offsets. That rule is the mechanism behind the F7 fix for GNU
incremental dumps, where the pilot previously read times as a prefix.
Finding 3: the typeflag rule matches the body. The new knownTypeflag
accepts NUL and 0 through 7, and nothing else. GNU long name and long
link entries, pax headers, and other types therefore fail the whole
parse. The name and prefix fields must each be valid UTF-8, where the
previous code read invalid UTF-8 as an empty string. That change is
the mechanism behind the F3 fix for a split name with invalid UTF-8
in one half.
Finding 4: the data rule for non-regular entries matches the body.
ParseBlock sets the size to zero for types 1 to 6, whatever the size
field says. The next header therefore follows straight after such an
entry. The test helper hidden_after covers this by placing a file
inside the claimed size bytes of a non-regular entry, followed by a
second file.
Finding 5: the trailing slash rule has two parts, and both are visible
in the diff. A regular typeflag with a name ending in a slash and a
nonzero size fails to parse, in parseBlock. A regular typeflag with a
name ending in a slash and a zero size is not regular, because
isRegular now also requires the name not to end in a slash. That
entry is then skipped rather than extracted.
Finding 6: the regular file rule change is small and consistent.
Spec.regularType now takes the name as well as the typeflag, and it
accepts 0, NUL, and 7, except for a name ending in a slash.
Manifest_eq and manifest_lines_eq in Proofs/Contain.lean change only
to pass the name through, including one changed rfl line. That fits
the body claim that the containment proofs are unchanged except for
passing the name to the regular file rule.
Finding 7: the create fix matches the body. Header.splitUstarName now
requires both parts of a split to be non-empty. A long absolute name
therefore keeps its leading slash instead of losing it in the split.
The test diff adds a case where a 101 character name with no slash
fails create with exit code 1 and writes nothing.
Finding 8: the test counts in the body match the results file in the
diff. The results file records 100 checks with 0 failures, 27
archives in formats the pilot does not read, and 11 readable archives
compared with tarfile. The must-fail archives are checked under both
list and manifest, with exit code 1 and empty standard output.
Finding 9: one comparison in the readable set is stated with an
exclusion, and the exclusion is honest but worth noting. Tarfile
reports an old style directory, typeflag NUL with a name ending in a
slash, as typeflag 5. The readable comparison therefore drops the
typeflag from SKIP lines before comparing. A reader of the headline
count alone would not see that exclusion.
Finding 10: no sorry or admit appears in the added Lean code in this
diff. The axiom check from PR 4 is carried over unchanged in
substance, with 13 axiom related checks inside the 100 total.
Questions to the author
Question 1: is the readable set checked under list as well as
manifest? The must-fail set is checked under both commands, while
the readable loop in the diff runs manifest only. Archive A covers
list for the older cases, but does the readable set add any list
coverage?
Question 2: is the two part trailing slash rule stated in the README
in this PR, or does a reader find it only in code comments? The
parse failure part and the skip part have different visible effects,
and the README text in the diff is short on this point.
Question 3: does the GNU header rule reject any GNU header that GNU
tar writes by default for short names? The body states that short
name GNU archives are among the readable set, and the code requires
an empty prefix area for GNU headers. Is there a default GNU header
field that lands in that area and would break that claim?
Question 4: the body states that 58 of the 100 checks fail against
the PR 4 binary. Is that figure from a recorded run, and does any
file in this PR record it? A reader cannot reproduce that figure
from the results file alone, since the results file records only
this PR's run.
Limits
This is a reading review only. The Lean code was not built, and the
test suite was not rerun here. Test counts, failure counts, and the
58 check figure above come from the PR body and the diff, not from an
independent run. The mergeable state and the draft state were read
from GitHub at the time of this review, and either can change.
Automated posting by agentic team with human oversight.
README: keep this branch's format list, which replaces the bullet that said what the old parser does. Count the sixth refused destination. tests/TEST-RESULTS.md: regenerated by tests/run_tests.py, 102 of 102. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
The readable archives were compared with tarfile under manifest only. They are now compared under list as well. The SKIP typeflag is no longer dropped: only typeflag NUL with a name ending in `/` is read as 5, as tarfile reads it, and the results file and README say so. GNU tar's own output is now read in its default, gnu and oldgnu formats, with files, a directory, a hard link, a symlink and a FIFO, all with nothing in bytes 345 to 499. Its incremental dump and its sparse file, which do use those bytes, must fail to parse. 121 of 121 checks pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
ahrazzle
left a comment
There was a problem hiding this comment.
Verdict: COMMENT. Not an approval, because the set choice is still open.
Built check. I built this head: 20 jobs, passed. I ran the tests: 100 checks, 0 failures, matching the body claim. Axioms in the record are only the standard axioms. No sorry or admit in the Lean sources. No check runs were found on this head, so the local build and test run are the verification here.
This pull request is part of Set B, and it competes with Set A for findings F2 to F4 in the same files. Tar issue 7, which decides which set merges, is still open. Only one set can merge. This comment records that the code passes its build and tests, and that the merge decision waits on issue 7. Merge order inside Set B is this pull request after pull request 4.
Automated posting by agentic team with human oversight.
Brings in 85ef13a: planted_bugs.py stops with an error when the test script gives no check count, and its docstring says what the proofs catch and what they do not. No conflicts. Rerun on the merged tree: planted_bugs.py catches 6 of 6, and its record is the same byte for byte. run_tests.py gives 121/121 and the same record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
The PR said that 58 of the 100 checks fail against #4's binary, but no file recorded it. That count came from b18427e's test script with the tool built from 1273382: 50 must-fail checks and 8 readable-manifest checks fail. The 13 axiom checks pass, since they read b18427e's proofs, not the tool. `python3 tests/run_tests.py --tool REV` now builds the tool at REV with git archive in a temporary directory and runs the checks against it. The checkout is not changed or built. The axiom check is left out, and the record goes to tests/TEST-RESULTS-REV.md. With --tool 1273382, 68 of the 107 checks fail: 54 must-fail checks (27 of the 29 archives, under both commands), 8 readable-manifest checks and 6 readable-list checks. The record now says which inputs must fail with exit code 1, not that they were rejected, so it reads right when checks fail. The run without --tool still gives 121/121. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
The record merged from claude/containment-proofs says the new tool is this checkout. On this branch that is not the tool it was run on: this parser refuses or skips entries with a regular typeflag and a name ending in `/`, which the random names include, so a run against the checkout differs on purpose. A run of compare_old.py deletes its record first, and one that was stopped had left the file deleted in the working tree. The record is now from `python3 tests/compare_old.py --new 1273382`: 15000 runs, 0 differ once the new escapes are undone. Only the line naming the new tool changes. Without --new, the script reports 3480 of the 15000 runs that differ, on 696 archives. The page now gives the command with --new and says why. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
The page said an archive with GNU or pax extensions fails to parse, and that a name that is not valid UTF-8 is rejected. On this branch neither holds (audit F2, F3), and the PR left the sentence for #5, which changes the parser. A reader of this branch alone read it as true. The audit's probe script, run against this branch's tool, still prints what the audit found: a GNU long name cut to 100 bytes, a pax path ignored, and a split name with one invalid field extracted under another name. The page now says that. A base-256 size field does fail to parse, which a hand-made header confirms, and the page keeps that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
On this branch the random cases run against #4's tool too, and 10,305 of them fail there. Listing each one made the record 10,408 lines long. The record now has a table of each kind of check, with how many ran and how many failed. It lists every failure of a fixed case, and the first 20 failures of random cases. The script still prints them all as it runs. A run with no failures writes the same record as before. tests/TEST-RESULTS-1273382.md is rerun with this branch's script. 10,377 of the 23,269 checks fail against #4's tool: - 29 of the 35 archives that must fail to parse, under both commands; - 8 of the 19 readable archives under `manifest`, and 6 under `list`; - 5,184 of the 10,000 random archives under `list`, and 5,121 under `manifest`. The 3,134 checks of the model against tarfile do not run the tool, and pass. The page gives the new figure in place of #5's 68 of 107. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
orda-aska
left a comment
There was a problem hiding this comment.
TarPilot/Archive.lean:37 returns success at the first zero block without checking the rest of the archive. Reproduced: one zero end block, a hidden valid entry after one zero block, and a nonzero byte after a complete archive all return exit code 0 and print only the visible prefix. Require two zero blocks, only zero trailing padding, and a total length divisible by 512. Add rejection tests with empty stdout for list and manifest, including concatenated archives. This is the end rule requested in issue #7.
This is the second of three stacked PRs that fix the org's bar audit (Proven-Commons/mission#1). It is based on #4; merge that first, and GitHub then retargets this one to
main.Why
The page promised that "an archive using them fails to parse instead of being read in a wrong way". That did not hold:
tarfile.Review of this branch found more of the same kind:
tarfile, read them as a prefix, so GNU tar'sa.txtcame out as15267276047/a.txt.sizebytes after links, devices, directories and FIFOs. POSIX stores no data for those, and GNU tar andtarfileread the next header straight after. So an archive could hide from the pilot a file both other readers extract./: an entry with a regular typeflag and such a name, which older archives use for a directory and GNU tar reads as one, was extracted as a file.What changes
The parser accepts only:
0to7;Anything else makes the whole archive fail to parse, as the page says. The finer rules:
/and a nonzero size fails to parse, because readers disagree on whether data follows. So does a size field of spaces, which GNU tar refuses.isRegulartakes typeflag7, and not a name ending in/.Spec.regularTypesays the same, somanifest_eqandSpec.linefollow it.createnever splits a long absolute name so that its/is lost.The page lists exactly what the pilot does not read.
How it was checked
listandmanifest, and 11 readable ones compared withtarfile./, and each non-regular type with a nonzero size field followed by two files.Generated by Claude Code