From 0488d2199f905b7bbee10333018d75e2ecc754b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 27 Dec 2025 10:24:25 +0100 Subject: [PATCH 1/3] gh-143195: fix UAF in `{bytearray,memoryview}.hex(sep)` via re-entrant `sep.__len__` --- Lib/test/test_bytes.py | 13 +++++++++++++ Lib/test/test_memoryview.py | 17 +++++++++++++++++ ...25-12-27-10-14-26.gh-issue-143195.MNldfr.rst | 3 +++ Objects/bytearrayobject.c | 8 +++++++- 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-27-10-14-26.gh-issue-143195.MNldfr.rst diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 21be61e4fec720..44b16c7d91e996 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2092,6 +2092,19 @@ def make_case(): with self.assertRaises(BufferError): ba.rsplit(evil) + def test_hex_use_after_free(self): + # Prevent UAF in bytearray.hex(sep) with re-entrant sep.__len__. + # Regression test for https://github.com/python/cpython/issues/143195. + ba = bytearray(b'\xAA') + + class S(bytes): + def __len__(self): + ba.clear() + return 1 + + self.assertRaises(BufferError, ba.hex, S(b':')) + + class AssortedBytesTest(unittest.TestCase): # # Test various combinations of bytes and bytearray diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 1bd58eb6408833..23050072ce4ad9 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -4,6 +4,7 @@ are in test_buffer. """ +import contextlib import unittest import test.support import sys @@ -442,6 +443,22 @@ def test_issue22668(self): self.assertEqual(c.format, "H") self.assertEqual(d.format, "H") + def test_hex_use_after_free(self): + # Prevent UAF in memoryview.hex(sep) with re-entrant sep.__len__. + # Regression test for https://github.com/python/cpython/issues/143195. + ba = bytearray(b'A' * 1024) + mv = memoryview(ba) + + class S(bytes): + def __len__(self): + mv.release() + ba.clear() + return 1 + + # The following should not crash but it may not necessarily raise. + with contextlib.suppress(BufferError): + mv.hex(S(b':')) + # Variations on source objects for the buffer: bytes-like objects, then arrays # with itemsize > 1. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-27-10-14-26.gh-issue-143195.MNldfr.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-27-10-14-26.gh-issue-143195.MNldfr.rst new file mode 100644 index 00000000000000..66dc5e22f0ab23 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-27-10-14-26.gh-issue-143195.MNldfr.rst @@ -0,0 +1,3 @@ +Fix use-after-free crashes in :meth:`bytearray.hex` and :meth:`memoryview.hex` +when the separator's :meth:`~object.__len__` mutates the original object. +Patch by Bénédikt Tran. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 338c71ad38f7aa..8a454aa48a0930 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -2664,7 +2664,13 @@ bytearray_hex_impl(PyByteArrayObject *self, PyObject *sep, int bytes_per_sep) { char* argbuf = PyByteArray_AS_STRING(self); Py_ssize_t arglen = PyByteArray_GET_SIZE(self); - return _Py_strhex_with_sep(argbuf, arglen, sep, bytes_per_sep); + // Prevent 'self' from being freed if computing len(sep) mutates 'self' + // in _Py_strhex_with_sep(). + // See: https://github.com/python/cpython/issues/143195. + self->ob_exports++; + PyObject *res = _Py_strhex_with_sep(argbuf, arglen, sep, bytes_per_sep); + self->ob_exports--; + return res; } static PyObject * From 0b1553fa2f4db04cd8b6045029782943c4a34180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:31:12 +0100 Subject: [PATCH 2/3] explicitly fix `memoryview.hex` --- Lib/test/test_memoryview.py | 5 +---- Objects/memoryobject.c | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 23050072ce4ad9..51b107103f6836 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -4,7 +4,6 @@ are in test_buffer. """ -import contextlib import unittest import test.support import sys @@ -455,9 +454,7 @@ def __len__(self): ba.clear() return 1 - # The following should not crash but it may not necessarily raise. - with contextlib.suppress(BufferError): - mv.hex(S(b':')) + self.assertRaises(BufferError, mv.hex, S(b':')) # Variations on source objects for the buffer: bytes-like objects, then arrays diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index f1232f389210ea..8bd27a5ca8e749 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -2349,7 +2349,10 @@ memoryview_hex_impl(PyMemoryViewObject *self, PyObject *sep, CHECK_RELEASED(self); if (MV_C_CONTIGUOUS(self->flags)) { - return _Py_strhex_with_sep(src->buf, src->len, sep, bytes_per_sep); + self->exports++; + PyObject *ret = _Py_strhex_with_sep(src->buf, src->len, sep, bytes_per_sep); + self->exports--; + return ret; } PyBytesWriter *writer = PyBytesWriter_Create(src->len); From f2d9558cb80168423ba90aa074b75c2f8658a0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:32:11 +0100 Subject: [PATCH 3/3] add comment --- Objects/memoryobject.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index 8bd27a5ca8e749..2fd1d784b92ec8 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -2349,6 +2349,9 @@ memoryview_hex_impl(PyMemoryViewObject *self, PyObject *sep, CHECK_RELEASED(self); if (MV_C_CONTIGUOUS(self->flags)) { + // Prevent 'self' from being freed if computing len(sep) mutates 'self' + // in _Py_strhex_with_sep(). + // See: https://github.com/python/cpython/issues/143195. self->exports++; PyObject *ret = _Py_strhex_with_sep(src->buf, src->len, sep, bytes_per_sep); self->exports--;