From 887afac6011c066d83a804bf639b445637a6cc98 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 11 Dec 2025 16:42:15 +0800 Subject: [PATCH 01/45] fix: bytearray: prevent UAF in search-like methods by exporting self buffer --- Lib/test/test_bytes.py | 18 ++++++ Objects/bytearrayobject.c | 131 ++++++++++++++++++++++++++++---------- 2 files changed, 114 insertions(+), 35 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index a55ec6cf3b8353..00174bec57edfc 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2060,6 +2060,24 @@ 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): + ba = bytearray(b"A") + class Evil: + def __index__(self): + ba.clear() + return 65 # ord('A') + with self.assertRaises(BufferError): + ba.find(Evil()) + with self.assertRaises(BufferError): + ba.count(Evil()) + with self.assertRaises(BufferError): + ba.index(Evil()) + with self.assertRaises(BufferError): + ba.rindex(Evil()) + with self.assertRaises(BufferError): + ba.rfind(Evil()) + + class AssortedBytesTest(unittest.TestCase): # diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 99e1c9b13f7879..12ff7105ac6e15 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1233,8 +1233,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_find((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1250,8 +1256,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_count((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1299,8 +1311,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_index((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1318,8 +1336,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_rfind((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1337,19 +1361,26 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_rindex((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } static int bytearray_contains(PyObject *self, PyObject *arg) { int ret; - Py_BEGIN_CRITICAL_SECTION(self); - ret = _Py_bytes_contains(PyByteArray_AS_STRING(self), - PyByteArray_GET_SIZE(self), - arg); - Py_END_CRITICAL_SECTION(); + Py_buffer selfbuf; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return -1; + } + ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); + PyBuffer_Release(&selfbuf); return ret; } @@ -1375,8 +1406,15 @@ 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), + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_startswith((const char *)selfbuf.buf, selfbuf.len, subobj, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1401,8 +1439,15 @@ 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), + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_endswith((const char *)selfbuf.buf, selfbuf.len, subobj, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1767,26 +1812,34 @@ 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; + Py_buffer selfbuf, vsub; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } 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, + (const char *)selfbuf.buf, selfbuf.len, + maxsplit); + PyBuffer_Release(&selfbuf); + return list; + } - if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) + if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) { + PyBuffer_Release(&selfbuf); return NULL; - sub = vsub.buf; - n = vsub.len; + } list = stringlib_split( - (PyObject*) self, s, len, sub, n, maxsplit + (PyObject*) self, (const char *)selfbuf.buf, selfbuf.len, + (const char *)vsub.buf, vsub.len, maxsplit ); PyBuffer_Release(&vsub); + PyBuffer_Release(&selfbuf); return list; } @@ -1885,26 +1938,34 @@ 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; + Py_buffer selfbuf, vsub; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } 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, + (const char *)selfbuf.buf, selfbuf.len, + maxsplit); + PyBuffer_Release(&selfbuf); + return list; + } - if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) + if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) { + PyBuffer_Release(&selfbuf); return NULL; - sub = vsub.buf; - n = vsub.len; + } list = stringlib_rsplit( - (PyObject*) self, s, len, sub, n, maxsplit + (PyObject*) self, (const char *)selfbuf.buf, selfbuf.len, + (const char *)vsub.buf, vsub.len, maxsplit ); PyBuffer_Release(&vsub); + PyBuffer_Release(&selfbuf); return list; } From 2dd958ba17789968fa8aeec7dcd0a484d9c62f8f Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 11 Dec 2025 23:00:00 +0800 Subject: [PATCH 02/45] chore: add blurb --- .../Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst 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..a9c70c8de7249c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst @@ -0,0 +1,3 @@ +Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by +exporting the instance’s buffer to prevent reallocation during the +operation. From 2cb388b8484c3a8ca7247523cb6c70d459a18409 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 00:12:11 +0800 Subject: [PATCH 03/45] fix: fix new format --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a9c70c8de7249c..15e752441be524 100644 --- 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 @@ -1,3 +1,3 @@ Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by -exporting the instance’s buffer to prevent reallocation during the +exporting the instance's buffer to prevent reallocation during the operation. From b954fc74ecf93d71e70d9515f25324f80435e18a Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 13:42:29 +0800 Subject: [PATCH 04/45] fix: fix python lint error and make code more simple --- Lib/test/test_bytes.py | 1 - Objects/bytearrayobject.c | 81 ++++++++++++--------------------------- 2 files changed, 24 insertions(+), 58 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 00174bec57edfc..3fe7cb051a5d26 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2078,7 +2078,6 @@ def __index__(self): ba.rfind(Evil()) - class AssortedBytesTest(unittest.TestCase): # # Test various combinations of bytes and bytearray diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 12ff7105ac6e15..3c488cabc24fc2 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1228,21 +1228,32 @@ Return the lowest index in B where subsection 'sub' is found, such that 'sub' is Return -1 on failure. [clinic start generated code]*/ +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_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, - Py_ssize_t end) -/*[clinic end generated code: output=413e1cab2ae87da0 input=df3aa94840d893a7]*/ +_bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, + Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) { - Py_buffer selfbuf; + Py_buffer view; PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { return NULL; } - res = _Py_bytes_find((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); + res = op((const char *)view.buf, view.len, sub, start, end); + PyBuffer_Release(&view); return res; } +static PyObject * +bytearray_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, + Py_ssize_t end) +/*[clinic end generated code: output=413e1cab2ae87da0 input=df3aa94840d893a7]*/ +{ + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_find); +} + /*[clinic input] @permit_long_summary @critical_section @@ -1256,14 +1267,7 @@ bytearray_count_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=a21ee2692e4f1233 input=e8fcdca8272857e0]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_count((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_count); } /*[clinic input] @@ -1311,14 +1315,7 @@ bytearray_index_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=067a1e78efc672a7 input=c37f177cfee19fe4]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_index((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_index); } /*[clinic input] @@ -1336,14 +1333,7 @@ bytearray_rfind_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=51bf886f932b283c input=1265b11c437d2750]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_rfind((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rfind); } /*[clinic input] @@ -1361,14 +1351,7 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=38e1cf66bafb08b9 input=7d198b3d6b0a62ce]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_rindex((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rindex); } static int @@ -1406,15 +1389,7 @@ bytearray_startswith_impl(PyByteArrayObject *self, PyObject *subobj, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=a3d9b6d44d3662a6 input=93f9ffee684f109a]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_startswith((const char *)selfbuf.buf, selfbuf.len, - subobj, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_startswith); } /*[clinic input] @@ -1439,15 +1414,7 @@ bytearray_endswith_impl(PyByteArrayObject *self, PyObject *subobj, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=e75ea8c227954caa input=d158b030a11d0b06]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_endswith((const char *)selfbuf.buf, selfbuf.len, - subobj, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_endswith); } /*[clinic input] From 87b5ccec76e5a67a89a03bce9847b0f319d1e6ed Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 14:52:51 +0800 Subject: [PATCH 05/45] fix: fix code not regenerate --- Objects/bytearrayobject.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 3c488cabc24fc2..38432733a97267 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -90,6 +90,24 @@ 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, PyObject *sub, + Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) +{ + Py_buffer view; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = op((const char *)view.buf, view.len, sub, start, end); + PyBuffer_Release(&view); + return res; +} + static int _canresize(PyByteArrayObject *self) { @@ -1228,24 +1246,6 @@ Return the lowest index in B where subsection 'sub' is found, such that 'sub' is Return -1 on failure. [clinic start generated code]*/ -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, PyObject *sub, - Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) -{ - Py_buffer view; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = op((const char *)view.buf, view.len, sub, start, end); - PyBuffer_Release(&view); - return res; -} - static PyObject * bytearray_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) From f1b759665b28bb78c7b4a38bd697a70eb0b3bb6c Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 15:19:37 +0800 Subject: [PATCH 06/45] fix: fix ubuntu test failed for ass_subscript2 --- Lib/test/test_bytes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 3fe7cb051a5d26..d1300f7532580c 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2615,7 +2615,10 @@ def ass_subscript(b, a): # MODIFIES! def ass_subscript2(b, a, c): # MODIFIES! b.wait() - a[:] = c + try: + a[:] = c + except BufferError: + return assert b'\xdd' not in a def mod(b, a): From 13a646998ddac858e31fae3042476b59e37650eb Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 19:58:17 +0800 Subject: [PATCH 07/45] chore: add comment for method test_search_methods_reentrancy_raises_buffererror --- Lib/test/test_bytes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index d1300f7532580c..2413005172e808 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2061,6 +2061,7 @@ def __index__(self): 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. ba = bytearray(b"A") class Evil: def __index__(self): From b5efa3f66294a8041d0817144c5c39b2b8f7bd4c Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 20:08:44 +0800 Subject: [PATCH 08/45] refactor: refactor test_search_methods_reentrancy_raises_buffererror make it more readable --- Lib/test/test_bytes.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 2413005172e808..e1007fe94fc6af 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2062,21 +2062,21 @@ def __index__(self): def test_search_methods_reentrancy_raises_buffererror(self): # gh-142560: Raise BufferError if buffer mutates during search arg conversion. - ba = bytearray(b"A") class Evil: + def __init__(self, ba): + self.ba = ba def __index__(self): - ba.clear() - return 65 # ord('A') - with self.assertRaises(BufferError): - ba.find(Evil()) - with self.assertRaises(BufferError): - ba.count(Evil()) - with self.assertRaises(BufferError): - ba.index(Evil()) - with self.assertRaises(BufferError): - ba.rindex(Evil()) - with self.assertRaises(BufferError): - ba.rfind(Evil()) + self.ba.clear() + return 65 + + 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.assertRaises(BufferError): + getattr(ba, name)(evil) class AssortedBytesTest(unittest.TestCase): From 62b2801ed695828bfb5fd4d3a24abfa764f2d1ff Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 20:12:50 +0800 Subject: [PATCH 09/45] fix: fix pre commit failed --- Lib/test/test_bytes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e1007fe94fc6af..d450c00ea3ae13 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2068,7 +2068,7 @@ def __init__(self, ba): def __index__(self): self.ba.clear() return 65 - + def make_case(): ba = bytearray(b"A") return ba, Evil(ba) From 84196509710dc237422daaede22516e3338f45ce Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 20:45:31 +0800 Subject: [PATCH 10/45] chore: using subTest to distinguish which method failed --- Lib/test/test_bytes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index d450c00ea3ae13..707930110532f9 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2067,7 +2067,7 @@ def __init__(self, ba): self.ba = ba def __index__(self): self.ba.clear() - return 65 + return ord("A") def make_case(): ba = bytearray(b"A") @@ -2075,8 +2075,9 @@ def make_case(): for name in ("find", "count", "index", "rindex", "rfind"): ba, evil = make_case() - with self.assertRaises(BufferError): - getattr(ba, name)(evil) + with self.subTest(name): + with self.assertRaises(BufferError): + getattr(ba, name)(evil) class AssortedBytesTest(unittest.TestCase): From 2df5acb86982e9683a82af7ce3e4c958c130ee10 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 21:21:03 +0800 Subject: [PATCH 11/45] chore: using more light operation instead of heavy operation --- Objects/bytearrayobject.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 38432733a97267..426afd4d127e1f 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -98,13 +98,19 @@ static PyObject * _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) { - Py_buffer view; PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = op((const char *)view.buf, view.len, sub, start, end); - PyBuffer_Release(&view); + + Py_BEGIN_CRITICAL_SECTION(self); + self->ob_exports++; + Py_END_CRITICAL_SECTION(); + + res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end); + + Py_BEGIN_CRITICAL_SECTION(self); + self->ob_exports--; + assert(self->ob_exports >= 0); + Py_END_CRITICAL_SECTION(); + return res; } From e6c622e19b8ccba77b73c1b045966becbf2a56b6 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 21:23:18 +0800 Subject: [PATCH 12/45] fix: fix pre-commit error --- Objects/bytearrayobject.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 426afd4d127e1f..0e9313ed6ef4b3 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -110,7 +110,6 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, self->ob_exports--; assert(self->ob_exports >= 0); Py_END_CRITICAL_SECTION(); - return res; } From 796403309af069d67ba751d15b9b11cfdcafd595 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 09:35:52 +0800 Subject: [PATCH 13/45] chore: resolve comment --- Lib/test/test_bytes.py | 5 +---- .../2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 4 +--- Objects/bytearrayobject.c | 8 +------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 707930110532f9..9a6d8a2656027d 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2617,10 +2617,7 @@ def ass_subscript(b, a): # MODIFIES! def ass_subscript2(b, a, c): # MODIFIES! b.wait() - try: - a[:] = c - except BufferError: - return + a[:] = c assert b'\xdd' not in a def mod(b, a): 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 index 15e752441be524..b5ed3ee7a17413 100644 --- 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 @@ -1,3 +1 @@ -Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by -exporting the instance's buffer to prevent reallocation during the -operation. +Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by exporting the instance's buffer to prevent reallocation during the operation. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 0e9313ed6ef4b3..24f001cf1a600f 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -100,16 +100,10 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, { PyObject *res; - Py_BEGIN_CRITICAL_SECTION(self); self->ob_exports++; - Py_END_CRITICAL_SECTION(); - res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end); - - Py_BEGIN_CRITICAL_SECTION(self); self->ob_exports--; - assert(self->ob_exports >= 0); - Py_END_CRITICAL_SECTION(); + return res; } From 8b1cea2cc18373570404861335f5083dee3ef44f Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 09:36:58 +0800 Subject: [PATCH 14/45] chore: resolve comment --- Objects/bytearrayobject.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 24f001cf1a600f..39f070666f37f5 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -100,6 +100,8 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, { PyObject *res; + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); + self->ob_exports++; res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end); self->ob_exports--; From 782cab1d602f522bd41f8c46dd4eaaa7d9e92785 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 10:21:36 +0800 Subject: [PATCH 15/45] chore: update the news --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index b5ed3ee7a17413..c75b39dc8de283 100644 --- 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 @@ -1 +1 @@ -Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by exporting the instance's buffer to prevent reallocation during the operation. +Fix a potential use‑after‑free in bytearray search‑like methods by exporting the buffer during the call. bytearray.split() and bytearray.rsplit() now also export the buffer; subclasses overriding the buffer protocol may observe behavior changes. From 14963ead0c8807c9365f9059428cf2e9a191f1e9 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 10:44:26 +0800 Subject: [PATCH 16/45] chore: bytearray_contains add Py_BEGIN_CRITICAL_SECTION back --- Objects/bytearrayobject.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 39f070666f37f5..6073fdd90b5a7b 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1359,12 +1359,15 @@ static int bytearray_contains(PyObject *self, PyObject *arg) { int ret; + Py_BEGIN_CRITICAL_SECTION(self); Py_buffer selfbuf; if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + Py_END_CRITICAL_SECTION(); return -1; } ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); PyBuffer_Release(&selfbuf); + Py_END_CRITICAL_SECTION(); return ret; } From 87225adc90ea0c6027de2e9033f46bd88956793c Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 11:02:26 +0800 Subject: [PATCH 17/45] fix: fix build failed --- Objects/bytearrayobject.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 6073fdd90b5a7b..eec87555122e10 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1359,14 +1359,13 @@ static int bytearray_contains(PyObject *self, PyObject *arg) { int ret; - Py_BEGIN_CRITICAL_SECTION(self); Py_buffer selfbuf; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - Py_END_CRITICAL_SECTION(); - return -1; + Py_BEGIN_CRITICAL_SECTION(self); + ret = -1; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) == 0) { + ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); + PyBuffer_Release(&selfbuf); } - ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); - PyBuffer_Release(&selfbuf); Py_END_CRITICAL_SECTION(); return ret; } From 4214d561eb4fe722990664c960734d578285b462 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 13:39:06 +0800 Subject: [PATCH 18/45] chore: resolve comment --- .../Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 3 ++- Objects/bytearrayobject.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 index c75b39dc8de283..71a30071323487 100644 --- 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 @@ -1 +1,2 @@ -Fix a potential use‑after‑free in bytearray search‑like methods by exporting the buffer during the call. bytearray.split() and bytearray.rsplit() now also export the buffer; subclasses overriding the buffer protocol may observe behavior changes. +Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as +exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. \ No newline at end of file diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index eec87555122e10..16a5515f31cb40 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -102,6 +102,7 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, _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--; @@ -1358,10 +1359,9 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub, static int bytearray_contains(PyObject *self, PyObject *arg) { - int ret; + int ret = -1; Py_buffer selfbuf; Py_BEGIN_CRITICAL_SECTION(self); - ret = -1; if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) == 0) { ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); PyBuffer_Release(&selfbuf); From 01fcc680ac87442918f27d8d254b52075f0d3bfd Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 13:44:44 +0800 Subject: [PATCH 19/45] fix: fix pre commit error --- .../Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index 71a30071323487..aba2b7757fdc5b 100644 --- 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 @@ -1,2 +1 @@ -Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as -exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. \ No newline at end of file +Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. From 6a58ffeb0351d2bafd4f0055589e53348112e67f Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 13:57:42 +0800 Subject: [PATCH 20/45] fix: fix news format --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index aba2b7757fdc5b..26ab658c6c3674 100644 --- 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 @@ -1 +1 @@ -Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. +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 :meth:`~bytearray.__contains__`, :meth:`~bytearray.split`, and :meth:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. From abb9fb77290aef925f207b28387fd049ad63151c Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 14:01:08 +0800 Subject: [PATCH 21/45] fix: fix news format --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 26ab658c6c3674..9c0657214b0751 100644 --- 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 @@ -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 :meth:`~bytearray.__contains__`, :meth:`~bytearray.split`, and :meth:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. +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. From 81a611def1711b4cb399ce4183d3ada397d2678d Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 09:38:57 +0800 Subject: [PATCH 22/45] chore: change args order --- Objects/bytearrayobject.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 16a5515f31cb40..3f8fb284240e9a 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -95,8 +95,8 @@ typedef PyObject* (*_ba_bytes_op)(const char *buf, Py_ssize_t len, Py_ssize_t end); static PyObject * -_bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, - Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) +_bytearray_with_buffer(PyByteArrayObject *self, _ba_bytes_op op, PyObject *sub, + Py_ssize_t start, Py_ssize_t end) { PyObject *res; @@ -1253,7 +1253,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_find); + return _bytearray_with_buffer(self, _Py_bytes_find, sub, start, end); } /*[clinic input] @@ -1269,7 +1269,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_count); + return _bytearray_with_buffer(self, _Py_bytes_count, sub, start, end); } /*[clinic input] @@ -1317,7 +1317,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_index); + return _bytearray_with_buffer(self, _Py_bytes_index, sub, start, end); } /*[clinic input] @@ -1335,7 +1335,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rfind); + return _bytearray_with_buffer(self, _Py_bytes_rfind, sub, start, end); } /*[clinic input] @@ -1353,7 +1353,7 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=38e1cf66bafb08b9 input=7d198b3d6b0a62ce]*/ { - return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rindex); + return _bytearray_with_buffer(self, _Py_bytes_rindex, sub, start, end); } static int @@ -1392,7 +1392,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 _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_startswith); + return _bytearray_with_buffer(self, _Py_bytes_startswith, subobj, start, end); } /*[clinic input] @@ -1417,7 +1417,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 _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_endswith); + return _bytearray_with_buffer(self, _Py_bytes_endswith, subobj, start, end); } /*[clinic input] From fb25b7d3163a6bd2dc851bafaf3c50f3cb0469e5 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 11 Dec 2025 16:42:15 +0800 Subject: [PATCH 23/45] fix: bytearray: prevent UAF in search-like methods by exporting self buffer --- Lib/test/test_bytes.py | 18 ++++++ Objects/bytearrayobject.c | 131 ++++++++++++++++++++++++++++---------- 2 files changed, 114 insertions(+), 35 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index a55ec6cf3b8353..00174bec57edfc 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2060,6 +2060,24 @@ 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): + ba = bytearray(b"A") + class Evil: + def __index__(self): + ba.clear() + return 65 # ord('A') + with self.assertRaises(BufferError): + ba.find(Evil()) + with self.assertRaises(BufferError): + ba.count(Evil()) + with self.assertRaises(BufferError): + ba.index(Evil()) + with self.assertRaises(BufferError): + ba.rindex(Evil()) + with self.assertRaises(BufferError): + ba.rfind(Evil()) + + class AssortedBytesTest(unittest.TestCase): # diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 25cc0bfcbaba45..974d12e8a4c851 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1248,8 +1248,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_find((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1265,8 +1271,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_count((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1314,8 +1326,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_index((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1333,8 +1351,14 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_rfind((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1352,19 +1376,26 @@ 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); + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_rindex((const char *)selfbuf.buf, selfbuf.len, sub, start, end); + PyBuffer_Release(&selfbuf); + return res; } static int bytearray_contains(PyObject *self, PyObject *arg) { int ret; - Py_BEGIN_CRITICAL_SECTION(self); - ret = _Py_bytes_contains(PyByteArray_AS_STRING(self), - PyByteArray_GET_SIZE(self), - arg); - Py_END_CRITICAL_SECTION(); + Py_buffer selfbuf; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return -1; + } + ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); + PyBuffer_Release(&selfbuf); return ret; } @@ -1390,8 +1421,15 @@ 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), + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_startswith((const char *)selfbuf.buf, selfbuf.len, subobj, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1416,8 +1454,15 @@ 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), + Py_buffer selfbuf; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = _Py_bytes_endswith((const char *)selfbuf.buf, selfbuf.len, subobj, start, end); + PyBuffer_Release(&selfbuf); + return res; } /*[clinic input] @@ -1782,26 +1827,34 @@ 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; + Py_buffer selfbuf, vsub; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } 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, + (const char *)selfbuf.buf, selfbuf.len, + maxsplit); + PyBuffer_Release(&selfbuf); + return list; + } - if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) + if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) { + PyBuffer_Release(&selfbuf); return NULL; - sub = vsub.buf; - n = vsub.len; + } list = stringlib_split( - (PyObject*) self, s, len, sub, n, maxsplit + (PyObject*) self, (const char *)selfbuf.buf, selfbuf.len, + (const char *)vsub.buf, vsub.len, maxsplit ); PyBuffer_Release(&vsub); + PyBuffer_Release(&selfbuf); return list; } @@ -1900,26 +1953,34 @@ 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; + Py_buffer selfbuf, vsub; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + return NULL; + } 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, + (const char *)selfbuf.buf, selfbuf.len, + maxsplit); + PyBuffer_Release(&selfbuf); + return list; + } - if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) + if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) { + PyBuffer_Release(&selfbuf); return NULL; - sub = vsub.buf; - n = vsub.len; + } list = stringlib_rsplit( - (PyObject*) self, s, len, sub, n, maxsplit + (PyObject*) self, (const char *)selfbuf.buf, selfbuf.len, + (const char *)vsub.buf, vsub.len, maxsplit ); PyBuffer_Release(&vsub); + PyBuffer_Release(&selfbuf); return list; } From 176bcb284043e60ba59a4b5ba161e0b307254823 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 11 Dec 2025 23:00:00 +0800 Subject: [PATCH 24/45] chore: add blurb --- .../Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst 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..a9c70c8de7249c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst @@ -0,0 +1,3 @@ +Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by +exporting the instance’s buffer to prevent reallocation during the +operation. From ca7f893ec18c66d74dc51a03b042b0014e858615 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 00:12:11 +0800 Subject: [PATCH 25/45] fix: fix new format --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a9c70c8de7249c..15e752441be524 100644 --- 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 @@ -1,3 +1,3 @@ Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by -exporting the instance’s buffer to prevent reallocation during the +exporting the instance's buffer to prevent reallocation during the operation. From e4bdc6c7512ac40b3080d40fcd0d4b3e5b4475d1 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 13:42:29 +0800 Subject: [PATCH 26/45] fix: fix python lint error and make code more simple --- Lib/test/test_bytes.py | 1 - Objects/bytearrayobject.c | 81 ++++++++++++--------------------------- 2 files changed, 24 insertions(+), 58 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 00174bec57edfc..3fe7cb051a5d26 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2078,7 +2078,6 @@ def __index__(self): ba.rfind(Evil()) - class AssortedBytesTest(unittest.TestCase): # # Test various combinations of bytes and bytearray diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 974d12e8a4c851..0cecf1bcb6a447 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1243,21 +1243,32 @@ Return the lowest index in B where subsection 'sub' is found, such that 'sub' is Return -1 on failure. [clinic start generated code]*/ +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_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, - Py_ssize_t end) -/*[clinic end generated code: output=413e1cab2ae87da0 input=df3aa94840d893a7]*/ +_bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, + Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) { - Py_buffer selfbuf; + Py_buffer view; PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { return NULL; } - res = _Py_bytes_find((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); + res = op((const char *)view.buf, view.len, sub, start, end); + PyBuffer_Release(&view); return res; } +static PyObject * +bytearray_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, + Py_ssize_t end) +/*[clinic end generated code: output=413e1cab2ae87da0 input=df3aa94840d893a7]*/ +{ + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_find); +} + /*[clinic input] @permit_long_summary @critical_section @@ -1271,14 +1282,7 @@ bytearray_count_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=a21ee2692e4f1233 input=e8fcdca8272857e0]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_count((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_count); } /*[clinic input] @@ -1326,14 +1330,7 @@ bytearray_index_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=067a1e78efc672a7 input=c37f177cfee19fe4]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_index((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_index); } /*[clinic input] @@ -1351,14 +1348,7 @@ bytearray_rfind_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=51bf886f932b283c input=1265b11c437d2750]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_rfind((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rfind); } /*[clinic input] @@ -1376,14 +1366,7 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=38e1cf66bafb08b9 input=7d198b3d6b0a62ce]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_rindex((const char *)selfbuf.buf, selfbuf.len, sub, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rindex); } static int @@ -1421,15 +1404,7 @@ bytearray_startswith_impl(PyByteArrayObject *self, PyObject *subobj, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=a3d9b6d44d3662a6 input=93f9ffee684f109a]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_startswith((const char *)selfbuf.buf, selfbuf.len, - subobj, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_startswith); } /*[clinic input] @@ -1454,15 +1429,7 @@ bytearray_endswith_impl(PyByteArrayObject *self, PyObject *subobj, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=e75ea8c227954caa input=d158b030a11d0b06]*/ { - Py_buffer selfbuf; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = _Py_bytes_endswith((const char *)selfbuf.buf, selfbuf.len, - subobj, start, end); - PyBuffer_Release(&selfbuf); - return res; + return _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_endswith); } /*[clinic input] From c8905e37653be11695b3f2c7dd1fd37f93bc9184 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 14:52:51 +0800 Subject: [PATCH 27/45] fix: fix code not regenerate --- Objects/bytearrayobject.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 0cecf1bcb6a447..104f5b67a2cac8 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -90,6 +90,24 @@ 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, PyObject *sub, + Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) +{ + Py_buffer view; + PyObject *res; + if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { + return NULL; + } + res = op((const char *)view.buf, view.len, sub, start, end); + PyBuffer_Release(&view); + return res; +} + static int _canresize(PyByteArrayObject *self) { @@ -1243,24 +1261,6 @@ Return the lowest index in B where subsection 'sub' is found, such that 'sub' is Return -1 on failure. [clinic start generated code]*/ -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, PyObject *sub, - Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) -{ - Py_buffer view; - PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = op((const char *)view.buf, view.len, sub, start, end); - PyBuffer_Release(&view); - return res; -} - static PyObject * bytearray_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) From 9d2e8015629b8df58017672d123dbacd94eff692 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 15:19:37 +0800 Subject: [PATCH 28/45] fix: fix ubuntu test failed for ass_subscript2 --- Lib/test/test_bytes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 3fe7cb051a5d26..d1300f7532580c 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2615,7 +2615,10 @@ def ass_subscript(b, a): # MODIFIES! def ass_subscript2(b, a, c): # MODIFIES! b.wait() - a[:] = c + try: + a[:] = c + except BufferError: + return assert b'\xdd' not in a def mod(b, a): From b816ac7c75491f114a37e5e5d25943b58814784d Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 19:58:17 +0800 Subject: [PATCH 29/45] chore: add comment for method test_search_methods_reentrancy_raises_buffererror --- Lib/test/test_bytes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index d1300f7532580c..2413005172e808 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2061,6 +2061,7 @@ def __index__(self): 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. ba = bytearray(b"A") class Evil: def __index__(self): From ac5bf091b4f2bcee299872f372eb29ad2b7f6bfb Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 20:08:44 +0800 Subject: [PATCH 30/45] refactor: refactor test_search_methods_reentrancy_raises_buffererror make it more readable --- Lib/test/test_bytes.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 2413005172e808..e1007fe94fc6af 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2062,21 +2062,21 @@ def __index__(self): def test_search_methods_reentrancy_raises_buffererror(self): # gh-142560: Raise BufferError if buffer mutates during search arg conversion. - ba = bytearray(b"A") class Evil: + def __init__(self, ba): + self.ba = ba def __index__(self): - ba.clear() - return 65 # ord('A') - with self.assertRaises(BufferError): - ba.find(Evil()) - with self.assertRaises(BufferError): - ba.count(Evil()) - with self.assertRaises(BufferError): - ba.index(Evil()) - with self.assertRaises(BufferError): - ba.rindex(Evil()) - with self.assertRaises(BufferError): - ba.rfind(Evil()) + self.ba.clear() + return 65 + + 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.assertRaises(BufferError): + getattr(ba, name)(evil) class AssortedBytesTest(unittest.TestCase): From 09b5edf672fd323c18f15f0859ae10596736b967 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 20:12:50 +0800 Subject: [PATCH 31/45] fix: fix pre commit failed --- Lib/test/test_bytes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e1007fe94fc6af..d450c00ea3ae13 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2068,7 +2068,7 @@ def __init__(self, ba): def __index__(self): self.ba.clear() return 65 - + def make_case(): ba = bytearray(b"A") return ba, Evil(ba) From 70507f0719183d9f98aa20b1e17c66ebb3ac2c22 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 20:45:31 +0800 Subject: [PATCH 32/45] chore: using subTest to distinguish which method failed --- Lib/test/test_bytes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index d450c00ea3ae13..707930110532f9 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2067,7 +2067,7 @@ def __init__(self, ba): self.ba = ba def __index__(self): self.ba.clear() - return 65 + return ord("A") def make_case(): ba = bytearray(b"A") @@ -2075,8 +2075,9 @@ def make_case(): for name in ("find", "count", "index", "rindex", "rfind"): ba, evil = make_case() - with self.assertRaises(BufferError): - getattr(ba, name)(evil) + with self.subTest(name): + with self.assertRaises(BufferError): + getattr(ba, name)(evil) class AssortedBytesTest(unittest.TestCase): From f1ddb09de7e9cdfc71cea494623eaf353fe609c8 Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 21:21:03 +0800 Subject: [PATCH 33/45] chore: using more light operation instead of heavy operation --- Objects/bytearrayobject.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 104f5b67a2cac8..afa54a1731acc0 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -98,13 +98,19 @@ static PyObject * _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) { - Py_buffer view; PyObject *res; - if (PyObject_GetBuffer((PyObject *)self, &view, PyBUF_SIMPLE) != 0) { - return NULL; - } - res = op((const char *)view.buf, view.len, sub, start, end); - PyBuffer_Release(&view); + + Py_BEGIN_CRITICAL_SECTION(self); + self->ob_exports++; + Py_END_CRITICAL_SECTION(); + + res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end); + + Py_BEGIN_CRITICAL_SECTION(self); + self->ob_exports--; + assert(self->ob_exports >= 0); + Py_END_CRITICAL_SECTION(); + return res; } From 8c12c99c766bd6f5077687a7f6fe82ec8384a28d Mon Sep 17 00:00:00 2001 From: fatelei Date: Fri, 12 Dec 2025 21:23:18 +0800 Subject: [PATCH 34/45] fix: fix pre-commit error --- Objects/bytearrayobject.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index afa54a1731acc0..38fef6f3373d3f 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -110,7 +110,6 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, self->ob_exports--; assert(self->ob_exports >= 0); Py_END_CRITICAL_SECTION(); - return res; } From fa2f4a7d1d7e4ebb8681f4cb83e8a6709a82536b Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 09:35:52 +0800 Subject: [PATCH 35/45] chore: resolve comment --- Lib/test/test_bytes.py | 5 +---- .../2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 4 +--- Objects/bytearrayobject.c | 8 +------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 707930110532f9..9a6d8a2656027d 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2617,10 +2617,7 @@ def ass_subscript(b, a): # MODIFIES! def ass_subscript2(b, a, c): # MODIFIES! b.wait() - try: - a[:] = c - except BufferError: - return + a[:] = c assert b'\xdd' not in a def mod(b, a): 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 index 15e752441be524..b5ed3ee7a17413 100644 --- 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 @@ -1,3 +1 @@ -Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by -exporting the instance's buffer to prevent reallocation during the -operation. +Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by exporting the instance's buffer to prevent reallocation during the operation. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 38fef6f3373d3f..16f65081be50ec 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -100,16 +100,10 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, { PyObject *res; - Py_BEGIN_CRITICAL_SECTION(self); self->ob_exports++; - Py_END_CRITICAL_SECTION(); - res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end); - - Py_BEGIN_CRITICAL_SECTION(self); self->ob_exports--; - assert(self->ob_exports >= 0); - Py_END_CRITICAL_SECTION(); + return res; } From db79ab38bb18a03cbd3f803ffbcc2349644f4084 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 09:36:58 +0800 Subject: [PATCH 36/45] chore: resolve comment --- Objects/bytearrayobject.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 16f65081be50ec..277d5cc3978637 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -100,6 +100,8 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, { PyObject *res; + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); + self->ob_exports++; res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end); self->ob_exports--; From a1d1d66081dba57f2225453a251a90741f61aeac Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 10:21:36 +0800 Subject: [PATCH 37/45] chore: update the news --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index b5ed3ee7a17413..c75b39dc8de283 100644 --- 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 @@ -1 +1 @@ -Fix a potential use‑after‑free in :class:`bytearray` search‑like methods by exporting the instance's buffer to prevent reallocation during the operation. +Fix a potential use‑after‑free in bytearray search‑like methods by exporting the buffer during the call. bytearray.split() and bytearray.rsplit() now also export the buffer; subclasses overriding the buffer protocol may observe behavior changes. From f7583b2ddec16acf3c73bad4ef79ed709247504f Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 10:44:26 +0800 Subject: [PATCH 38/45] chore: bytearray_contains add Py_BEGIN_CRITICAL_SECTION back --- Objects/bytearrayobject.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 277d5cc3978637..800023447775a5 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1374,12 +1374,15 @@ static int bytearray_contains(PyObject *self, PyObject *arg) { int ret; + Py_BEGIN_CRITICAL_SECTION(self); Py_buffer selfbuf; if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { + Py_END_CRITICAL_SECTION(); return -1; } ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); PyBuffer_Release(&selfbuf); + Py_END_CRITICAL_SECTION(); return ret; } From d2179848c7cc731cdf98ec1679504b399aa54019 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 11:02:26 +0800 Subject: [PATCH 39/45] fix: fix build failed --- Objects/bytearrayobject.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 800023447775a5..7519e7007919bc 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1374,14 +1374,13 @@ static int bytearray_contains(PyObject *self, PyObject *arg) { int ret; - Py_BEGIN_CRITICAL_SECTION(self); Py_buffer selfbuf; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - Py_END_CRITICAL_SECTION(); - return -1; + Py_BEGIN_CRITICAL_SECTION(self); + ret = -1; + if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) == 0) { + ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); + PyBuffer_Release(&selfbuf); } - ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); - PyBuffer_Release(&selfbuf); Py_END_CRITICAL_SECTION(); return ret; } From 8d3e0de6e3435dd85d91140569c35bd2cbaede24 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 13:39:06 +0800 Subject: [PATCH 40/45] chore: resolve comment --- .../Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 3 ++- Objects/bytearrayobject.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 index c75b39dc8de283..71a30071323487 100644 --- 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 @@ -1 +1,2 @@ -Fix a potential use‑after‑free in bytearray search‑like methods by exporting the buffer during the call. bytearray.split() and bytearray.rsplit() now also export the buffer; subclasses overriding the buffer protocol may observe behavior changes. +Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as +exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. \ No newline at end of file diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 7519e7007919bc..d1e13f86fe7456 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -102,6 +102,7 @@ _bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, _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--; @@ -1373,10 +1374,9 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub, static int bytearray_contains(PyObject *self, PyObject *arg) { - int ret; + int ret = -1; Py_buffer selfbuf; Py_BEGIN_CRITICAL_SECTION(self); - ret = -1; if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) == 0) { ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); PyBuffer_Release(&selfbuf); From fab4b604ac8e3ad81392d25776dbcbff0e7cad08 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 13:44:44 +0800 Subject: [PATCH 41/45] fix: fix pre commit error --- .../Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index 71a30071323487..aba2b7757fdc5b 100644 --- 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 @@ -1,2 +1 @@ -Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as -exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. \ No newline at end of file +Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. From 08c6159acf95aaa4fb4ae591f2ef91f44c4ae397 Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 13:57:42 +0800 Subject: [PATCH 42/45] fix: fix news format --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index aba2b7757fdc5b..26ab658c6c3674 100644 --- 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 @@ -1 +1 @@ -Fix use-after-free in :class:`bytearray` search-like methods (:func:`~bytearray.find`, :func:`~bytearray.count`, :func:`~bytearray.index`, :func:`~bytearray.rindex`, and :func:`~bytearray.rfind`) by marking the storage as exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~bytearray.contains`, :func:`~bytearray.split`, and :func:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. +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 :meth:`~bytearray.__contains__`, :meth:`~bytearray.split`, and :meth:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. From 13c110d0258f6f8b8ca1a28d2cb073aca57a138f Mon Sep 17 00:00:00 2001 From: fatelei Date: Mon, 15 Dec 2025 14:01:08 +0800 Subject: [PATCH 43/45] fix: fix news format --- .../next/Library/2025-12-11-22-59-33.gh-issue-142560.GkJrkk.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 26ab658c6c3674..9c0657214b0751 100644 --- 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 @@ -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 :meth:`~bytearray.__contains__`, :meth:`~bytearray.split`, and :meth:`~bytearray.rsplit` the :ref:`buffer protocol ` is used for this. +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. From 8d04ec37e04f10a329256fca7d2118e6bf4e3e99 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 09:38:57 +0800 Subject: [PATCH 44/45] chore: change args order --- Objects/bytearrayobject.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index d1e13f86fe7456..5baad43db0c9ad 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -95,8 +95,8 @@ typedef PyObject* (*_ba_bytes_op)(const char *buf, Py_ssize_t len, Py_ssize_t end); static PyObject * -_bytearray_with_buffer(PyByteArrayObject *self, PyObject *sub, - Py_ssize_t start, Py_ssize_t end, _ba_bytes_op op) +_bytearray_with_buffer(PyByteArrayObject *self, _ba_bytes_op op, PyObject *sub, + Py_ssize_t start, Py_ssize_t end) { PyObject *res; @@ -1268,7 +1268,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_find); + return _bytearray_with_buffer(self, _Py_bytes_find, sub, start, end); } /*[clinic input] @@ -1284,7 +1284,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_count); + return _bytearray_with_buffer(self, _Py_bytes_count, sub, start, end); } /*[clinic input] @@ -1332,7 +1332,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_index); + return _bytearray_with_buffer(self, _Py_bytes_index, sub, start, end); } /*[clinic input] @@ -1350,7 +1350,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 _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rfind); + return _bytearray_with_buffer(self, _Py_bytes_rfind, sub, start, end); } /*[clinic input] @@ -1368,7 +1368,7 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start, Py_ssize_t end) /*[clinic end generated code: output=38e1cf66bafb08b9 input=7d198b3d6b0a62ce]*/ { - return _bytearray_with_buffer(self, sub, start, end, _Py_bytes_rindex); + return _bytearray_with_buffer(self, _Py_bytes_rindex, sub, start, end); } static int @@ -1407,7 +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 _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_startswith); + return _bytearray_with_buffer(self, _Py_bytes_startswith, subobj, start, end); } /*[clinic input] @@ -1432,7 +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 _bytearray_with_buffer(self, subobj, start, end, _Py_bytes_endswith); + return _bytearray_with_buffer(self, _Py_bytes_endswith, subobj, start, end); } /*[clinic input] From bed7f4df2ee353d22101ab33c1f6a38f7c5191d3 Mon Sep 17 00:00:00 2001 From: fatelei Date: Thu, 18 Dec 2025 22:04:46 +0800 Subject: [PATCH 45/45] feat: contains, split, rsplit using ob_exports --- Lib/test/test_bytes.py | 13 +++++++ Objects/bytearrayobject.c | 79 ++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 9a6d8a2656027d..f03a0017734211 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2065,6 +2065,11 @@ def test_search_methods_reentrancy_raises_buffererror(self): 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") @@ -2079,6 +2084,14 @@ def make_case(): 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/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 5baad43db0c9ad..4a3ca8e846de2a 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1375,12 +1375,13 @@ static int bytearray_contains(PyObject *self, PyObject *arg) { int ret = -1; - Py_buffer selfbuf; Py_BEGIN_CRITICAL_SECTION(self); - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) == 0) { - ret = _Py_bytes_contains((const char *)selfbuf.buf, selfbuf.len, arg); - PyBuffer_Release(&selfbuf); - } + 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; } @@ -1793,38 +1794,34 @@ 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]*/ +bytearray_split_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) { - PyObject *list; - Py_buffer selfbuf, vsub; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } + 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) { - list = stringlib_split_whitespace((PyObject*) self, - (const char *)selfbuf.buf, selfbuf.len, - maxsplit); - PyBuffer_Release(&selfbuf); - return list; + list = stringlib_split_whitespace((PyObject*)self, sbuf, slen, maxsplit); + goto done; } + Py_buffer vsub; if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) { - PyBuffer_Release(&selfbuf); - return NULL; + goto done; } - list = stringlib_split( - (PyObject*) self, (const char *)selfbuf.buf, selfbuf.len, - (const char *)vsub.buf, vsub.len, maxsplit - ); + list = stringlib_split((PyObject*)self, sbuf, slen, + (const char *)vsub.buf, vsub.len, maxsplit); PyBuffer_Release(&vsub); - PyBuffer_Release(&selfbuf); + +done: + self->ob_exports--; return list; } @@ -1923,34 +1920,32 @@ bytearray_rsplit_impl(PyByteArrayObject *self, PyObject *sep, Py_ssize_t maxsplit) /*[clinic end generated code: output=a55e0b5a03cb6190 input=60e9abf305128ff4]*/ { - PyObject *list; - Py_buffer selfbuf, vsub; - if (PyObject_GetBuffer((PyObject *)self, &selfbuf, PyBUF_SIMPLE) != 0) { - return NULL; - } + 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) { - list = stringlib_rsplit_whitespace((PyObject*) self, - (const char *)selfbuf.buf, selfbuf.len, - maxsplit); - PyBuffer_Release(&selfbuf); - return list; + list = stringlib_rsplit_whitespace((PyObject*)self, sbuf, slen, maxsplit); + goto done; } + Py_buffer vsub; if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) { - PyBuffer_Release(&selfbuf); - return NULL; + goto done; } - list = stringlib_rsplit( - (PyObject*) self, (const char *)selfbuf.buf, selfbuf.len, - (const char *)vsub.buf, vsub.len, maxsplit - ); + list = stringlib_rsplit((PyObject*)self, sbuf, slen, + (const char *)vsub.buf, vsub.len, maxsplit); PyBuffer_Release(&vsub); - PyBuffer_Release(&selfbuf); + +done: + self->ob_exports--; return list; }