https://github.com/python/cpython/commit/3b2e6a1ac45c16ecee1f3f982fe707c440430154
commit: 3b2e6a1ac45c16ecee1f3f982fe707c440430154
branch: main
author: Rudra <[email protected]>
committer: ethanfurman <[email protected]>
date: 2026-11-05T09:56:11-08:00
summary:
gh-159435: Fix tarfile offsets after PAX headers and GNU long names (GH-159532)
files:
A Misc/NEWS.d/next/Library/2026-10-16-13-00-00.gh-issue-159435.OMNI.rst
M Lib/tarfile.py
M Lib/test/test_tarfile.py
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index a75adc520e5e90..227a28766b634d 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -1628,6 +1628,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.
@@ -1649,7 +1651,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 a55eb8d7bc2580..2ad6d0452ce29a 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -1511,6 +1511,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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]