Download raw body.
python 3.12.13 update
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/
python 3.12.13 update