forked from pool/python312
CVE-2025-11468: to preserve parens when folding comments.
(bsc#1257029, gh#python/cpython#143935)
CVE-2025-11468-email-hdr-fold-comment.patch
CVE-2025-12781: fix decoding with non-standard Base64 alphabet
(bsc#1257108, gh#python/cpython#125346)
CVE-2025-12781-b64decode-alt-chars.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-13836: to prevent reading an HTTP response from
Content-Length per default as the length. (bsc#1254400,
gh#python/cpython#119451)
CVE-2025-13836-http-resp-cont-len.patch
CVE-2025-12084: prevent quadratic behavior in node ID cache
clearing. (bsc#1254997, gh#python/cpython#142145)
CVE-2025-12084-minidom-quad-search.patch
CVE-2025-13837: protect against OOM when loading malicious
content. (bsc#1254401, gh#python/cpython#119342)
CVE-2025-13837-plistlib-mailicious-length.patch
- gh-99242: os.getloadavg() may throw OSError when running
regression tests under certain conditions (e.g. chroot).
This error is now caught and ignored, since reporting load
average is optional.
- gh-121160: Add a test for readline.set_history_length().
Note that this test may fail on readline libraries.
- gh-121200: Fix test_expanduser_pwd2() of test_posixpath.
Call getpwnam() to get pw_dir, since it can be different
than getpwall() pw_dir. Patch by Victor Stinner.
- gh-121188: When creating the JUnit XML file, regrtest now
escapes characters which are invalid in XML, such as the
chr(27) control character used in ANSI escape sequences.
Patch by Victor Stinner.
- CVE-2026-1299 and CVE-2024-6923: email headers with
embedded newlines are now quoted on output. The generator
will now refuse to serialize (write) headers that are
unsafely folded or delimited; see verify_generated_headers.
(Contributed by Bas Bloemsaat and Petr Viktorin in
bsc#1228780, gh-121650; bsc#1257181, gh-121650).
- gh-120495: Fix incorrect exception handling in Tab Nanny.
Patch by Wulian233.
would produce incorrect results if type parameters in
a class scope were overridden by assignments in a class
scope and from __future__ import annotations semantics were
- gh-81936: help() and showtopic() methods now respect
a configured output argument to pydoc.Helper and not use
the pager in such cases. Patch by Enrico Tröger.
- gh-119577: The DeprecationWarning emitted when testing the
truth value of an xml.etree.ElementTree.Element now
- gh-121871: Documentation HTML varies from timestamp. Patch
by Bernhard M. Wiedemann (bsc#1227999).
- gh-122029: Emit c_call events in sys.setprofile() when
a PyMethodObject pointing to a PyCFunction is called.
modification of a list object, where one thread assigns
a slice and another clears it.
bytes and bytearray objects when using protocol version 5.
Patch by Bénédikt Tran.
184 lines
8.2 KiB
Diff
184 lines
8.2 KiB
Diff
From 9729bdf210967c806f3364c76663fc2ade58e389 Mon Sep 17 00:00:00 2001
|
|
From: Seth Michael Larson <seth@python.org>
|
|
Date: Tue, 20 Jan 2026 15:23:42 -0600
|
|
Subject: [PATCH] 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 <seth@python.org>
|
|
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
|
|
Co-authored-by: sobolevn <mail@sobolevn.me>
|
|
---
|
|
Doc/library/http.cookies.rst | 4
|
|
Lib/http/cookies.py | 25 ++++
|
|
Lib/test/test_http_cookies.py | 52 +++++++++-
|
|
Misc/NEWS.d/next/Security/2026-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
|
|
|
|
Index: Python-3.12.12/Doc/library/http.cookies.rst
|
|
===================================================================
|
|
--- Python-3.12.12.orig/Doc/library/http.cookies.rst 2025-10-09 13:07:00.000000000 +0200
|
|
+++ Python-3.12.12/Doc/library/http.cookies.rst 2026-02-10 22:17:53.970413039 +0100
|
|
@@ -272,9 +272,9 @@
|
|
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"] = "/"
|
|
Index: Python-3.12.12/Lib/http/cookies.py
|
|
===================================================================
|
|
--- Python-3.12.12.orig/Lib/http/cookies.py 2026-02-10 22:15:03.102916960 +0100
|
|
+++ Python-3.12.12/Lib/http/cookies.py 2026-02-10 22:17:53.970584713 +0100
|
|
@@ -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 @@
|
|
})
|
|
|
|
_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 @@
|
|
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 @@
|
|
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
|
|
@@ -486,7 +502,10 @@
|
|
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
|
|
Index: Python-3.12.12/Lib/test/test_http_cookies.py
|
|
===================================================================
|
|
--- Python-3.12.12.orig/Lib/test/test_http_cookies.py 2026-02-10 22:15:05.120676729 +0100
|
|
+++ Python-3.12.12/Lib/test/test_http_cookies.py 2026-02-10 22:17:53.970780833 +0100
|
|
@@ -17,10 +17,10 @@
|
|
'repr': "<SimpleCookie: chips='ahoy' vienna='finger'>",
|
|
'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': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=\\n;'>''',
|
|
- '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': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=;'>''',
|
|
+ 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=;"'},
|
|
|
|
# Check illegal cookies that have an '=' char in an unquoted value
|
|
{'data': 'keebler=E=mc2',
|
|
@@ -563,6 +563,50 @@
|
|
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 load_tests(loader, tests, pattern):
|
|
tests.addTest(doctest.DocTestSuite(cookies))
|
|
Index: Python-3.12.12/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ Python-3.12.12/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst 2026-02-10 22:17:53.970970320 +0100
|
|
@@ -0,0 +1 @@
|
|
+Reject control characters in :class:`http.cookies.Morsel` fields and values.
|