diff options
| author | Leonidas Da Silva Barbosa <[email protected]> | 2025-11-06 10:44:16 -0300 |
|---|---|---|
| committer | git-ubuntu importer <[email protected]> | 2025-11-24 14:22:28 +0000 |
| commit | d93ce35cfee42c22e87053c493216bb2696879df (patch) | |
| tree | 3d974115ce9fcaecf217eb1c1e4f496331636d63 | |
| parent | 201279d890e23dcac5f72604fc363ff69535b583 (diff) | |
3.12.3-1ubuntu0.9 (patches unapplied)import/3.12.3-1ubuntu0.9ubuntu/noble-updatesubuntu/noble-securityubuntu/noble-devel
Imported using git-ubuntu import.
Notes
Notes:
* SECURITY UPDATE: Possible payload obfuscation
- debian/patches/CVE-2025-8291.patch: check consistency of
the zip64 end of central dir record in Lib/zipfile.py,
Lib/test/test_zipfile.py.
- CVE-2025-8291
* SECURITY UPDATE: Performance degradation
- debian/patches/CVE-2025-6075.patch: fix quadratic complexity
in os.path.expandvars() in Lib/ntpatch.py, Lib/posixpath.py,
Lib/test/test_genericpatch.py, Lib/test/test_npath.py.
- CVE-2025-6075
| -rw-r--r-- | debian/changelog | 15 | ||||
| -rw-r--r-- | debian/patches/CVE-2025-6075-12.patch | 351 | ||||
| -rw-r--r-- | debian/patches/CVE-2025-8291.patch | 284 | ||||
| -rw-r--r-- | debian/patches/series | 2 |
4 files changed, 652 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog index a77ec77c..68eb4544 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +python3.12 (3.12.3-1ubuntu0.9) noble-security; urgency=medium + + * SECURITY UPDATE: Possible payload obfuscation + - debian/patches/CVE-2025-8291.patch: check consistency of + the zip64 end of central dir record in Lib/zipfile.py, + Lib/test/test_zipfile.py. + - CVE-2025-8291 + * SECURITY UPDATE: Performance degradation + - debian/patches/CVE-2025-6075.patch: fix quadratic complexity + in os.path.expandvars() in Lib/ntpatch.py, Lib/posixpath.py, + Lib/test/test_genericpatch.py, Lib/test/test_npath.py. + - CVE-2025-6075 + + -- Leonidas Da Silva Barbosa <[email protected]> Thu, 06 Nov 2025 10:44:16 -0300 + python3.12 (3.12.3-1ubuntu0.8) noble-security; urgency=medium * SECURITY UPDATE: Regular expression denial of service. diff --git a/debian/patches/CVE-2025-6075-12.patch b/debian/patches/CVE-2025-6075-12.patch new file mode 100644 index 00000000..9acf2f6a --- /dev/null +++ b/debian/patches/CVE-2025-6075-12.patch @@ -0,0 +1,351 @@ +From c8a5f3435c342964e0a432cc9fb448b7dbecd1ba Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=C5=81ukasz=20Langa?= <[email protected]> +Date: Fri, 31 Oct 2025 17:50:42 +0100 +Subject: [PATCH] [3.12] gh-136065: Fix quadratic complexity in + os.path.expandvars() (GH-134952) (GH-140847) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +(cherry picked from commit f029e8db626ddc6e3a3beea4eff511a71aaceb5c) + +Co-authored-by: Serhiy Storchaka <[email protected]> +Co-authored-by: Ćukasz Langa <[email protected]> +--- + Lib/ntpath.py | 126 ++++++------------ + Lib/posixpath.py | 43 +++--- + Lib/test/test_genericpath.py | 14 ++ + Lib/test/test_ntpath.py | 23 +++- + ...-05-30-22-33-27.gh-issue-136065.bu337o.rst | 1 + + 5 files changed, 94 insertions(+), 113 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst + +--- python3.12-3.12.3.orig/Lib/ntpath.py ++++ python3.12-3.12.3/Lib/ntpath.py +@@ -409,17 +409,23 @@ def expanduser(path): + # XXX With COMMAND.COM you can use any characters in a variable name, + # XXX except '^|<>='. + ++_varpattern = r"'[^']*'?|%(%|[^%]*%?)|\$(\$|[-\w]+|\{[^}]*\}?)" ++_varsub = None ++_varsubb = None ++ + def expandvars(path): + """Expand shell variables of the forms $var, ${var} and %var%. + + Unknown variables are left unchanged.""" + path = os.fspath(path) ++ global _varsub, _varsubb + if isinstance(path, bytes): + if b'$' not in path and b'%' not in path: + return path +- import string +- varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii') +- quote = b'\'' ++ if not _varsubb: ++ import re ++ _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub ++ sub = _varsubb + percent = b'%' + brace = b'{' + rbrace = b'}' +@@ -428,94 +434,44 @@ def expandvars(path): + else: + if '$' not in path and '%' not in path: + return path +- import string +- varchars = string.ascii_letters + string.digits + '_-' +- quote = '\'' ++ if not _varsub: ++ import re ++ _varsub = re.compile(_varpattern, re.ASCII).sub ++ sub = _varsub + percent = '%' + brace = '{' + rbrace = '}' + dollar = '$' + environ = os.environ +- res = path[:0] +- index = 0 +- pathlen = len(path) +- while index < pathlen: +- c = path[index:index+1] +- if c == quote: # no expansion within single quotes +- path = path[index + 1:] +- pathlen = len(path) +- try: +- index = path.index(c) +- res += c + path[:index + 1] +- except ValueError: +- res += c + path +- index = pathlen - 1 +- elif c == percent: # variable or '%' +- if path[index + 1:index + 2] == percent: +- res += c +- index += 1 +- else: +- path = path[index+1:] +- pathlen = len(path) +- try: +- index = path.index(percent) +- except ValueError: +- res += percent + path +- index = pathlen - 1 +- else: +- var = path[:index] +- try: +- if environ is None: +- value = os.fsencode(os.environ[os.fsdecode(var)]) +- else: +- value = environ[var] +- except KeyError: +- value = percent + var + percent +- res += value +- elif c == dollar: # variable or '$$' +- if path[index + 1:index + 2] == dollar: +- res += c +- index += 1 +- elif path[index + 1:index + 2] == brace: +- path = path[index+2:] +- pathlen = len(path) +- try: +- index = path.index(rbrace) +- except ValueError: +- res += dollar + brace + path +- index = pathlen - 1 +- else: +- var = path[:index] +- try: +- if environ is None: +- value = os.fsencode(os.environ[os.fsdecode(var)]) +- else: +- value = environ[var] +- except KeyError: +- value = dollar + brace + var + rbrace +- res += value +- else: +- var = path[:0] +- index += 1 +- c = path[index:index + 1] +- while c and c in varchars: +- var += c +- index += 1 +- c = path[index:index + 1] +- try: +- if environ is None: +- value = os.fsencode(os.environ[os.fsdecode(var)]) +- else: +- value = environ[var] +- except KeyError: +- value = dollar + var +- res += value +- if c: +- index -= 1 ++ ++ def repl(m): ++ lastindex = m.lastindex ++ if lastindex is None: ++ return m[0] ++ name = m[lastindex] ++ if lastindex == 1: ++ if name == percent: ++ return name ++ if not name.endswith(percent): ++ return m[0] ++ name = name[:-1] + else: +- res += c +- index += 1 +- return res ++ if name == dollar: ++ return name ++ if name.startswith(brace): ++ if not name.endswith(rbrace): ++ return m[0] ++ name = name[1:-1] ++ ++ try: ++ if environ is None: ++ return os.fsencode(os.environ[os.fsdecode(name)]) ++ else: ++ return environ[name] ++ except KeyError: ++ return m[0] ++ ++ return sub(repl, path) + + + # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. +--- python3.12-3.12.3.orig/Lib/posixpath.py ++++ python3.12-3.12.3/Lib/posixpath.py +@@ -314,42 +314,41 @@ def expanduser(path): + # This expands the forms $variable and ${variable} only. + # Non-existent variables are left unchanged. + +-_varprog = None +-_varprogb = None ++_varpattern = r'\$(\w+|\{[^}]*\}?)' ++_varsub = None ++_varsubb = None + + def expandvars(path): + """Expand shell variables of form $var and ${var}. Unknown variables + are left unchanged.""" + path = os.fspath(path) +- global _varprog, _varprogb ++ global _varsub, _varsubb + if isinstance(path, bytes): + if b'$' not in path: + return path +- if not _varprogb: ++ if not _varsubb: + import re +- _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII) +- search = _varprogb.search ++ _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub ++ sub = _varsubb + start = b'{' + end = b'}' + environ = getattr(os, 'environb', None) + else: + if '$' not in path: + return path +- if not _varprog: ++ if not _varsub: + import re +- _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII) +- search = _varprog.search ++ _varsub = re.compile(_varpattern, re.ASCII).sub ++ sub = _varsub + start = '{' + end = '}' + environ = os.environ +- i = 0 +- while True: +- m = search(path, i) +- if not m: +- break +- i, j = m.span(0) +- name = m.group(1) +- if name.startswith(start) and name.endswith(end): ++ ++ def repl(m): ++ name = m[1] ++ if name.startswith(start): ++ if not name.endswith(end): ++ return m[0] + name = name[1:-1] + try: + if environ is None: +@@ -357,13 +356,11 @@ def expandvars(path): + else: + value = environ[name] + except KeyError: +- i = j ++ return m[0] + else: +- tail = path[j:] +- path = path[:i] + value +- i = len(path) +- path += tail +- return path ++ return value ++ ++ return sub(repl, path) + + + # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. +--- python3.12-3.12.3.orig/Lib/test/test_genericpath.py ++++ python3.12-3.12.3/Lib/test/test_genericpath.py +@@ -7,6 +7,7 @@ import os + import sys + import unittest + import warnings ++from test import support + from test.support import is_emscripten + from test.support import os_helper + from test.support import warnings_helper +@@ -434,6 +435,19 @@ class CommonTest(GenericTest): + os.fsencode('$bar%s bar' % nonascii)) + check(b'$spam}bar', os.fsencode('%s}bar' % nonascii)) + ++ @support.requires_resource('cpu') ++ def test_expandvars_large(self): ++ expandvars = self.pathmodule.expandvars ++ with os_helper.EnvironmentVarGuard() as env: ++ env.clear() ++ env["A"] = "B" ++ n = 100_000 ++ self.assertEqual(expandvars('$A'*n), 'B'*n) ++ self.assertEqual(expandvars('${A}'*n), 'B'*n) ++ self.assertEqual(expandvars('$A!'*n), 'B!'*n) ++ self.assertEqual(expandvars('${A}A'*n), 'BA'*n) ++ self.assertEqual(expandvars('${'*10*n), '${'*10*n) ++ + def test_abspath(self): + self.assertIn("foo", self.pathmodule.abspath("foo")) + with warnings.catch_warnings(): +--- python3.12-3.12.3.orig/Lib/test/test_ntpath.py ++++ python3.12-3.12.3/Lib/test/test_ntpath.py +@@ -7,8 +7,8 @@ import sys + import unittest + import warnings + from ntpath import ALLOW_MISSING +-from test.support import cpython_only, os_helper +-from test.support import TestFailed, is_emscripten ++from test import support ++from test.support import os_helper, is_emscripten + from test.support.os_helper import FakePath + from test import test_genericpath + from tempfile import TemporaryFile +@@ -58,7 +58,7 @@ def tester(fn, wantResult): + fn = fn.replace("\\", "\\\\") + gotResult = eval(fn) + if wantResult != gotResult and _norm(wantResult) != _norm(gotResult): +- raise TestFailed("%s should return: %s but returned: %s" \ ++ raise support.TestFailed("%s should return: %s but returned: %s" \ + %(str(fn), str(wantResult), str(gotResult))) + + # then with bytes +@@ -74,7 +74,7 @@ def tester(fn, wantResult): + warnings.simplefilter("ignore", DeprecationWarning) + gotResult = eval(fn) + if _norm(wantResult) != _norm(gotResult): +- raise TestFailed("%s should return: %s but returned: %s" \ ++ raise support.TestFailed("%s should return: %s but returned: %s" \ + %(str(fn), str(wantResult), repr(gotResult))) + + +@@ -927,6 +927,19 @@ class TestNtpath(NtpathTestCase): + check('%spam%bar', '%sbar' % nonascii) + check('%{}%bar'.format(nonascii), 'ham%sbar' % nonascii) + ++ @support.requires_resource('cpu') ++ def test_expandvars_large(self): ++ expandvars = ntpath.expandvars ++ with os_helper.EnvironmentVarGuard() as env: ++ env.clear() ++ env["A"] = "B" ++ n = 100_000 ++ self.assertEqual(expandvars('%A%'*n), 'B'*n) ++ self.assertEqual(expandvars('%A%A'*n), 'BA'*n) ++ self.assertEqual(expandvars("''"*n + '%%'), "''"*n + '%') ++ self.assertEqual(expandvars("%%"*n), "%"*n) ++ self.assertEqual(expandvars("$$"*n), "$"*n) ++ + def test_expanduser(self): + tester('ntpath.expanduser("test")', 'test') + +@@ -1228,7 +1241,7 @@ class TestNtpath(NtpathTestCase): + self.assertTrue(os.path.exists(r"\\.\CON")) + + @unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32") +- @cpython_only ++ @support.cpython_only + def test_fast_paths_in_use(self): + # There are fast paths of these functions implemented in posixmodule.c. + # Confirm that they are being used, and not the Python fallbacks in +--- /dev/null ++++ python3.12-3.12.3/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst +@@ -0,0 +1 @@ ++Fix quadratic complexity in :func:`os.path.expandvars`. diff --git a/debian/patches/CVE-2025-8291.patch b/debian/patches/CVE-2025-8291.patch new file mode 100644 index 00000000..2ed526b0 --- /dev/null +++ b/debian/patches/CVE-2025-8291.patch @@ -0,0 +1,284 @@ +From 162997bb70e067668c039700141770687bc8f267 Mon Sep 17 00:00:00 2001 +From: Serhiy Storchaka <[email protected]> +Date: Tue, 7 Oct 2025 20:15:26 +0300 +Subject: [PATCH] gh-139700: Check consistency of the zip64 end of central + directory record (GH-139702) + +Support records with "zip64 extensible data" if there are no bytes +prepended to the ZIP file. +--- python3.12-3.12.3.orig/Lib/test/test_zipfile/test_core.py ++++ python3.12-3.12.3/Lib/test/test_zipfile/test_core.py +@@ -884,6 +884,8 @@ class StoredTestZip64InSmallFiles(Abstra + self, file_size_64_set=False, file_size_extra=False, + compress_size_64_set=False, compress_size_extra=False, + header_offset_64_set=False, header_offset_extra=False, ++ extensible_data=b'', ++ end_of_central_dir_size=None, offset_to_end_of_central_dir=None, + ): + """Generate bytes sequence for a zip with (incomplete) zip64 data. + +@@ -937,6 +939,12 @@ class StoredTestZip64InSmallFiles(Abstra + + central_dir_size = struct.pack('<Q', 58 + 8 * len(central_zip64_fields)) + offset_to_central_dir = struct.pack('<Q', 50 + 8 * len(local_zip64_fields)) ++ if end_of_central_dir_size is None: ++ end_of_central_dir_size = 44 + len(extensible_data) ++ if offset_to_end_of_central_dir is None: ++ offset_to_end_of_central_dir = (108 ++ + 8 * len(local_zip64_fields) ++ + 8 * len(central_zip64_fields)) + + local_extra_length = struct.pack("<H", 4 + 8 * len(local_zip64_fields)) + central_extra_length = struct.pack("<H", 4 + 8 * len(central_zip64_fields)) +@@ -965,14 +973,17 @@ class StoredTestZip64InSmallFiles(Abstra + + filename + + central_extra + # Zip64 end of central directory +- + b"PK\x06\x06,\x00\x00\x00\x00\x00\x00\x00-\x00-" +- + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00" ++ + b"PK\x06\x06" ++ + struct.pack('<Q', end_of_central_dir_size) ++ + b"-\x00-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00" + + b"\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" + + central_dir_size + + offset_to_central_dir ++ + extensible_data + # Zip64 end of central directory locator +- + b"PK\x06\x07\x00\x00\x00\x00l\x00\x00\x00\x00\x00\x00\x00\x01" +- + b"\x00\x00\x00" ++ + b"PK\x06\x07\x00\x00\x00\x00" ++ + struct.pack('<Q', offset_to_end_of_central_dir) ++ + b"\x01\x00\x00\x00" + # end of central directory + + b"PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00:\x00\x00\x002\x00" + + b"\x00\x00\x00\x00" +@@ -1003,6 +1014,7 @@ class StoredTestZip64InSmallFiles(Abstra + with self.assertRaises(zipfile.BadZipFile) as e: + zipfile.ZipFile(io.BytesIO(missing_file_size_extra)) + self.assertIn('file size', str(e.exception).lower()) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(missing_file_size_extra))) + + # zip64 file size present, zip64 compress size present, one field in + # extra, expecting two, equals missing compress size. +@@ -1014,6 +1026,7 @@ class StoredTestZip64InSmallFiles(Abstra + with self.assertRaises(zipfile.BadZipFile) as e: + zipfile.ZipFile(io.BytesIO(missing_compress_size_extra)) + self.assertIn('compress size', str(e.exception).lower()) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(missing_compress_size_extra))) + + # zip64 compress size present, no fields in extra, expecting one, + # equals missing compress size. +@@ -1023,6 +1036,7 @@ class StoredTestZip64InSmallFiles(Abstra + with self.assertRaises(zipfile.BadZipFile) as e: + zipfile.ZipFile(io.BytesIO(missing_compress_size_extra)) + self.assertIn('compress size', str(e.exception).lower()) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(missing_compress_size_extra))) + + # zip64 file size present, zip64 compress size present, zip64 header + # offset present, two fields in extra, expecting three, equals missing +@@ -1037,6 +1051,7 @@ class StoredTestZip64InSmallFiles(Abstra + with self.assertRaises(zipfile.BadZipFile) as e: + zipfile.ZipFile(io.BytesIO(missing_header_offset_extra)) + self.assertIn('header offset', str(e.exception).lower()) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(missing_header_offset_extra))) + + # zip64 compress size present, zip64 header offset present, one field + # in extra, expecting two, equals missing header offset +@@ -1049,6 +1064,7 @@ class StoredTestZip64InSmallFiles(Abstra + with self.assertRaises(zipfile.BadZipFile) as e: + zipfile.ZipFile(io.BytesIO(missing_header_offset_extra)) + self.assertIn('header offset', str(e.exception).lower()) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(missing_header_offset_extra))) + + # zip64 file size present, zip64 header offset present, one field in + # extra, expecting two, equals missing header offset +@@ -1061,6 +1077,7 @@ class StoredTestZip64InSmallFiles(Abstra + with self.assertRaises(zipfile.BadZipFile) as e: + zipfile.ZipFile(io.BytesIO(missing_header_offset_extra)) + self.assertIn('header offset', str(e.exception).lower()) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(missing_header_offset_extra))) + + # zip64 header offset present, no fields in extra, expecting one, + # equals missing header offset +@@ -1072,6 +1089,63 @@ class StoredTestZip64InSmallFiles(Abstra + with self.assertRaises(zipfile.BadZipFile) as e: + zipfile.ZipFile(io.BytesIO(missing_header_offset_extra)) + self.assertIn('header offset', str(e.exception).lower()) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(missing_header_offset_extra))) ++ ++ def test_bad_zip64_end_of_central_dir(self): ++ zipdata = self.make_zip64_file(end_of_central_dir_size=0) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'Corrupt.*record'): ++ zipfile.ZipFile(io.BytesIO(zipdata)) ++ self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata))) ++ ++ zipdata = self.make_zip64_file(end_of_central_dir_size=100) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'Corrupt.*record'): ++ zipfile.ZipFile(io.BytesIO(zipdata)) ++ self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata))) ++ ++ zipdata = self.make_zip64_file(offset_to_end_of_central_dir=0) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'Corrupt.*record'): ++ zipfile.ZipFile(io.BytesIO(zipdata)) ++ self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata))) ++ ++ zipdata = self.make_zip64_file(offset_to_end_of_central_dir=1000) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'Corrupt.*locator'): ++ zipfile.ZipFile(io.BytesIO(zipdata)) ++ self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata))) ++ ++ def test_zip64_end_of_central_dir_record_not_found(self): ++ zipdata = self.make_zip64_file() ++ zipdata = zipdata.replace(b"PK\x06\x06", b'\x00'*4) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'record not found'): ++ zipfile.ZipFile(io.BytesIO(zipdata)) ++ self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata))) ++ ++ zipdata = self.make_zip64_file( ++ extensible_data=b'\xca\xfe\x04\x00\x00\x00data') ++ zipdata = zipdata.replace(b"PK\x06\x06", b'\x00'*4) ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'record not found'): ++ zipfile.ZipFile(io.BytesIO(zipdata)) ++ self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata))) ++ ++ def test_zip64_extensible_data(self): ++ # These values are what is set in the make_zip64_file method. ++ expected_file_size = 8 ++ expected_compress_size = 8 ++ expected_header_offset = 0 ++ expected_content = b"test1234" ++ ++ zipdata = self.make_zip64_file( ++ extensible_data=b'\xca\xfe\x04\x00\x00\x00data') ++ with zipfile.ZipFile(io.BytesIO(zipdata)) as zf: ++ zinfo = zf.infolist()[0] ++ self.assertEqual(zinfo.file_size, expected_file_size) ++ self.assertEqual(zinfo.compress_size, expected_compress_size) ++ self.assertEqual(zinfo.header_offset, expected_header_offset) ++ self.assertEqual(zf.read(zinfo), expected_content) ++ self.assertTrue(zipfile.is_zipfile(io.BytesIO(zipdata))) ++ ++ with self.assertRaisesRegex(zipfile.BadZipFile, 'record not found'): ++ zipfile.ZipFile(io.BytesIO(b'prepended' + zipdata)) ++ self.assertFalse(zipfile.is_zipfile(io.BytesIO(b'prepended' + zipdata))) + + def test_generated_valid_zip64_extra(self): + # These values are what is set in the make_zip64_file method. +--- python3.12-3.12.3.orig/Lib/zipfile/__init__.py ++++ python3.12-3.12.3/Lib/zipfile/__init__.py +@@ -231,7 +231,7 @@ def is_zipfile(filename): + else: + with open(filename, "rb") as fp: + result = _check_zipfile(fp) +- except OSError: ++ except (OSError, BadZipFile): + pass + return result + +@@ -239,16 +239,15 @@ def _EndRecData64(fpin, offset, endrec): + """ + Read the ZIP64 end-of-archive records and use that to update endrec + """ +- try: +- fpin.seek(offset - sizeEndCentDir64Locator, 2) +- except OSError: +- # If the seek fails, the file is not large enough to contain a ZIP64 ++ offset -= sizeEndCentDir64Locator ++ if offset < 0: ++ # The file is not large enough to contain a ZIP64 + # end-of-archive record, so just return the end record we were given. + return endrec +- ++ fpin.seek(offset) + data = fpin.read(sizeEndCentDir64Locator) + if len(data) != sizeEndCentDir64Locator: +- return endrec ++ raise OSError("Unknown I/O error") + sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data) + if sig != stringEndArchive64Locator: + return endrec +@@ -256,16 +255,33 @@ def _EndRecData64(fpin, offset, endrec): + if diskno != 0 or disks > 1: + raise BadZipFile("zipfiles that span multiple disks are not supported") + +- # Assume no 'zip64 extensible data' +- fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2) ++ offset -= sizeEndCentDir64 ++ if reloff > offset: ++ raise BadZipFile("Corrupt zip64 end of central directory locator") ++ # First, check the assumption that there is no prepended data. ++ fpin.seek(reloff) ++ extrasz = offset - reloff + data = fpin.read(sizeEndCentDir64) + if len(data) != sizeEndCentDir64: +- return endrec ++ raise OSError("Unknown I/O error") ++ if not data.startswith(stringEndArchive64) and reloff != offset: ++ # Since we already have seen the Zip64 EOCD Locator, it's ++ # possible we got here because there is prepended data. ++ # Assume no 'zip64 extensible data' ++ fpin.seek(offset) ++ extrasz = 0 ++ data = fpin.read(sizeEndCentDir64) ++ if len(data) != sizeEndCentDir64: ++ raise OSError("Unknown I/O error") ++ if not data.startswith(stringEndArchive64): ++ raise BadZipFile("Zip64 end of central directory record not found") ++ + sig, sz, create_version, read_version, disk_num, disk_dir, \ + dircount, dircount2, dirsize, diroffset = \ + struct.unpack(structEndArchive64, data) +- if sig != stringEndArchive64: +- return endrec ++ if (diroffset + dirsize != reloff or ++ sz + 12 != sizeEndCentDir64 + extrasz): ++ raise BadZipFile("Corrupt zip64 end of central directory record") + + # Update the original endrec using data from the ZIP64 record + endrec[_ECD_SIGNATURE] = sig +@@ -275,6 +291,7 @@ def _EndRecData64(fpin, offset, endrec): + endrec[_ECD_ENTRIES_TOTAL] = dircount2 + endrec[_ECD_SIZE] = dirsize + endrec[_ECD_OFFSET] = diroffset ++ endrec[_ECD_LOCATION] = offset - extrasz + return endrec + + +@@ -308,7 +325,7 @@ def _EndRecData(fpin): + endrec.append(filesize - sizeEndCentDir) + + # Try to read the "Zip64 end of central directory" structure +- return _EndRecData64(fpin, -sizeEndCentDir, endrec) ++ return _EndRecData64(fpin, filesize - sizeEndCentDir, endrec) + + # Either this is not a ZIP file, or it is a ZIP file with an archive + # comment. Search the end of the file for the "end of central directory" +@@ -332,8 +349,7 @@ def _EndRecData(fpin): + endrec.append(maxCommentStart + start) + + # Try to read the "Zip64 end of central directory" structure +- return _EndRecData64(fpin, maxCommentStart + start - filesize, +- endrec) ++ return _EndRecData64(fpin, maxCommentStart + start, endrec) + + # Unable to find a valid end of central directory structure + return None +@@ -1422,9 +1438,6 @@ class ZipFile: + + # "concat" is zero, unless zip was concatenated to another file + concat = endrec[_ECD_LOCATION] - size_cd - offset_cd +- if endrec[_ECD_SIGNATURE] == stringEndArchive64: +- # If Zip64 extension structures are present, account for them +- concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator) + + if self.debug > 2: + inferred = concat + offset_cd +@@ -2034,7 +2047,7 @@ class ZipFile: + " would require ZIP64 extensions") + zip64endrec = struct.pack( + structEndArchive64, stringEndArchive64, +- 44, 45, 45, 0, 0, centDirCount, centDirCount, ++ sizeEndCentDir64 - 12, 45, 45, 0, 0, centDirCount, centDirCount, + centDirSize, centDirOffset) + self.fp.write(zip64endrec) + diff --git a/debian/patches/series b/debian/patches/series index e8b405cf..b813419d 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -48,3 +48,5 @@ CVE-2025-4516.patch CVE-202x-12718-4138-4x3x-4517.patch CVE-2025-6069.patch CVE-2025-8194.patch +CVE-2025-6075-12.patch +CVE-2025-8291.patch |
