Skip to content

Fix tar parser correctness issues - #6430

Open
mstange wants to merge 5 commits into
firefox-devtools:mainfrom
mstange:push-umouvtxtptkz
Open

mstange wants to merge 5 commits into
firefox-devtools:mainfrom
mstange:push-umouvtxtptkz

Conversation

@mstange

@mstange mstange commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Main | Deploy preview

In #3778 I added a tar parser in order to support cargo:... paths.
A few days ago I read https://astral.sh/blog/tar-codec and thought it might be a good idea to do an audit on our parser.
The biggest issue found is that we weren't supporting long filenames (member names >= 100 bytes), which means that we weren't able to load some source files, such as the one in this profile: https://share.firefox.dev/4ziKT9t

This PR fixes the long path issue. It contains other correctness fixes as well, but they didn't really have any impact on the usability of the profiler.

- Parse octal fields with a stricter parser: Compared to parseInt,
  this parser won't accept negative values or numbers with trailing
  garbage.
- Use the octal parser for the checksum and device number fields.
  These were incorrectly parsed as decimal.
- Verify the header checksum.
- Fix end-of-archive detection: The end is marked by a full zero block.
  We were trusting the size written in the header and not detecting
  truncated input.
TextDecoder's 'ascii' label actually means windows-1252, so UTF-8 names
written by cargo were garbled and never matched the requested path. With
fatal: true, some non-ASCII bytes also made the whole archive fail.
Replace the recursive _readNextFile with a loop that collects a pending
pax 'x' header and then resolves the next member's name, linkname and
size before reading its data. Previously, the pax records were patched
onto the entry after it had been read, so:

 - a pax 'size' record changed entry.size but not the buffer or the
   position of the next header, desynchronizing the parse;
 - with two stacked 'x' headers, the earlier one won;
 - a tombstoned 'path' left entry.name undefined;
 - every record was copied onto the entry object, so 'buffer=1' replaced
   the file contents, and all-digit paths became numbers;
 - many extension headers in a row overflowed the stack.

Now only the 'path', 'linkpath' and 'size' keywords are recognized and
other records are ignored. A second 'x' header for the same member, an
empty resolved name, an 'x' header at the end of the archive, duplicate
records and malformed records are rejected. Global 'g' headers may not
contain any of the recognized keywords, so their records don't need to
be kept.

Since hasNext() can no longer know whether a member follows without
parsing extension headers, next() now returns null at the end of the
archive. TarFileEntry is reduced to the fields that are resolved
properly and that callers can use.
The parser treated any header containing 'ustar' in its magic field as
UStar, including GNU headers. GNU headers don't have a name prefix
field; those bytes hold atime, ctime and other fields, so an archive
with atime set would produce names like '<octal atime>/<name>'.

Classify each header by its exact magic and version as UStar, GNU or v7,
and only read the prefix for UStar. Reject unknown formats, archives
that mix formats, and pax headers in non-UStar archives.
cargo writes crates in the GNU tar format. In that format, names longer
than 100 bytes are stored in a preceding 'L' entry, and long link names
in a 'K' entry. We treated these as ordinary entries and returned the
following member with its name truncated to 100 bytes, so files with
long paths, such as 95 of the files in aws-sdk-iam-1.132.0, could never
be found in the archive.

Apply the payload of 'L' and 'K' entries to the next member. Reject more
than one of each per member, and 'L'/'K' entries in non-GNU archives.
@mstange
mstange requested a review from canova October 29, 2026 14:35
@codecov

codecov Bot commented Oct 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.77301% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.92%. Comparing base (1a67b19) to head (4823eb8).
⚠️ Report is 20 commits behind head on main.

Files with missing lines Patch % Lines
src/utils/untar.ts 98.76% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6430      +/-   ##
==========================================
+ Coverage   84.79%   84.92%   +0.13%     
==========================================
  Files         358      358              
  Lines       38957    38994      +37     
  Branches    11020    11064      +44     
==========================================
+ Hits        33032    33116      +84     
+ Misses       5489     5442      -47     
  Partials      436      436              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

1 participant