Skip to content

_hashlib: Use-After-Free + Double Free in _hashopenssl.cpy_hashentry_table_new() #145301

Description

@raminfp

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 (constpy_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 itif (_Py_hashtable_set(ht, (constvoid*)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, (constvoid*)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++;
}
}
returnht;
error:
_Py_hashtable_destroy(ht); // [line 280] destroy callback called on already-freed entryreturnNULL;

Build

mkdir build-asan &&cd build-asan
../configure --with-pydebug --with-address-sanitizer --without-pymalloc
make -j$(nproc)
importsubprocessimportsyscode= (
"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
)
ifresult.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, _testcapiif '_hashlib' in sys.modules: del sys.modules['_hashlib']_testcapi.set_nomemory(40, 41)try: import _hashlibexcept (MemoryError, ImportError): passfinally: _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+

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13bugs and security fixes3.14bugs and security fixes3.15pre-release feature fixes, bugs and security fixeseasyextension-modulesC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions