Skip to content

Segfault in atomdict setdefault and DefaultAtomDict __missing__ with key coercion #260

@devdanzin

Description

@devdanzin

AtomDict_setdefault (atomdict.cpp:166) and DefaultAtomDict_missing (atomdict.cpp:311) re-lookup a value using the original key after AtomDict_ass_subscript stored it under a validated (possibly coerced) key. If the key validator coerces the key (e.g., Coerced(int) turns '42' into 42), the re-lookup with the original key fails. PyDict_GetItem returns NULL, which is passed to cppy::incref(NULL) — segfault.

Git history shows the pattern was introduced in commit 57957af (setdefault, March 2023) and copy-paste propagated to __missing__ in commit 0ff1ba6 (April 2023), including the identical comment: "Get the dictionary from the dict itself in case it was coerced.".

Reproducer 1 — setdefault:

import gc; gc.disable()
import os
from atom.api import Atom, Dict, Coerced

class MyAtom(Atom):
    d = Dict(key=Coerced(int))

a = MyAtom()
a.d[42] = "direct int"
a.d.setdefault("42", "coerced default")  # Segmentation fault
os._exit(0)

Reproducer 2 — missing:

import gc; gc.disable()
import os
from atom.api import Atom, Coerced
from atom.dict import DefaultDict

class MyAtom(Atom):
    d = DefaultDict(key=Coerced(int), missing=lambda: "generated")

a = MyAtom()
a.d["42"]  # Segmentation fault
os._exit(0)

Fix: the re-lookup should use the validated key. Since the validated key is not currently saved, the function needs restructuring. One approach for setdefault:

// Save the return of AtomDict_ass_subscript or use the validated key
cppy::ptr key_ptr( cppy::incref( key ) );
if( should_validate( self ) )
{
    key_ptr = validate_key( self, key_ptr.get() );
    if( !key_ptr )
        return 0;
}
// ... store with key_ptr, then re-lookup with key_ptr
return cppy::incref( PyDict_GetItem( pyobject_cast( self ), key_ptr.get() ) );

Found by cext-review-toolkit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions