From 1274766d3c29007ab77245a72abbf8dce2a9db4d Mon Sep 17 00:00:00 2001 From: Seth Larson Date: Tue, 21 Apr 2026 09:29:07 -0500 Subject: [PATCH 1/3] =?UTF-8?q?gh-148808:=20Add=20boundary=20check=20to=20?= =?UTF-8?q?asyncio.AbstractEventLoop.sock=5Frecvf=E2=80=A6=20(#148809)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/test/test_asyncio/test_sock_lowlevel.py | 21 +++++++++++++++++++ ...-04-20-15-31-37.gh-issue-148808._Z8JL0.rst | 3 +++ Modules/overlapped.c | 5 +++++ 3 files changed, 29 insertions(+) create mode 100644 Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst diff --git a/Lib/test/test_asyncio/test_sock_lowlevel.py b/Lib/test/test_asyncio/test_sock_lowlevel.py index df4ec7948975f6..f32dcd589e2de2 100644 --- a/Lib/test/test_asyncio/test_sock_lowlevel.py +++ b/Lib/test/test_asyncio/test_sock_lowlevel.py @@ -427,6 +427,27 @@ def test_recvfrom_into(self): self.loop.run_until_complete( self._basetest_datagram_recvfrom_into(server_address)) + async def _basetest_datagram_recvfrom_into_wrong_size(self, server_address): + # Call sock_sendto() with a size larger than the buffer + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.setblocking(False) + + buf = bytearray(5000) + data = b'\x01' * 4096 + wrong_size = len(buf) + 1 + await self.loop.sock_sendto(sock, data, server_address) + with self.assertRaises(ValueError): + await self.loop.sock_recvfrom_into( + sock, buf, wrong_size) + + size, addr = await self.loop.sock_recvfrom_into(sock, buf) + self.assertEqual(buf[:size], data) + + def test_recvfrom_into_wrong_size(self): + with test_utils.run_udp_echo_server() as server_address: + self.loop.run_until_complete( + self._basetest_datagram_recvfrom_into_wrong_size(server_address)) + async def _basetest_datagram_sendto_blocking(self, server_address): # Sad path, sock.sendto() raises BlockingIOError # This involves patching sock.sendto() to raise BlockingIOError but diff --git a/Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst b/Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst new file mode 100644 index 00000000000000..0b5cf85fedfba1 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-04-20-15-31-37.gh-issue-148808._Z8JL0.rst @@ -0,0 +1,3 @@ +Added buffer boundary check when using ``nbytes`` parameter with +:meth:`!asyncio.AbstractEventLoop.sock_recvfrom_into`. Only +relevant for Windows and the :class:`asyncio.ProactorEventLoop`. diff --git a/Modules/overlapped.c b/Modules/overlapped.c index 822e1ce4bdc28d..51aee5afd35b6d 100644 --- a/Modules/overlapped.c +++ b/Modules/overlapped.c @@ -1910,6 +1910,11 @@ _overlapped_Overlapped_WSARecvFromInto_impl(OverlappedObject *self, } #endif + if (bufobj->len < (Py_ssize_t)size) { + PyErr_SetString(PyExc_ValueError, "nbytes is greater than the length of the buffer"); + return NULL; + } + wsabuf.buf = bufobj->buf; wsabuf.len = size; From 33e82be1746a964b595b2bba64f38a5787681eb3 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 21 Apr 2026 17:20:18 +0100 Subject: [PATCH 2/3] gh-148801: Fix unbound C recursion in `Element.__deepcopy__()` (#148802) --- Lib/test/test_xml_etree.py | 13 +++++++++++ ...-04-20-18-29-21.gh-issue-148801.ROeNqs.rst | 2 ++ Modules/_elementtree.c | 23 +++++++++++++------ 3 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index b380d0276b0169..51af46f124cac6 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -3190,6 +3190,19 @@ def __deepcopy__(self, memo): self.assertEqual([c.tag for c in children[3:]], [a.tag, b.tag, a.tag, b.tag]) + @support.skip_if_unlimited_stack_size + @support.skip_emscripten_stack_overflow() + @support.skip_wasi_stack_overflow() + def test_deeply_nested_deepcopy(self): + # This should raise a RecursionError and not crash. + # See https://github.com/python/cpython/issues/148801. + root = cur = ET.Element('s') + for _ in range(150_000): + cur = ET.SubElement(cur, 'u') + with support.infinite_recursion(): + with self.assertRaises(RecursionError): + copy.deepcopy(root) + class MutationDeleteElementPath(str): def __new__(cls, elem, *args): diff --git a/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst new file mode 100644 index 00000000000000..6fcd30e8f057b9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst @@ -0,0 +1,2 @@ +:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.__deepcopy__ +` on deeply nested trees. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index e2185c4bd03aad..32150924292a05 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -16,6 +16,7 @@ #endif #include "Python.h" +#include "pycore_ceval.h" // _Py_EnterRecursiveCall() #include "pycore_dict.h" // _PyDict_CopyAsDict() #include "pycore_pyhash.h" // _Py_HashSecret #include "pycore_tuple.h" // _PyTuple_FromPair @@ -811,26 +812,31 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) /*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/ { Py_ssize_t i; - ElementObject* element; + ElementObject* element = NULL; PyObject* tag; PyObject* attrib; PyObject* text; PyObject* tail; PyObject* id; + if (_Py_EnterRecursiveCall(" in Element.__deepcopy__")) { + return NULL; + } + PyTypeObject *tp = Py_TYPE(self); elementtreestate *st = get_elementtree_state_by_type(tp); // The deepcopy() helper takes care of incrementing the refcount // of the object to copy so to avoid use-after-frees. tag = deepcopy(st, self->tag, memo); - if (!tag) - return NULL; + if (!tag) { + goto error; + } if (self->extra && self->extra->attrib) { attrib = deepcopy(st, self->extra->attrib, memo); if (!attrib) { Py_DECREF(tag); - return NULL; + goto error; } } else { attrib = NULL; @@ -841,8 +847,9 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) Py_DECREF(tag); Py_XDECREF(attrib); - if (!element) - return NULL; + if (!element) { + goto error; + } text = deepcopy(st, JOIN_OBJ(self->text), memo); if (!text) @@ -904,10 +911,12 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) if (i < 0) goto error; + _Py_LeaveRecursiveCall(); return (PyObject*) element; error: - Py_DECREF(element); + _Py_LeaveRecursiveCall(); + Py_XDECREF(element); return NULL; } From 0b9146e90b4969af8cd6ed39c3d97bb71bfc6a7a Mon Sep 17 00:00:00 2001 From: Rida Zouga <96395950+ZougaRida@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:17:02 +0100 Subject: [PATCH 3/3] [Enum] Improve clarity of comparison sentence (GH-148753) Co-authored-by: Ethan Furman --- Doc/howto/enum.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index 5260c2ca4add47..2fe5814bb04a73 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -371,7 +371,7 @@ Equality comparisons are defined though:: >>> Color.BLUE == Color.BLUE True -Comparisons against non-enumeration values will always compare not equal +Equality comparisons against non-enumeration values will always return ``False`` (again, :class:`IntEnum` was explicitly designed to behave differently, see below)::