File CVE-2025-13462-tarinfo-header-parse.patch of Package python.43465
From 8ce97a5730eb7732a332f419a2139071405b997d Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
Date: Wed, 11 Mar 2026 08:47:55 -0500
Subject: [PATCH] gh-141707: Skip TarInfo DIRTYPE normalization during GNU long
name handling (cherry picked from commit
42d754e34c06e57ad6b8e7f92f32af679912d8ab)
Co-authored-by: Seth Michael Larson <seth@python.org>
Co-authored-by: Eashwar Ranganathan <eashwar@eashwar.com>
---
Lib/tarfile.py | 29 ++++++++--
Lib/test/test_tarfile.py | 22 +++++++
Misc/ACKS | 1
Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst | 2
4 files changed, 50 insertions(+), 4 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst
Index: Python-2.7.18/Lib/tarfile.py
===================================================================
--- Python-2.7.18.orig/Lib/tarfile.py 2026-03-26 23:59:23.686189130 +0100
+++ Python-2.7.18/Lib/tarfile.py 2026-03-26 23:59:23.916353712 +0100
@@ -1200,6 +1200,20 @@
@classmethod
def frombuf(cls, buf):
"""Construct a TarInfo object from a 512 byte string buffer.
+
+ To support the old v7 tar format AREGTYPE headers are
+ transformed to DIRTYPE headers if their name ends in '/'.
+ """
+ return cls._frombuf(buf)
+
+ @classmethod
+ def _frombuf(cls, buf, dircheck=True):
+ """Construct a TarInfo object from a 512 byte string buffer.
+
+ If ``dircheck`` is set to ``True`` then ``AREGTYPE`` headers will
+ be normalized to ``DIRTYPE`` if the name ends in a trailing slash.
+ ``dircheck`` must be set to ``False`` if this function is called
+ on a follow-up header such as ``GNUTYPE_LONGNAME``.
"""
if len(buf) == 0:
raise EmptyHeaderError("empty header")
@@ -1231,7 +1245,7 @@
# Old V7 tar format represents a directory as a regular
# file with a trailing slash.
- if obj.type == AREGTYPE and obj.name.endswith("/"):
+ if dircheck and obj.type == AREGTYPE and obj.name.endswith("/"):
obj.type = DIRTYPE
# Remove redundant slashes from directories.
@@ -1248,8 +1262,15 @@
"""Return the next TarInfo object from TarFile object
tarfile.
"""
+ return cls._fromtarfile(tarfile)
+
+ @classmethod
+ def _fromtarfile(cls, tarfile, dircheck=True):
+ """
+ See dircheck documentation in _frombuf().
+ """
buf = tarfile.fileobj.read(BLOCKSIZE)
- obj = cls.frombuf(buf)
+ obj = cls._frombuf(buf, dircheck=dircheck)
obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
return obj._proc_member(tarfile)
@@ -1302,7 +1323,7 @@
# Fetch the next header and process it.
try:
- next = self.fromtarfile(tarfile)
+ next = self._fromtarfile(tarfile, dircheck=False)
except HeaderError:
raise SubsequentHeaderError("missing or bad subsequent header")
@@ -1412,7 +1433,7 @@
# Fetch the next header.
try:
- next = self.fromtarfile(tarfile)
+ next = self._fromtarfile(tarfile, dircheck=False)
except HeaderError:
raise SubsequentHeaderError("missing or bad subsequent header")
Index: Python-2.7.18/Lib/test/test_tarfile.py
===================================================================
--- Python-2.7.18.orig/Lib/test/test_tarfile.py 2026-03-26 23:59:23.686753884 +0100
+++ Python-2.7.18/Lib/test/test_tarfile.py 2026-03-26 23:59:23.917570235 +0100
@@ -1,6 +1,7 @@
import sys
import os
import shutil
+import io
import StringIO
from binascii import unhexlify
from hashlib import md5
@@ -729,10 +730,30 @@
self.assertEqual(tarinfo.type, self.longnametype)
+ def test_longname_file_not_directory(self):
+ # Test reading a longname file and ensure it is not handled as a directory
+ # Issue #141707
+ buf = io.BytesIO()
+ with tarfile.open(mode='w', fileobj=buf, format=self.format) as tar:
+ ti = tarfile.TarInfo()
+ ti.type = tarfile.AREGTYPE
+ ti.name = ('a' * 99) + '/' + ('b' * 3)
+ tar.addfile(ti)
+
+ expected = {t.name: t.type for t in tar.getmembers()}
+
+ buf.seek(0)
+ with tarfile.open(mode='r', fileobj=buf) as tar:
+ actual = {t.name: t.type for t in tar.getmembers()}
+
+ self.assertEqual(expected, actual)
+
+
class GNUReadTest(LongnameTest):
subdir = "gnu"
longnametype = tarfile.GNUTYPE_LONGNAME
+ format = tarfile.GNU_FORMAT
def test_sparse_file(self):
tarinfo1 = self.tar.getmember("ustar/sparse")
@@ -747,6 +768,7 @@
subdir = "pax"
longnametype = tarfile.XHDTYPE
+ format = tarfile.PAX_FORMAT
def test_pax_global_headers(self):
tar = tarfile.open(tarname, encoding="iso8859-1")
Index: Python-2.7.18/Misc/ACKS
===================================================================
--- Python-2.7.18.orig/Misc/ACKS 2020-04-19 23:13:39.000000000 +0200
+++ Python-2.7.18/Misc/ACKS 2026-03-26 23:59:23.918007288 +0100
@@ -1154,6 +1154,7 @@
Burton Radons
Jeff Ramnani
Bayard Randel
+Eashwar Ranganathan
Varpu Rantala
Brodie Rao
Rémi Rampin
Index: Python-2.7.18/Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-2.7.18/Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst 2026-03-26 23:59:23.918319988 +0100
@@ -0,0 +1,2 @@
+Don't change :class:`tarfile.TarInfo` type from ``AREGTYPE`` to ``DIRTYPE`` when parsing
+GNU long name or link headers.