https://github.com/python/cpython/commit/220f0b107776391201a399c54dd01692c36fcdf4
commit: 220f0b107776391201a399c54dd01692c36fcdf4
branch: main
author: wangxiaolei <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2025-12-19T08:02:23Z
summary:
gh-142560: prevent use-after-free in search-like methods by exporting buffer in
bytearray (#142938)
files:
A Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst
M Lib/test/test_bytes.py
M Objects/bytearrayobject.c
diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index a55ec6cf3b8353..21be61e4fec720 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -2060,6 +2060,37 @@ def __index__(self):
self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not
altered")
self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object
altered")
+ def test_search_methods_reentrancy_raises_buffererror(self):
+ # gh-142560: Raise BufferError if buffer mutates during search arg
conversion.
+ class Evil:
+ def __init__(self, ba):
+ self.ba = ba
+ def __buffer__(self, flags):
+ self.ba.clear()
+ return memoryview(self.ba)
+ def __release_buffer__(self, view: memoryview) -> None:
+ view.release()
+ def __index__(self):
+ self.ba.clear()
+ return ord("A")
+
+ def make_case():
+ ba = bytearray(b"A")
+ return ba, Evil(ba)
+
+ for name in ("find", "count", "index", "rindex", "rfind"):
+ ba, evil = make_case()
+ with self.subTest(name):
+ with self.assertRaises(BufferError):
+ getattr(ba, name)(evil)
+
+ ba, evil = make_case()
+ with self.assertRaises(BufferError):
+ evil in ba
+ with self.assertRaises(BufferError):
+ ba.split(evil)
+ with self.assertRaises(BufferError):
+ ba.rsplit(evil)
class AssortedBytesTest(unittest.TestCase):
#
diff --git
a/Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst
b/Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst
new file mode 100644
index 00000000000000..9c0657214b0751
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst
@@ -0,0 +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.
diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c
index 25cc0bfcbaba45..338c71ad38f7aa 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -90,6 +90,25 @@ bytearray_releasebuffer(PyObject *self, Py_buffer *view)
Py_END_CRITICAL_SECTION();
}
+typedef PyObject* (*_ba_bytes_op)(const char *buf, Py_ssize_t len,
+ PyObject *sub, Py_ssize_t start,
+ Py_ssize_t end);
+
+static PyObject *
+_bytearray_with_buffer(PyByteArrayObject *self, _ba_bytes_op op, PyObject *sub,
+ Py_ssize_t start, Py_ssize_t end)
+{
+ PyObject *res;
+
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
+ /* Increase exports to prevent bytearray storage from changing during op.
*/
+ self->ob_exports++;
+ res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end);
+ self->ob_exports--;
+ return res;
+}
+
static int
_canresize(PyByteArrayObject *self)
{
@@ -1248,8 +1267,7 @@ bytearray_find_impl(PyByteArrayObject *self, PyObject
*sub, Py_ssize_t start,
Py_ssize_t end)
/*[clinic end generated code: output=413e1cab2ae87da0 input=df3aa94840d893a7]*/
{
- return _Py_bytes_find(PyByteArray_AS_STRING(self),
PyByteArray_GET_SIZE(self),
- sub, start, end);
+ return _bytearray_with_buffer(self, _Py_bytes_find, sub, start, end);
}
/*[clinic input]
@@ -1265,8 +1283,7 @@ bytearray_count_impl(PyByteArrayObject *self, PyObject
*sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=a21ee2692e4f1233 input=e8fcdca8272857e0]*/
{
- return _Py_bytes_count(PyByteArray_AS_STRING(self),
PyByteArray_GET_SIZE(self),
- sub, start, end);
+ return _bytearray_with_buffer(self, _Py_bytes_count, sub, start, end);
}
/*[clinic input]
@@ -1314,8 +1331,7 @@ bytearray_index_impl(PyByteArrayObject *self, PyObject
*sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=067a1e78efc672a7 input=c37f177cfee19fe4]*/
{
- return _Py_bytes_index(PyByteArray_AS_STRING(self),
PyByteArray_GET_SIZE(self),
- sub, start, end);
+ return _bytearray_with_buffer(self, _Py_bytes_index, sub, start, end);
}
/*[clinic input]
@@ -1333,8 +1349,7 @@ bytearray_rfind_impl(PyByteArrayObject *self, PyObject
*sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=51bf886f932b283c input=1265b11c437d2750]*/
{
- return _Py_bytes_rfind(PyByteArray_AS_STRING(self),
PyByteArray_GET_SIZE(self),
- sub, start, end);
+ return _bytearray_with_buffer(self, _Py_bytes_rfind, sub, start, end);
}
/*[clinic input]
@@ -1352,18 +1367,22 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject
*sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=38e1cf66bafb08b9 input=7d198b3d6b0a62ce]*/
{
- return _Py_bytes_rindex(PyByteArray_AS_STRING(self),
PyByteArray_GET_SIZE(self),
- sub, start, end);
+ return _bytearray_with_buffer(self, _Py_bytes_rindex, sub, start, end);
}
static int
bytearray_contains(PyObject *self, PyObject *arg)
{
- int ret;
+ int ret = -1;
Py_BEGIN_CRITICAL_SECTION(self);
- ret = _Py_bytes_contains(PyByteArray_AS_STRING(self),
+ PyByteArrayObject *ba = _PyByteArray_CAST(self);
+
+ /* Increase exports to prevent bytearray storage from changing during
_Py_bytes_contains(). */
+ ba->ob_exports++;
+ ret = _Py_bytes_contains(PyByteArray_AS_STRING(ba),
PyByteArray_GET_SIZE(self),
arg);
+ ba->ob_exports--;
Py_END_CRITICAL_SECTION();
return ret;
}
@@ -1390,8 +1409,7 @@ bytearray_startswith_impl(PyByteArrayObject *self,
PyObject *subobj,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=a3d9b6d44d3662a6 input=93f9ffee684f109a]*/
{
- return _Py_bytes_startswith(PyByteArray_AS_STRING(self),
PyByteArray_GET_SIZE(self),
- subobj, start, end);
+ return _bytearray_with_buffer(self, _Py_bytes_startswith, subobj, start,
end);
}
/*[clinic input]
@@ -1416,8 +1434,7 @@ bytearray_endswith_impl(PyByteArrayObject *self, PyObject
*subobj,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=e75ea8c227954caa input=d158b030a11d0b06]*/
{
- return _Py_bytes_endswith(PyByteArray_AS_STRING(self),
PyByteArray_GET_SIZE(self),
- subobj, start, end);
+ return _bytearray_with_buffer(self, _Py_bytes_endswith, subobj, start,
end);
}
/*[clinic input]
@@ -1782,26 +1799,32 @@ bytearray_split_impl(PyByteArrayObject *self, PyObject
*sep,
Py_ssize_t maxsplit)
/*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/
{
- Py_ssize_t len = PyByteArray_GET_SIZE(self), n;
- const char *s = PyByteArray_AS_STRING(self), *sub;
- PyObject *list;
- Py_buffer vsub;
+ PyObject *list = NULL;
+
+ /* Increase exports to prevent bytearray storage from changing during
_Py_bytes_contains(). */
+ self->ob_exports++;
+ const char *sbuf = PyByteArray_AS_STRING(self);
+ Py_ssize_t slen = PyByteArray_GET_SIZE((PyObject *)self);
if (maxsplit < 0)
maxsplit = PY_SSIZE_T_MAX;
- if (sep == Py_None)
- return stringlib_split_whitespace((PyObject*) self, s, len, maxsplit);
+ if (sep == Py_None) {
+ list = stringlib_split_whitespace((PyObject*)self, sbuf, slen,
maxsplit);
+ goto done;
+ }
- if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0)
- return NULL;
- sub = vsub.buf;
- n = vsub.len;
+ Py_buffer vsub;
+ if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) {
+ goto done;
+ }
- list = stringlib_split(
- (PyObject*) self, s, len, sub, n, maxsplit
- );
+ list = stringlib_split((PyObject*)self, sbuf, slen,
+ (const char *)vsub.buf, vsub.len, maxsplit);
PyBuffer_Release(&vsub);
+
+done:
+ self->ob_exports--;
return list;
}
@@ -1900,26 +1923,32 @@ bytearray_rsplit_impl(PyByteArrayObject *self, PyObject
*sep,
Py_ssize_t maxsplit)
/*[clinic end generated code: output=a55e0b5a03cb6190 input=60e9abf305128ff4]*/
{
- Py_ssize_t len = PyByteArray_GET_SIZE(self), n;
- const char *s = PyByteArray_AS_STRING(self), *sub;
- PyObject *list;
- Py_buffer vsub;
+ PyObject *list = NULL;
+
+ /* Increase exports to prevent bytearray storage from changing during
_Py_bytes_contains(). */
+ self->ob_exports++;
+ const char *sbuf = PyByteArray_AS_STRING(self);
+ Py_ssize_t slen = PyByteArray_GET_SIZE((PyObject *)self);
if (maxsplit < 0)
maxsplit = PY_SSIZE_T_MAX;
- if (sep == Py_None)
- return stringlib_rsplit_whitespace((PyObject*) self, s, len, maxsplit);
+ if (sep == Py_None) {
+ list = stringlib_rsplit_whitespace((PyObject*)self, sbuf, slen,
maxsplit);
+ goto done;
+ }
- if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0)
- return NULL;
- sub = vsub.buf;
- n = vsub.len;
+ Py_buffer vsub;
+ if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) {
+ goto done;
+ }
- list = stringlib_rsplit(
- (PyObject*) self, s, len, sub, n, maxsplit
- );
+ list = stringlib_rsplit((PyObject*)self, sbuf, slen,
+ (const char *)vsub.buf, vsub.len, maxsplit);
PyBuffer_Release(&vsub);
+
+done:
+ self->ob_exports--;
return list;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]