Skip to content

Commit 632daaf

Browse files
[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) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> Co-authored-by: tonghuaroot <23011166+tonghuaroot@users.noreply.github.com>
1 parent 4ef6a37 commit 632daaf

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
@@ -7449,6 +7449,62 @@ def detach():
74497449
pass
74507450

74517451

7452+
class ReentrantMutationTests(unittest.TestCase):
7453+
"""Regression tests for re-entrant mutation in sendmsg/recvmsg_into.
7454+
7455+
These tests verify that mutating sequences during argument parsing
7456+
via __buffer__ protocol does not cause crashes.
7457+
7458+
See: https://github.com/python/cpython/issues/143988
7459+
"""
7460+
7461+
@unittest.skipUnless(hasattr(socket.socket, "sendmsg"),
7462+
"sendmsg not supported")
7463+
def test_sendmsg_reentrant_data_mutation(self):
7464+
seq = []
7465+
7466+
class MutBuffer:
7467+
def __init__(self):
7468+
self.tripped = False
7469+
7470+
def __buffer__(self, flags):
7471+
if not self.tripped:
7472+
self.tripped = True
7473+
seq.clear()
7474+
return memoryview(b'Hello')
7475+
7476+
seq = [MutBuffer(), b'World', b'Test']
7477+
7478+
left, right = socket.socketpair()
7479+
with left, right:
7480+
left.sendmsg(seq)
7481+
self.assertEqual(right.recv(1024), b'HelloWorldTest')
7482+
7483+
@unittest.skipUnless(hasattr(socket.socket, "recvmsg_into"),
7484+
"recvmsg_into not supported")
7485+
def test_recvmsg_into_reentrant_buffer_mutation(self):
7486+
seq = []
7487+
buf1 = bytearray(100)
7488+
7489+
class MutBuffer:
7490+
def __init__(self):
7491+
self.tripped = False
7492+
7493+
def __buffer__(self, flags):
7494+
if not self.tripped:
7495+
self.tripped = True
7496+
seq.clear()
7497+
return memoryview(buf1)
7498+
7499+
seq = [MutBuffer(), bytearray(100), bytearray(100)]
7500+
7501+
left, right = socket.socketpair()
7502+
with left, right:
7503+
left.send(b'Hello World!')
7504+
right.recvmsg_into(seq)
7505+
self.assertEqual(buf1, b'Hello World!'.ljust(100, b'\x00'))
7506+
7507+
74527508
def setUpModule():
74537509
thread_info = threading_helper.threading_setup()
74547510
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
@@ -4497,17 +4497,19 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
44974497
struct iovec *iovs = NULL;
44984498
Py_ssize_t i, nitems, nbufs = 0;
44994499
Py_buffer *bufs = NULL;
4500-
PyObject *buffers_arg, *fast, *retval = NULL;
4500+
PyObject *buffers_arg, *buffers_tuple, *retval = NULL;
45014501

45024502
if (!PyArg_ParseTuple(args, "O|ni:recvmsg_into",
45034503
&buffers_arg, &ancbufsize, &flags))
45044504
return NULL;
45054505

4506-
if ((fast = PySequence_Fast(buffers_arg,
4507-
"recvmsg_into() argument 1 must be an "
4508-
"iterable")) == NULL)
4506+
buffers_tuple = PySequence_Tuple(buffers_arg);
4507+
if (buffers_tuple == NULL) {
4508+
PyErr_SetString(PyExc_TypeError,
4509+
"recvmsg_into() argument 1 must be an iterable");
45094510
return NULL;
4510-
nitems = PySequence_Fast_GET_SIZE(fast);
4511+
}
4512+
nitems = PyTuple_GET_SIZE(buffers_tuple);
45114513
if (nitems > INT_MAX) {
45124514
PyErr_SetString(PyExc_OSError, "recvmsg_into() argument 1 is too long");
45134515
goto finally;
@@ -4521,7 +4523,7 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45214523
goto finally;
45224524
}
45234525
for (; nbufs < nitems; nbufs++) {
4524-
if (!PyArg_Parse(PySequence_Fast_GET_ITEM(fast, nbufs),
4526+
if (!PyArg_Parse(PyTuple_GET_ITEM(buffers_tuple, nbufs),
45254527
"w*;recvmsg_into() argument 1 must be an iterable "
45264528
"of single-segment read-write buffers",
45274529
&bufs[nbufs]))
@@ -4537,7 +4539,7 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45374539
PyBuffer_Release(&bufs[i]);
45384540
PyMem_Free(bufs);
45394541
PyMem_Free(iovs);
4540-
Py_DECREF(fast);
4542+
Py_DECREF(buffers_tuple);
45414543
return retval;
45424544
}
45434545

@@ -4836,14 +4838,14 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48364838

48374839
/* Fill in an iovec for each message part, and save the Py_buffer
48384840
structs to release afterwards. */
4839-
data_fast = PySequence_Fast(data_arg,
4840-
"sendmsg() argument 1 must be an "
4841-
"iterable");
4841+
data_fast = PySequence_Tuple(data_arg);
48424842
if (data_fast == NULL) {
4843+
PyErr_SetString(PyExc_TypeError,
4844+
"sendmsg() argument 1 must be an iterable");
48434845
goto finally;
48444846
}
48454847

4846-
ndataparts = PySequence_Fast_GET_SIZE(data_fast);
4848+
ndataparts = PyTuple_GET_SIZE(data_fast);
48474849
if (ndataparts > INT_MAX) {
48484850
PyErr_SetString(PyExc_OSError, "sendmsg() argument 1 is too long");
48494851
goto finally;
@@ -4865,7 +4867,7 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48654867
}
48664868
}
48674869
for (; ndatabufs < ndataparts; ndatabufs++) {
4868-
if (!PyArg_Parse(PySequence_Fast_GET_ITEM(data_fast, ndatabufs),
4870+
if (!PyArg_Parse(PyTuple_GET_ITEM(data_fast, ndatabufs),
48694871
"y*;sendmsg() argument 1 must be an iterable of "
48704872
"bytes-like objects",
48714873
&databufs[ndatabufs]))

0 commit comments

Comments
 (0)