Crash report
What happened?
In py_hashentry_table_new(), when _Py_hashtable_set() fails for an alias key (line 270), entry is explicitly freed via PyMem_Free(entry) at line 271. However, this same entry was already successfully inserted into the hashtable under py_name at line 263. The goto error path then calls _Py_hashtable_destroy(ht) (line 280), which invokes the destroy callback py_hashentry_t_destroy_value() on the already-freed entry.
// Modules/_hashopenssl.c - py_hashentry_table_new() for (const py_hashentry_t *h = py_hashes; h->py_name != NULL; h++) { py_hashentry_t *entry = (py_hashentry_t *)PyMem_Malloc(sizeof(py_hashentry_t)); if (entry == NULL) { goto error; } memcpy(entry, h, sizeof(py_hashentry_t)); // [line 263] entry inserted into hashtable under py_name - hashtable now owns it if (_Py_hashtable_set(ht, (const void*)entry->py_name, (void*)entry) < 0) { PyMem_Free(entry); goto error; } entry->refcnt = 1; if (h->py_alias != NULL) { // [line 270] second insert fails (e.g. OOM) if (_Py_hashtable_set(ht, (const void*)entry->py_alias, (void*)entry) < 0) { PyMem_Free(entry); // [line 271] BUG: entry freed, but still in hashtable under py_name goto error; // [line 272] jumps to error path } entry->refcnt++; } } return ht; error: _Py_hashtable_destroy(ht); // [line 280] destroy callback called on already-freed entry return NULL;
Build
mkdir build-asan && cd build-asan ../configure --with-pydebug --with-address-sanitizer --without-pymalloc make -j$(nproc)
import subprocess import sys code = ( "import sys, _testcapi\n" "if '_hashlib' in sys.modules:\n" " del sys.modules['_hashlib']\n" "_testcapi.set_nomemory(40, 41)\n" "try:\n" " import _hashlib\n" "except (MemoryError, ImportError):\n" " pass\n" "finally:\n" " _testcapi.remove_mem_hooks()\n" ) result = subprocess.run( [sys.executable, '-c', code], capture_output=True, text=True, timeout=10 ) if result.returncode != 0: print(f"[*] CRASH confirmed (rc={result.returncode})") print(f"[*] {result.stderr.strip().split(chr(10))[-1]}") else: print("[*] No crash (try different start values)")
$ ./build-asan/python uaf_asan.py [*] CRASH confirmed (rc=-6) [*] python: ../Include/internal/pycore_stackref.h:554: PyStackRef_FromPyObjectSteal: Assertion `obj != NULL' failed.
GDB backtrace
$ gdb -batch -ex run -ex bt --args ./build-asan/python -c " import sys, _testcapi if '_hashlib' in sys.modules: del sys.modules['_hashlib'] _testcapi.set_nomemory(40, 41) try: import _hashlib except (MemoryError, ImportError): pass finally: _testcapi.remove_mem_hooks() "
python: ../Include/internal/pycore_stackref.h:554: PyStackRef_FromPyObjectSteal: Assertion `obj != NULL' failed.
Program received signal SIGABRT, Aborted.
#0 __pthread_kill_implementation at ./nptl/pthread_kill.c:44
#1 __pthread_kill_internal at ./nptl/pthread_kill.c:78
#2 __GI___pthread_kill at ./nptl/pthread_kill.c:89
#3 __GI_raise at ../sysdeps/posix/raise.c:26
#4 __GI_abort at ./stdlib/abort.c:79
#5 __assert_fail_base โ Assertion `obj != NULL' failed.
#6 __assert_fail at ./assert/assert.c:105
#7 PyStackRef_FromPyObjectSteal at ../Include/internal/pycore_stackref.h:554
#8 _PyEval_EvalFrameDefault at ../Python/generated_cases.c.h:292
...
#15 import_find_and_load โ importing _hashlib
...
#19 _PyEval_EvalFrameDefault at ../Python/generated_cases.c.h:6424
The assertion fires because memory corruption during _hashlib module init (caused by the UAF/double-free) propagates a NULL into the eval loop.
Suggested Fix
Remove PyMem_Free(entry) at line 271. The entry is already owned by the hashtable (under py_name with refcnt=1), so _Py_hashtable_destroy() in the error path will correctly clean it up via the destroy callback.
if (h->py_alias != NULL) {
if (_Py_hashtable_set(ht, (const void*)entry->py_alias, (void*)entry) < 0) {
- PyMem_Free(entry);
goto error;
}
entry->refcnt++;
}CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.15.0a6+