From 539e53b74e21ccb33f66e62a62b47bbff0587f63094b112fc84f996e1b1deded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Thu, 12 Feb 2026 01:12:49 +0100 Subject: [PATCH] Fix six CVEs CVE-2025-11468: preserving parens when folding comments in email headers (bsc#1257029, gh#python/cpython#143935). CVE-2025-11468-email-hdr-fold-comment.patch CVE-2026-0672: rejects control characters in http cookies. (bsc#1257031, gh#python/cpython#143919) CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch CVE-2026-0865: rejecting control characters in wsgiref.headers.Headers, which could be abused for injecting false HTTP headers. (bsc#1257042, gh#python/cpython#143916) CVE-2026-0865-wsgiref-ctrl-chars.patch CVE-2025-15366: basically the same as the previous patch for IMAP protocol. (bsc#1257044, gh#python/cpython#143921) CVE-2025-15366-imap-ctrl-chars.patch CVE-2025-15282: basically the same as the previous patch for urllib library. (bsc#1257046, gh#python/cpython#143925) CVE-2025-15282-urllib-ctrl-chars.patch CVE-2025-15367: basically the same as the previous patch for poplib library. (bsc#1257041, gh#python/cpython#143923) CVE-2025-15367-poplib-ctrl-chars.patch --- CVE-2025-11468-email-hdr-fold-comment.patch | 116 +++++++++++ CVE-2025-15282-urllib-ctrl-chars.patch | 64 ++++++ CVE-2025-15366-imap-ctrl-chars.patch | 56 ++++++ CVE-2025-15367-poplib-ctrl-chars.patch | 56 ++++++ ...6-0672-http-hdr-inject-cookie-Morsel.patch | 184 ++++++++++++++++++ CVE-2026-0865-wsgiref-ctrl-chars.patch | 97 +++++++++ python310.changes | 23 +++ python310.spec | 20 +- 8 files changed, 615 insertions(+), 1 deletion(-) create mode 100644 CVE-2025-11468-email-hdr-fold-comment.patch create mode 100644 CVE-2025-15282-urllib-ctrl-chars.patch create mode 100644 CVE-2025-15366-imap-ctrl-chars.patch create mode 100644 CVE-2025-15367-poplib-ctrl-chars.patch create mode 100644 CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch create mode 100644 CVE-2026-0865-wsgiref-ctrl-chars.patch diff --git a/CVE-2025-11468-email-hdr-fold-comment.patch b/CVE-2025-11468-email-hdr-fold-comment.patch new file mode 100644 index 0000000..06d33ef --- /dev/null +++ b/CVE-2025-11468-email-hdr-fold-comment.patch @@ -0,0 +1,116 @@ +From 2065aa5b8f2bcdea2f628686c57974793a62c42b Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Mon, 19 Jan 2026 06:38:22 -0600 +Subject: [PATCH] [3.10] gh-143935: Email preserve parens when folding comments + (GH-143936) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fix a bug in the folding of comments when flattening an email message +using a modern email policy. Comments consisting of a very long sequence of +non-foldable characters could trigger a forced line wrap that omitted the +required leading space on the continuation line, causing the remainder of +the comment to be interpreted as a new header field. This enabled header +injection with carefully crafted inputs. +(cherry picked from commit 17d1490) + +Co-authored-by: Seth Michael Larson seth@python.org +Co-authored-by: Denis Ledoux dle@odoo.com + +- Issue: Fix folding of long comments of unfoldable characters in email headers #143935 + +Signed-off-by: Edgar Ramírez Mondragón +--- + Lib/email/_header_value_parser.py | 15 +++++++++++- + .../test_email/test__header_value_parser.py | 23 +++++++++++++++++++ + ...-01-16-14-40-31.gh-issue-143935.U2YtKl.rst | 6 +++++ + 3 files changed, 43 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst + +diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py +index dbc0bd8196af52..2c05abeadea22b 100644 +--- a/Lib/email/_header_value_parser.py ++++ b/Lib/email/_header_value_parser.py +@@ -101,6 +101,12 @@ def make_quoted_pairs(value): + return str(value).replace('\\', '\\\\').replace('"', '\\"') + + ++def make_parenthesis_pairs(value): ++ """Escape parenthesis and backslash for use within a comment.""" ++ return str(value).replace('\\', '\\\\') \ ++ .replace('(', '\\(').replace(')', '\\)') ++ ++ + def quote_string(value): + escaped = make_quoted_pairs(value) + return f'"{escaped}"' +@@ -927,7 +933,7 @@ def value(self): + return ' ' + + def startswith_fws(self): +- return True ++ return self and self[0] in WSP + + + class ValueTerminal(Terminal): +@@ -2865,6 +2871,13 @@ def _refold_parse_tree(parse_tree, *, policy): + [ValueTerminal(make_quoted_pairs(p), 'ptext') + for p in newparts] + + [ValueTerminal('"', 'ptext')]) ++ if part.token_type == 'comment': ++ newparts = ( ++ [ValueTerminal('(', 'ptext')] + ++ [ValueTerminal(make_parenthesis_pairs(p), 'ptext') ++ if p.token_type == 'ptext' else p ++ for p in newparts] + ++ [ValueTerminal(')', 'ptext')]) + if not part.as_ew_allowed: + wrap_as_ew_blocked += 1 + newparts.append(end_ew_not_allowed) +diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py +index 6a4ecafd68b4ab..2eaaaaef675284 100644 +--- a/Lib/test/test_email/test__header_value_parser.py ++++ b/Lib/test/test_email/test__header_value_parser.py +@@ -2973,6 +2973,29 @@ def test_address_list_with_specials_in_long_quoted_string(self): + with self.subTest(to=to): + self._test(parser.get_address_list(to)[0], folded, policy=policy) + ++ def test_address_list_with_long_unwrapable_comment(self): ++ policy = self.policy.clone(max_line_length=40) ++ cases = [ ++ # (to, folded) ++ ('(loremipsumdolorsitametconsecteturadipi)', ++ '(loremipsumdolorsitametconsecteturadipi)\n'), ++ ('(loremipsumdolorsitametconsecteturadipi)', ++ '(loremipsumdolorsitametconsecteturadipi)\n'), ++ ('(loremipsum dolorsitametconsecteturadipi)', ++ '(loremipsum dolorsitametconsecteturadipi)\n'), ++ ('(loremipsum dolorsitametconsecteturadipi)', ++ '(loremipsum\n dolorsitametconsecteturadipi)\n'), ++ ('(Escaped \\( \\) chars \\\\ in comments stay escaped)', ++ '(Escaped \\( \\) chars \\\\ in comments stay\n escaped)\n'), ++ ('((loremipsum)(loremipsum)(loremipsum)(loremipsum))', ++ '((loremipsum)(loremipsum)(loremipsum)(loremipsum))\n'), ++ ('((loremipsum)(loremipsum)(loremipsum) (loremipsum))', ++ '((loremipsum)(loremipsum)(loremipsum)\n (loremipsum))\n'), ++ ] ++ for (to, folded) in cases: ++ with self.subTest(to=to): ++ self._test(parser.get_address_list(to)[0], folded, policy=policy) ++ + def test_address_list_with_specials_in_encoded_word(self): + # An encoded-word parsed from a structured header must remain + # encoded when it contains specials. Regression for gh-121284. +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst b/Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst +new file mode 100644 +index 00000000000000..c3d864936884ac +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst +@@ -0,0 +1,6 @@ ++Fixed a bug in the folding of comments when flattening an email message ++using a modern email policy. Comments consisting of a very long sequence of ++non-foldable characters could trigger a forced line wrap that omitted the ++required leading space on the continuation line, causing the remainder of ++the comment to be interpreted as a new header field. This enabled header ++injection with carefully crafted inputs. diff --git a/CVE-2025-15282-urllib-ctrl-chars.patch b/CVE-2025-15282-urllib-ctrl-chars.patch new file mode 100644 index 0000000..761803d --- /dev/null +++ b/CVE-2025-15282-urllib-ctrl-chars.patch @@ -0,0 +1,64 @@ +From d6c6f0880dbc6ffd770b859087f4cd749a1d0dbb Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Tue, 20 Jan 2026 14:45:58 -0600 +Subject: [PATCH] [3.10] gh-143925: Reject control characters in data: URL + mediatypes (cherry picked from commit + f25509e78e8be6ea73c811ac2b8c928c28841b9f) (cherry picked from commit + 2c9c746077d8119b5bcf5142316992e464594946) + +Co-authored-by: Seth Michael Larson +--- + Lib/test/test_urllib.py | 8 ++++++++ + Lib/urllib/request.py | 5 +++++ + Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst | 1 + + 3 files changed, 14 insertions(+) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst + +Index: Python-3.10.19/Lib/test/test_urllib.py +=================================================================== +--- Python-3.10.19.orig/Lib/test/test_urllib.py 2026-02-12 01:05:56.127447144 +0100 ++++ Python-3.10.19/Lib/test/test_urllib.py 2026-02-12 01:08:02.226352573 +0100 +@@ -11,6 +11,7 @@ + from test import support + from test.support import os_helper + from test.support import warnings_helper ++from test.support import control_characters_c0 + import os + try: + import ssl +@@ -683,6 +684,13 @@ + # missing padding character + self.assertRaises(ValueError,urllib.request.urlopen,'data:;base64,Cg=') + ++ def test_invalid_mediatype(self): ++ for c0 in control_characters_c0(): ++ self.assertRaises(ValueError,urllib.request.urlopen, ++ f'data:text/html;{c0},data') ++ for c0 in control_characters_c0(): ++ self.assertRaises(ValueError,urllib.request.urlopen, ++ f'data:text/html{c0};base64,ZGF0YQ==') + + class urlretrieve_FileTests(unittest.TestCase): + """Test urllib.urlretrieve() on local files""" +Index: Python-3.10.19/Lib/urllib/request.py +=================================================================== +--- Python-3.10.19.orig/Lib/urllib/request.py 2026-02-12 01:05:56.627830069 +0100 ++++ Python-3.10.19/Lib/urllib/request.py 2026-02-12 01:08:02.226810828 +0100 +@@ -1654,6 +1654,11 @@ + scheme, data = url.split(":",1) + mediatype, data = data.split(",",1) + ++ # Disallow control characters within mediatype. ++ if re.search(r"[\x00-\x1F\x7F]", mediatype): ++ raise ValueError( ++ "Control characters not allowed in data: mediatype") ++ + # even base64 encoded data URLs might be quoted so unquote in any case: + data = unquote_to_bytes(data) + if mediatype.endswith(";base64"): +Index: Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst 2026-02-12 01:08:02.227192287 +0100 +@@ -0,0 +1 @@ ++Reject control characters in ``data:`` URL media types. diff --git a/CVE-2025-15366-imap-ctrl-chars.patch b/CVE-2025-15366-imap-ctrl-chars.patch new file mode 100644 index 0000000..ed45499 --- /dev/null +++ b/CVE-2025-15366-imap-ctrl-chars.patch @@ -0,0 +1,56 @@ +From 7485ee5e2cf81d3e5ad0d9c3be73cecd2ab4eec7 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Fri, 16 Jan 2026 10:54:09 -0600 +Subject: [PATCH 1/2] Add 'test.support' fixture for C0 control characters + +--- + Lib/imaplib.py | 4 +++- + Lib/test/test_imaplib.py | 6 ++++++ + Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst | 1 + + 3 files changed, 10 insertions(+), 1 deletion(-) + +Index: Python-3.10.19/Lib/imaplib.py +=================================================================== +--- Python-3.10.19.orig/Lib/imaplib.py 2026-02-12 01:05:53.821319313 +0100 ++++ Python-3.10.19/Lib/imaplib.py 2026-02-12 01:06:28.558652908 +0100 +@@ -132,7 +132,7 @@ + # We compile these in _mode_xxx. + _Literal = br'.*{(?P\d+)}$' + _Untagged_status = br'\* (?P\d+) (?P[A-Z-]+)( (?P.*))?' +- ++_control_chars = re.compile(b'[\x00-\x1F\x7F]') + + + class IMAP4: +@@ -994,6 +994,8 @@ + if arg is None: continue + if isinstance(arg, str): + arg = bytes(arg, self._encoding) ++ if _control_chars.search(arg): ++ raise ValueError("Control characters not allowed in commands") + data = data + b' ' + arg + + literal = self.literal +Index: Python-3.10.19/Lib/test/test_imaplib.py +=================================================================== +--- Python-3.10.19.orig/Lib/test/test_imaplib.py 2026-02-12 01:05:55.293033311 +0100 ++++ Python-3.10.19/Lib/test/test_imaplib.py 2026-02-12 01:07:45.387053336 +0100 +@@ -538,6 +538,12 @@ + self.assertEqual(data[0], b'Returned to authenticated state. (Success)') + self.assertEqual(client.state, 'AUTH') + ++ def test_control_characters(self): ++ client, _ = self._setup(SimpleIMAPHandler) ++ for c0 in support.control_characters_c0(): ++ with self.assertRaises(ValueError): ++ client.login(f'user{c0}', 'pass') ++ + + class NewIMAPTests(NewIMAPTestsMixin, unittest.TestCase): + imap_class = imaplib.IMAP4 +Index: Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst 2026-02-12 01:06:28.559224837 +0100 +@@ -0,0 +1 @@ ++Reject control characters in IMAP commands. diff --git a/CVE-2025-15367-poplib-ctrl-chars.patch b/CVE-2025-15367-poplib-ctrl-chars.patch new file mode 100644 index 0000000..345f6b1 --- /dev/null +++ b/CVE-2025-15367-poplib-ctrl-chars.patch @@ -0,0 +1,56 @@ +From b6f733b285b1c4f27dacb5c2e1f292c914e8b933 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Fri, 16 Jan 2026 10:54:09 -0600 +Subject: [PATCH 1/2] Add 'test.support' fixture for C0 control characters + +--- + Lib/poplib.py | 2 ++ + Lib/test/test_poplib.py | 8 ++++++++ + Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst | 1 + + 3 files changed, 11 insertions(+) + +Index: Python-3.10.19/Lib/poplib.py +=================================================================== +--- Python-3.10.19.orig/Lib/poplib.py 2026-02-12 01:05:54.262197434 +0100 ++++ Python-3.10.19/Lib/poplib.py 2026-02-12 01:12:02.945762019 +0100 +@@ -122,6 +122,8 @@ + def _putcmd(self, line): + if self._debugging: print('*cmd*', repr(line)) + line = bytes(line, self.encoding) ++ if re.search(b'[\x00-\x1F\x7F]', line): ++ raise ValueError('Control characters not allowed in commands') + self._putline(line) + + +Index: Python-3.10.19/Lib/test/test_poplib.py +=================================================================== +--- Python-3.10.19.orig/Lib/test/test_poplib.py 2026-02-12 01:05:55.796995175 +0100 ++++ Python-3.10.19/Lib/test/test_poplib.py 2026-02-12 01:12:32.837694637 +0100 +@@ -15,6 +15,7 @@ + from test.support import hashlib_helper + from test.support import socket_helper + from test.support import threading_helper ++from test.support import control_characters_c0 + + import warnings + with warnings.catch_warnings(): +@@ -365,6 +366,13 @@ + self.assertIsNone(self.client.sock) + self.assertIsNone(self.client.file) + ++ def test_control_characters(self): ++ for c0 in control_characters_c0(): ++ with self.assertRaises(ValueError): ++ self.client.user(f'user{c0}') ++ with self.assertRaises(ValueError): ++ self.client.pass_(f'{c0}pass') ++ + @requires_ssl + def test_stls_capa(self): + capa = self.client.capa() +Index: Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst 2026-02-12 01:12:02.946199975 +0100 +@@ -0,0 +1 @@ ++Reject control characters in POP3 commands. diff --git a/CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch b/CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch new file mode 100644 index 0000000..f20c480 --- /dev/null +++ b/CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch @@ -0,0 +1,184 @@ +From 57c5ecd7e61fbb24e7de76eafd95332bd0ae4dea Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Tue, 20 Jan 2026 15:23:42 -0600 +Subject: [PATCH] [3.10] gh-143919: Reject control characters in http cookies + (cherry picked from commit 95746b3a13a985787ef53b977129041971ed7f70) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: Seth Michael Larson +Co-authored-by: Bartosz Sławecki +Co-authored-by: sobolevn +--- + Doc/library/http.cookies.rst | 4 +- + Lib/http/cookies.py | 25 +++++++-- + Lib/test/test_http_cookies.py | 52 +++++++++++++++++-- + ...-01-16-11-13-15.gh-issue-143919.kchwZV.rst | 1 + + 4 files changed, 73 insertions(+), 9 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst + +diff --git a/Doc/library/http.cookies.rst b/Doc/library/http.cookies.rst +index a2c1eb00d8b33d..4cb563c230ea5e 100644 +--- a/Doc/library/http.cookies.rst ++++ b/Doc/library/http.cookies.rst +@@ -270,9 +270,9 @@ The following example demonstrates how to use the :mod:`http.cookies` module. + Set-Cookie: chips=ahoy + Set-Cookie: vienna=finger + >>> C = cookies.SimpleCookie() +- >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";') ++ >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";') + >>> print(C) +- Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;" ++ Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;" + >>> C = cookies.SimpleCookie() + >>> C["oreo"] = "doublestuff" + >>> C["oreo"]["path"] = "/" +diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py +index 2c1f021d0abede..5cfa7a8072c7f7 100644 +--- a/Lib/http/cookies.py ++++ b/Lib/http/cookies.py +@@ -87,9 +87,9 @@ + such trickeries do not confuse it. + + >>> C = cookies.SimpleCookie() +- >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";') ++ >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";') + >>> print(C) +- Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;" ++ Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;" + + Each element of the Cookie also supports all of the RFC 2109 + Cookie attributes. Here's an example which sets the Path +@@ -170,6 +170,15 @@ class CookieError(Exception): + }) + + _is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch ++_control_character_re = re.compile(r'[\x00-\x1F\x7F]') ++ ++ ++def _has_control_character(*val): ++ """Detects control characters within a value. ++ Supports any type, as header values can be any type. ++ """ ++ return any(_control_character_re.search(str(v)) for v in val) ++ + + def _quote(str): + r"""Quote a string for use in a cookie header. +@@ -292,12 +301,16 @@ def __setitem__(self, K, V): + K = K.lower() + if not K in self._reserved: + raise CookieError("Invalid attribute %r" % (K,)) ++ if _has_control_character(K, V): ++ raise CookieError(f"Control characters are not allowed in cookies {K!r} {V!r}") + dict.__setitem__(self, K, V) + + def setdefault(self, key, val=None): + key = key.lower() + if key not in self._reserved: + raise CookieError("Invalid attribute %r" % (key,)) ++ if _has_control_character(key, val): ++ raise CookieError("Control characters are not allowed in cookies %r %r" % (key, val,)) + return dict.setdefault(self, key, val) + + def __eq__(self, morsel): +@@ -333,6 +346,9 @@ def set(self, key, val, coded_val): + raise CookieError('Attempt to set a reserved key %r' % (key,)) + if not _is_legal_key(key): + raise CookieError('Illegal key %r' % (key,)) ++ if _has_control_character(key, val, coded_val): ++ raise CookieError( ++ "Control characters are not allowed in cookies %r %r %r" % (key, val, coded_val,)) + + # It's a good key, so save it. + self._key = key +@@ -484,7 +500,10 @@ def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"): + result = [] + items = sorted(self.items()) + for key, value in items: +- result.append(value.output(attrs, header)) ++ value_output = value.output(attrs, header) ++ if _has_control_character(value_output): ++ raise CookieError("Control characters are not allowed in cookies") ++ result.append(value_output) + return sep.join(result) + + __str__ = output +diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py +index 644e75cd5b742e..1f2c049fa811fa 100644 +--- a/Lib/test/test_http_cookies.py ++++ b/Lib/test/test_http_cookies.py +@@ -17,10 +17,10 @@ def test_basic(self): + 'repr': "", + 'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'}, + +- {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"', +- 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'}, +- 'repr': '''''', +- 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'}, ++ {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=;"', ++ 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=;'}, ++ 'repr': '''''', ++ 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=;"'}, + + # Check illegal cookies that have an '=' char in an unquoted value + {'data': 'keebler=E=mc2', +@@ -517,6 +517,50 @@ def test_repr(self): + r'Set-Cookie: key=coded_val; ' + r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+') + ++ def test_control_characters(self): ++ for c0 in support.control_characters_c0(): ++ morsel = cookies.Morsel() ++ ++ # .__setitem__() ++ with self.assertRaises(cookies.CookieError): ++ morsel[c0] = "val" ++ with self.assertRaises(cookies.CookieError): ++ morsel["path"] = c0 ++ ++ # .setdefault() ++ with self.assertRaises(cookies.CookieError): ++ morsel.setdefault("path", c0) ++ with self.assertRaises(cookies.CookieError): ++ morsel.setdefault(c0, "val") ++ ++ # .set() ++ with self.assertRaises(cookies.CookieError): ++ morsel.set(c0, "val", "coded-value") ++ with self.assertRaises(cookies.CookieError): ++ morsel.set("path", c0, "coded-value") ++ with self.assertRaises(cookies.CookieError): ++ morsel.set("path", "val", c0) ++ ++ def test_control_characters_output(self): ++ # Tests that even if the internals of Morsel are modified ++ # that a call to .output() has control character safeguards. ++ for c0 in support.control_characters_c0(): ++ morsel = cookies.Morsel() ++ morsel.set("key", "value", "coded-value") ++ morsel._key = c0 # Override private variable. ++ cookie = cookies.SimpleCookie() ++ cookie["cookie"] = morsel ++ with self.assertRaises(cookies.CookieError): ++ cookie.output() ++ ++ morsel = cookies.Morsel() ++ morsel.set("key", "value", "coded-value") ++ morsel._coded_value = c0 # Override private variable. ++ cookie = cookies.SimpleCookie() ++ cookie["cookie"] = morsel ++ with self.assertRaises(cookies.CookieError): ++ cookie.output() ++ + def test_main(): + run_unittest(CookieTests, MorselTests) + run_doctest(cookies) +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst +new file mode 100644 +index 00000000000000..788c3e4ac2ebf7 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst +@@ -0,0 +1 @@ ++Reject control characters in :class:`http.cookies.Morsel` fields and values. diff --git a/CVE-2026-0865-wsgiref-ctrl-chars.patch b/CVE-2026-0865-wsgiref-ctrl-chars.patch new file mode 100644 index 0000000..702c81e --- /dev/null +++ b/CVE-2026-0865-wsgiref-ctrl-chars.patch @@ -0,0 +1,97 @@ +From 123bfbbe9074ef7fa28e1e7b25575665296560fa Mon Sep 17 00:00:00 2001 +From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> +Date: Sat, 17 Jan 2026 10:23:57 -0800 +Subject: [PATCH] [3.10] gh-143916: Reject control characters in + wsgiref.headers.Headers (GH-143917) (GH-143973) + +gh-143916: Reject control characters in wsgiref.headers.Headers (GH-143917) + +* Add 'test.support' fixture for C0 control characters +* gh-143916: Reject control characters in wsgiref.headers.Headers + +(cherry picked from commit f7fceed79ca1bceae8dbe5ba5bc8928564da7211) +(cherry picked from commit 22e4d55285cee52bc4dbe061324e5f30bd4dee58) + +Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> +Co-authored-by: Seth Michael Larson +--- + Lib/test/support/__init__.py | 7 +++++++ + Lib/test/test_wsgiref.py | 12 +++++++++++- + Lib/wsgiref/headers.py | 3 +++ + .../2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst | 2 ++ + 4 files changed, 23 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst + +diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py +index 0d3b9634f10248..d0492fe1914343 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -2157,3 +2157,10 @@ def adjust_int_max_str_digits(max_digits): + yield + finally: + sys.set_int_max_str_digits(current) ++ ++ ++def control_characters_c0() -> list[str]: ++ """Returns a list of C0 control characters as strings. ++ C0 control characters defined as the byte range 0x00-0x1F, and 0x7F. ++ """ ++ return [chr(c) for c in range(0x00, 0x20)] + ["\x7F"] +diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py +index 42094f467731d4..01ca51ba458587 100644 +--- a/Lib/test/test_wsgiref.py ++++ b/Lib/test/test_wsgiref.py +@@ -1,6 +1,6 @@ + from unittest import mock + from test import support +-from test.support import socket_helper ++from test.support import socket_helper, control_characters_c0 + from test.support import warnings_helper + from test.test_httpservers import NoLogRequestHandler + from unittest import TestCase +@@ -527,6 +527,16 @@ def testExtras(self): + '\r\n' + ) + ++ def testRaisesControlCharacters(self): ++ headers = Headers() ++ for c0 in control_characters_c0(): ++ self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", "val") ++ self.assertRaises(ValueError, headers.__setitem__, "key", f"val{c0}") ++ self.assertRaises(ValueError, headers.add_header, f"key{c0}", "val", param="param") ++ self.assertRaises(ValueError, headers.add_header, "key", f"val{c0}", param="param") ++ self.assertRaises(ValueError, headers.add_header, "key", "val", param=f"param{c0}") ++ ++ + class ErrorHandler(BaseCGIHandler): + """Simple handler subclass for testing BaseHandler""" + +diff --git a/Lib/wsgiref/headers.py b/Lib/wsgiref/headers.py +index fab851c5a44430..fd98e85d75492b 100644 +--- a/Lib/wsgiref/headers.py ++++ b/Lib/wsgiref/headers.py +@@ -9,6 +9,7 @@ + # existence of which force quoting of the parameter value. + import re + tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') ++_control_chars_re = re.compile(r'[\x00-\x1F\x7F]') + + def _formatparam(param, value=None, quote=1): + """Convenience function to format and return a key=value pair. +@@ -41,6 +42,8 @@ def __init__(self, headers=None): + def _convert_string_type(self, value): + """Convert/check value type.""" + if type(value) is str: ++ if _control_chars_re.search(value): ++ raise ValueError("Control characters not allowed in headers") + return value + raise AssertionError("Header names/values must be" + " of type str (got {0})".format(repr(value))) +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst +new file mode 100644 +index 00000000000000..44bd0b27059f94 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst +@@ -0,0 +1,2 @@ ++Reject C0 control characters within wsgiref.headers.Headers fields, values, ++and parameters. diff --git a/python310.changes b/python310.changes index 0272bbd..22ddcfd 100644 --- a/python310.changes +++ b/python310.changes @@ -1,3 +1,26 @@ +------------------------------------------------------------------- +Wed Feb 11 23:49:49 CET 2026 - Matej Cepl + +- CVE-2025-11468: preserving parens when folding comments in + email headers (bsc#1257029, gh#python/cpython#143935). + CVE-2025-11468-email-hdr-fold-comment.patch +- CVE-2026-0672: rejects control characters in http cookies. + (bsc#1257031, gh#python/cpython#143919) + CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch +- CVE-2026-0865: rejecting control characters in + wsgiref.headers.Headers, which could be abused for injecting + false HTTP headers. (bsc#1257042, gh#python/cpython#143916) + CVE-2026-0865-wsgiref-ctrl-chars.patch +- CVE-2025-15366: basically the same as the previous patch for + IMAP protocol. (bsc#1257044, gh#python/cpython#143921) + CVE-2025-15366-imap-ctrl-chars.patch +- CVE-2025-15282: basically the same as the previous patch for + urllib library. (bsc#1257046, gh#python/cpython#143925) + CVE-2025-15282-urllib-ctrl-chars.patch +- CVE-2025-15367: basically the same as the previous patch for + poplib library. (bsc#1257041, gh#python/cpython#143923) + CVE-2025-15367-poplib-ctrl-chars.patch + ------------------------------------------------------------------- Thu Dec 18 10:33:44 UTC 2025 - Matej Cepl diff --git a/python310.spec b/python310.spec index 782d548..b266f52 100644 --- a/python310.spec +++ b/python310.spec @@ -160,7 +160,6 @@ Patch07: bpo-31046_ensurepip_honours_prefix.patch # PATCH-FIX-SLE no-skipif-doctests.patch jsc#SLE-13738 mcepl@suse.com # SLE-15 version of Sphinx doesn't know about skipif directive in doctests. Patch11: no-skipif-doctests.patch - # PATCH-FIX-SLE skip-test_pyobject_freed_is_freed.patch mcepl@suse.com # skip a test failing on SLE-15 Patch15: skip-test_pyobject_freed_is_freed.patch @@ -216,6 +215,25 @@ Patch32: CVE-2025-12084-minidom-quad-search.patch # PATCH-FIX-UPSTREAM CVE-2025-13837-plistlib-mailicious-length.patch bsc#1254401 mcepl@suse.com # protect against OOM when loading malicious content Patch33: CVE-2025-13837-plistlib-mailicious-length.patch +# PATCH-FIX-UPSTREAM CVE-2025-11468-email-hdr-fold-comment.patch bsc#1257029 mcepl@suse.com +# this patch makes things totally awesome +Patch34: CVE-2025-11468-email-hdr-fold-comment.patch +# PATCH-FIX-UPSTREAM CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch bsc#1257031 mcepl@suse.com +# rejects control characters in http cookies. +Patch35: CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch +# PATCH-FIX-UPSTREAM CVE-2026-0865-wsgiref-ctrl-chars.patch bsc#1257042 mcepl@suse.com +# Reject control characters in wsgiref.headers.Headers +Patch37: CVE-2026-0865-wsgiref-ctrl-chars.patch +# PATCH-FIX-UPSTREAM CVE-2025-15366-imap-ctrl-chars.patch bsc#1257044 mcepl@suse.com +# Reject control characters in wsgiref.headers.Headers +Patch38: CVE-2025-15366-imap-ctrl-chars.patch +# PATCH-FIX-UPSTREAM CVE-2025-15282-urllib-ctrl-chars.patch bsc#1257046 mcepl@suse.com +# Reject control characters in urllib +Patch39: CVE-2025-15282-urllib-ctrl-chars.patch +# PATCH-FIX-UPSTREAM CVE-2025-15367-poplib-ctrl-chars.patch bsc#1257041 mcepl@suse.com +# Reject control characters in poplib +Patch40: CVE-2025-15367-poplib-ctrl-chars.patch +### END OF PATCHES BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes