Skip to content

Commit 78c18ed

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 78c18ed

File tree

3 files changed

+103
-10
lines changed

3 files changed

+103
-10
lines changed

Lib/test/test_socket_reentrant.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
Tests for re-entrant mutation vulnerabilities in socket module.
3+
4+
These tests verify that mutating sequences during argument parsing
5+
via __buffer__ protocol does not cause crashes.
6+
"""
7+
8+
import socket
9+
import unittest
10+
11+
12+
class ReentrantMutationTests(unittest.TestCase):
13+
"""Tests for re-entrant mutation vulnerabilities in sendmsg/recvmsg_into."""
14+
15+
@unittest.skipUnless(hasattr(socket.socket, "sendmsg"),
16+
"sendmsg not supported")
17+
def test_sendmsg_reentrant_data_mutation(self):
18+
# Test that sendmsg() handles re-entrant mutation of data buffers
19+
# via __buffer__ protocol.
20+
seq = []
21+
22+
class MutBuffer:
23+
def __init__(self, data):
24+
self._data = bytes(data)
25+
self.tripped = False
26+
27+
def __buffer__(self, flags):
28+
if not self.tripped:
29+
self.tripped = True
30+
seq.clear()
31+
return memoryview(self._data)
32+
33+
seq[:] = [
34+
MutBuffer(b'Hello'),
35+
b'World',
36+
b'Test',
37+
]
38+
39+
left, right = socket.socketpair()
40+
try:
41+
# Should not crash
42+
try:
43+
left.sendmsg(seq)
44+
except (TypeError, OSError):
45+
pass # Expected - the important thing is no crash
46+
finally:
47+
left.close()
48+
right.close()
49+
50+
@unittest.skipUnless(hasattr(socket.socket, "recvmsg_into"),
51+
"recvmsg_into not supported")
52+
def test_recvmsg_into_reentrant_buffer_mutation(self):
53+
# Test that recvmsg_into() handles re-entrant mutation of buffers
54+
# via __buffer__ protocol.
55+
seq = []
56+
57+
class MutBuffer:
58+
def __init__(self, data):
59+
self._data = bytearray(data)
60+
self.tripped = False
61+
62+
def __buffer__(self, flags):
63+
if not self.tripped:
64+
self.tripped = True
65+
seq.clear()
66+
return memoryview(self._data)
67+
68+
seq[:] = [
69+
MutBuffer(b'x' * 100),
70+
bytearray(100),
71+
bytearray(100),
72+
]
73+
74+
left, right = socket.socketpair()
75+
try:
76+
left.send(b'Hello World!')
77+
# Should not crash
78+
try:
79+
right.recvmsg_into(seq)
80+
except (TypeError, OSError):
81+
pass # Expected - the important thing is no crash
82+
finally:
83+
left.close()
84+
right.close()
85+
86+
87+
if __name__ == '__main__':
88+
unittest.main()
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)