From 1d3eab6df611e8a21f7aa1767ac2ae548a901226 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 Oct 2026 00:30:12 +0200 Subject: [PATCH 1/6] gh-158583: Fix uninitialized memory read in bytes.fromhex() --- Objects/bytesobject.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 7e677108983f0fd..9cc8005b9fa4757 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2685,9 +2685,10 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray) if (Py_ISSPACE(*str)) { do { str++; + if (str >= end) { + goto done; + } } while (Py_ISSPACE(*str)); - if (str >= end) - break; } top = _PyLong_DigitValue[*str]; @@ -2696,6 +2697,9 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray) goto error; } str++; + if (str >= end) { + break; + } bot = _PyLong_DigitValue[*str]; if (bot >= 16) { @@ -2712,6 +2716,7 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray) *buf++ = (unsigned char)((top << 4) + bot); } + done: if (view.obj != NULL) { PyBuffer_Release(&view); } From babf5ffd2605461eb5ea00d6792460adce277e57 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 Oct 2026 00:46:11 +0200 Subject: [PATCH 2/6] Fix --- Objects/bytesobject.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 9cc8005b9fa4757..f5f7785dd01eaa4 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2696,9 +2696,11 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray) invalid_char = str - start; goto error; } + str++; if (str >= end) { - break; + invalid_char = -1; + goto error; } bot = _PyLong_DigitValue[*str]; From d1caff7abfe6845880c52fd087153a13cc9d06ff Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 3 Oct 2026 16:03:49 +0200 Subject: [PATCH 3/6] Add tests --- Lib/test/test_bytes.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index b55863256cc37c3..126c086f6a73e81 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -517,6 +517,15 @@ def test_fromhex(self): self.type2test.fromhex(data) self.assertIn('at position %s' % pos, str(cm.exception)) + # gh-158583: Check for out of bounds reads (uninitialized bytes). + # Create an array from a list to not overallocate. + a = array.array('B', list(b'1234 ')) # Py_ISSPACE() loop + self.assertEqual(self.type2test.fromhex(a), b'\x12\x34') + + a = array.array('B', list(b'12345')) # Missing second digit + with self.assertRaises(ValueError) as cm: + self.type2test.fromhex(a) + def test_hex(self): self.assertRaises(TypeError, self.type2test.hex) self.assertRaises(TypeError, self.type2test.hex, 1) From 583809cc6d06e168de8ac6b9b312ffda6a7c8f6a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 3 Oct 2026 16:13:18 +0200 Subject: [PATCH 4/6] Remove dead code --- Objects/bytesobject.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index f5f7785dd01eaa4..678e630956e34ac 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2706,11 +2706,7 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray) bot = _PyLong_DigitValue[*str]; if (bot >= 16) { /* Check if we had a second digit */ - if (str >= end){ - invalid_char = -1; - } else { - invalid_char = str - start; - } + invalid_char = str - start; goto error; } str++; From dc6aad92bb0de403d7f9945b528146a87a83ab98 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 3 Oct 2026 16:14:28 +0200 Subject: [PATCH 5/6] Add NEWS entry --- .../2026-10-03-16-14-26.gh-issue-158583.7EUMvS.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-16-14-26.gh-issue-158583.7EUMvS.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-16-14-26.gh-issue-158583.7EUMvS.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-16-14-26.gh-issue-158583.7EUMvS.rst new file mode 100644 index 000000000000000..c94fcc58add88c5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-16-14-26.gh-issue-158583.7EUMvS.rst @@ -0,0 +1,2 @@ +:meth:`bytes.fromhex` and :meth:`bytearray.fromhex`: Fix uninitialized +memory read. Patch by Victor Stinner. From aaf8f23fe2750cd237132628ac5a23be1b2392f7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 4 Oct 2026 01:18:03 +0200 Subject: [PATCH 6/6] Address Stan's review --- Lib/test/test_bytes.py | 2 +- Objects/bytesobject.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 126c086f6a73e81..115e1cf5821627e 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -523,7 +523,7 @@ def test_fromhex(self): self.assertEqual(self.type2test.fromhex(a), b'\x12\x34') a = array.array('B', list(b'12345')) # Missing second digit - with self.assertRaises(ValueError) as cm: + with self.assertRaises(ValueError): self.type2test.fromhex(a) def test_hex(self): diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 678e630956e34ac..8395e05a8f86d23 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2705,7 +2705,6 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray) bot = _PyLong_DigitValue[*str]; if (bot >= 16) { - /* Check if we had a second digit */ invalid_char = str - start; goto error; }