Skip to content

Commit 9bcd825

Browse files
sethmlarsonmcepl
authored andcommitted
[CVE-2025-13462] Skip TarInfo DIRTYPE normalization during GNU long name handling
This corrects `tarfile` to skip the directory-type (`DIRTYPE`) normalization when reading GNU-format long archive member names, so that a regular file using a GNU long name is no longer misidentified as a directory and silently cut off from the archive listing. Fixes: bsc#1259611 (CVE-2025-13462) Fixes: gh#python#141707 From-PR: gh#python/cpython!143934 Patch: CVE-2025-13462-tarinfo-header-parse.patch
1 parent 869f263 commit 9bcd825

4 files changed

Lines changed: 49 additions & 4 deletions

File tree

‎Lib/tarfile.py‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,20 @@ def _create_pax_generic_header(cls, pax_headers, type, encoding):
12141214
@classmethod
12151215
def frombuf(cls, buf, encoding, errors):
12161216
"""Construct a TarInfo object from a 512 byte bytes object.
1217+
1218+
To support the old v7 tar format AREGTYPE headers are
1219+
transformed to DIRTYPE headers if their name ends in '/'.
1220+
"""
1221+
return cls._frombuf(buf, encoding, errors)
1222+
1223+
@classmethod
1224+
def _frombuf(cls, buf, encoding, errors, *, dircheck=True):
1225+
"""Construct a TarInfo object from a 512 byte bytes object.
1226+
1227+
If ``dircheck`` is set to ``True`` then ``AREGTYPE`` headers will
1228+
be normalized to ``DIRTYPE`` if the name ends in a trailing slash.
1229+
``dircheck`` must be set to ``False`` if this function is called
1230+
on a follow-up header such as ``GNUTYPE_LONGNAME``.
12171231
"""
12181232
if len(buf) == 0:
12191233
raise EmptyHeaderError("empty header")
@@ -1244,7 +1258,7 @@ def frombuf(cls, buf, encoding, errors):
12441258

12451259
# Old V7 tar format represents a directory as a regular
12461260
# file with a trailing slash.
1247-
if obj.type == AREGTYPE and obj.name.endswith("/"):
1261+
if dircheck and obj.type == AREGTYPE and obj.name.endswith("/"):
12481262
obj.type = DIRTYPE
12491263

12501264
# The old GNU sparse format occupies some of the unused
@@ -1279,8 +1293,15 @@ def fromtarfile(cls, tarfile):
12791293
"""Return the next TarInfo object from TarFile object
12801294
tarfile.
12811295
"""
1296+
return cls._fromtarfile(tarfile)
1297+
1298+
@classmethod
1299+
def _fromtarfile(cls, tarfile, *, dircheck=True):
1300+
"""
1301+
See dircheck documentation in _frombuf().
1302+
"""
12821303
buf = tarfile.fileobj.read(BLOCKSIZE)
1283-
obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
1304+
obj = cls._frombuf(buf, tarfile.encoding, tarfile.errors, dircheck=dircheck)
12841305
obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
12851306
return obj._proc_member(tarfile)
12861307

@@ -1333,7 +1354,7 @@ def _proc_gnulong(self, tarfile):
13331354

13341355
# Fetch the next header and process it.
13351356
try:
1336-
next = self.fromtarfile(tarfile)
1357+
next = self._fromtarfile(tarfile, dircheck=False)
13371358
except HeaderError:
13381359
raise SubsequentHeaderError("missing or bad subsequent header")
13391360

@@ -1464,7 +1485,7 @@ def _proc_pax(self, tarfile):
14641485

14651486
# Fetch the next header.
14661487
try:
1467-
next = self.fromtarfile(tarfile)
1488+
next = self._fromtarfile(tarfile, dircheck=False)
14681489
except HeaderError:
14691490
raise SubsequentHeaderError("missing or bad subsequent header")
14701491

‎Lib/test/test_tarfile.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,10 +948,30 @@ def test_header_offset(self):
948948
self.assertEqual(tarinfo.type, self.longnametype)
949949

950950

951+
def test_longname_file_not_directory(self):
952+
# Test reading a longname file and ensure it is not handled as a directory
953+
# Issue #141707
954+
buf = io.BytesIO()
955+
with tarfile.open(mode='w', fileobj=buf, format=self.format) as tar:
956+
ti = tarfile.TarInfo()
957+
ti.type = tarfile.AREGTYPE
958+
ti.name = ('a' * 99) + '/' + ('b' * 3)
959+
tar.addfile(ti)
960+
961+
expected = {t.name: t.type for t in tar.getmembers()}
962+
963+
buf.seek(0)
964+
with tarfile.open(mode='r', fileobj=buf) as tar:
965+
actual = {t.name: t.type for t in tar.getmembers()}
966+
967+
self.assertEqual(expected, actual)
968+
969+
951970
class GNUReadTest(LongnameTest, ReadTest, unittest.TestCase):
952971

953972
subdir = "gnu"
954973
longnametype = tarfile.GNUTYPE_LONGNAME
974+
format = tarfile.GNU_FORMAT
955975

956976
# Since 3.2 tarfile is supposed to accurately restore sparse members and
957977
# produce files with holes. This is what we actually want to test here.
@@ -1006,6 +1026,7 @@ class PaxReadTest(LongnameTest, ReadTest, unittest.TestCase):
10061026

10071027
subdir = "pax"
10081028
longnametype = tarfile.XHDTYPE
1029+
format = tarfile.PAX_FORMAT
10091030

10101031
def test_pax_global_headers(self):
10111032
tar = tarfile.open(tarname, encoding="iso8859-1")

‎Misc/ACKS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,7 @@ Abhilash Raj
12651265
Shorya Raj
12661266
Jeff Ramnani
12671267
Bayard Randel
1268+
Eashwar Ranganathan
12681269
Varpu Rantala
12691270
Brodie Rao
12701271
Rémi Rampin
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Don't change :class:`tarfile.TarInfo` type from ``AREGTYPE`` to ``DIRTYPE`` when parsing
2+
GNU long name or link headers.

0 commit comments

Comments
 (0)