diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 934b7137096bc0..670066e4603233 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3573,6 +3573,41 @@ class SendmsgStreamTests(SendmsgTests): # Tests for sendmsg() which require a stream socket and do not # involve recvmsg() or recvmsg_into(). + @unittest.skipUnless(hasattr(socket.socket, "sendmsg"), + "sendmsg not supported") + def test_sendmsg_reentrant_ancillary_mutation(self): + self._test_sendmsg_reentrant_ancillary_mutation() + + def _test_sendmsg_reentrant_ancillary_mutation(self): + import socket + + seq = [] + + class Mut: + def __init__(self): + self.tripped = False + def __index__(self): + if not self.tripped: + self.tripped = True + seq.clear() + return 0 + + seq[:] = [ + (socket.SOL_SOCKET, Mut(), b'x'), + (socket.SOL_SOCKET, 0, b'x'), + ] + + left, right = socket.socketpair() + self.addCleanup(left.close) + self.addCleanup(right.close) + + self.assertRaises( + (TypeError, OSError), + left.sendmsg, + [b'x'], + seq, + ) + def testSendmsgExplicitNoneAddr(self): # Check that peer address can be specified as None. self.assertEqual(self.serv_sock.recv(len(MSG)), MSG) diff --git a/Misc/NEWS.d/next/Library/2026-01-17-08-44-25.gh-issue-143637.qyPqDo.rst b/Misc/NEWS.d/next/Library/2026-01-17-08-44-25.gh-issue-143637.qyPqDo.rst new file mode 100644 index 00000000000000..cbb21194d5b387 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-17-08-44-25.gh-issue-143637.qyPqDo.rst @@ -0,0 +1 @@ +Fixed a crash in socket.sendmsg() that could occur if ancillary data is mutated re-entrantly during argument parsing. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 1ef359cb265ed4..ad93f9a3a0ed5a 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -4977,11 +4977,13 @@ _socket_socket_sendmsg_impl(PySocketSockObject *s, PyObject *data_arg, if (cmsg_arg == NULL) ncmsgs = 0; else { - if ((cmsg_fast = PySequence_Fast(cmsg_arg, - "sendmsg() argument 2 must be an " - "iterable")) == NULL) + cmsg_fast = PySequence_Tuple(cmsg_arg); + if (cmsg_fast == NULL) { + PyErr_SetString(PyExc_TypeError, + "sendmsg() argument 2 must be an iterable"); goto finally; - ncmsgs = PySequence_Fast_GET_SIZE(cmsg_fast); + } + ncmsgs = PyTuple_GET_SIZE(cmsg_fast); } #ifndef CMSG_SPACE @@ -5001,20 +5003,21 @@ _socket_socket_sendmsg_impl(PySocketSockObject *s, PyObject *data_arg, controllen = controllen_last = 0; while (ncmsgbufs < ncmsgs) { size_t bufsize, space; + PyObject *item; - if (!PyArg_Parse(PySequence_Fast_GET_ITEM(cmsg_fast, ncmsgbufs), - "(iiy*):[sendmsg() ancillary data items]", - &cmsgs[ncmsgbufs].level, - &cmsgs[ncmsgbufs].type, - &cmsgs[ncmsgbufs].data)) + item = PyTuple_GET_ITEM(cmsg_fast, ncmsgbufs); + + if (!PyArg_Parse(item, + "(iiy*):[sendmsg() ancillary data items]", + &cmsgs[ncmsgbufs].level, + &cmsgs[ncmsgbufs].type, + &cmsgs[ncmsgbufs].data)){ goto finally; + } + bufsize = cmsgs[ncmsgbufs++].data.len; -#ifdef CMSG_SPACE - if (!get_CMSG_SPACE(bufsize, &space)) { -#else - if (!get_CMSG_LEN(bufsize, &space)) { -#endif + if(!get_CMSG_SPACE(bufsize, &space)){ PyErr_SetString(PyExc_OSError, "ancillary data item too large"); goto finally; }