Index | Thread | Search

From:
Theo Buehler <tb@openbsd.org>
Subject:
Re: python 3.12.13 update
To:
Kurt Mosiejczuk <kurt@cranky.work>, ports <ports@openbsd.org>
Date:
Tue, 17 Mar 2026 14:52:01 +0100

Download raw body.

Thread
On Mon, Mar 16, 2026 at 11:30:09PM +0000, Stuart Henderson wrote:
> On 2026/03/16 23:23, 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.
> 
> There's no commit on the 3.12 branch for those CVEs yet; probably
> doesn't need a lot of work but for now here's the 3.12.13 update with
> the required bits to cope with our expat build.

Here's the diff with the backports from the still not merged

https://github.com/python/cpython/pull/146025
https://github.com/python/cpython/pull/145999

The relevant regress tests pass. This one's also ok by me

Index: Makefile
===================================================================
RCS file: /cvs/ports/lang/python/3/Makefile,v
diff -u -p -r1.15 Makefile
--- Makefile	14 Jun 2025 20:08:20 -0000	1.15
+++ Makefile	17 Mar 2026 13:49:57 -0000
@@ -3,12 +3,15 @@
 # requirement of the PSF license, if it constitutes a change to
 # Python itself.
 
-FULL_VERSION =		3.12.11
+FULL_VERSION =		3.12.13
 SHARED_LIBS =		python3.12 0.0
 VERSION_SPEC =		>=3.12,<3.13
 PORTROACH =		limit:^3\.12
 
 AUTOCONF_VERSION =	2.71
+
+# XXX - should come from expat_config.h, which isn't installed in base
+CFLAGS +=	-DXML_DTD -DXML_GE=1
 
 CONFIGURE_ENV +=	ac_cv_working_openssl_hashlib=yes \
 	TCLTK_CFLAGS="-I${MODTCL_INCDIR} -I${MODTK_INCDIR} -I${X11BASE}/include" \
Index: distinfo
===================================================================
RCS file: /cvs/ports/lang/python/3/distinfo,v
diff -u -p -r1.6 distinfo
--- distinfo	14 Jun 2025 20:08:20 -0000	1.6
+++ distinfo	17 Mar 2026 13:49:57 -0000
@@ -1,2 +1,2 @@
-SHA256 (Python-3.12.11.tgz) = e41Zr4IWBE0jE96BIL/CzACpvS5ULxV5Xh1hbFH689Y=
-SIZE (Python-3.12.11.tgz) = 27126512
+SHA256 (Python-3.12.13.tgz) = CBbEdhyX7Ns/UKOSTeCpP9eMtj7o5sBCAd367cpQCws=
+SIZE (Python-3.12.13.tgz) = 27262093
Index: files/CHANGES.OpenBSD
===================================================================
RCS file: /cvs/ports/lang/python/3/files/CHANGES.OpenBSD,v
diff -u -p -r1.2 CHANGES.OpenBSD
--- files/CHANGES.OpenBSD	21 Dec 2024 11:32:44 -0000	1.2
+++ files/CHANGES.OpenBSD	17 Mar 2026 13:49:57 -0000
@@ -25,5 +25,9 @@ which results in loading an incorrect ve
 
 8.  Adjust #ifdef to use LibreSSL's version of X509_STORE_get1_objects().
 
+9.  Work around expat_config.h missing from base.
+
+10. 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	17 Mar 2026 13:49:57 -0000
@@ -0,0 +1,73 @@
+https://github.com/python/cpython/pull/146025
+
+From 736de53d3d91e83e9ff8a5d81b418bd8ea033e35 Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
+Date: Mon, 16 Mar 2026 13:43:43 +0000
+Subject: [PATCH] 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_outpu
+t`.
+
+Reject control characters in :class:`http.cookies.Morsel`
+:meth:`~http.cookies.Morsel.update` and
+:meth:`~http.cookies.BaseCookie.js_output`.
+This addresses :cve:`2026-3644`.
+
+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	17 Mar 2026 13:49:57 -0000
@@ -0,0 +1,78 @@
+https://github.com/python/cpython/pull/146025
+
+From 736de53d3d91e83e9ff8a5d81b418bd8ea033e35 Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
+Date: Mon, 16 Mar 2026 13:43:43 +0000
+Subject: [PATCH] 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_outpu
+t`.
+
+Reject control characters in :class:`http.cookies.Morsel`
+:meth:`~http.cookies.Morsel.update` and
+:meth:`~http.cookies.BaseCookie.js_output`.
+This addresses :cve:`2026-3644`.
+
+Index: Lib/test/test_http_cookies.py
+--- Lib/test/test_http_cookies.py.orig
++++ Lib/test/test_http_cookies.py
+@@ -573,6 +573,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)
+@@ -587,6 +595,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.
+@@ -606,6 +626,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	17 Mar 2026 13:49:57 -0000
@@ -0,0 +1,41 @@
+https://github.com/python/cpython/pull/145999
+
+From e5caf45faac74b0ed869e3336420cffd3510ce6e Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
+Date: Sun, 15 Mar 2026 21:46:06 +0000
+Subject: [PATCH] [3.12] gh-145986: Avoid unbound C recursion in
+ `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987)
+
+:mod:`xml.parsers.expat`: Fixed a crash caused by unbounded C recursion when
+converting deeply nested XML content models with
+:meth:`~xml.parsers.expat.xmlparser.ElementDeclHandler`.
+This addresses :cve:`2026-4224`.
+
+Index: Lib/test/test_pyexpat.py
+--- Lib/test/test_pyexpat.py.orig
++++ Lib/test/test_pyexpat.py
+@@ -675,6 +675,24 @@ class ChardataBufferTest(unittest.TestCase):
+         parser.Parse(xml2, True)
+         self.assertEqual(self.n, 4)
+ 
++class ElementDeclHandlerTest(unittest.TestCase):
++    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: patches/patch-Modules_pyexpat_c
diff -N patches/patch-Modules_pyexpat_c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ patches/patch-Modules_pyexpat_c	17 Mar 2026 13:49:57 -0000
@@ -0,0 +1,63 @@
+- remove #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
+
+- https://github.com/python/cpython/pull/145999
+
+From e5caf45faac74b0ed869e3336420cffd3510ce6e Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
+Date: Sun, 15 Mar 2026 21:46:06 +0000
+Subject: [PATCH] [3.12] gh-145986: Avoid unbound C recursion in
+ `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987)
+
+:mod:`xml.parsers.expat`: Fixed a crash caused by unbounded C recursion when
+converting deeply nested XML content models with
+:meth:`~xml.parsers.expat.xmlparser.ElementDeclHandler`.
+This addresses :cve:`2026-4224`.
+
+Index: Modules/pyexpat.c
+--- Modules/pyexpat.c.orig
++++ Modules/pyexpat.c
+@@ -3,12 +3,12 @@
+ #endif
+ 
+ #include "Python.h"
++#include "pycore_ceval.h"           // _Py_EnterRecursiveCall()
+ #include "pycore_runtime.h"         // _Py_ID()
+ #include <ctype.h>
+ 
+ #include <stdbool.h>
+ #include "structmember.h"         // PyMemberDef
+-#include "expat_config.h"
+ #include "expat.h"
+ 
+ #include "pyexpat.h"
+@@ -578,6 +578,10 @@ static PyObject *
+ conv_content_model(XML_Content * const model,
+                    PyObject *(*conv_string)(const XML_Char *))
+ {
++    if (_Py_EnterRecursiveCall(" in conv_content_model")) {
++        return NULL;
++    }
++
+     PyObject *result = NULL;
+     PyObject *children = PyTuple_New(model->numchildren);
+     int i;
+@@ -589,7 +593,7 @@ conv_content_model(XML_Content * const model,
+                                                  conv_string);
+             if (child == NULL) {
+                 Py_XDECREF(children);
+-                return NULL;
++                goto done;
+             }
+             PyTuple_SET_ITEM(children, i, child);
+         }
+@@ -597,6 +601,8 @@ conv_content_model(XML_Content * const model,
+                                model->type, model->quant,
+                                conv_string,model->name, children);
+     }
++done:
++    _Py_LeaveRecursiveCall();
+     return result;
+ }
+ 
Index: pkg/PLIST-tests
===================================================================
RCS file: /cvs/ports/lang/python/3/pkg/PLIST-tests,v
diff -u -p -r1.8 PLIST-tests
--- pkg/PLIST-tests	15 Apr 2025 20:36:43 -0000	1.8
+++ pkg/PLIST-tests	17 Mar 2026 13:49:57 -0000
@@ -4820,7 +4820,7 @@ lib/python3.12/test/typinganndata/ann_mo
 lib/python3.12/test/typinganndata/ann_module9.py
 lib/python3.12/test/typinganndata/mod_generics_cache.py
 lib/python3.12/test/wheeldata/
-lib/python3.12/test/wheeldata/setuptools-67.6.1-py3-none-any.whl
+lib/python3.12/test/wheeldata/setuptools-79.0.1-py3-none-any.whl
 lib/python3.12/test/wheeldata/wheel-0.40.0-py3-none-any.whl
 lib/python3.12/test/win_console_handler.py
 lib/python3.12/test/xmltestdata/