Conversation
A PAX record is `"%d %s=%s\n"`. The number at the start counts the whole record. `PaxExtensions` split the body on `\n` first and checked the number only after that. Splitting first creates two problems: - a record whose value contains a newline fails to parse — for example a symlink target with a newline in it, or a binary `security.capability` xattr that contains `0x0a`; - the second half of such a value becomes its own "line", and that line can pass the length check. A value that holds `"\n22 linkpath=/etc/evil\n"` makes `Entry::link_name()` return `/etc/evil`, while a reader that walks by length finds no `linkpath` at all. Three broken bodies that used to return records now return an error: a last record without a trailing newline, an empty line inside the body, and a length with a sign. GNU tar 1.35 rejects all three as well. After an error the iteration stops instead of looking for the next newline, which would bring back the second problem above. Closes composefs#452 Closes composefs#428 Signed-off-by: Ivan Glushkov <ivan.glushkov@gmail.com>
This was referenced Sep 21, 2026
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.
A PAX extended header stores records as
"%d %s=%s\n". The number at the start counts the whole record, so a value can hold any byte,\nincluded.PaxExtensionssplits the body on\nfirst and checks the number only after that, so every record with a newline in its value fails. This is what #452 and #428 report.Splitting also invents records. The second part of such a value becomes its own "line" and can pass the length check. A value holding
"\n22 linkpath=/etc/evil\n"makesEntry::link_name()return/etc/evil, where GNU tar returns thelinknamefrom the ustar header.We hit this while converting OCI layers in nydus and first tried to fix it there (dragonflyoss/nydus#2064). It was too complicated. The crate keeps the header body private, so the builder has to rebuild it from a rolling window of the tar stream, and its input is a FIFO that cannot be read twice. The parsing was never the hard part.
Here the fix is to walk the body by the length prefix. The commit message has the details.
Closes #452, closes #428.
Additional information
Compared with the old parser and GNU tar 1.35
Most bodies parse the same. Those that differ:
16 linkpath=a\nb\nlinkpath="a\nb"a\nb…user.a=AAA\n22 linkpath=/etc/evil\nlinkpath=/etc/evillinkpathlinkname12 path=one(no final\n)path=one+13 path=one\npath=one12 path=one\n\n12 path=two\npath=onepath=one, error10 pathon\n12 path=two\n(no=)path=twonameWhat can break
The last two rows. Where the old parser skipped to the next newline and found later records, this one stops.
path()andlink_name()usefilter_map(|f| f.ok()), so on a broken body they fall back to the ustar header without an error. This needs a writer that emits a broken record followed by good ones; neither this crate nor GNU tar does that.Stopping cannot be separated from the fix: skipping to the next newline is what lets a piece of a value become a record.
Archive::entries()already stops on error for the same reason (#284).No public API changes —
PaxExtensionshas one private field.Not in this PR
path_bytesreturnsCow<[u8]>, so the error cannot be propagated without changing public signatures.Relation to #470
Same idea, opened first, still a draft. It differs in two places: a framed record with no
=lets iteration continue, and a+in the length is accepted.AI disclosure
Generated-by: AI
I read and carefully reviewed the code, tests, commit and PR messages.