Skip to content

Commit 896f7fd

Browse files
authored
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. Co-authored-by: tonghuaroot <23011166+tonghuaroot@users.noreply.github.com>
1 parent a621e8a commit 896f7fd

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
@@ -7529,6 +7529,62 @@ def detach():
75297529
pass
75307530

75317531

7532+
class ReentrantMutationTests(unittest.TestCase):
7533+
"""Regression tests for re-entrant mutation in sendmsg/recvmsg_into.
7534+
7535+
These tests verify that mutating sequences during argument parsing
7536+
via __buffer__ protocol does not cause crashes.
7537+
7538+
See: https://github.com/python/cpython/issues/143988
7539+
"""
7540+
7541+
@unittest.skipUnless(hasattr(socket.socket, "sendmsg"),
7542+
"sendmsg not supported")
7543+
def test_sendmsg_reentrant_data_mutation(self):
7544+
seq = []
7545+
7546+
class MutBuffer:
7547+
def __init__(self):
7548+
self.tripped = False
7549+
7550+
def __buffer__(self, flags):
7551+
if not self.tripped:
7552+
self.tripped = True
7553+
seq.clear()
7554+
return memoryview(b'Hello')
7555+
7556+
seq = [MutBuffer(), b'World', b'Test']
7557+
7558+
left, right = socket.socketpair()
7559+
with left, right:
7560+
left.sendmsg(seq)
7561+
self.assertEqual(right.recv(1024), b'HelloWorldTest')
7562+
7563+
@unittest.skipUnless(hasattr(socket.socket, "recvmsg_into"),
7564+
"recvmsg_into not supported")
7565+
def test_recvmsg_into_reentrant_buffer_mutation(self):
7566+
seq = []
7567+
buf1 = bytearray(100)
7568+
7569+
class MutBuffer:
7570+
def __init__(self):
7571+
self.tripped = False
7572+
7573+
def __buffer__(self, flags):
7574+
if not self.tripped:
7575+
self.tripped = True
7576+
seq.clear()
7577+
return memoryview(buf1)
7578+
7579+
seq = [MutBuffer(), bytearray(100), bytearray(100)]
7580+
7581+
left, right = socket.socketpair()
7582+
with left, right:
7583+
left.send(b'Hello World!')
7584+
right.recvmsg_into(seq)
7585+
self.assertEqual(buf1, b'Hello World!'.ljust(100, b'\x00'))
7586+
7587+
75327588
def setUpModule():
75337589
thread_info = threading_helper.threading_setup()
75347590
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
@@ -4526,17 +4526,19 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45264526
struct iovec *iovs = NULL;
45274527
Py_ssize_t i, nitems, nbufs = 0;
45284528
Py_buffer *bufs = NULL;
4529-
PyObject *buffers_arg, *fast, *retval = NULL;
4529+
PyObject *buffers_arg, *buffers_tuple, *retval = NULL;
45304530

45314531
if (!PyArg_ParseTuple(args, "O|ni:recvmsg_into",
45324532
&buffers_arg, &ancbufsize, &flags))
45334533
return NULL;
45344534

4535-
if ((fast = PySequence_Fast(buffers_arg,
4536-
"recvmsg_into() argument 1 must be an "
4537-
"iterable")) == NULL)
4535+
buffers_tuple = PySequence_Tuple(buffers_arg);
4536+
if (buffers_tuple == NULL) {
4537+
PyErr_SetString(PyExc_TypeError,
4538+
"recvmsg_into() argument 1 must be an iterable");
45384539
return NULL;
4539-
nitems = PySequence_Fast_GET_SIZE(fast);
4540+
}
4541+
nitems = PyTuple_GET_SIZE(buffers_tuple);
45404542
if (nitems > INT_MAX) {
45414543
PyErr_SetString(PyExc_OSError, "recvmsg_into() argument 1 is too long");
45424544
goto finally;
@@ -4550,7 +4552,7 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45504552
goto finally;
45514553
}
45524554
for (; nbufs < nitems; nbufs++) {
4553-
if (!PyArg_Parse(PySequence_Fast_GET_ITEM(fast, nbufs),
4555+
if (!PyArg_Parse(PyTuple_GET_ITEM(buffers_tuple, nbufs),
45544556
"w*;recvmsg_into() argument 1 must be an iterable "
45554557
"of single-segment read-write buffers",
45564558
&bufs[nbufs]))
@@ -4566,7 +4568,7 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45664568
PyBuffer_Release(&bufs[i]);
45674569
PyMem_Free(bufs);
45684570
PyMem_Free(iovs);
4569-
Py_DECREF(fast);
4571+
Py_DECREF(buffers_tuple);
45704572
return retval;
45714573
}
45724574

@@ -4861,14 +4863,14 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48614863

48624864
/* Fill in an iovec for each message part, and save the Py_buffer
48634865
structs to release afterwards. */
4864-
data_fast = PySequence_Fast(data_arg,
4865-
"sendmsg() argument 1 must be an "
4866-
"iterable");
4866+
data_fast = PySequence_Tuple(data_arg);
48674867
if (data_fast == NULL) {
4868+
PyErr_SetString(PyExc_TypeError,
4869+
"sendmsg() argument 1 must be an iterable");
48684870
goto finally;
48694871
}
48704872

4871-
ndataparts = PySequence_Fast_GET_SIZE(data_fast);
4873+
ndataparts = PyTuple_GET_SIZE(data_fast);
48724874
if (ndataparts > INT_MAX) {
48734875
PyErr_SetString(PyExc_OSError, "sendmsg() argument 1 is too long");
48744876
goto finally;
@@ -4890,7 +4892,7 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48904892
}
48914893
}
48924894
for (; ndatabufs < ndataparts; ndatabufs++) {
4893-
if (PyObject_GetBuffer(PySequence_Fast_GET_ITEM(data_fast, ndatabufs),
4895+
if (PyObject_GetBuffer(PyTuple_GET_ITEM(data_fast, ndatabufs),
48944896
&databufs[ndatabufs], PyBUF_SIMPLE) < 0)
48954897
goto finally;
48964898
iovs[ndatabufs].iov_base = databufs[ndatabufs].buf;

0 commit comments

Comments
 (0)