Skip to content

Commit 3b2e6a1

Browse files
gh-159435: Fix tarfile offsets after PAX headers and GNU long names (GH-159532)
1 parent d003448 commit 3b2e6a1

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

‎Lib/tarfile.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,8 @@ def _proc_pax(self, tarfile):
16281628
except HeaderError as e:
16291629
raise SubsequentHeaderError(str(e)) from None
16301630

1631+
data_offset = next.offset_data
1632+
16311633
# Process GNU sparse information.
16321634
if "GNU.sparse.map" in pax_headers:
16331635
# GNU extended sparse format version 0.1.
@@ -1649,7 +1651,7 @@ def _proc_pax(self, tarfile):
16491651
# If the extended header replaces the size field,
16501652
# we need to recalculate the offset where the next
16511653
# header starts.
1652-
offset = next.offset + BLOCKSIZE
1654+
offset = data_offset
16531655
if next.isreg() or next.type not in SUPPORTED_TYPES:
16541656
try:
16551657
size = PAX_NUMBER_FIELDS["size"](pax_headers["size"])

‎Lib/test/test_tarfile.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,34 @@ def test_sparse_file_10_pax_size(self):
15111511
self.assertEqual(tar.getnames(), ["sparse", "last"])
15121512
self.assertEqual(tar.extractfile("last").read(), data)
15131513

1514+
def test_pax_size_with_gnu_longname(self):
1515+
# gh-159435: a GNU long-name header between a pax header and its
1516+
# member must not make the member header get read a second time.
1517+
long_name = "long-name-" * 12 + "file.txt"
1518+
1519+
def header(name, type, size):
1520+
info = tarfile.TarInfo(name)
1521+
info.type = type
1522+
info.size = size
1523+
return info.tobuf(tarfile.USTAR_FORMAT)
1524+
1525+
def padded(data):
1526+
return data + b"\0" * (-len(data) % tarfile.BLOCKSIZE)
1527+
1528+
pax_record = b"9 size=5\n"
1529+
buf = header("././@PaxHeader", tarfile.XHDTYPE, len(pax_record))
1530+
buf += padded(pax_record)
1531+
longname = long_name.encode() + b"\0"
1532+
buf += header("././@LongLink", tarfile.GNUTYPE_LONGNAME, len(longname))
1533+
buf += padded(longname)
1534+
buf += header("member-short", tarfile.REGTYPE, 5) + padded(b"hello")
1535+
buf += header("next.txt", tarfile.REGTYPE, 3) + padded(b"bye")
1536+
buf += b"\0" * (tarfile.BLOCKSIZE * 2)
1537+
1538+
with tarfile.open(fileobj=io.BytesIO(buf)) as tar:
1539+
self.assertEqual(tar.getnames(), [long_name, "next.txt"])
1540+
self.assertEqual(tar.extractfile("next.txt").read(), b"bye")
1541+
15141542
@staticmethod
15151543
def _fs_supports_holes():
15161544
# Return True if the platform knows the st_blocks stat attribute and
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an offset calculation in :mod:`tarfile` when a PAX header with a ``size``
2+
record is followed by a GNU long-name header. Such archives could duplicate or
3+
skip members while being read.

0 commit comments

Comments
 (0)