From fdc8b7dbef75bf1a70a244eb1eef609df242eede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hrn=C4=8Diar?= Date: Fri, 27 Feb 2026 10:16:27 +0100 Subject: [PATCH] Security fixes for CVE-2026-0865, CVE-2025-15366, CVE-2025-15367 and CVE-2026-1299 Resolves: RHEL-143065 Resolves: RHEL-143122 Resolves: RHEL-144862 --- 00473-cve-2026-0865.patch | 90 ++++++++++++++++++++++++++++++ 00474-cve-2025-15366.patch | 61 ++++++++++++++++++++ 00475-cve-2025-15367.patch | 61 ++++++++++++++++++++ 00476-cve-2026-1299.patch | 110 +++++++++++++++++++++++++++++++++++++ python3.12.spec | 39 ++++++++++++- 5 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 00473-cve-2026-0865.patch create mode 100644 00474-cve-2025-15366.patch create mode 100644 00475-cve-2025-15367.patch create mode 100644 00476-cve-2026-1299.patch diff --git a/00473-cve-2026-0865.patch b/00473-cve-2026-0865.patch new file mode 100644 index 0000000..3a93b65 --- /dev/null +++ b/00473-cve-2026-0865.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Sat, 17 Jan 2026 11:46:21 -0600 +Subject: 00473: CVE-2026-0865 + + 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 +--- + 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 4c42234ccc..26c0af4b13 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -2599,3 +2599,10 @@ def __iter__(self): + if self.iter_raises: + 1/0 + return self ++ ++ ++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 9316d0ecbc..28e3656632 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.test_httpservers import NoLogRequestHandler + from unittest import TestCase + from wsgiref.util import setup_testing_defaults +@@ -503,6 +503,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 fab851c5a4..fd98e85d75 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 0000000000..44bd0b2705 +--- /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/00474-cve-2025-15366.patch b/00474-cve-2025-15366.patch new file mode 100644 index 0000000..50f62d9 --- /dev/null +++ b/00474-cve-2025-15366.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Tue, 20 Jan 2026 14:45:42 -0600 +Subject: 00474: CVE-2025-15366 + +gh-143921: Reject control characters in IMAP commands + +(cherry-picked from commit 6262704b134db2a4ba12e85ecfbd968534f28b45) +--- + Lib/imaplib.py | 4 +++- + Lib/test/test_imaplib.py | 6 ++++++ + .../Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst | 1 + + 3 files changed, 10 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst + +diff --git a/Lib/imaplib.py b/Lib/imaplib.py +index e337fe6471..c7f44f05b1 100644 +--- a/Lib/imaplib.py ++++ b/Lib/imaplib.py +@@ -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 @@ def _command(self, name, *args): + 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 +diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py +index 4429a90050..73c25bc733 100644 +--- a/Lib/test/test_imaplib.py ++++ b/Lib/test/test_imaplib.py +@@ -504,6 +504,12 @@ def test_login(self): + self.assertEqual(data[0], b'LOGIN completed') + 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') ++ + def test_logout(self): + client, _ = self._setup(SimpleIMAPHandler) + typ, data = client.login('user', 'pass') +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst +new file mode 100644 +index 0000000000..4e13fe92bc +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst +@@ -0,0 +1 @@ ++Reject control characters in IMAP commands. diff --git a/00475-cve-2025-15367.patch b/00475-cve-2025-15367.patch new file mode 100644 index 0000000..12b945f --- /dev/null +++ b/00475-cve-2025-15367.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Tue, 20 Jan 2026 14:46:32 -0600 +Subject: 00475: CVE-2025-15367 + +gh-143923: Reject control characters in POP3 commands + +(cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7) +--- + Lib/poplib.py | 2 ++ + Lib/test/test_poplib.py | 8 ++++++++ + .../2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst | 1 + + 3 files changed, 11 insertions(+) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst + +diff --git a/Lib/poplib.py b/Lib/poplib.py +index 9eb662d000..5c83522504 100644 +--- a/Lib/poplib.py ++++ b/Lib/poplib.py +@@ -122,6 +122,8 @@ def _putline(self, line): + 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) + + +diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py +index f1ebbeafe0..50d8c255d6 100644 +--- a/Lib/test/test_poplib.py ++++ b/Lib/test/test_poplib.py +@@ -12,6 +12,7 @@ + import unittest + from unittest import TestCase, skipUnless + from test import support as test_support ++from test.support import control_characters_c0 + from test.support import hashlib_helper + from test.support import socket_helper + from test.support import threading_helper +@@ -395,6 +396,13 @@ def test_quit(self): + 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() +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst +new file mode 100644 +index 0000000000..3cde4df3e0 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst +@@ -0,0 +1 @@ ++Reject control characters in POP3 commands. diff --git a/00476-cve-2026-1299.patch b/00476-cve-2026-1299.patch new file mode 100644 index 0000000..ae6a4b2 --- /dev/null +++ b/00476-cve-2026-1299.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Fri, 13 Feb 2026 17:04:54 +0100 +Subject: [PATCH] [3.12] gh-144125: email: verify headers are sound in + BytesGenerator + +gh-144125: email: verify headers are sound in BytesGenerator +(cherry picked from commit 052e55e7d44718fe46cbba0ca995cb8fcc359413) + +Co-authored-by: Seth Michael Larson +Co-authored-by: Denis Ledoux +Co-authored-by: Denis Ledoux <5822488+beledouxdenis@users.noreply.github.com> +Co-authored-by: Petr Viktorin <302922+encukou@users.noreply.github.com> +Co-authored-by: Bas Bloemsaat <1586868+basbloemsaat@users.noreply.github.com> +Co-authored-by: Petr Viktorin +--- + Lib/email/generator.py | 12 +++++++++++- + Lib/test/test_email/test_generator.py | 4 +++- + Lib/test/test_email/test_policy.py | 6 +++++- + .../2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst | 4 ++++ + 4 files changed, 23 insertions(+), 3 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst + +diff --git a/Lib/email/generator.py b/Lib/email/generator.py +index 47b9df8f4e6090..8cbc43ef5bc647 100644 +--- a/Lib/email/generator.py ++++ b/Lib/email/generator.py +@@ -22,6 +22,7 @@ + NLCRE = re.compile(r'\r\n|\r|\n') + fcre = re.compile(r'^From ', re.MULTILINE) + NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]') ++NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]') + + + class Generator: +@@ -429,7 +430,16 @@ def _write_headers(self, msg): + # This is almost the same as the string version, except for handling + # strings with 8bit bytes. + for h, v in msg.raw_items(): +- self._fp.write(self.policy.fold_binary(h, v)) ++ folded = self.policy.fold_binary(h, v) ++ if self.policy.verify_generated_headers: ++ linesep = self.policy.linesep.encode() ++ if not folded.endswith(linesep): ++ raise HeaderWriteError( ++ f'folded header does not end with {linesep!r}: {folded!r}') ++ if NEWLINE_WITHOUT_FWSP_BYTES.search(folded.removesuffix(linesep)): ++ raise HeaderWriteError( ++ f'folded header contains newline: {folded!r}') ++ self._fp.write(folded) + # A blank line always separates headers from body + self.write(self._NL) + +diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py +index c75a842c33578e..3ca79edf6a65d9 100644 +--- a/Lib/test/test_email/test_generator.py ++++ b/Lib/test/test_email/test_generator.py +@@ -313,7 +313,7 @@ def test_flatten_unicode_linesep(self): + self.assertEqual(s.getvalue(), self.typ(expected)) + + def test_verify_generated_headers(self): +- """gh-121650: by default the generator prevents header injection""" ++ # gh-121650: by default the generator prevents header injection + class LiteralHeader(str): + name = 'Header' + def fold(self, **kwargs): +@@ -334,6 +334,8 @@ def fold(self, **kwargs): + + with self.assertRaises(email.errors.HeaderWriteError): + message.as_string() ++ with self.assertRaises(email.errors.HeaderWriteError): ++ message.as_bytes() + + + class TestBytesGenerator(TestGeneratorBase, TestEmailBase): +diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py +index baa35fd68e49c5..71ec0febb0fd86 100644 +--- a/Lib/test/test_email/test_policy.py ++++ b/Lib/test/test_email/test_policy.py +@@ -296,7 +296,7 @@ def test_short_maxlen_error(self): + policy.fold("Subject", subject) + + def test_verify_generated_headers(self): +- """Turning protection off allows header injection""" ++ # Turning protection off allows header injection + policy = email.policy.default.clone(verify_generated_headers=False) + for text in ( + 'Header: Value\r\nBad: Injection\r\n', +@@ -319,6 +319,10 @@ def fold(self, **kwargs): + message.as_string(), + f"{text}\nBody", + ) ++ self.assertEqual( ++ message.as_bytes(), ++ f"{text}\nBody".encode(), ++ ) + + # XXX: Need subclassing tests. + # For adding subclassed objects, make sure the usual rules apply (subclass +diff --git a/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst +new file mode 100644 +index 00000000000000..e6333e724972c5 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst +@@ -0,0 +1,4 @@ ++:mod:`~email.generator.BytesGenerator` will now refuse to serialize (write) headers ++that are unsafely folded or delimited; see ++:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas ++Bloemsaat and Petr Viktorin in :gh:`121650`). diff --git a/python3.12.spec b/python3.12.spec index 42d485b..1b6ac61 100644 --- a/python3.12.spec +++ b/python3.12.spec @@ -20,7 +20,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 2%{?dist} +Release: 3%{?dist} License: Python-2.0.1 @@ -413,6 +413,37 @@ Patch471: 00471-CVE-2025-12084.patch # of sent data. Patch472: 00472-cve-2025-13836.patch +# 00473 # dd705786aa0c1ccfde913858598e34e1f196be2e +# CVE-2026-0865 +# +# 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 +Patch473: 00473-cve-2026-0865.patch + +# 00474 # 837ddca0372fa87ff9cee47142200caa21e77def +# CVE-2025-15366 +# +# gh-143921: Reject control characters in IMAP commands +# +# (cherry-picked from commit 6262704b134db2a4ba12e85ecfbd968534f28b45) +Patch474: 00474-cve-2025-15366.patch + +# 00475 # 3748209a316662d4e85981ca1a7418547a1d25c6 +# CVE-2025-15367 +# +# gh-143923: Reject control characters in POP3 commands +# +# (cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7) +Patch475: 00475-cve-2025-15367.patch + +# 00476 +# CVE-2026-1299 +# +# gh-144125: email: verify headers are sound in BytesGenerator +Patch476: 00476-cve-2026-1299.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1903,6 +1934,12 @@ fi # ====================================================== %changelog +* Fri Feb 27 2026 Tomáš Hrnčiar - 3.12.12-3 +- Security fixes for CVE-2026-0865, CVE-2025-15366, CVE-2025-15367 and CVE-2026-1299 +Resolves: RHEL-143065 +Resolves: RHEL-143122 +Resolves: RHEL-144862 + * Fri Jan 16 2026 Lumír Balhar - 3.12.12-2 - Security fix for CVE-2025-13836 Resolves: RHEL-140993