Skip to content

Commit 9261f8b

Browse files
[3.15] gh-143988: Fix re-entrant mutation crashes in socket sendmsg/recvmsg_into (GH-143987) (#151246)
gh-143988: Fix re-entrant mutation crashes in socket sendmsg/recvmsg_into (GH-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) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> Co-authored-by: tonghuaroot <23011166+tonghuaroot@users.noreply.github.com>
1 parent 637746d commit 9261f8b

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
@@ -7534,6 +7534,62 @@ def detach():
75347534
pass
75357535

75367536

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

45234523
if (!PyArg_ParseTuple(args, "O|ni:recvmsg_into",
45244524
&buffers_arg, &ancbufsize, &flags))
45254525
return NULL;
45264526

4527-
if ((fast = PySequence_Fast(buffers_arg,
4528-
"recvmsg_into() argument 1 must be an "
4529-
"iterable")) == NULL)
4527+
buffers_tuple = PySequence_Tuple(buffers_arg);
4528+
if (buffers_tuple == NULL) {
4529+
PyErr_SetString(PyExc_TypeError,
4530+
"recvmsg_into() argument 1 must be an iterable");
45304531
return NULL;
4531-
nitems = PySequence_Fast_GET_SIZE(fast);
4532+
}
4533+
nitems = PyTuple_GET_SIZE(buffers_tuple);
45324534
if (nitems > INT_MAX) {
45334535
PyErr_SetString(PyExc_OSError, "recvmsg_into() argument 1 is too long");
45344536
goto finally;
@@ -4542,7 +4544,7 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45424544
goto finally;
45434545
}
45444546
for (; nbufs < nitems; nbufs++) {
4545-
if (!PyArg_Parse(PySequence_Fast_GET_ITEM(fast, nbufs),
4547+
if (!PyArg_Parse(PyTuple_GET_ITEM(buffers_tuple, nbufs),
45464548
"w*;recvmsg_into() argument 1 must be an iterable "
45474549
"of single-segment read-write buffers",
45484550
&bufs[nbufs]))
@@ -4558,7 +4560,7 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45584560
PyBuffer_Release(&bufs[i]);
45594561
PyMem_Free(bufs);
45604562
PyMem_Free(iovs);
4561-
Py_DECREF(fast);
4563+
Py_DECREF(buffers_tuple);
45624564
return retval;
45634565
}
45644566

@@ -4853,14 +4855,14 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48534855

48544856
/* Fill in an iovec for each message part, and save the Py_buffer
48554857
structs to release afterwards. */
4856-
data_fast = PySequence_Fast(data_arg,
4857-
"sendmsg() argument 1 must be an "
4858-
"iterable");
4858+
data_fast = PySequence_Tuple(data_arg);
48594859
if (data_fast == NULL) {
4860+
PyErr_SetString(PyExc_TypeError,
4861+
"sendmsg() argument 1 must be an iterable");
48604862
goto finally;
48614863
}
48624864

4863-
ndataparts = PySequence_Fast_GET_SIZE(data_fast);
4865+
ndataparts = PyTuple_GET_SIZE(data_fast);
48644866
if (ndataparts > INT_MAX) {
48654867
PyErr_SetString(PyExc_OSError, "sendmsg() argument 1 is too long");
48664868
goto finally;
@@ -4882,7 +4884,7 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48824884
}
48834885
}
48844886
for (; ndatabufs < ndataparts; ndatabufs++) {
4885-
if (PyObject_GetBuffer(PySequence_Fast_GET_ITEM(data_fast, ndatabufs),
4887+
if (PyObject_GetBuffer(PyTuple_GET_ITEM(data_fast, ndatabufs),
48864888
&databufs[ndatabufs], PyBUF_SIMPLE) < 0)
48874889
goto finally;
48884890
iovs[ndatabufs].iov_base = databufs[ndatabufs].buf;

0 commit comments

Comments
 (0)