https://github.com/python/cpython/commit/a9e068f0be93f197818f9722e0add7ab37a3c620
commit: a9e068f0be93f197818f9722e0add7ab37a3c620
branch: 3.13
author: Cody Maloney <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-12-24T19:13:47Z
summary:

[3.13] gh-142560: prevent use-after-free in search-like methods by exporting 
buffer in bytearray (GH-142938) (GH-142986)

(cherry picked from commit 220f0b107776391201a399c54dd01692c36fcdf4)

Co-authored-by: wangxiaolei <[email protected]>

files:
A Misc/NEWS.d/next/Core and 
Builtins/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 2b4705a5f8b49f..cc0a46bb56e4ee 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -1897,6 +1897,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/Core and 
Builtins/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst b/Misc/NEWS.d/next/Core 
and Builtins/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/Core and 
Builtins/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 5382b1bdfa59aa..f940e8c12546c7 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -64,6 +64,24 @@ bytearray_releasebuffer(PyByteArrayObject *obj, Py_buffer 
*view)
     assert(obj->ob_exports >= 0);
 }
 
+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;
+
+    /* 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)
 {
@@ -1146,8 +1164,7 @@ bytearray_find_impl(PyByteArrayObject *self, PyObject 
*sub, Py_ssize_t start,
                     Py_ssize_t end)
 /*[clinic end generated code: output=413e1cab2ae87da0 input=793dfad803e2952f]*/
 {
-    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]
@@ -1161,8 +1178,7 @@ bytearray_count_impl(PyByteArrayObject *self, PyObject 
*sub,
                      Py_ssize_t start, Py_ssize_t end)
 /*[clinic end generated code: output=a21ee2692e4f1233 input=4deb529db38deda8]*/
 {
-    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]
@@ -1207,8 +1223,7 @@ bytearray_index_impl(PyByteArrayObject *self, PyObject 
*sub,
                      Py_ssize_t start, Py_ssize_t end)
 /*[clinic end generated code: output=067a1e78efc672a7 input=8cbaf6836dbd2a9a]*/
 {
-    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]
@@ -1224,8 +1239,7 @@ bytearray_rfind_impl(PyByteArrayObject *self, PyObject 
*sub,
                      Py_ssize_t start, Py_ssize_t end)
 /*[clinic end generated code: output=51bf886f932b283c input=eaa107468a158423]*/
 {
-    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]
@@ -1241,14 +1255,22 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject 
*sub,
                       Py_ssize_t start, Py_ssize_t end)
 /*[clinic end generated code: output=38e1cf66bafb08b9 input=81cf49d0af4d5bd0]*/
 {
-    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)
 {
-    return _Py_bytes_contains(PyByteArray_AS_STRING(self), 
PyByteArray_GET_SIZE(self), arg);
+    int ret = -1;
+    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--;
+    return ret;
 }
 
 /*[clinic input]
@@ -1271,8 +1293,7 @@ bytearray_startswith_impl(PyByteArrayObject *self, 
PyObject *subobj,
                           Py_ssize_t start, Py_ssize_t end)
 /*[clinic end generated code: output=a3d9b6d44d3662a6 input=76385e0b376b45c1]*/
 {
-    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]
@@ -1295,8 +1316,7 @@ bytearray_endswith_impl(PyByteArrayObject *self, PyObject 
*subobj,
                         Py_ssize_t start, Py_ssize_t end)
 /*[clinic end generated code: output=e75ea8c227954caa input=9b8baa879aa3d74b]*/
 {
-    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]
@@ -1539,26 +1559,32 @@ bytearray_split_impl(PyByteArrayObject *self, PyObject 
*sep,
                      Py_ssize_t maxsplit)
 /*[clinic end generated code: output=833e2cf385d9a04d input=24f82669f41bf523]*/
 {
-    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;
 }
 
@@ -1650,26 +1676,32 @@ bytearray_rsplit_impl(PyByteArrayObject *self, PyObject 
*sep,
                       Py_ssize_t maxsplit)
 /*[clinic end generated code: output=a55e0b5a03cb6190 input=a68286e4dd692ffe]*/
 {
-    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]

Reply via email to