Repository navigation
Segfault from _PyDict_NewKeysForClass under memory pressure #153182
Description
Crash report
What happened?
import _testcapi
def make_class():
class C:
def f(self):
self.x = 1
return C
for start in range(100):
_testcapi.set_nomemory(start, start + 1)
try:
x = make_class()
except MemoryError:
passfish: Job 1, './python.exe staticattrs.py' terminated by signal SIGSEGV (Address boundary error)
If an allocation failure happens when defining a class that has a non-empty __static_attributes__, then you can get a null pointer deref.
Root cause
Early during the class build process, __static_attributes__ is correctly populated with the list of detected static attributes.
Then later during PyType_Ready(): type_ready_managed_dict is called to make the shared key cache.
This calls _PyDict_NewKeysForClass and this line:
Line 7219 in 9f9787d
| PyDictKeysObject *keys = new_keys_object(NEXT_LOG2_SHARED_KEYS_MAX_SIZE, 1); |
Tries to allocate a keys object, and if that allocation fails, just clears the MemoryError and continues, skipping out some keys setup steps.
However, later on in that _PyDict_NewKeysForClass it fetches the __static_attributes__ tuple and tries to populate them into the keys object:
Lines 7232 to 7238 in 9f9787d
| for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(attrs); i++) { | |
| PyObject *key = PyTuple_GET_ITEM(attrs, i); | |
| Py_hash_t hash; | |
| if (PyUnicode_CheckExact(key) && (hash = unicode_get_hash(key)) != -1) { | |
| if (insert_split_key(keys, key, hash) == DKIX_EMPTY) { | |
| break; | |
| } |
And insert_split_key reasonably assumes that keys is a valid Keys object, resulting in a relative pointer from NULL dereferenec.
Stacktrace
frame #0 _DK_ENTRIES(dk=<unavailable>) at pycore_dict.h:269
frame #1 do_lookup(mp=0x0, dk=0x0, ...) at dictobject.c:1090
frame #2 unicodekeys_lookup_unicode(dk=0x0, ...) at dictobject.c:1183
frame #3 insert_split_key(keys=0x0, ...) at dictobject.c:1931
frame #4 _PyDict_NewKeysForClass(cls=0x0000aaaaabfffa70) at dictobject.c:7236
frame #5 type_ready_managed_dict(type=0x0000aaaaabfffa70) at typeobject.c:9473
frame #6 type_ready(...) at typeobject.c:9578
frame #7 PyType_Ready(...) at typeobject.c:9622
Interestingly bisect failed here, because before this change, there was a different allocation failure segfault from class definition, so either this one is shadowing the other on (I'll check separately) or the other issue has already/accidentally been fixed.
Suggested fix.
Given that _PyDict_NewKeysForClass returns keys at the end anyway, it seems reasonable to just return NULL from the keys == NULL branch here:
Lines 7220 to 7222 in 9f9787d
| if (keys == NULL) { | |
| PyErr_Clear(); | |
| } |
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.16.0a0 (heads/main-dirty:639a552, Jul 4 2026, 21:13:52) [Clang 22.1.6 ]
Activity
stestagg commentedon Jul 7, 2026
I believe this PR:
#150490: Raise PyType_Modified for insertion into split dictionary (take 2) (#152914)
Has since fixed the issue