Skip to content

tarfile produces invalid pax archives when a gname is exactly 32 bytes long #159322

Description

@woodruffw

Bug report

This is a pretty minor bug, but it's also technically a form of pax non-conformance.

See astral-sh/uv#22510 for context.

Summary

TL;DR: the original tar format allocates a fixed 32 byte block for the gname field. pax says that this field should always be NUL terminated, meaning that any group name above 31 bytes in length should be encoded via a gname extension instead:

Ref: pax spec

However, what tarfile does is use all of the 32 bytes for content, with no NUL terminator. Many tar parsers recover from this, since the pax spec confusingly does allow this kind of non-termination in other fields (like name, linkname, and prefix). However, it's always incorrect for gname as well as magic and uname.

The fix here is behavior preserving: pax also allowing supplying the gname as an arbitrary-length pax extension, and pax emitters should emit that extension when a gname is longer than 31 bytes. pax extensions are always given precedence over their header counterparts, so this is fully compatible.

(A conforming pax emitter can also choose to emit pax extensions unconditionally, even when the values would fit in the header.)

Expected behavior

tarfile uses the gname pax extension to store overlong group names (and similar), rather than removing the required NUL terminator from the tar header representation.

Actual behavior

tarfile produces a pax-style tar that strict pax parsers reject.

In particular, tarfile should honor the NUL termination rule for the uname, gname, and magic fields in a pax archive. The latter is trivial since magic is fixed.

MRE

Copying the MRE from the issue linked above:

# make_sdist.py
import io, sys, tarfile

n = int(sys.argv[1])  # group name length
files = {
    "demo-0.1.0/pyproject.toml": b'[project]\nname = "demo"\nversion = "0.1.0"\n[build-system]\nrequires = ["setuptools>=77"]\nbuild-backend = "setuptools.build_meta"\n',
    "demo-0.1.0/demo/__init__.py": b"",
    "demo-0.1.0/PKG-INFO": b"Metadata-Version: 2.1\nName: demo\nVersion: 0.1.0\n",
}
with tarfile.open("demo-0.1.0.tar.gz", "w:gz", format=tarfile.PAX_FORMAT) as t:
    for name, data in files.items():
        info = tarfile.TarInfo(name)
        info.size = len(data)
        info.gname = "g" * n
        t.addfile(info, io.BytesIO(data))

Try with n=32 and n=40 to demonstrate slightly different encoding states (both result in an invalid gname header encoding, but the latter also produces the pax extension block).

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Activity

  1. woodruffw commented on Oct 13, 2026

    @woodruffw
    ContributorAuthor

    (For context: we got this as a user report after switching to a much stricter tar parser in 0.13 🙂. We intend to support it for compatibility on our end, but it'd be great to have a fix as well!)

  2. woodruffw commented on Oct 13, 2026

    @woodruffw
    ContributorAuthor

    Ah, I guess there's one subtlety: pax says to ignore a header field when its pax extension equivalent is present, so the n=40 case is arguably "correct" in the sense that a pax parser "should" ignore the malformed field rather than rejecting it.

    (But not emitting the malformed field is strictly easier anyways.)

  3. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 13, 2026
  4. added a commit that references this issue on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions