Skip to content

Commit 37c6159

Browse files
[AutoPR- Security] Patch python3 for CVE-2026-1299, CVE-2026-0672 [MEDIUM] (#15600)
Co-authored-by: Kanishk Bansal <103916909+Kanishk-Bansal@users.noreply.github.com>
1 parent 2a49222 commit 37c6159

File tree

7 files changed

+333
-27
lines changed

7 files changed

+333
-27
lines changed

SPECS/python3/CVE-2026-0672.patch

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
From a86828435272a1f33327aff7931b14b44026ca6f Mon Sep 17 00:00:00 2001
2+
From: Seth Michael Larson <seth@python.org>
3+
Date: Tue, 20 Jan 2026 15:23:42 -0600
4+
Subject: [PATCH] gh-143919: Reject control characters in http cookies (cherry
5+
picked from commit 95746b3a13a985787ef53b977129041971ed7f70)
6+
MIME-Version: 1.0
7+
Content-Type: text/plain; charset=UTF-8
8+
Content-Transfer-Encoding: 8bit
9+
10+
Co-authored-by: Seth Michael Larson <seth@python.org>
11+
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
12+
Co-authored-by: sobolevn <mail@sobolevn.me>
13+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
14+
Upstream-reference: https://github.com/python/cpython/pull/144094.patch
15+
---
16+
Doc/library/http.cookies.rst | 4 +-
17+
Lib/http/cookies.py | 25 +++++++--
18+
Lib/test/test_http_cookies.py | 52 +++++++++++++++++--
19+
...-01-16-11-13-15.gh-issue-143919.kchwZV.rst | 1 +
20+
4 files changed, 73 insertions(+), 9 deletions(-)
21+
create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst
22+
23+
diff --git a/Doc/library/http.cookies.rst b/Doc/library/http.cookies.rst
24+
index 17792b2..07c1a68 100644
25+
--- a/Doc/library/http.cookies.rst
26+
+++ b/Doc/library/http.cookies.rst
27+
@@ -270,9 +270,9 @@ The following example demonstrates how to use the :mod:`http.cookies` module.
28+
Set-Cookie: chips=ahoy
29+
Set-Cookie: vienna=finger
30+
>>> C = cookies.SimpleCookie()
31+
- >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
32+
+ >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
33+
>>> print(C)
34+
- Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
35+
+ Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
36+
>>> C = cookies.SimpleCookie()
37+
>>> C["oreo"] = "doublestuff"
38+
>>> C["oreo"]["path"] = "/"
39+
diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
40+
index 2c1f021..5cfa7a8 100644
41+
--- a/Lib/http/cookies.py
42+
+++ b/Lib/http/cookies.py
43+
@@ -87,9 +87,9 @@ within a string. Escaped quotation marks, nested semicolons, and other
44+
such trickeries do not confuse it.
45+
46+
>>> C = cookies.SimpleCookie()
47+
- >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
48+
+ >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
49+
>>> print(C)
50+
- Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
51+
+ Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
52+
53+
Each element of the Cookie also supports all of the RFC 2109
54+
Cookie attributes. Here's an example which sets the Path
55+
@@ -170,6 +170,15 @@ _Translator.update({
56+
})
57+
58+
_is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch
59+
+_control_character_re = re.compile(r'[\x00-\x1F\x7F]')
60+
+
61+
+
62+
+def _has_control_character(*val):
63+
+ """Detects control characters within a value.
64+
+ Supports any type, as header values can be any type.
65+
+ """
66+
+ return any(_control_character_re.search(str(v)) for v in val)
67+
+
68+
69+
def _quote(str):
70+
r"""Quote a string for use in a cookie header.
71+
@@ -292,12 +301,16 @@ class Morsel(dict):
72+
K = K.lower()
73+
if not K in self._reserved:
74+
raise CookieError("Invalid attribute %r" % (K,))
75+
+ if _has_control_character(K, V):
76+
+ raise CookieError(f"Control characters are not allowed in cookies {K!r} {V!r}")
77+
dict.__setitem__(self, K, V)
78+
79+
def setdefault(self, key, val=None):
80+
key = key.lower()
81+
if key not in self._reserved:
82+
raise CookieError("Invalid attribute %r" % (key,))
83+
+ if _has_control_character(key, val):
84+
+ raise CookieError("Control characters are not allowed in cookies %r %r" % (key, val,))
85+
return dict.setdefault(self, key, val)
86+
87+
def __eq__(self, morsel):
88+
@@ -333,6 +346,9 @@ class Morsel(dict):
89+
raise CookieError('Attempt to set a reserved key %r' % (key,))
90+
if not _is_legal_key(key):
91+
raise CookieError('Illegal key %r' % (key,))
92+
+ if _has_control_character(key, val, coded_val):
93+
+ raise CookieError(
94+
+ "Control characters are not allowed in cookies %r %r %r" % (key, val, coded_val,))
95+
96+
# It's a good key, so save it.
97+
self._key = key
98+
@@ -484,7 +500,10 @@ class BaseCookie(dict):
99+
result = []
100+
items = sorted(self.items())
101+
for key, value in items:
102+
- result.append(value.output(attrs, header))
103+
+ value_output = value.output(attrs, header)
104+
+ if _has_control_character(value_output):
105+
+ raise CookieError("Control characters are not allowed in cookies")
106+
+ result.append(value_output)
107+
return sep.join(result)
108+
109+
__str__ = output
110+
diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
111+
index 644e75c..1f2c049 100644
112+
--- a/Lib/test/test_http_cookies.py
113+
+++ b/Lib/test/test_http_cookies.py
114+
@@ -17,10 +17,10 @@ class CookieTests(unittest.TestCase):
115+
'repr': "<SimpleCookie: chips='ahoy' vienna='finger'>",
116+
'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'},
117+
118+
- {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
119+
- 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'},
120+
- 'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=\\n;'>''',
121+
- 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'},
122+
+ {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=;"',
123+
+ 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=;'},
124+
+ 'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=;'>''',
125+
+ 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=;"'},
126+
127+
# Check illegal cookies that have an '=' char in an unquoted value
128+
{'data': 'keebler=E=mc2',
129+
@@ -517,6 +517,50 @@ class MorselTests(unittest.TestCase):
130+
r'Set-Cookie: key=coded_val; '
131+
r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+')
132+
133+
+ def test_control_characters(self):
134+
+ for c0 in support.control_characters_c0():
135+
+ morsel = cookies.Morsel()
136+
+
137+
+ # .__setitem__()
138+
+ with self.assertRaises(cookies.CookieError):
139+
+ morsel[c0] = "val"
140+
+ with self.assertRaises(cookies.CookieError):
141+
+ morsel["path"] = c0
142+
+
143+
+ # .setdefault()
144+
+ with self.assertRaises(cookies.CookieError):
145+
+ morsel.setdefault("path", c0)
146+
+ with self.assertRaises(cookies.CookieError):
147+
+ morsel.setdefault(c0, "val")
148+
+
149+
+ # .set()
150+
+ with self.assertRaises(cookies.CookieError):
151+
+ morsel.set(c0, "val", "coded-value")
152+
+ with self.assertRaises(cookies.CookieError):
153+
+ morsel.set("path", c0, "coded-value")
154+
+ with self.assertRaises(cookies.CookieError):
155+
+ morsel.set("path", "val", c0)
156+
+
157+
+ def test_control_characters_output(self):
158+
+ # Tests that even if the internals of Morsel are modified
159+
+ # that a call to .output() has control character safeguards.
160+
+ for c0 in support.control_characters_c0():
161+
+ morsel = cookies.Morsel()
162+
+ morsel.set("key", "value", "coded-value")
163+
+ morsel._key = c0 # Override private variable.
164+
+ cookie = cookies.SimpleCookie()
165+
+ cookie["cookie"] = morsel
166+
+ with self.assertRaises(cookies.CookieError):
167+
+ cookie.output()
168+
+
169+
+ morsel = cookies.Morsel()
170+
+ morsel.set("key", "value", "coded-value")
171+
+ morsel._coded_value = c0 # Override private variable.
172+
+ cookie = cookies.SimpleCookie()
173+
+ cookie["cookie"] = morsel
174+
+ with self.assertRaises(cookies.CookieError):
175+
+ cookie.output()
176+
+
177+
def test_main():
178+
run_unittest(CookieTests, MorselTests)
179+
run_doctest(cookies)
180+
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
181+
new file mode 100644
182+
index 0000000..788c3e4
183+
--- /dev/null
184+
+++ b/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst
185+
@@ -0,0 +1 @@
186+
+Reject control characters in :class:`http.cookies.Morsel` fields and values.
187+
--
188+
2.45.4
189+

SPECS/python3/CVE-2026-1299.patch

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
From 04a864ee5032269b1b2756da7d59ffed5d21e12e Mon Sep 17 00:00:00 2001
2+
From: Seth Michael Larson <seth@python.org>
3+
Date: Fri, 23 Jan 2026 08:59:35 -0600
4+
Subject: [PATCH] gh-144125: email: verify headers are sound in BytesGenerator
5+
(cherry picked from commit 052e55e7d44718fe46cbba0ca995cb8fcc359413)
6+
7+
Co-authored-by: Seth Michael Larson <seth@python.org>
8+
Co-authored-by: Denis Ledoux <dle@odoo.com>
9+
Co-authored-by: Denis Ledoux <5822488+beledouxdenis@users.noreply.github.com>
10+
Co-authored-by: Petr Viktorin <302922+encukou@users.noreply.github.com>
11+
Co-authored-by: Bas Bloemsaat <1586868+basbloemsaat@users.noreply.github.com>
12+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
13+
Upstream-reference: https://github.com/python/cpython/pull/144180.patch
14+
---
15+
Lib/email/generator.py | 12 +++++++++++-
16+
Lib/test/test_email/test_generator.py | 4 +++-
17+
Lib/test/test_email/test_policy.py | 6 +++++-
18+
.../2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst | 4 ++++
19+
4 files changed, 23 insertions(+), 3 deletions(-)
20+
create mode 100644 Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
21+
22+
diff --git a/Lib/email/generator.py b/Lib/email/generator.py
23+
index 89224ae..98cc4a0 100644
24+
--- a/Lib/email/generator.py
25+
+++ b/Lib/email/generator.py
26+
@@ -22,6 +22,7 @@ NL = '\n' # XXX: no longer used by the code below.
27+
NLCRE = re.compile(r'\r\n|\r|\n')
28+
fcre = re.compile(r'^From ', re.MULTILINE)
29+
NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
30+
+NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
31+
32+
33+
34+
@@ -430,7 +431,16 @@ class BytesGenerator(Generator):
35+
# This is almost the same as the string version, except for handling
36+
# strings with 8bit bytes.
37+
for h, v in msg.raw_items():
38+
- self._fp.write(self.policy.fold_binary(h, v))
39+
+ folded = self.policy.fold_binary(h, v)
40+
+ if self.policy.verify_generated_headers:
41+
+ linesep = self.policy.linesep.encode()
42+
+ if not folded.endswith(linesep):
43+
+ raise HeaderWriteError(
44+
+ f'folded header does not end with {linesep!r}: {folded!r}')
45+
+ if NEWLINE_WITHOUT_FWSP_BYTES.search(folded.removesuffix(linesep)):
46+
+ raise HeaderWriteError(
47+
+ f'folded header contains newline: {folded!r}')
48+
+ self._fp.write(folded)
49+
# A blank line always separates headers from body
50+
self.write(self._NL)
51+
52+
diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py
53+
index d29400f..a641f87 100644
54+
--- a/Lib/test/test_email/test_generator.py
55+
+++ b/Lib/test/test_email/test_generator.py
56+
@@ -264,7 +264,7 @@ class TestGenerator(TestGeneratorBase, TestEmailBase):
57+
typ = str
58+
59+
def test_verify_generated_headers(self):
60+
- """gh-121650: by default the generator prevents header injection"""
61+
+ # gh-121650: by default the generator prevents header injection
62+
class LiteralHeader(str):
63+
name = 'Header'
64+
def fold(self, **kwargs):
65+
@@ -285,6 +285,8 @@ class TestGenerator(TestGeneratorBase, TestEmailBase):
66+
67+
with self.assertRaises(email.errors.HeaderWriteError):
68+
message.as_string()
69+
+ with self.assertRaises(email.errors.HeaderWriteError):
70+
+ message.as_bytes()
71+
72+
73+
class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
74+
diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py
75+
index ff1ddf7..d4a5eb3 100644
76+
--- a/Lib/test/test_email/test_policy.py
77+
+++ b/Lib/test/test_email/test_policy.py
78+
@@ -279,7 +279,7 @@ class PolicyAPITests(unittest.TestCase):
79+
policy.fold("Subject", subject)
80+
81+
def test_verify_generated_headers(self):
82+
- """Turning protection off allows header injection"""
83+
+ # Turning protection off allows header injection
84+
policy = email.policy.default.clone(verify_generated_headers=False)
85+
for text in (
86+
'Header: Value\r\nBad: Injection\r\n',
87+
@@ -302,6 +302,10 @@ class PolicyAPITests(unittest.TestCase):
88+
message.as_string(),
89+
f"{text}\nBody",
90+
)
91+
+ self.assertEqual(
92+
+ message.as_bytes(),
93+
+ f"{text}\nBody".encode(),
94+
+ )
95+
96+
# XXX: Need subclassing tests.
97+
# For adding subclassed objects, make sure the usual rules apply (subclass
98+
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
99+
new file mode 100644
100+
index 0000000..e6333e7
101+
--- /dev/null
102+
+++ b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
103+
@@ -0,0 +1,4 @@
104+
+:mod:`~email.generator.BytesGenerator` will now refuse to serialize (write) headers
105+
+that are unsafely folded or delimited; see
106+
+:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas
107+
+Bloemsaat and Petr Viktorin in :gh:`121650`).
108+
--
109+
2.45.4
110+

SPECS/python3/python3.spec

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
Summary: A high-level scripting language
1313
Name: python3
1414
Version: 3.9.19
15-
Release: 17%{?dist}
15+
Release: 18%{?dist}
1616
License: PSF
1717
Vendor: Microsoft Corporation
1818
Distribution: Mariner
@@ -39,6 +39,8 @@ Patch15: CVE-2025-4138.patch
3939
Patch16: CVE-2025-8194.patch
4040
Patch17: CVE-2025-8291.patch
4141
Patch18: CVE-2025-6075.patch
42+
Patch19: CVE-2026-0672.patch
43+
Patch20: CVE-2026-1299.patch
4244

4345
# Patch for setuptools, resolved in 65.5.1
4446
Patch1000: CVE-2022-40897.patch
@@ -204,6 +206,8 @@ The test package contains all regression tests for Python as well as the modules
204206
%patch16 -p1
205207
%patch17 -p1
206208
%patch18 -p1
209+
%patch19 -p1
210+
%patch20 -p1
207211

208212
%build
209213
# Remove GCC specs and build environment linker scripts
@@ -379,6 +383,9 @@ make test TESTOPTS="-x test_multiprocessing_spawn -x test_socket -x test_email"
379383
%{_libdir}/python%{majmin}/test/*
380384

381385
%changelog
386+
* Wed Jan 28 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 3.9.19-18
387+
- Patch for CVE-2026-1299, CVE-2026-0672
388+
382389
* Tue Nov 04 2025 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 3.9.19-17
383390
- Patch for CVE-2025-6075
384391

toolkit/resources/manifests/package/pkggen_core_aarch64.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,10 @@ ca-certificates-base-2.0.0-25.cm2.noarch.rpm
237237
ca-certificates-2.0.0-25.cm2.noarch.rpm
238238
dwz-0.14-2.cm2.aarch64.rpm
239239
unzip-6.0-22.cm2.aarch64.rpm
240-
python3-3.9.19-17.cm2.aarch64.rpm
241-
python3-devel-3.9.19-17.cm2.aarch64.rpm
242-
python3-libs-3.9.19-17.cm2.aarch64.rpm
243-
python3-setuptools-3.9.19-17.cm2.noarch.rpm
240+
python3-3.9.19-18.cm2.aarch64.rpm
241+
python3-devel-3.9.19-18.cm2.aarch64.rpm
242+
python3-libs-3.9.19-18.cm2.aarch64.rpm
243+
python3-setuptools-3.9.19-18.cm2.noarch.rpm
244244
python3-pygments-2.4.2-7.cm2.noarch.rpm
245245
which-2.21-8.cm2.aarch64.rpm
246246
libselinux-3.2-1.cm2.aarch64.rpm

toolkit/resources/manifests/package/pkggen_core_x86_64.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,10 @@ ca-certificates-base-2.0.0-25.cm2.noarch.rpm
237237
ca-certificates-2.0.0-25.cm2.noarch.rpm
238238
dwz-0.14-2.cm2.x86_64.rpm
239239
unzip-6.0-22.cm2.x86_64.rpm
240-
python3-3.9.19-17.cm2.x86_64.rpm
241-
python3-devel-3.9.19-17.cm2.x86_64.rpm
242-
python3-libs-3.9.19-17.cm2.x86_64.rpm
243-
python3-setuptools-3.9.19-17.cm2.noarch.rpm
240+
python3-3.9.19-18.cm2.x86_64.rpm
241+
python3-devel-3.9.19-18.cm2.x86_64.rpm
242+
python3-libs-3.9.19-18.cm2.x86_64.rpm
243+
python3-setuptools-3.9.19-18.cm2.noarch.rpm
244244
python3-pygments-2.4.2-7.cm2.noarch.rpm
245245
which-2.21-8.cm2.x86_64.rpm
246246
libselinux-3.2-1.cm2.x86_64.rpm

0 commit comments

Comments
 (0)