Skip to content

Commit 4b592ef

Browse files
committed
gh-143988: Fix re-entrant mutation crashes in socket sendmsg/recvmsg_into via __buffer__
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 vulnerability 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. This addresses two vulnerabilities related to gh-143637: - sendmsg() argument 1 (data buffers) - via __buffer__ - recvmsg_into() argument 1 (buffers) - via __buffer__
1 parent 63cc125 commit 4b592ef

File tree

3 files changed

+81
-10
lines changed

3 files changed

+81
-10
lines changed

Lib/test/test_socket.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7491,6 +7491,72 @@ def detach():
74917491
pass
74927492

74937493

7494+
class ReentrantMutationTests(unittest.TestCase):
7495+
"""Regression tests for re-entrant mutation vulnerabilities in sendmsg/recvmsg_into.
7496+
7497+
These tests verify that mutating sequences during argument parsing
7498+
via __buffer__ protocol does not cause crashes.
7499+
7500+
See: https://github.com/python/cpython/issues/143988
7501+
"""
7502+
7503+
@unittest.skipUnless(hasattr(socket.socket, "sendmsg"),
7504+
"sendmsg not supported")
7505+
def test_sendmsg_reentrant_data_mutation(self):
7506+
# Test that sendmsg() handles re-entrant mutation of data buffers
7507+
# via __buffer__ protocol.
7508+
# See: https://github.com/python/cpython/issues/143988
7509+
seq = []
7510+
7511+
class MutBuffer:
7512+
def __init__(self):
7513+
self.tripped = False
7514+
7515+
def __buffer__(self, flags):
7516+
if not self.tripped:
7517+
self.tripped = True
7518+
seq.clear()
7519+
return memoryview(b'Hello')
7520+
7521+
seq = [MutBuffer(), b'World', b'Test']
7522+
7523+
left, right = socket.socketpair()
7524+
self.addCleanup(left.close)
7525+
self.addCleanup(right.close)
7526+
# Should not crash - may raise TypeError or OSError
7527+
with self.assertRaises((TypeError, OSError)):
7528+
left.sendmsg(seq)
7529+
7530+
@unittest.skipUnless(hasattr(socket.socket, "recvmsg_into"),
7531+
"recvmsg_into not supported")
7532+
def test_recvmsg_into_reentrant_buffer_mutation(self):
7533+
# Test that recvmsg_into() handles re-entrant mutation of buffers
7534+
# via __buffer__ protocol.
7535+
# See: https://github.com/python/cpython/issues/143988
7536+
seq = []
7537+
7538+
class MutBuffer:
7539+
def __init__(self, data):
7540+
self._data = bytearray(data)
7541+
self.tripped = False
7542+
7543+
def __buffer__(self, flags):
7544+
if not self.tripped:
7545+
self.tripped = True
7546+
seq.clear()
7547+
return memoryview(self._data)
7548+
7549+
seq = [MutBuffer(b'x' * 100), bytearray(100), bytearray(100)]
7550+
7551+
left, right = socket.socketpair()
7552+
self.addCleanup(left.close)
7553+
self.addCleanup(right.close)
7554+
left.send(b'Hello World!')
7555+
# Should not crash - may raise TypeError or OSError
7556+
with self.assertRaises((TypeError, OSError)):
7557+
right.recvmsg_into(seq)
7558+
7559+
74947560
def setUpModule():
74957561
thread_info = threading_helper.threading_setup()
74967562
unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed crashes in :meth:`socket.socket.sendmsg` and :meth:`socket.socket.recvmsg_into`
2+
that could occur if buffer sequences are mutated re-entrantly during argument parsing
3+
via ``__buffer__`` protocol callbacks.

Modules/socketmodule.c

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4527,11 +4527,13 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45274527
&buffers_arg, &ancbufsize, &flags))
45284528
return NULL;
45294529

4530-
if ((fast = PySequence_Fast(buffers_arg,
4531-
"recvmsg_into() argument 1 must be an "
4532-
"iterable")) == NULL)
4530+
fast = PySequence_Tuple(buffers_arg);
4531+
if (fast == NULL) {
4532+
PyErr_SetString(PyExc_TypeError,
4533+
"recvmsg_into() argument 1 must be an iterable");
45334534
return NULL;
4534-
nitems = PySequence_Fast_GET_SIZE(fast);
4535+
}
4536+
nitems = PyTuple_GET_SIZE(fast);
45354537
if (nitems > INT_MAX) {
45364538
PyErr_SetString(PyExc_OSError, "recvmsg_into() argument 1 is too long");
45374539
goto finally;
@@ -4545,7 +4547,7 @@ sock_recvmsg_into(PyObject *self, PyObject *args)
45454547
goto finally;
45464548
}
45474549
for (; nbufs < nitems; nbufs++) {
4548-
if (!PyArg_Parse(PySequence_Fast_GET_ITEM(fast, nbufs),
4550+
if (!PyArg_Parse(PyTuple_GET_ITEM(fast, nbufs),
45494551
"w*;recvmsg_into() argument 1 must be an iterable "
45504552
"of single-segment read-write buffers",
45514553
&bufs[nbufs]))
@@ -4854,14 +4856,14 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48544856

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

4864-
ndataparts = PySequence_Fast_GET_SIZE(data_fast);
4866+
ndataparts = PyTuple_GET_SIZE(data_fast);
48654867
if (ndataparts > INT_MAX) {
48664868
PyErr_SetString(PyExc_OSError, "sendmsg() argument 1 is too long");
48674869
goto finally;
@@ -4883,7 +4885,7 @@ sock_sendmsg_iovec(PySocketSockObject *s, PyObject *data_arg,
48834885
}
48844886
}
48854887
for (; ndatabufs < ndataparts; ndatabufs++) {
4886-
if (PyObject_GetBuffer(PySequence_Fast_GET_ITEM(data_fast, ndatabufs),
4888+
if (PyObject_GetBuffer(PyTuple_GET_ITEM(data_fast, ndatabufs),
48874889
&databufs[ndatabufs], PyBUF_SIMPLE) < 0)
48884890
goto finally;
48894891
iovs[ndatabufs].iov_base = databufs[ndatabufs].buf;

0 commit comments

Comments
 (0)