From 3f4fedf4ca39db8f0d3fb0369ce8779dffcdbe04 Mon Sep 17 00:00:00 2001 From: Rudra Date: Fri, 16 Oct 2026 18:54:17 +0530 Subject: [PATCH 1/2] gh-159435: fix tarfile pax long-name offsets --- Lib/tarfile.py | 4 ++- Lib/test/test_tarfile.py | 28 +++++++++++++++++++ ...26-10-16-13-00-00.gh-issue-159435.OMNI-rst | 3 ++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI-rst diff --git a/Lib/tarfile.py b/Lib/tarfile.py index c0b9f3f429c10e..8b1018c56b0ce5 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -1616,6 +1616,8 @@ def _proc_pax(self, tarfile): except HeaderError as e: raise SubsequentHeaderError(str(e)) from None + data_offset = next.offset_data + # Process GNU sparse information. if "GNU.sparse.map" in pax_headers: # GNU extended sparse format version 0.1. @@ -1637,7 +1639,7 @@ def _proc_pax(self, tarfile): # If the extended header replaces the size field, # we need to recalculate the offset where the next # header starts. - offset = next.offset + BLOCKSIZE + offset = data_offset if next.isreg() or next.type not in SUPPORTED_TYPES: try: size = PAX_NUMBER_FIELDS["size"](pax_headers["size"]) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 2b4cf748dffc1a..40b6cb9eee9d47 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1444,6 +1444,34 @@ def test_sparse_file_10_pax_size(self): self.assertEqual(tar.getnames(), ["sparse", "last"]) self.assertEqual(tar.extractfile("last").read(), data) + def test_pax_size_with_gnu_longname(self): + # gh-159435: a GNU long-name header between a pax header and its + # member must not make the member header get read a second time. + long_name = "long-name-" * 12 + "file.txt" + + def header(name, type, size): + info = tarfile.TarInfo(name) + info.type = type + info.size = size + return info.tobuf(tarfile.USTAR_FORMAT) + + def padded(data): + return data + b"\0" * (-len(data) % tarfile.BLOCKSIZE) + + pax_record = b"9 size=5\n" + buf = header("././@PaxHeader", tarfile.XHDTYPE, len(pax_record)) + buf += padded(pax_record) + longname = long_name.encode() + b"\0" + buf += header("././@LongLink", tarfile.GNUTYPE_LONGNAME, len(longname)) + buf += padded(longname) + buf += header("member-short", tarfile.REGTYPE, 5) + padded(b"hello") + buf += header("next.txt", tarfile.REGTYPE, 3) + padded(b"bye") + buf += b"\0" * (tarfile.BLOCKSIZE * 2) + + with tarfile.open(fileobj=io.BytesIO(buf)) as tar: + self.assertEqual(tar.getnames(), [long_name, "next.txt"]) + self.assertEqual(tar.extractfile("next.txt").read(), b"bye") + @staticmethod def _fs_supports_holes(): # Return True if the platform knows the st_blocks stat attribute and diff --git a/Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI-rst b/Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI-rst new file mode 100644 index 00000000000000..3f4c71c2c2e0a6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI-rst @@ -0,0 +1,3 @@ +Fix an offset calculation in :mod:`tarfile` when a PAX header with a ``size`` +record is followed by a GNU long-name header. Such archives could duplicate or +skip members while being read. From 605d578a54ff7285a4c678eede929f90f163c9cf Mon Sep 17 00:00:00 2001 From: Rudra Date: Fri, 16 Oct 2026 18:58:19 +0530 Subject: [PATCH 2/2] fix: correct NEWS entry filename --- ...9435.OMNI-rst => 2026-10-16-13-00-00.gh-issue-159435.OMNI.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2026-10-16-13-00-00.gh-issue-159435.OMNI-rst => 2026-10-16-13-00-00.gh-issue-159435.OMNI.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI-rst b/Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI-rst rename to Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI.rst