Skip to content

gh-158583: Fix uninitialized memory read in bytes.fromhex() - #158584

Merged
vstinner merged 6 commits into
python:mainfrom
vstinner:bytes_fromhex
Oct 3, 2026
Merged

vstinner merged 6 commits into
python:mainfrom
vstinner:bytes_fromhex

Conversation

@vstinner

@vstinner vstinner commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

@vstinner vstinner added skip news needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels Oct 1, 2026
@vstinner

vstinner commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

cc @StanFromIreland

bytes.fromhex() was modified in Python 3.14 to accept more types; Python 3.13 only accepts str. So the undefined behavior was introduced in Python 3.14. So far, before me running Valgrind, nobody reported the issue. So I'm not sure if it's needed to add a Changelog entry.

The modified code is tested by BytesTest.test_fromhex() and ByteArrayTest.test_fromhex() of test_bytes. For example, running the test in Valgrind reports the issue.

@vstinner

vstinner commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

In Python 3.13, reading str[hexlen] is fine because str object always allocate a trailing null character: https://docs.python.org/dev/c-api/unicode.html.

The internal buffer always includes an extra trailing null character for compatibility with null terminated C strings.

@vstinner

vstinner commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

I'm also curious why only Valgrind reports the issue. In the CI, we are running the Python test suite on Python built with Address Sanitizer. This build doesn't detect usage of uninitialized memory?

@StanFromIreland
StanFromIreland self-requested a review October 2, 2026 07:08
@encukou

encukou commented Oct 2, 2026

Copy link
Copy Markdown
Member

Apparently, when array.array() created from bytes, it's overallocated. One created from a list gives me a crash under ASAN; could you add a test with it?

bytes.fromhex(array.array('B', [ord('2')] * 5))

@StanFromIreland

StanFromIreland commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

In the CI, we are running the Python test suite on Python built with Address Sanitizer. This build doesn't detect usage of uninitialized memory?

We only run with ASan and UBSan, however neither track uninitialised memory. We'd have to run with MSan.

Edit: I went off on a little tangent, but I wrote a patch to add MSan to the CI: #158625

Comment thread Objects/bytesobject.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dead logic now.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh well spotted! I removed the dead code.

@StanFromIreland

Copy link
Copy Markdown
Member

Also, I think we should add an entry, it's a bug fix after all.

@vstinner

vstinner commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

Apparently, when array.array() created from bytes, it's overallocated. One created from a list gives me a crash under ASAN

Oh, implementation details can be very surprising sometimes :-D

could you add a test with it?

Sure, I added two tests to test the two modified code paths, using an array created from a list.

I tested manually that the two tests are detected by Valgrind without the fix:

test_fromhex_ub (test.test_bytes.BytesTest.test_fromhex_ub) ... 

==2131686== Invalid read of size 1
==2131686==    at 0x497DA4: _PyBytes_FromHex (bytesobject.c:2705)
==2131686==    by 0x497E62: bytes_fromhex_impl (bytesobject.c:2635)
==2131686==    by 0x497EA7: bytes_fromhex (bytesobject.c.h:1261)
==2131686==    by 0x4F51FC: cfunction_vectorcall_O (methodobject.c:535)
==2131686==    by 0x49BFED: _PyObject_VectorcallTstate (pycore_call.h:149)
==2131686==    by 0x49C0B5: PyObject_Vectorcall (call.c:327)
==2131686==    by 0x5B1597: _Py_VectorCallInstrumentation_StackRefSteal (ceval.c:770)
==2131686==    by 0x5B8723: _PyEval_EvalFrameDefault (generated_cases.c.h:1906)
==2131686==    by 0x5D03B6: _PyEval_EvalFrame (pycore_ceval.h:122)
==2131686==    by 0x5D0581: _PyEval_Vector (ceval.c:2176)
==2131686==    by 0x49BCD9: _PyFunction_Vectorcall (call.c:413)
==2131686==    by 0x49BFED: _PyObject_VectorcallTstate (pycore_call.h:149)
==2131686==  Address 0x14630416 is 0 bytes after a block of size 6 alloc'd
==2131686==    at 0x4841AE6: malloc (vg_replace_malloc.c:447)
==2131686==    by 0x4FFD6A: _PyMem_RawMalloc (obmalloc.c:66)
==2131686==    by 0x514579: PyMem_Malloc (obmalloc.c:1257)
==2131686==    by 0x158A7FBB: newarrayobject_untracked (arraymodule.c:793)
==2131686==    by 0x158A9E49: array_new (arraymodule.c:3003)
==2131686==    by 0x5309ED: type_call (typeobject.c:2442)
==2131686==    by 0x49BE61: _PyObject_MakeTpCall (call.c:242)
==2131686==    by 0x49C067: _PyObject_VectorcallTstate (pycore_call.h:147)
==2131686==    by 0x49C0B5: PyObject_Vectorcall (call.c:327)
==2131686==    by 0x5B1597: _Py_VectorCallInstrumentation_StackRefSteal (ceval.c:770)
==2131686==    by 0x5B8723: _PyEval_EvalFrameDefault (generated_cases.c.h:1906)
==2131686==    by 0x5D03B6: _PyEval_EvalFrame (pycore_ceval.h:122)
==2131686== 
==2131686== Invalid read of size 1
==2131686==    at 0x497D5D: _PyBytes_FromHex (bytesobject.c:2717)
==2131686==    by 0x497E62: bytes_fromhex_impl (bytesobject.c:2635)
==2131686==    by 0x497EA7: bytes_fromhex (bytesobject.c.h:1261)
==2131686==    by 0x4F51FC: cfunction_vectorcall_O (methodobject.c:535)
==2131686==    by 0x49BFED: _PyObject_VectorcallTstate (pycore_call.h:149)
==2131686==    by 0x49C0B5: PyObject_Vectorcall (call.c:327)
==2131686==    by 0x5B1597: _Py_VectorCallInstrumentation_StackRefSteal (ceval.c:770)
==2131686==    by 0x5B8723: _PyEval_EvalFrameDefault (generated_cases.c.h:1906)
==2131686==    by 0x5D03B6: _PyEval_EvalFrame (pycore_ceval.h:122)
==2131686==    by 0x5D0581: _PyEval_Vector (ceval.c:2176)
==2131686==    by 0x49BCD9: _PyFunction_Vectorcall (call.c:413)
==2131686==    by 0x49BFED: _PyObject_VectorcallTstate (pycore_call.h:149)
==2131686==  Address 0x14728e35 is 0 bytes after a block of size 5 alloc'd
==2131686==    at 0x4841AE6: malloc (vg_replace_malloc.c:447)
==2131686==    by 0x4FFD6A: _PyMem_RawMalloc (obmalloc.c:66)
==2131686==    by 0x514579: PyMem_Malloc (obmalloc.c:1257)
==2131686==    by 0x158A7FBB: newarrayobject_untracked (arraymodule.c:793)
==2131686==    by 0x158A9E49: array_new (arraymodule.c:3003)
==2131686==    by 0x5309ED: type_call (typeobject.c:2442)
==2131686==    by 0x49BE61: _PyObject_MakeTpCall (call.c:242)
==2131686==    by 0x49C067: _PyObject_VectorcallTstate (pycore_call.h:147)
==2131686==    by 0x49C0B5: PyObject_Vectorcall (call.c:327)
==2131686==    by 0x5B1597: _Py_VectorCallInstrumentation_StackRefSteal (ceval.c:770)
==2131686==    by 0x5B8723: _PyEval_EvalFrameDefault (generated_cases.c.h:1906)
==2131686==    by 0x5D03B6: _PyEval_EvalFrame (pycore_ceval.h:122)
==2131686== 

@vstinner

vstinner commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

@StanFromIreland:

Also, I think we should add an entry, it's a bug fix after all.

Ok, I added a Changelog entry. I also removed dead code.

@StanFromIreland StanFromIreland left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just two little nits.

Comment thread Lib/test/test_bytes.py Outdated
Comment thread Objects/bytesobject.c Outdated
@vstinner
vstinner enabled auto-merge (squash) October 3, 2026 23:22
@vstinner
vstinner merged commit 9d22a53 into python:main Oct 3, 2026
54 checks passed
@vstinner
vstinner deleted the bytes_fromhex branch October 3, 2026 23:51
@miss-islington-app

Copy link
Copy Markdown

Thanks @vstinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.14, 3.15.
🐍🍒⛏🤖

@bedevere-app

bedevere-app Bot commented Oct 3, 2026

Copy link
Copy Markdown

GH-158691 is a backport of this pull request to the 3.15 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label Oct 3, 2026
@bedevere-app

bedevere-app Bot commented Oct 3, 2026

Copy link
Copy Markdown

GH-158692 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Oct 3, 2026
@vstinner

vstinner commented Oct 4, 2026

Copy link
Copy Markdown
Member Author

Merged. Thanks for reviews!

vstinner added a commit that referenced this pull request Oct 4, 2026
…H-158584) (#158692)

gh-158583: Fix uninitialized memory read in bytes.fromhex() (GH-158584)
(cherry picked from commit 9d22a53)

Co-authored-by: Victor Stinner <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants