Tuesday, March 17, 2026

Re: python 3.13.12 update + CVE patches

On Mon, Mar 16, 2026 at 11:23:40PM +0000, Stuart Henderson wrote:
> This updates to February's release, and cherry-picks two recent CVE
> fixes (med/high). I expect another release will be on the way at some
> point but seems worth bringing them in already.

> Diff to follow for 7.8-stable.

Looks good to me.

ok kmos

--Kurt

> Index: Makefile
> ===================================================================
> RCS file: /cvs/ports/lang/python/3/Makefile,v
> diff -u -p -r1.22 Makefile
> --- Makefile 31 Jan 2026 18:24:22 -0000 1.22
> +++ Makefile 16 Mar 2026 23:08:45 -0000
> @@ -3,14 +3,10 @@
> # requirement of the PSF license, if it constitutes a change to
> # Python itself.
>
> -FULL_VERSION = 3.13.11
> +FULL_VERSION = 3.13.12
> SHARED_LIBS = python3.13 0.0
> VERSION_SPEC = >=3.13,<3.14
> PORTROACH = limit:^3\.12
> -
> -# XXX keep REVISION-main above 7.8-stable and do not sync the
> -# XXX @conflict or @pkgpath for glade/py-bsddb3
> -REVISION-main = 2
>
> AUTOCONF_VERSION = 2.71
>
> Index: distinfo
> ===================================================================
> RCS file: /cvs/ports/lang/python/3/distinfo,v
> diff -u -p -r1.9 distinfo
> --- distinfo 12 Dec 2025 02:44:50 -0000 1.9
> +++ distinfo 16 Mar 2026 23:08:45 -0000
> @@ -1,2 +1,2 @@
> -SHA256 (Python-3.13.11.tgz) = A8/tvgbOIbxEzgkkXgkad/L+6eyb5cUgaQSKGBMAsgI=
> -SIZE (Python-3.13.11.tgz) = 29362906
> +SHA256 (Python-3.13.12.tgz) = EufLFwrS0aaa7pahzH/I3lsel6K9rFFoOj2wFuyaKZY=
> +SIZE (Python-3.13.12.tgz) = 29803459
> Index: files/CHANGES.OpenBSD
> ===================================================================
> RCS file: /cvs/ports/lang/python/3/files/CHANGES.OpenBSD,v
> diff -u -p -r1.4 CHANGES.OpenBSD
> --- files/CHANGES.OpenBSD 12 Dec 2025 02:44:50 -0000 1.4
> +++ files/CHANGES.OpenBSD 16 Mar 2026 23:08:45 -0000
> @@ -24,5 +24,7 @@ which results in loading an incorrect ve
>
> 8. Work around expat_config.h missing from base.
>
> +9. Cherry-pick fixes for CVE-2026-3644, CVE-2026-4224
> +
> These changes are available in the OpenBSD CVS repository
> <http://www.openbsd.org/anoncvs.html> in ports/lang/python/3.
> Index: patches/patch-Lib_http_cookies_py
> ===================================================================
> RCS file: patches/patch-Lib_http_cookies_py
> diff -N patches/patch-Lib_http_cookies_py
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ patches/patch-Lib_http_cookies_py 16 Mar 2026 23:08:45 -0000
> @@ -0,0 +1,71 @@
> +https://mail.python.org/archives/list/security-announce@python.org/thread/H6CADMBCDRFGWCMOXWUIHFJNV43GABJ7/
> +
> +From d16ecc6c3626f0e2cc8f08c309c83934e8a979dd Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Mon, 16 Mar 2026 15:05:13 +0100
> +Subject: [PATCH] [3.13] gh-145599, CVE 2026-3644: Reject control characters in
> + `http.cookies.Morsel.update()` (GH-145600) (#146024)
> +
> +gh-145599, CVE 2026-3644: Reject control characters in `http.cookies.Morsel.update()` (GH-145600)
> +
> +Reject control characters in `http.cookies.Morsel.update()` and `http.cookies.BaseCookie.js_output`.
> +(cherry picked from commit 57e88c1cf95e1481b94ae57abe1010469d47a6b4)
> +
> +Index: Lib/http/cookies.py
> +--- Lib/http/cookies.py.orig
> ++++ Lib/http/cookies.py
> +@@ -335,9 +335,16 @@ class Morsel(dict):
> + 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 "
> ++ f"cookies {key!r} {val!r}")
> + data[key] = val
> + dict.update(self, data)
> +
> ++ def __ior__(self, values):
> ++ self.update(values)
> ++ return self
> ++
> + def isReservedKey(self, K):
> + return K.lower() in self._reserved
> +
> +@@ -363,9 +370,15 @@ class Morsel(dict):
> + }
> +
> + def __setstate__(self, state):
> +- self._key = state['key']
> +- self._value = state['value']
> +- self._coded_value = state['coded_value']
> ++ key = state['key']
> ++ value = state['value']
> ++ coded_value = state['coded_value']
> ++ if _has_control_character(key, value, coded_value):
> ++ raise CookieError("Control characters are not allowed in cookies "
> ++ f"{key!r} {value!r} {coded_value!r}")
> ++ self._key = key
> ++ self._value = value
> ++ self._coded_value = coded_value
> +
> + def output(self, attrs=None, header="Set-Cookie:"):
> + return "%s %s" % (header, self.OutputString(attrs))
> +@@ -377,13 +390,16 @@ class Morsel(dict):
> +
> + def js_output(self, attrs=None):
> + # Print javascript
> ++ output_string = self.OutputString(attrs)
> ++ if _has_control_character(output_string):
> ++ raise CookieError("Control characters are not allowed in cookies")
> + return """
> + <script type="text/javascript">
> + <!-- begin hiding
> + document.cookie = \"%s\";
> + // end hiding -->
> + </script>
> +- """ % (self.OutputString(attrs).replace('"', r'\"'))
> ++ """ % (output_string.replace('"', r'\"'))
> +
> + def OutputString(self, attrs=None):
> + # Build up our result
> Index: patches/patch-Lib_test_test_http_cookies_py
> ===================================================================
> RCS file: patches/patch-Lib_test_test_http_cookies_py
> diff -N patches/patch-Lib_test_test_http_cookies_py
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ patches/patch-Lib_test_test_http_cookies_py 16 Mar 2026 23:08:45 -0000
> @@ -0,0 +1,79 @@
> +https://mail.python.org/archives/list/security-announce@python.org/thread/H6CADMBCDRFGWCMOXWUIHFJNV43GABJ7/
> +
> +From d16ecc6c3626f0e2cc8f08c309c83934e8a979dd Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Mon, 16 Mar 2026 15:05:13 +0100
> +Subject: [PATCH] [3.13] gh-145599, CVE 2026-3644: Reject control
> +characters in
> + `http.cookies.Morsel.update()` (GH-145600) (#146024)
> +
> +gh-145599, CVE 2026-3644: Reject control characters in
> +`http.cookies.Morsel.update()` (GH-145600)
> +
> +Reject control characters in `http.cookies.Morsel.update()` and
> +`http.cookies.BaseCookie.js_output`.
> +(cherry picked from commit 57e88c1cf95e1481b94ae57abe1010469d47a6b4)
> +
> +Index: Lib/test/test_http_cookies.py
> +--- Lib/test/test_http_cookies.py.orig
> ++++ Lib/test/test_http_cookies.py
> +@@ -574,6 +574,14 @@ class MorselTests(unittest.TestCase):
> + with self.assertRaises(cookies.CookieError):
> + morsel["path"] = c0
> +
> ++ # .__setstate__()
> ++ with self.assertRaises(cookies.CookieError):
> ++ morsel.__setstate__({'key': c0, 'value': 'val', 'coded_value': 'coded'})
> ++ with self.assertRaises(cookies.CookieError):
> ++ morsel.__setstate__({'key': 'key', 'value': c0, 'coded_value': 'coded'})
> ++ with self.assertRaises(cookies.CookieError):
> ++ morsel.__setstate__({'key': 'key', 'value': 'val', 'coded_value': c0})
> ++
> + # .setdefault()
> + with self.assertRaises(cookies.CookieError):
> + morsel.setdefault("path", c0)
> +@@ -588,6 +596,18 @@ class MorselTests(unittest.TestCase):
> + with self.assertRaises(cookies.CookieError):
> + morsel.set("path", "val", c0)
> +
> ++ # .update()
> ++ with self.assertRaises(cookies.CookieError):
> ++ morsel.update({"path": c0})
> ++ with self.assertRaises(cookies.CookieError):
> ++ morsel.update({c0: "val"})
> ++
> ++ # .__ior__()
> ++ with self.assertRaises(cookies.CookieError):
> ++ morsel |= {"path": c0}
> ++ with self.assertRaises(cookies.CookieError):
> ++ morsel |= {c0: "val"}
> ++
> + 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.
> +@@ -607,6 +627,24 @@ class MorselTests(unittest.TestCase):
> + cookie["cookie"] = morsel
> + with self.assertRaises(cookies.CookieError):
> + cookie.output()
> ++
> ++ # Tests that .js_output() also 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.js_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.js_output()
> +
> +
> + def load_tests(loader, tests, pattern):
> Index: patches/patch-Lib_test_test_pyexpat_py
> ===================================================================
> RCS file: patches/patch-Lib_test_test_pyexpat_py
> diff -N patches/patch-Lib_test_test_pyexpat_py
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ patches/patch-Lib_test_test_pyexpat_py 16 Mar 2026 23:08:45 -0000
> @@ -0,0 +1,47 @@
> +https://mail.python.org/archives/list/security-announce@python.org/thread/5M7CGUW3XBRY7II4DK43KF7NQQ3TPZ6R/
> +
> +From 196edfb06a7458377d4d0f4b3cd41724c1f3bd4a Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Mon, 16 Mar 2026 10:09:27 +0100
> +Subject: [PATCH] [3.13] gh-145986: Avoid unbound C recursion in
> + `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987) (#145996)
> +MIME-Version: 1.0
> +Content-Type: text/plain; charset=UTF-8
> +Content-Transfer-Encoding: 8bit
> +
> +* gh-145986: Avoid unbound C recursion in `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987)
> +
> +Fix C stack overflow (CVE-2026-4224) when an Expat parser
> +with a registered `ElementDeclHandler` parses inline DTD
> +containing deeply nested content model.
> +
> +---------
> +(cherry picked from commit eb0e8be3a7e11b87d198a2c3af1ed0eccf532768)
> +
> +Index: Lib/test/test_pyexpat.py
> +--- Lib/test/test_pyexpat.py.orig
> ++++ Lib/test/test_pyexpat.py
> +@@ -688,6 +688,22 @@ class ElementDeclHandlerTest(unittest.TestCase):
> + parser.ElementDeclHandler = lambda _1, _2: None
> + self.assertRaises(TypeError, parser.Parse, data, True)
> +
> ++ def test_deeply_nested_content_model(self):
> ++ # This should raise a RecursionError and not crash.
> ++ # See https://github.com/python/cpython/issues/145986.
> ++ N = 500_000
> ++ data = (
> ++ b'<!DOCTYPE root [\n<!ELEMENT root '
> ++ + b'(a, ' * N + b'a' + b')' * N
> ++ + b'>\n]>\n<root/>\n'
> ++ )
> ++
> ++ parser = expat.ParserCreate()
> ++ parser.ElementDeclHandler = lambda _1, _2: None
> ++ with support.infinite_recursion():
> ++ with self.assertRaises(RecursionError):
> ++ parser.Parse(data)
> ++
> + class MalformedInputTest(unittest.TestCase):
> + def test1(self):
> + xml = b"\0\r\n"
> Index: patches/patch-Modules_pyexpat_c
> ===================================================================
> RCS file: /cvs/ports/lang/python/3/patches/patch-Modules_pyexpat_c,v
> diff -u -p -r1.1 patch-Modules_pyexpat_c
> --- patches/patch-Modules_pyexpat_c 12 Dec 2025 02:44:50 -0000 1.1
> +++ patches/patch-Modules_pyexpat_c 16 Mar 2026 23:08:45 -0000
> @@ -1,11 +1,75 @@
> +- one hunk removes #include "expat_config.h"; this (or an alternative,
> +see textproc/xmlwf for another approach) needs to be kept unless expat
> +in base starts providing this config header
> +
> +- other hunks are:
> +
> +https://mail.python.org/archives/list/security-announce@python.org/thread/5M7CGUW3XBRY7II4DK43KF7NQQ3TPZ6R/
> +
> +From 196edfb06a7458377d4d0f4b3cd41724c1f3bd4a Mon Sep 17 00:00:00 2001
> +From: "Miss Islington (bot)"
> + <31488909+miss-islington@users.noreply.github.com>
> +Date: Mon, 16 Mar 2026 10:09:27 +0100
> +Subject: [PATCH] [3.13] gh-145986: Avoid unbound C recursion in
> + `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987) (#145996)
> +MIME-Version: 1.0
> +Content-Type: text/plain; charset=UTF-8
> +Content-Transfer-Encoding: 8bit
> +
> +* gh-145986: Avoid unbound C recursion in `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987)
> +
> +Fix C stack overflow (CVE-2026-4224) when an Expat parser
> +with a registered `ElementDeclHandler` parses inline DTD
> +containing deeply nested content model.
> +
> +---------
> +(cherry picked from commit eb0e8be3a7e11b87d198a2c3af1ed0eccf532768)
> +
> Index: Modules/pyexpat.c
> --- Modules/pyexpat.c.orig
> +++ Modules/pyexpat.c
> -@@ -10,7 +10,6 @@
> +@@ -3,6 +3,7 @@
> +

No comments:

Post a Comment