Skip to content

Segfault from _PyDict_NewKeysForClass under memory pressure #153182

Description

@stestagg

Crash report

What happened?

import_testcapidefmake_class():
classC:
deff(self):
self.x=1returnCforstartinrange(100):
_testcapi.set_nomemory(start, start+1)
try:
x=make_class()
exceptMemoryError:
pass
fish: 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:

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:

for (Py_ssize_ti=0; i<PyTuple_GET_SIZE(attrs); i++) {
PyObject*key=PyTuple_GET_ITEM(attrs, i);
Py_hash_thash;
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:

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 ]

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-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