diff --git a/1010-CVE-2024-7592.patch b/1010-CVE-2024-7592.patch new file mode 100644 index 0000000000000000000000000000000000000000..a792d1e75631301d93f0272cff39c8e6073034dd --- /dev/null +++ b/1010-CVE-2024-7592.patch @@ -0,0 +1,133 @@ +From d4ac921a4b081f7f996a5d2b101684b67ba0ed7f Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Wed, 4 Sep 2024 17:50:00 +0200 +Subject: [PATCH] [3.11] gh-123067: Fix quadratic complexity in parsing + "-quoted cookie values with backslashes (GH-123075) (#123105) + +This fixes CVE-2024-7592. +(cherry picked from commit 44e458357fca05ca0ae2658d62c8c595b048b5ef) + +Co-authored-by: Serhiy Storchaka +--- + Lib/http/cookies.py | 34 ++++------------- + Lib/test/test_http_cookies.py | 38 +++++++++++++++++++ + ...-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst | 1 + + 3 files changed, 47 insertions(+), 26 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst + +diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py +index 35ac2dc6ae280c..2c1f021d0abede 100644 +--- a/Lib/http/cookies.py ++++ b/Lib/http/cookies.py +@@ -184,8 +184,13 @@ def _quote(str): + return '"' + str.translate(_Translator) + '"' + + +-_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") +-_QuotePatt = re.compile(r"[\\].") ++_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub ++ ++def _unquote_replace(m): ++ if m[1]: ++ return chr(int(m[1], 8)) ++ else: ++ return m[2] + + def _unquote(str): + # If there aren't any doublequotes, +@@ -205,30 +210,7 @@ def _unquote(str): + # \012 --> \n + # \" --> " + # +- i = 0 +- n = len(str) +- res = [] +- while 0 <= i < n: +- o_match = _OctalPatt.search(str, i) +- q_match = _QuotePatt.search(str, i) +- if not o_match and not q_match: # Neither matched +- res.append(str[i:]) +- break +- # else: +- j = k = -1 +- if o_match: +- j = o_match.start(0) +- if q_match: +- k = q_match.start(0) +- if q_match and (not o_match or k < j): # QuotePatt matched +- res.append(str[i:k]) +- res.append(str[k+1]) +- i = k + 2 +- else: # OctalPatt matched +- res.append(str[i:j]) +- res.append(chr(int(str[j+1:j+4], 8))) +- i = j + 4 +- return _nulljoin(res) ++ return _unquote_sub(_unquote_replace, str) + + # The _getdate() routine is used to set the expiration time in the cookie's HTTP + # header. By default, _getdate() returns the current time in the appropriate +diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py +index 925c8697f60de6..8879902a6e2f41 100644 +--- a/Lib/test/test_http_cookies.py ++++ b/Lib/test/test_http_cookies.py +@@ -5,6 +5,7 @@ + import doctest + from http import cookies + import pickle ++from test import support + + + class CookieTests(unittest.TestCase): +@@ -58,6 +59,43 @@ def test_basic(self): + for k, v in sorted(case['dict'].items()): + self.assertEqual(C[k].value, v) + ++ def test_unquote(self): ++ cases = [ ++ (r'a="b=\""', 'b="'), ++ (r'a="b=\\"', 'b=\\'), ++ (r'a="b=\="', 'b=='), ++ (r'a="b=\n"', 'b=n'), ++ (r'a="b=\042"', 'b="'), ++ (r'a="b=\134"', 'b=\\'), ++ (r'a="b=\377"', 'b=\xff'), ++ (r'a="b=\400"', 'b=400'), ++ (r'a="b=\42"', 'b=42'), ++ (r'a="b=\\042"', 'b=\\042'), ++ (r'a="b=\\134"', 'b=\\134'), ++ (r'a="b=\\\""', 'b=\\"'), ++ (r'a="b=\\\042"', 'b=\\"'), ++ (r'a="b=\134\""', 'b=\\"'), ++ (r'a="b=\134\042"', 'b=\\"'), ++ ] ++ for encoded, decoded in cases: ++ with self.subTest(encoded): ++ C = cookies.SimpleCookie() ++ C.load(encoded) ++ self.assertEqual(C['a'].value, decoded) ++ ++ @support.requires_resource('cpu') ++ def test_unquote_large(self): ++ n = 10**6 ++ for encoded in r'\\', r'\134': ++ with self.subTest(encoded): ++ data = 'a="b=' + encoded*n + ';"' ++ C = cookies.SimpleCookie() ++ C.load(data) ++ value = C['a'].value ++ self.assertEqual(value[:3], 'b=\\') ++ self.assertEqual(value[-2:], '\\;') ++ self.assertEqual(len(value), n + 3) ++ + def test_load(self): + C = cookies.SimpleCookie() + C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme') +diff --git a/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst +new file mode 100644 +index 00000000000000..6a234561fe31a3 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst +@@ -0,0 +1 @@ ++Fix quadratic complexity in parsing ``"``-quoted cookie values with backslashes by :mod:`http.cookies`. diff --git a/1011-fix-CVE-2024-8088.patch b/1011-fix-CVE-2024-8088.patch new file mode 100644 index 0000000000000000000000000000000000000000..d14ebf335691a3263489f4d6d8b8c87750006ef2 --- /dev/null +++ b/1011-fix-CVE-2024-8088.patch @@ -0,0 +1,146 @@ +From a012256c25a773475c9389cfb8c109d7b7317eb3 Mon Sep 17 00:00:00 2001 +From: lzq11122 +Date: Thu, 10 Jul 2025 01:10:41 -0400 +Subject: [PATCH 1/1] fix CVE-2024-8088 + +--- + Lib/test/test_zipfile.py | 76 ++++++++++++++++++++++++++++++++++++++++ + Lib/zipfile.py | 11 ++++-- + 2 files changed, 84 insertions(+), 3 deletions(-) + +diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py +index c8e0159..d0025e2 100644 +--- a/Lib/test/test_zipfile.py ++++ b/Lib/test/test_zipfile.py +@@ -3512,6 +3512,82 @@ with zipfile.ZipFile(io.BytesIO(), "w") as zf: + zipfile.Path(zf) + zf.extractall(source_path.parent) + ++ def test_malformed_paths(self): ++ """ ++ Path should handle malformed paths gracefully. ++ ++ Paths with leading slashes are not visible. ++ ++ Paths with dots are treated like regular files. ++ """ ++ data = io.BytesIO() ++ zf = zipfile.ZipFile(data, "w") ++ zf.writestr("/one-slash.txt", b"content") ++ zf.writestr("//two-slash.txt", b"content") ++ zf.writestr("../parent.txt", b"content") ++ zf.filename = '' ++ root = zipfile.Path(zf) ++ assert list(map(str, root.iterdir())) == ['../'] ++ assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content' ++ ++ def test_unsupported_names(self): ++ """ ++ Path segments with special characters are readable. ++ ++ On some platforms or file systems, characters like ++ ``:`` and ``?`` are not allowed, but they are valid ++ in the zip file. ++ """ ++ data = io.BytesIO() ++ zf = zipfile.ZipFile(data, "w") ++ zf.writestr("path?", b"content") ++ zf.writestr("V: NMS.flac", b"fLaC...") ++ zf.filename = '' ++ root = zipfile.Path(zf) ++ contents = root.iterdir() ++ assert next(contents).name == 'path?' ++ assert next(contents).name == 'V: NMS.flac' ++ assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..." ++ ++ def test_backslash_not_separator(self): ++ """ ++ In a zip file, backslashes are not separators. ++ """ ++ data = io.BytesIO() ++ zf = zipfile.ZipFile(data, "w") ++ zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content") ++ zf.filename = '' ++ root = zipfile.Path(zf) ++ (first,) = root.iterdir() ++ assert not first.is_dir() ++ assert first.name == 'foo\\bar' ++ ++ ++class DirtyZipInfo(zipfile.ZipInfo): ++ """ ++ Bypass name sanitization. ++ """ ++ ++ def __init__(self, filename, *args, **kwargs): ++ super().__init__(filename, *args, **kwargs) ++ self.filename = filename ++ ++ @classmethod ++ def for_name(cls, name, archive): ++ """ ++ Construct the same way that ZipFile.writestr does. ++ ++ TODO: extract this functionality and re-use ++ """ ++ self = cls(filename=name, date_time=time.localtime(time.time())[:6]) ++ self.compress_type = archive.compression ++ self.compress_level = archive.compresslevel ++ if self.filename.endswith('/'): # pragma: no cover ++ self.external_attr = 0o40775 << 16 # drwxrwxr-x ++ self.external_attr |= 0x10 # MS-DOS directory flag ++ else: ++ self.external_attr = 0o600 << 16 # ?rw------- ++ return self + + class EncodedMetadataTests(unittest.TestCase): + file_names = ['\u4e00', '\u4e8c', '\u4e09'] # Han 'one', 'two', 'three' +diff --git a/Lib/zipfile.py b/Lib/zipfile.py +index 6189db5..49d0ad3 100644 +--- a/Lib/zipfile.py ++++ b/Lib/zipfile.py +@@ -9,6 +9,7 @@ import io + import itertools + import os + import posixpath ++import re + import shutil + import stat + import struct +@@ -2192,7 +2193,7 @@ def _parents(path): + def _ancestry(path): + """ + Given a path with elements separated by +- posixpath.sep, generate all elements of that path ++ posixpath.sep, generate all elements of that path. + + >>> list(_ancestry('b/d')) + ['b/d', 'b'] +@@ -2204,9 +2205,14 @@ def _ancestry(path): + ['b'] + >>> list(_ancestry('')) + [] ++ ++ Multiple separators are treated like a single. ++ ++ >>> list(_ancestry('//b//d///f//')) ++ ['//b//d///f', '//b//d', '//b'] + """ + path = path.rstrip(posixpath.sep) +- while path and path != posixpath.sep: ++ while path.rstrip(posixpath.sep): + yield path + path, tail = posixpath.split(path) + +@@ -2222,7 +2228,6 @@ def _difference(minuend, subtrahend): + """ + return itertools.filterfalse(set(subtrahend).__contains__, minuend) + +- + class CompleteDirs(ZipFile): + """ + A ZipFile subclass that ensures that implied directories +-- +2.41.0 + + diff --git a/python3.spec b/python3.spec index a4b74d62f56ff40996ad3adebdc0d2d848a22e50..07331ef1d614815578e93a63d6b75194c76ce9ab 100644 --- a/python3.spec +++ b/python3.spec @@ -1,4 +1,4 @@ -%define anolis_release 7 +%define anolis_release 8 %global pybasever 3.11 # pybasever without the dot: @@ -249,6 +249,11 @@ Patch1007: fix-CVE-2024-0397.patch Patch1008: fix-CVE-2025-4516.patch # https://github.com/python/cpython/commit/7040aa54f14676938970e10c5f74ea93cd56aa38 Patch1009: fix-CVE-2025-8194.patch +# https://github.com/python/cpython/commit/d4ac921a4b081f7f996a5d2b101684b67ba0ed7f +Patch1010: 1010-CVE-2024-7592.patch +# https://github.com/python/cpython/commit/795f2597a4be988e2bb19b69ff9958e981cb894e +# https://github.com/python/cpython/commit/fc0b8259e693caa8400fa8b6ac1e494e47ea7798 +Patch1011: 1011-fix-CVE-2024-8088.patch # ========================================== # Descriptions, and metadata for subpackages @@ -1521,6 +1526,9 @@ CheckPython optimized # ====================================================== %changelog +* Thu Aug 07 2025 mgb01105731 - 3.11.6-8 +- Add patch to fix CVE-2024-7592 and CVE-2024-8088 + * Wed Aug 06 2025 wenxin - 3.11.6-7 - Add patch to fix CVE-2025-8194