From 157f39753b5b8007518f7020c96d6c4ef384e32d Mon Sep 17 00:00:00 2001 From: krylosov-aa Date: Fri, 27 Feb 2026 19:02:19 +0300 Subject: [PATCH 1/6] gh-145301: Fix double-free in _hashlib module initialization --- .../Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst | 3 +++ Modules/_hashopenssl.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst diff --git a/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst new file mode 100644 index 00000000000000..55ba7ba00ef308 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst @@ -0,0 +1,3 @@ +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 diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 77832a768e0cbc..e19eb1abcf2c4d 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -268,7 +268,7 @@ py_hashentry_table_new(void) { if (h->py_alias != NULL) { if (_Py_hashtable_set(ht, (const void*)entry->py_alias, (void*)entry) < 0) { - PyMem_Free(entry); + /* entry is already in ht, will be freed by _Py_hashtable_destroy() */ goto error; } entry->refcnt++; From 799cd4899529278ba286682e749dfe227fed0169 Mon Sep 17 00:00:00 2001 From: krylosov-aa Date: Fri, 27 Feb 2026 21:06:09 +0300 Subject: [PATCH 2/6] gh-145301: Fix double-free in hmac module initialization --- .../Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst | 4 +--- Modules/hmacmodule.c | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst index 55ba7ba00ef308..35185fa98e4b47 100644 --- a/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst +++ b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst @@ -1,3 +1 @@ -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 +Fix a crash when :mod:`hashlib` or :mod:`hmac` C extension module initialization fails. \ No newline at end of file diff --git a/Modules/hmacmodule.c b/Modules/hmacmodule.c index f074f24807703c..b78f6f2ddc42b1 100644 --- a/Modules/hmacmodule.c +++ b/Modules/hmacmodule.c @@ -1457,7 +1457,10 @@ py_hmac_hinfo_ht_new(void) do { \ 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() */ \ + if (value->refcnt == 0) { \ + PyMem_Free(value); \ + } \ goto error; \ } \ else if (rc == 1) { \ From 6b2f0d38d5d307453525b2db692bc38c26d734f9 Mon Sep 17 00:00:00 2001 From: krylosov-aa Date: Fri, 27 Feb 2026 21:20:47 +0300 Subject: [PATCH 3/6] gh-145301: Fix double-free in hmac module initialization --- .../next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst index 35185fa98e4b47..18f3b101c49a18 100644 --- a/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst +++ b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst @@ -1 +1 @@ -Fix a crash when :mod:`hashlib` or :mod:`hmac` C extension module initialization fails. \ No newline at end of file +Fix a crash when :mod:`hashlib` or :mod:`hmac` C extension module initialization fails. From dc3e19cb100fc5b1e1f2ed357bc9ce7779126d0f Mon Sep 17 00:00:00 2001 From: krylosov-aa Date: Sat, 28 Feb 2026 00:18:07 +0300 Subject: [PATCH 4/6] gh-145301: Fix double-free in hmac module initialization --- Lib/test/test_hashlib.py | 39 +++++++++++++++++++++++++++++++++++++++ Modules/hmacmodule.c | 3 ++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index f0e2d527af2615..b842b5965410a4 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -18,6 +18,8 @@ import tempfile import threading import unittest +import subprocess +import textwrap from test import support from test.support import _4G, bigmemtest from test.support import hashlib_helper @@ -1201,6 +1203,43 @@ def test_readonly_types(self): with self.assertRaisesRegex(TypeError, "immutable type"): hash_type.value = False + @unittest.skipUnless(HASH is not None, 'need _hashlib') + def test_hashlib_init_memory_error_no_df(self): + """gh-145301 regression test.""" + + try: + import _testcapi + if not hasattr(_testcapi, 'set_nomemory'): + self.skipTest('requires _testcapi.set_nomemory') + except ImportError: + self.skipTest('requires _testcapi') + + code = textwrap.dedent(""" + import sys + import _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() + """) + + rc = subprocess.call( + [sys.executable, '-c', code], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + # rc < 0 means crash (signal on Unix), which indicates double-free + # rc >= 0 means normal exit (even with MemoryError), which is expected + self.assertGreaterEqual(rc, 0, + "Process crashed - Loss double-free in _hashlib") + class KDFTests(unittest.TestCase): diff --git a/Modules/hmacmodule.c b/Modules/hmacmodule.c index b78f6f2ddc42b1..95bf31c15394fb 100644 --- a/Modules/hmacmodule.c +++ b/Modules/hmacmodule.c @@ -1457,7 +1457,8 @@ py_hmac_hinfo_ht_new(void) do { \ int rc = py_hmac_hinfo_ht_add(table, KEY, value); \ if (rc < 0) { \ - /* entry may already be in ht, will be freed by _Py_hashtable_destroy() */ \ + /* entry may already be in ht, will be freed by \ + _Py_hashtable_destroy() */ \ if (value->refcnt == 0) { \ PyMem_Free(value); \ } \ From d791cd9f8339421c17121805942ed5abb3fea080 Mon Sep 17 00:00:00 2001 From: krylosov-aa Date: Sat, 28 Feb 2026 01:02:10 +0300 Subject: [PATCH 5/6] gh-145301: Fix double-free in hmac module initialization --- Lib/test/test_hashlib.py | 34 +++++-------------- ...-02-27-19-00-26.gh-issue-145301.2Wih4b.rst | 3 +- ...-02-28-00-55-00.gh-issue-145301.Lk2bRl.rst | 2 ++ Modules/hmacmodule.c | 27 +++++++-------- 4 files changed, 25 insertions(+), 41 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-02-28-00-55-00.gh-issue-145301.Lk2bRl.rst diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index b842b5965410a4..04c39a12de07d8 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -16,16 +16,16 @@ import sys import sysconfig import tempfile +import textwrap import threading import unittest -import subprocess -import textwrap from test import support from test.support import _4G, bigmemtest from test.support import hashlib_helper -from test.support.import_helper import import_fresh_module +from test.support import import_helper from test.support import requires_resource from test.support import threading_helper +from test.support.script_helper import assert_python_ok from http.client import HTTPException @@ -39,7 +39,7 @@ builtin_hashes = set(map(str.strip, builtin_hash_names)) # Public 'hashlib' module with OpenSSL backend for PBKDF2. -openssl_hashlib = import_fresh_module('hashlib', fresh=['_hashlib']) +openssl_hashlib = import_helper.import_fresh_module('hashlib', fresh=['_hashlib']) try: import _hashlib @@ -68,6 +68,7 @@ def get_fips_mode(): requires_sha3 = unittest.skipUnless(_sha3, 'requires _sha3') +_testcapi = import_helper.import_module('_testcapi') def hexstr(s): assert isinstance(s, bytes), repr(s) @@ -1204,23 +1205,13 @@ def test_readonly_types(self): hash_type.value = False @unittest.skipUnless(HASH is not None, 'need _hashlib') + @unittest.skipUnless(hasattr(_testcapi, 'set_nomemory'), + 'need _testcapi.set_nomemory()') def test_hashlib_init_memory_error_no_df(self): """gh-145301 regression test.""" - - try: - import _testcapi - if not hasattr(_testcapi, 'set_nomemory'): - self.skipTest('requires _testcapi.set_nomemory') - except ImportError: - self.skipTest('requires _testcapi') - code = textwrap.dedent(""" import sys import _testcapi - - if '_hashlib' in sys.modules: - del sys.modules['_hashlib'] - _testcapi.set_nomemory(40, 41) try: import _hashlib @@ -1229,16 +1220,7 @@ def test_hashlib_init_memory_error_no_df(self): finally: _testcapi.remove_mem_hooks() """) - - rc = subprocess.call( - [sys.executable, '-c', code], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - # rc < 0 means crash (signal on Unix), which indicates double-free - # rc >= 0 means normal exit (even with MemoryError), which is expected - self.assertGreaterEqual(rc, 0, - "Process crashed - Loss double-free in _hashlib") + assert_python_ok('-c', code) class KDFTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst index 18f3b101c49a18..7aeb6a1145ab4c 100644 --- a/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst +++ b/Misc/NEWS.d/next/Library/2026-02-27-19-00-26.gh-issue-145301.2Wih4b.rst @@ -1 +1,2 @@ -Fix a crash when :mod:`hashlib` or :mod:`hmac` C extension module initialization fails. +:mod:`hashlib`: fix a crash when the initialization of the underlying C +extension module fails. diff --git a/Misc/NEWS.d/next/Library/2026-02-28-00-55-00.gh-issue-145301.Lk2bRl.rst b/Misc/NEWS.d/next/Library/2026-02-28-00-55-00.gh-issue-145301.Lk2bRl.rst new file mode 100644 index 00000000000000..436ff316b2c327 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-28-00-55-00.gh-issue-145301.Lk2bRl.rst @@ -0,0 +1,2 @@ +:mod:`hmac`: fix a crash when the initialization of the underlying C +extension module fails. diff --git a/Modules/hmacmodule.c b/Modules/hmacmodule.c index 95bf31c15394fb..a61b03c533ba3b 100644 --- a/Modules/hmacmodule.c +++ b/Modules/hmacmodule.c @@ -1453,20 +1453,19 @@ py_hmac_hinfo_ht_new(void) assert(value->display_name == NULL); value->refcnt = 0; -#define Py_HMAC_HINFO_LINK(KEY) \ - do { \ - int rc = py_hmac_hinfo_ht_add(table, KEY, value); \ - if (rc < 0) { \ - /* entry may already be in ht, will be freed by \ - _Py_hashtable_destroy() */ \ - if (value->refcnt == 0) { \ - PyMem_Free(value); \ - } \ - goto error; \ - } \ - else if (rc == 1) { \ - value->refcnt++; \ - } \ +#define Py_HMAC_HINFO_LINK(KEY) \ + do { \ + int rc = py_hmac_hinfo_ht_add(table, KEY, value); \ + if (rc < 0) { \ + /* entry may already be in ht, freed upon exit */ \ + 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); From 154eda5b3128085d9fea1885d0056cd2cacaeced Mon Sep 17 00:00:00 2001 From: krylosov-aa Date: Sat, 28 Feb 2026 01:13:35 +0300 Subject: [PATCH 6/6] gh-145301: Fix double-free in hmac module initialization --- Lib/test/test_hashlib.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 04c39a12de07d8..b4861366b11162 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -68,7 +68,6 @@ def get_fips_mode(): requires_sha3 = unittest.skipUnless(_sha3, 'requires _sha3') -_testcapi = import_helper.import_module('_testcapi') def hexstr(s): assert isinstance(s, bytes), repr(s) @@ -1205,12 +1204,10 @@ def test_readonly_types(self): hash_type.value = False @unittest.skipUnless(HASH is not None, 'need _hashlib') - @unittest.skipUnless(hasattr(_testcapi, 'set_nomemory'), - 'need _testcapi.set_nomemory()') def test_hashlib_init_memory_error_no_df(self): """gh-145301 regression test.""" + import_helper.import_module('_testcapi') code = textwrap.dedent(""" - import sys import _testcapi _testcapi.set_nomemory(40, 41) try: