new file mode 100644
@@ -0,0 +1,219 @@
+From c9d9f78feb1467e73fd29356c040bde1c104f29f Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington@users.noreply.github.com>
+Date: Mon, 4 Aug 2025 13:45:06 +0200
+Subject: [PATCH] [3.12] gh-130577: tarfile now validates archives to ensure
+ member offsets are non-negative (GH-137027) (#137171)
+
+(cherry picked from commit 7040aa54f14676938970e10c5f74ea93cd56aa38)
+
+Co-authored-by: Alexander Urieles <aeurielesn@users.noreply.github.com>
+Co-authored-by: Gregory P. Smith <greg@krypto.org>
+
+CVE: CVE-2025-8194
+Upstream-Status: Backport [https://github.com/python/cpython/commit/c9d9f78feb1467e73fd29356c040bde1c104f29f]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ Lib/tarfile.py | 3 +
+ Lib/test/test_tarfile.py | 156 ++++++++++++++++++
+ ...-07-23-00-35-29.gh-issue-130577.c7EITy.rst | 3 +
+ 3 files changed, 162 insertions(+)
+ create mode 100644 Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
+
+diff --git a/Lib/tarfile.py b/Lib/tarfile.py
+index 9999a99d54..59d3f6e5cc 100755
+--- a/Lib/tarfile.py
++++ b/Lib/tarfile.py
+@@ -1613,6 +1613,9 @@ class TarInfo(object):
+ """Round up a byte count by BLOCKSIZE and return it,
+ e.g. _block(834) => 1024.
+ """
++ # Only non-negative offsets are allowed
++ if count < 0:
++ raise InvalidHeaderError("invalid offset")
+ blocks, remainder = divmod(count, BLOCKSIZE)
+ if remainder:
+ blocks += 1
+diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
+index a184ba75a8..759fa03ead 100644
+--- a/Lib/test/test_tarfile.py
++++ b/Lib/test/test_tarfile.py
+@@ -49,6 +49,7 @@ bz2name = os.path.join(TEMPDIR, "testtar.tar.bz2")
+ xzname = os.path.join(TEMPDIR, "testtar.tar.xz")
+ tmpname = os.path.join(TEMPDIR, "tmp.tar")
+ dotlessname = os.path.join(TEMPDIR, "testtar")
++SPACE = b" "
+
+ sha256_regtype = (
+ "e09e4bc8b3c9d9177e77256353b36c159f5f040531bbd4b024a8f9b9196c71ce"
+@@ -4273,6 +4274,161 @@ class TestExtractionFilters(unittest.TestCase):
+ self.expect_exception(TypeError) # errorlevel is not int
+
+
++class OffsetValidationTests(unittest.TestCase):
++ tarname = tmpname
++ invalid_posix_header = (
++ # name: 100 bytes
++ tarfile.NUL * tarfile.LENGTH_NAME
++ # mode, space, null terminator: 8 bytes
++ + b"000755" + SPACE + tarfile.NUL
++ # uid, space, null terminator: 8 bytes
++ + b"000001" + SPACE + tarfile.NUL
++ # gid, space, null terminator: 8 bytes
++ + b"000001" + SPACE + tarfile.NUL
++ # size, space: 12 bytes
++ + b"\xff" * 11 + SPACE
++ # mtime, space: 12 bytes
++ + tarfile.NUL * 11 + SPACE
++ # chksum: 8 bytes
++ + b"0011407" + tarfile.NUL
++ # type: 1 byte
++ + tarfile.REGTYPE
++ # linkname: 100 bytes
++ + tarfile.NUL * tarfile.LENGTH_LINK
++ # magic: 6 bytes, version: 2 bytes
++ + tarfile.POSIX_MAGIC
++ # uname: 32 bytes
++ + tarfile.NUL * 32
++ # gname: 32 bytes
++ + tarfile.NUL * 32
++ # devmajor, space, null terminator: 8 bytes
++ + tarfile.NUL * 6 + SPACE + tarfile.NUL
++ # devminor, space, null terminator: 8 bytes
++ + tarfile.NUL * 6 + SPACE + tarfile.NUL
++ # prefix: 155 bytes
++ + tarfile.NUL * tarfile.LENGTH_PREFIX
++ # padding: 12 bytes
++ + tarfile.NUL * 12
++ )
++ invalid_gnu_header = (
++ # name: 100 bytes
++ tarfile.NUL * tarfile.LENGTH_NAME
++ # mode, null terminator: 8 bytes
++ + b"0000755" + tarfile.NUL
++ # uid, null terminator: 8 bytes
++ + b"0000001" + tarfile.NUL
++ # gid, space, null terminator: 8 bytes
++ + b"0000001" + tarfile.NUL
++ # size, space: 12 bytes
++ + b"\xff" * 11 + SPACE
++ # mtime, space: 12 bytes
++ + tarfile.NUL * 11 + SPACE
++ # chksum: 8 bytes
++ + b"0011327" + tarfile.NUL
++ # type: 1 byte
++ + tarfile.REGTYPE
++ # linkname: 100 bytes
++ + tarfile.NUL * tarfile.LENGTH_LINK
++ # magic: 8 bytes
++ + tarfile.GNU_MAGIC
++ # uname: 32 bytes
++ + tarfile.NUL * 32
++ # gname: 32 bytes
++ + tarfile.NUL * 32
++ # devmajor, null terminator: 8 bytes
++ + tarfile.NUL * 8
++ # devminor, null terminator: 8 bytes
++ + tarfile.NUL * 8
++ # padding: 167 bytes
++ + tarfile.NUL * 167
++ )
++ invalid_v7_header = (
++ # name: 100 bytes
++ tarfile.NUL * tarfile.LENGTH_NAME
++ # mode, space, null terminator: 8 bytes
++ + b"000755" + SPACE + tarfile.NUL
++ # uid, space, null terminator: 8 bytes
++ + b"000001" + SPACE + tarfile.NUL
++ # gid, space, null terminator: 8 bytes
++ + b"000001" + SPACE + tarfile.NUL
++ # size, space: 12 bytes
++ + b"\xff" * 11 + SPACE
++ # mtime, space: 12 bytes
++ + tarfile.NUL * 11 + SPACE
++ # chksum: 8 bytes
++ + b"0010070" + tarfile.NUL
++ # type: 1 byte
++ + tarfile.REGTYPE
++ # linkname: 100 bytes
++ + tarfile.NUL * tarfile.LENGTH_LINK
++ # padding: 255 bytes
++ + tarfile.NUL * 255
++ )
++ valid_gnu_header = tarfile.TarInfo("filename").tobuf(tarfile.GNU_FORMAT)
++ data_block = b"\xff" * tarfile.BLOCKSIZE
++
++ def _write_buffer(self, buffer):
++ with open(self.tarname, "wb") as f:
++ f.write(buffer)
++
++ def _get_members(self, ignore_zeros=None):
++ with open(self.tarname, "rb") as f:
++ with tarfile.open(
++ mode="r", fileobj=f, ignore_zeros=ignore_zeros
++ ) as tar:
++ return tar.getmembers()
++
++ def _assert_raises_read_error_exception(self):
++ with self.assertRaisesRegex(
++ tarfile.ReadError, "file could not be opened successfully"
++ ):
++ self._get_members()
++
++ def test_invalid_offset_header_validations(self):
++ for tar_format, invalid_header in (
++ ("posix", self.invalid_posix_header),
++ ("gnu", self.invalid_gnu_header),
++ ("v7", self.invalid_v7_header),
++ ):
++ with self.subTest(format=tar_format):
++ self._write_buffer(invalid_header)
++ self._assert_raises_read_error_exception()
++
++ def test_early_stop_at_invalid_offset_header(self):
++ buffer = self.valid_gnu_header + self.invalid_gnu_header + self.valid_gnu_header
++ self._write_buffer(buffer)
++ members = self._get_members()
++ self.assertEqual(len(members), 1)
++ self.assertEqual(members[0].name, "filename")
++ self.assertEqual(members[0].offset, 0)
++
++ def test_ignore_invalid_archive(self):
++ # 3 invalid headers with their respective data
++ buffer = (self.invalid_gnu_header + self.data_block) * 3
++ self._write_buffer(buffer)
++ members = self._get_members(ignore_zeros=True)
++ self.assertEqual(len(members), 0)
++
++ def test_ignore_invalid_offset_headers(self):
++ for first_block, second_block, expected_offset in (
++ (
++ (self.valid_gnu_header),
++ (self.invalid_gnu_header + self.data_block),
++ 0,
++ ),
++ (
++ (self.invalid_gnu_header + self.data_block),
++ (self.valid_gnu_header),
++ 1024,
++ ),
++ ):
++ self._write_buffer(first_block + second_block)
++ members = self._get_members(ignore_zeros=True)
++ self.assertEqual(len(members), 1)
++ self.assertEqual(members[0].name, "filename")
++ self.assertEqual(members[0].offset, expected_offset)
++
++
+ def setUpModule():
+ os_helper.unlink(TEMPDIR)
+ os.makedirs(TEMPDIR)
+diff --git a/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst b/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
+new file mode 100644
+index 0000000000..342cabbc86
+--- /dev/null
++++ b/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
+@@ -0,0 +1,3 @@
++:mod:`tarfile` now validates archives to ensure member offsets are
++non-negative. (Contributed by Alexander Enrique Urieles Nieto in
++:gh:`130577`.)
@@ -37,6 +37,7 @@ SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \
file://0001-Avoid-shebang-overflow-on-python-config.py.patch \
file://0001-test_storlines-skip-due-to-load-variability.patch \
file://0001-gh-107811-tarfile-treat-overflow-in-UID-GID-as-failu.patch \
+ file://CVE-2025-8194.patch \
"
SRC_URI:append:class-native = " \
@@ -170,7 +171,7 @@ do_install:append:class-native() {
# when they're only used for python called with -O or -OO.
#find ${D} -name *opt-*.pyc -delete
# Remove all pyc files. There are a ton of them and it is probably faster to let
- # python create the ones it wants at runtime rather than manage in the sstate
+ # python create the ones it wants at runtime rather than manage in the sstate
# tarballs and sysroot creation.
find ${D} -name *.pyc -delete
@@ -206,7 +207,7 @@ do_install:append() {
rm -f ${D}${libdir}/python${PYTHON_MAJMIN}/test/__pycache__/test_range.cpython*
rm -f ${D}${libdir}/python${PYTHON_MAJMIN}/test/__pycache__/test_xml_etree.cpython*
- # Similar to the above, we're getting reproducibility issues with
+ # Similar to the above, we're getting reproducibility issues with
# /usr/lib/python3.10/__pycache__/traceback.cpython-310.pyc
# so remove it too
rm -f ${D}${libdir}/python${PYTHON_MAJMIN}/__pycache__/traceback.cpython*
@@ -266,7 +267,7 @@ py_package_preprocess () {
cd -
mv ${PKGD}/${bindir}/python${PYTHON_MAJMIN}-config ${PKGD}/${bindir}/python${PYTHON_MAJMIN}-config-${MULTILIB_SUFFIX}
-
+
#Remove the unneeded copy of target sysconfig data
rm -rf ${PKGD}/${libdir}/python-sysconfigdata
}