Skip to content

Segfault from _PyDict_NewKeysForClass under memory pressure #153182

Description

@stestagg

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:
        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:

cpython/Objects/dictobject.c

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:

cpython/Objects/dictobject.c

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 ]

Linked PRs

Activity

  1. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Jul 6, 2026
  2. stestagg commented on Jul 7, 2026

    @stestagg
    ContributorAuthor

    I believe this PR:
    #150490: Raise PyType_Modified for insertion into split dictionary (take 2) (#152914)

    Has since fixed the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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