Skip to content

Commit 1bb7bc0

Browse files
committed
fix: bytearray: prevent UAF in search-like methods by exporting self buffer
1 parent d2abd57 commit 1bb7bc0

File tree

3 files changed

+104
-42
lines changed

3 files changed

+104
-42
lines changed

Lib/test/test_bytes.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,6 +2060,37 @@ def __index__(self):
20602060
self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered")
20612061
self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered")
20622062

2063+
def test_search_methods_reentrancy_raises_buffererror(self):
2064+
# gh-142560: Raise BufferError if buffer mutates during search arg conversion.
2065+
class Evil:
2066+
def __init__(self, ba):
2067+
self.ba = ba
2068+
def __buffer__(self, flags):
2069+
self.ba.clear()
2070+
return memoryview(self.ba)
2071+
def __release_buffer__(self, view: memoryview) -> None:
2072+
view.release()
2073+
def __index__(self):
2074+
self.ba.clear()
2075+
return ord("A")
2076+
2077+
def make_case():
2078+
ba = bytearray(b"A")
2079+
return ba, Evil(ba)
2080+
2081+
for name in ("find", "count", "index", "rindex", "rfind"):
2082+
ba, evil = make_case()
2083+
with self.subTest(name):
2084+
with self.assertRaises(BufferError):
2085+
getattr(ba, name)(evil)
2086+
2087+
ba, evil = make_case()
2088+
with self.assertRaises(BufferError):
2089+
evil in ba
2090+
with self.assertRaises(BufferError):
2091+
ba.split(evil)
2092+
with self.assertRaises(BufferError):
2093+
ba.rsplit(evil)
20632094

20642095
class AssortedBytesTest(unittest.TestCase):
20652096
#
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix use-after-free in :class:`bytearray` search-like methods (:meth:`~bytearray.find`, :meth:`~bytearray.count`, :meth:`~bytearray.index`, :meth:`~bytearray.rindex`, and :meth:`~bytearray.rfind`) by marking the storage as exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~operator.contains`, :meth:`~bytearray.split`, and :meth:`~bytearray.rsplit` the :ref:`buffer protocol <bufferobjects>` is used for this.

Objects/bytearrayobject.c

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,25 @@ bytearray_releasebuffer(PyObject *self, Py_buffer *view)
9090
Py_END_CRITICAL_SECTION();
9191
}
9292

93+
typedef PyObject* (*_ba_bytes_op)(const char *buf, Py_ssize_t len,
94+
PyObject *sub, Py_ssize_t start,
95+
Py_ssize_t end);
96+
97+
static PyObject *
98+
_bytearray_with_buffer(PyByteArrayObject *self, _ba_bytes_op op, PyObject *sub,
99+
Py_ssize_t start, Py_ssize_t end)
100+
{
101+
PyObject *res;
102+
103+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
104+
105+
/* Increase exports to prevent bytearray storage from changing during op. */
106+
self->ob_exports++;
107+
res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end);
108+
self->ob_exports--;
109+
return res;
110+
}
111+
93112
static int
94113
_canresize(PyByteArrayObject *self)
95114
{
@@ -1248,8 +1267,7 @@ bytearray_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start,
12481267
Py_ssize_t end)
12491268
/*[clinic end generated code: output=413e1cab2ae87da0 input=df3aa94840d893a7]*/
12501269
{
1251-
return _Py_bytes_find(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1252-
sub, start, end);
1270+
return _bytearray_with_buffer(self, _Py_bytes_find, sub, start, end);
12531271
}
12541272

12551273
/*[clinic input]
@@ -1265,8 +1283,7 @@ bytearray_count_impl(PyByteArrayObject *self, PyObject *sub,
12651283
Py_ssize_t start, Py_ssize_t end)
12661284
/*[clinic end generated code: output=a21ee2692e4f1233 input=e8fcdca8272857e0]*/
12671285
{
1268-
return _Py_bytes_count(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1269-
sub, start, end);
1286+
return _bytearray_with_buffer(self, _Py_bytes_count, sub, start, end);
12701287
}
12711288

12721289
/*[clinic input]
@@ -1314,8 +1331,7 @@ bytearray_index_impl(PyByteArrayObject *self, PyObject *sub,
13141331
Py_ssize_t start, Py_ssize_t end)
13151332
/*[clinic end generated code: output=067a1e78efc672a7 input=c37f177cfee19fe4]*/
13161333
{
1317-
return _Py_bytes_index(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1318-
sub, start, end);
1334+
return _bytearray_with_buffer(self, _Py_bytes_index, sub, start, end);
13191335
}
13201336

13211337
/*[clinic input]
@@ -1333,8 +1349,7 @@ bytearray_rfind_impl(PyByteArrayObject *self, PyObject *sub,
13331349
Py_ssize_t start, Py_ssize_t end)
13341350
/*[clinic end generated code: output=51bf886f932b283c input=1265b11c437d2750]*/
13351351
{
1336-
return _Py_bytes_rfind(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1337-
sub, start, end);
1352+
return _bytearray_with_buffer(self, _Py_bytes_rfind, sub, start, end);
13381353
}
13391354

13401355
/*[clinic input]
@@ -1352,18 +1367,20 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub,
13521367
Py_ssize_t start, Py_ssize_t end)
13531368
/*[clinic end generated code: output=38e1cf66bafb08b9 input=7d198b3d6b0a62ce]*/
13541369
{
1355-
return _Py_bytes_rindex(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1356-
sub, start, end);
1370+
return _bytearray_with_buffer(self, _Py_bytes_rindex, sub, start, end);
13571371
}
13581372

13591373
static int
13601374
bytearray_contains(PyObject *self, PyObject *arg)
13611375
{
1362-
int ret;
1376+
int ret = -1;
13631377
Py_BEGIN_CRITICAL_SECTION(self);
1364-
ret = _Py_bytes_contains(PyByteArray_AS_STRING(self),
1378+
PyByteArrayObject *ba = _PyByteArray_CAST(self);
1379+
ba->ob_exports++;
1380+
ret = _Py_bytes_contains(PyByteArray_AS_STRING(ba),
13651381
PyByteArray_GET_SIZE(self),
13661382
arg);
1383+
ba->ob_exports--;
13671384
Py_END_CRITICAL_SECTION();
13681385
return ret;
13691386
}
@@ -1390,8 +1407,7 @@ bytearray_startswith_impl(PyByteArrayObject *self, PyObject *subobj,
13901407
Py_ssize_t start, Py_ssize_t end)
13911408
/*[clinic end generated code: output=a3d9b6d44d3662a6 input=93f9ffee684f109a]*/
13921409
{
1393-
return _Py_bytes_startswith(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1394-
subobj, start, end);
1410+
return _bytearray_with_buffer(self, _Py_bytes_startswith, subobj, start, end);
13951411
}
13961412

13971413
/*[clinic input]
@@ -1416,8 +1432,7 @@ bytearray_endswith_impl(PyByteArrayObject *self, PyObject *subobj,
14161432
Py_ssize_t start, Py_ssize_t end)
14171433
/*[clinic end generated code: output=e75ea8c227954caa input=d158b030a11d0b06]*/
14181434
{
1419-
return _Py_bytes_endswith(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
1420-
subobj, start, end);
1435+
return _bytearray_with_buffer(self, _Py_bytes_endswith, subobj, start, end);
14211436
}
14221437

14231438
/*[clinic input]
@@ -1781,27 +1796,36 @@ static PyObject *
17811796
bytearray_split_impl(PyByteArrayObject *self, PyObject *sep,
17821797
Py_ssize_t maxsplit)
17831798
/*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/
1799+
1800+
static PyObject *
1801+
bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit)
17841802
{
1785-
Py_ssize_t len = PyByteArray_GET_SIZE(self), n;
1786-
const char *s = PyByteArray_AS_STRING(self), *sub;
1787-
PyObject *list;
1788-
Py_buffer vsub;
1803+
PyObject *list = NULL;
1804+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self);
1805+
1806+
self->ob_exports++;
1807+
const char *sbuf = PyByteArray_AS_STRING(self);
1808+
Py_ssize_t slen = PyByteArray_GET_SIZE((PyObject *)self);
17891809

17901810
if (maxsplit < 0)
17911811
maxsplit = PY_SSIZE_T_MAX;
17921812

1793-
if (sep == Py_None)
1794-
return stringlib_split_whitespace((PyObject*) self, s, len, maxsplit);
1813+
if (sep == Py_None) {
1814+
list = stringlib_split_whitespace((PyObject*)self, sbuf, slen, maxsplit);
1815+
goto done;
1816+
}
17951817

1796-
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0)
1797-
return NULL;
1798-
sub = vsub.buf;
1799-
n = vsub.len;
1818+
Py_buffer vsub;
1819+
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) {
1820+
goto done;
1821+
}
18001822

1801-
list = stringlib_split(
1802-
(PyObject*) self, s, len, sub, n, maxsplit
1803-
);
1823+
list = stringlib_split((PyObject*)self, sbuf, slen,
1824+
(const char *)vsub.buf, vsub.len, maxsplit);
18041825
PyBuffer_Release(&vsub);
1826+
1827+
done:
1828+
self->ob_exports--;
18051829
return list;
18061830
}
18071831

@@ -1900,26 +1924,32 @@ bytearray_rsplit_impl(PyByteArrayObject *self, PyObject *sep,
19001924
Py_ssize_t maxsplit)
19011925
/*[clinic end generated code: output=a55e0b5a03cb6190 input=60e9abf305128ff4]*/
19021926
{
1903-
Py_ssize_t len = PyByteArray_GET_SIZE(self), n;
1904-
const char *s = PyByteArray_AS_STRING(self), *sub;
1905-
PyObject *list;
1906-
Py_buffer vsub;
1927+
PyObject *list = NULL;
1928+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self);
1929+
1930+
self->ob_exports++;
1931+
const char *sbuf = PyByteArray_AS_STRING(self);
1932+
Py_ssize_t slen = PyByteArray_GET_SIZE((PyObject *)self);
19071933

19081934
if (maxsplit < 0)
19091935
maxsplit = PY_SSIZE_T_MAX;
19101936

1911-
if (sep == Py_None)
1912-
return stringlib_rsplit_whitespace((PyObject*) self, s, len, maxsplit);
1937+
if (sep == Py_None) {
1938+
list = stringlib_rsplit_whitespace((PyObject*)self, sbuf, slen, maxsplit);
1939+
goto done;
1940+
}
19131941

1914-
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0)
1915-
return NULL;
1916-
sub = vsub.buf;
1917-
n = vsub.len;
1942+
Py_buffer vsub;
1943+
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) {
1944+
goto done;
1945+
}
19181946

1919-
list = stringlib_rsplit(
1920-
(PyObject*) self, s, len, sub, n, maxsplit
1921-
);
1947+
list = stringlib_rsplit((PyObject*)self, sbuf, slen,
1948+
(const char *)vsub.buf, vsub.len, maxsplit);
19221949
PyBuffer_Release(&vsub);
1950+
1951+
done:
1952+
self->ob_exports--;
19231953
return list;
19241954
}
19251955

0 commit comments

Comments
 (0)