Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34.2k
gh-145301: Fix double-free in hashlib and hmac module initialization#145321
gh-145301: Fix double-free in hashlib and hmac module initialization#145321krylosov-aa wants to merge 1 commit intopython:mainfrom
Conversation
python-cla-botbot commentedFeb 27, 2026 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
| Fix a double-free bug in :mod:`_hashlib` module initialization when | ||
| ``_Py_hashtable_set()`` fails while adding an algorithm alias to the hash | ||
| table after the primary name was already added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Too verbose:
| Fix a double-free bug in:mod:`_hashlib` module initialization when | |
| ``_Py_hashtable_set()`` fails while adding an algorithm alias to the hash | |
| table after the primary name was already added | |
| :mod:`hashlib`: fix a crash when the C extension module initialization fails. |
picnixz left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Please add the test repro as a regression test.
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
picnixz commentedFeb 27, 2026
Can you also check whether |
krylosov-aa commentedFeb 27, 2026
I checked the HMAC implementation in |
picnixz commentedFeb 27, 2026
I'm sorry, I meant the |
krylosov-aa commentedFeb 27, 2026 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Yes, I can suggest the following solution: #define Py_HMAC_HINFO_LINK(KEY) \ do { \ int rc = py_hmac_hinfo_ht_add(table, KEY, value); \ if (rc < 0) { \+ if (value->refcnt == 0) { \ PyMem_Free(value); \+ } \ goto error; \ } \ else if (rc == 1) { \ value->refcnt++; \ } \ } while (0) Py_HMAC_HINFO_LINK(e->name); Py_HMAC_HINFO_LINK(e->hashlib_name);#undef Py_HMAC_HINFO_LINK Should I include this fix in the same PR or create a separate one? |
picnixz commentedFeb 27, 2026
Yes, please do so, and put a comment as well. |
krylosov-aa commentedFeb 27, 2026 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I investigated adding a regression test using The Do you have any suggestions for how to reliably test this? |
Modules/hmacmodule.c Outdated
| int rc = py_hmac_hinfo_ht_add(table, KEY, value); \ | ||
| if (rc < 0) { \ | ||
| PyMem_Free(value); \ | ||
| /* entry may already be in ht, will be freed by _Py_hashtable_destroy() */ \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| /* entry may already be in ht, will befreed by _Py_hashtable_destroy() */ \ | |
| /*entrymayalreadybeinht,willbe\ | |
| *freedby_Py_hashtable_destroy()*/ \ |
- correctly align the
\. You can augment the column if it does not exceed 80 chars. If it does, properly wrap the comment. Tia.
picnixz commentedFeb 27, 2026
You have an ASASN reproducer, why not use it? |
Lib/test/test_hashlib.py Outdated
| import tempfile | ||
| import threading | ||
| import unittest | ||
| import subprocess |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Put the imports alphabetically sorted.
Lib/test/test_hashlib.py Outdated
| try: | ||
| import _testcapi | ||
| if not hasattr(_testcapi, 'set_nomemory'): | ||
| self.skipTest('requires _testcapi.set_nomemory') | ||
| except ImportError: | ||
| self.skipTest('requires _testcapi') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Don't use this, useimport_helper for that.
Lib/test/test_hashlib.py Outdated
| if '_hashlib' in sys.modules: | ||
| del sys.modules['_hashlib'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't think this is necessary.
Lib/test/test_hashlib.py Outdated
| _testcapi.remove_mem_hooks() | ||
| """) | ||
| rc = subprocess.call( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Useassert_python_ok instead.
| @@ -0,0 +1 @@ | |||
| Fix a crash when :mod:`hashlib` or :mod:`hmac` C extension module initialization fails. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Can you create two NEWS entries, one for hashlib and for hmac please with the same sentence:
:mod:`<name>`: fix a crash when the initialization of the underlying C extension module fails.
Modules/hmacmodule.c Outdated
| /* entry may already be in ht, will be freed by \ | ||
| _Py_hashtable_destroy() */ \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Just put re-align the\ directly and put the comment on one line (you can remove the "will be freed by _Py_hashtable_destroy() and mention that it will be freed "upon exit").
Lib/test/test_hashlib.py Outdated
| self.skipTest('requires _testcapi') | ||
| code = textwrap.dedent(""" | ||
| import sys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
sys is no more necessary
Lib/test/test_hashlib.py Outdated
| requires_sha3 = unittest.skipUnless(_sha3, 'requires _sha3') | ||
| _testcapi = import_helper.import_module('_testcapi') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Don't do it at at the module level, it will skip the entire test module. Do it inside the test directly.
Lib/test/test_hashlib.py Outdated
| hash_type.value = False | ||
| @unittest.skipUnless(HASH is not None, 'need _hashlib') | ||
| @unittest.skipUnless(hasattr(_testcapi, 'set_nomemory'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
When is _testcapi lacking this function?
krylosov-aa commentedFeb 28, 2026
I think that testing using I believe this is whythe macOS Intel test crashes. I don't know how to write a regression test for this scenario. I would suggest removing the test, since the fix is simple, comments have been left, and a regression seems unlikely to happen. |
Lib/test/test_hashlib.py Outdated
| import_helper.import_module('_testcapi') | ||
| code = textwrap.dedent(""" | ||
| import _testcapi | ||
| _testcapi.set_nomemory(40, 41) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yes, this seems flaky. It will change depending on the python's version and a platform.
Lib/test/test_hashlib.py Outdated
| from test.support import _4G, bigmemtest | ||
| from test.support import hashlib_helper | ||
| from test.support.import_helper importimport_fresh_module | ||
| from test.support importimport_helper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
nit: can we please revert this change if you decide to remove the test?
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
picnixz commentedMar 2, 2026
Oo for removig the test. |
krylosov-aa commentedMar 2, 2026
I have made the requested changes; please review again |
Thanks for making the requested changes! @picnixz: please review the changes made to this pull request. |
Uh oh!
There was an error while loading.Please reload this page.
Both
hashlibandhmacmodules have similar double-free bugs in their hashtable initialization code.Problem
hashlibIn
py_hashentry_table_new(), when_Py_hashtable_set()fails while adding an entry by itspy_aliaskey (after successfully adding it bypy_name), the code callsPyMem_Free(entry)beforegoto error. The error handler then calls_Py_hashtable_destroy()which frees the same entry again viapy_hashentry_t_destroy_value().hmacIn
py_hmac_hinfo_ht_new(), thePy_HMAC_HINFO_LINKmacro has the same issue. Whenpy_hmac_hinfo_ht_add()fails forhashlib_namekey (after successfully addingname), the code callsPyMem_Free(value)while the entry is already in the hashtable withrefcnt=1.Solution
hashlibRemove the manual
PyMem_Free(entry)call since the entry is already tracked by the hashtable under thepy_namekey and will be properly freed by_Py_hashtable_destroy().hmacAdd a
refcntcheck before freeing: only callPyMem_Free(value)ifvalue->refcnt == 0(meaning it was never added to the table). Ifrefcnt > 0, the entry is already in the table and will be freed by_Py_hashtable_destroy().Tests
No test added. I couldn't find a practical way to trigger this bug since it requires
_Py_hashtable_set()to fail during module initialization. This type of bug is best caught by sanitizers rather than unit tests.The fix can be verified by running the reproducer provided by the issue author:
Build with ASan:
Reproducer script (
uaf_asan.py):Before fix:
After fix: No crash.
_hashlib: Use-After-Free + Double Free in_hashopenssl.cpy_hashentry_table_new()#145301