From 1bb7bc03627935d32aab3698ed76a7ad4eec4f49 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 11 Dec 2025 16:42:15 +0800 Subject: [PATCH 1/9] fix: bytearray: prevent UAF in search-like methods by exporting self buffer --- Lib/test/test_bytes.py | 31 +++++ ...-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 1 + Objects/bytearrayobject.c | 114 +++++++++++------- 3 files changed, 104 insertions(+), 42 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst 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 ` is used for this. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 25cc0bfcbaba45..5f962146308d2e 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,20 @@ 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); + 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 +1407,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 +1432,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] @@ -1781,27 +1796,36 @@ static PyObject * bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) /*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/ + +static PyObject * +bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) { - 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; + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self); + + 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 +1924,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; + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self); + + 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; } From cf35869f76ec4929773d2e8fd4879bdbe5f91eac Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 22:34:52 +0800 Subject: [PATCH 2/9] fix: fix code regen --- Objects/bytearrayobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 5f962146308d2e..1c04c07dbbab87 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1797,6 +1797,7 @@ bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) /*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/ + static PyObject * bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) { From 0525ac5dee4ba26eed678d271fa2d3f6cb821450 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 22:37:32 +0800 Subject: [PATCH 3/9] fix: fix code regen --- Objects/bytearrayobject.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 1c04c07dbbab87..7d30d900561e12 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1792,11 +1792,6 @@ bytearray.split Return a list of the sections in the bytearray, using sep as the delimiter. [clinic start generated code]*/ -static PyObject * -bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, - Py_ssize_t maxsplit) -/*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/ - static PyObject * bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) From eb0a8a4a4c152100d0a5544a89b9b4a224f90231 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 22:42:32 +0800 Subject: [PATCH 4/9] fix: fix code regen --- Objects/bytearrayobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 7d30d900561e12..827c05d1209588 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1795,6 +1795,7 @@ Return a list of the sections in the bytearray, using sep as the delimiter. static PyObject * bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) +/*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/ { PyObject *list = NULL; _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self); From f2afe09d384dc08cf347a6c002c934cd93681dff Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 22:47:53 +0800 Subject: [PATCH 5/9] fix: fix code regen --- Objects/bytearrayobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 827c05d1209588..f007e6c42da7b2 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1792,9 +1792,9 @@ bytearray.split Return a list of the sections in the bytearray, using sep as the delimiter. [clinic start generated code]*/ - static PyObject * -bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) +bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, + Py_ssize_t maxsplit) /*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/ { PyObject *list = NULL; From 00f6a54d5cfef4879581cd8e43f5d399c534b0e5 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 22:49:39 +0800 Subject: [PATCH 6/9] chore: add comment for bytearray_contains --- Objects/bytearrayobject.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index f007e6c42da7b2..0ecbaae5594157 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1376,6 +1376,8 @@ bytearray_contains(PyObject *self, PyObject *arg) int ret = -1; Py_BEGIN_CRITICAL_SECTION(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), From be06c26800b59c70ef56d8b1b817bceb30037f55 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 22:50:26 +0800 Subject: [PATCH 7/9] chore: add comment for split and rsplit --- Objects/bytearrayobject.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 0ecbaae5594157..e800c493962132 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1802,6 +1802,7 @@ bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, PyObject *list = NULL; _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self); + /* 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); @@ -1926,6 +1927,7 @@ bytearray_rsplit_impl(PyByteArrayObject *self, PyObject *sep, PyObject *list = NULL; _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self); + /* 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); From 7f3b72f08cc2df426dfb3012dd293a5148a7f2aa Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 19 Dec 2025 13:06:00 +0530 Subject: [PATCH 8/9] Update Objects/bytearrayobject.c --- Objects/bytearrayobject.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index e800c493962132..253cfaea3b8f77 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1800,7 +1800,6 @@ bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, /*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/ { PyObject *list = NULL; - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self); /* Increase exports to prevent bytearray storage from changing during _Py_bytes_contains(). */ self->ob_exports++; From 1905c43b66a4e7d9816abcba821412003063e2c5 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 19 Dec 2025 13:06:08 +0530 Subject: [PATCH 9/9] Update Objects/bytearrayobject.c --- Objects/bytearrayobject.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 253cfaea3b8f77..338c71ad38f7aa 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1924,7 +1924,6 @@ bytearray_rsplit_impl(PyByteArrayObject *self, PyObject *sep, /*[clinic end generated code: output=a55e0b5a03cb6190 input=60e9abf305128ff4]*/ { PyObject *list = NULL; - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((PyObject *)self); /* Increase exports to prevent bytearray storage from changing during _Py_bytes_contains(). */ self->ob_exports++;