Skip to content

Commit 87b9729

Browse files
[3.13][3.14] gh-143988: Fix re-entrant mutation crashes in socket sendmsg/recvmsg_into (#143987) (#151251) (#151256)
[3.14] gh-143988: Fix re-entrant mutation crashes in socket sendmsg/recvmsg_into (#143987) (#151251) gh-143988: Fix re-entrant mutation crashes in socket sendmsg/recvmsg_into (#143987) Fix crashes in socket.sendmsg() and socket.recvmsg_into() that could occur if buffer sequences are mutated re-entrantly during argument parsing via __buffer__ protocol callbacks. The bug occurs because: 1. PySequence_Fast() returns the original list object when the input is already a list (not a copy). 2. During iteration, PyObject_GetBuffer() triggers __buffer__ callbacks which may clear the list. 3. Subsequent iterations access invalid memory (heap OOB read). The fix replaces PySequence_Fast() with PySequence_Tuple() which always creates a new tuple, ensuring the sequence cannot be mutated during iteration. (cherry picked from commit 896f7fd) (cherry picked from commit 632daaf) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> Co-authored-by: tonghuaroot <23011166+tonghuaroot@users.noreply.github.com>
1 parent a1f43ef commit 87b9729

3 files changed

Lines changed: 72 additions & 12 deletions

File tree

Lib/test/test_socket.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7284,6 +7284,62 @@ def close_fds(fds):
72847284
self.assertEqual(data, str(index).encode())
72857285

72867286

7287+
class ReentrantMutationTests(unittest.TestCase):
7288+
"""Regression tests for re-entrant mutation in sendmsg/recvmsg_into.
7289+
7290+
These tests verify that mutating sequences during argument parsing
7291+
via __buffer__ protocol does not cause crashes.
7292+
7293+
See: https://github.com/python/cpython/issues/143988
7294+
"""
7295+
7296+
@unittest.skipUnless(hasattr(socket.socket, "sendmsg"),
7297+
"sendmsg not supported")
7298+
def test_sendmsg_reentrant_data_mutation(self):
7299+
seq = []
7300+
7301+
class MutBuffer:
7302+
def __init__(self):
7303+
self.tripped = False
7304+
7305+
def __buffer__(self, flags):
7306+
if not self.tripped:
7307+
self.tripped = True
7308+
seq.clear()
7309+
return memoryview(b'Hello')
7310+
7311+
seq = [MutBuffer(), b'World', b'Test']
7312+
7313+
left, right = socket.socketpair()
7314+
with left, right:
7315+
left.sendmsg(seq)
7316+
self.assertEqual(right.recv(1024), b'HelloWorldTest')
7317+
7318+
@unittest.skipUnless(hasattr(socket.socket, "recvmsg_into"),
7319+
"recvmsg_into not supported")
7320+
def test_recvmsg_into_reentrant_buffer_mutation(self):
7321+
seq = []
7322+
buf1 = bytearray(100)
7323+
7324+
class MutBuffer:
7325+
def __init__(self):
7326+
self.tripped = False
7327+
7328+
def __buffer__(self, flags):
7329+
if not self.tripped:
7330+
self.tripped = True
7331+
seq.clear()
7332+
return memoryview(buf1)
7333+
7334+
seq = [MutBuffer(), bytearray(100), bytearray(100)]
7335+
7336+
left, right = socket.socketpair()
7337+
with left, right:
7338+
left.send(b'Hello World!')
7339+
right.recvmsg_into(seq)
7340+
self.assertEqual(buf1, b'Hello World!'.ljust(100, b'\x00'))
7341+
7342+
72877343
def setUpModule():
72887344
thread_info = threading_helper.threading_setup()
72897345
unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed crashes in :meth:`socket.socket.sendmsg` and :meth:`socket.socket.recvmsg_into`
2+
that could occur if buffer sequences are concurrently mutated.

Modules/socketmodule.c

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4296,17 +4296,19 @@ sock_recvmsg_into(PySocketSockObject *s, PyObject *args)
42964296
struct iovec *iovs = NULL;
42974297
Py_ssize_t i, nitems, nbufs = 0;
42984298
Py_buffer *bufs = NULL;
4299-
PyObject *buffers_arg, *fast, *retval = NULL;
4299+
PyObject *buffers_arg, *buffers_tuple, *retval = NULL;
43004300

43014301
if (!PyArg_ParseTuple(args, "O|ni:recvmsg_into",
43024302
&buffers_arg, &ancbufsize, &flags))
43034303
return NULL;
43044304

4305-
if ((fast = PySequence_Fast(buffers_arg,
4306-
"recvmsg_into() argument 1 must be an "
4307-
"iterable")) == NULL)
4305+
buffers_tuple = PySequence_Tuple(buffers_arg);
4306+
if (buffers_tuple == NULL) {
4307+
PyErr_SetString(PyExc_TypeError,
4308+
"recvmsg_into() argument 1 must be an iterable");
43084309
return NULL;
4309-
nitems = PySequence_Fast_GET_SIZE(fast);
4310+
}
4311+
nitems = PyTuple_GET_SIZE(buffers_tuple);
43104312
if (nitems > INT_MAX) {
43114313
PyErr_SetString(PyExc_OSError, "recvmsg_into() argument 1 is too long");
43124314
goto finally;
@@ -4320,7 +4322,7 @@ sock_recvmsg_into(PySocketSockObject *s, PyObject *args)
43204322
goto finally;
43214323
}
43224324
for (; nbufs < nitems; nbufs++) {
4323-
if (!PyArg_Parse(PySequence_Fast_GET_ITEM(fast, nbufs),
4325+
if (!PyArg_Parse(PyTuple_GET_ITEM(buffers_tuple, nbufs),
43244326
"w*;recvmsg_into() argument 1 must be an iterable "
43254327
"of single-segment read-write buffers",
43264328
&bufs[nbufs]))
@@ -4336,7 +4338,7 @@ sock_recvmsg_into(PySocketSockObject *s, PyObject *args)
43364338
PyBuffer_Release(&bufs[i]);
43374339
PyMem_Free(bufs);
43384340
PyMem_Free(iovs);
4339-
Py_DECREF(fast);
4341+
Py_DECREF(buffers_tuple);
43404342
return retval;
43414343
}
43424344

@@ -4629,14 +4631,14 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
46294631

46304632
/* Fill in an iovec for each message part, and save the Py_buffer
46314633
structs to release afterwards. */
4632-
data_fast = PySequence_Fast(data_arg,
4633-
"sendmsg() argument 1 must be an "
4634-
"iterable");
4634+
data_fast = PySequence_Tuple(data_arg);
46354635
if (data_fast == NULL) {
4636+
PyErr_SetString(PyExc_TypeError,
4637+
"sendmsg() argument 1 must be an iterable");
46364638
goto finally;
46374639
}
46384640

4639-
ndataparts = PySequence_Fast_GET_SIZE(data_fast);
4641+
ndataparts = PyTuple_GET_SIZE(data_fast);
46404642
if (ndataparts > INT_MAX) {
46414643
PyErr_SetString(PyExc_OSError, "sendmsg() argument 1 is too long");
46424644
goto finally;
@@ -4658,7 +4660,7 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
46584660
}
46594661
}
46604662
for (; ndatabufs < ndataparts; ndatabufs++) {
4661-
if (!PyArg_Parse(PySequence_Fast_GET_ITEM(data_fast, ndatabufs),
4663+
if (!PyArg_Parse(PyTuple_GET_ITEM(data_fast, ndatabufs),
46624664
"y*;sendmsg() argument 1 must be an iterable of "
46634665
"bytes-like objects",
46644666
&databufs[ndatabufs]))

0 commit comments

Comments
 (0)