Skip to content

Assertion failure from functools.partial using a str subclass that raises on __eq__ #146075

Closed

Description

@devdanzin

Crash report

What happened?

It's possible to make the interpreter abort by passing an evil str subclass that raises on __eq__ to be used as a keyword by partial.

Automated diagnosis:

Bug: PyDict_Contains -1 treated as truthy in partial.__call__ vectorcall. When a keyword key's __eq__ or __hash__ raises during the PyDict_Contains check, the error return (-1) is treated as "found" (truthy), silently swallowing the exception and taking the wrong code path for keyword merging.

File: Modules/_functoolsmodule.c, line 455

MRE:

from functools import partial

class BadStr(str):
    def __eq__(self, other):
        raise RuntimeError
    def __hash__(self):
        return str.__hash__(self)

def f(**kwargs):
    return kwargs

p = partial(f, poison="")
result = p(**{BadStr("poison"): "new_value"})

Backtrace:

python: Python/generated_cases.c.h:12739: PyObject *_PyEval_EvalFrameDefault(PyThreadState *, _PyInterpreterFrame *, int): Assertion `!_PyErr_Occurred(tstate)' failed.

Program received signal SIGABRT, Aborted.

#0  __pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:89
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:100
#3  0x00007ffff7c45e2e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7c28888 in __GI_abort () at ./stdlib/abort.c:77
#5  0x00007ffff7c287f0 in __assert_fail_base (fmt=<optimized out>, assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:118
#6  0x00007ffff7c3c19f in __assert_fail (assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:127
#7  0x0000555555e5d037 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:12739
#8  0x0000555555e57778 in _PyEval_EvalFrame (tstate=0x5555568f7b18 <_PyRuntime+360664>, frame=0x7e8ff6fe52a8, throwflag=0) at ./Include/internal/pycore_ceval.h:118
#9  _PyEval_Vector (tstate=<optimized out>, func=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0) at Python/ceval.c:2134
#10 0x0000555555ab9e00 in _PyObject_VectorcallTstate (tstate=tstate@entry=0x5555568f7b18 <_PyRuntime+360664>, callable=0x7cfff703cf60, args=args@entry=0x7bfff5b7e4a8,
    nargsf=nargsf@entry=9223372036854775809, kwnames=kwnames@entry=0x0) at ./Include/internal/pycore_call.h:136
#11 0x0000555555abcda5 in PyObject_CallOneArg (func=<optimized out>, arg=0x7d3ff7026870) at Objects/call.c:395
#12 0x0000555555c6fb74 in call_unbound_noarg (unbound=1, func=0x7cfff703cf60, self=0x7d3ff7026870) at Objects/typeobject.c:3033
#13 maybe_call_special_no_args (self=<optimized out>, attr=<optimized out>, attr_is_none=<optimized out>) at Objects/typeobject.c:3146
#14 0x0000555555caaeb6 in slot_tp_hash (self=<optimized out>) at Objects/typeobject.c:10705
#15 0x0000555555b97cd7 in _PyObject_HashFast (op=0x7d3ff7026870) at ./Include/internal/pycore_object.h:849
#16 setitem_take2_lock_held (mp=0x7c7ff70decc0, key=0x7d3ff7026870, value=0x7c6ff70eb5a0) at Objects/dictobject.c:2737
#17 0x000055555625d598 in partial_vectorcall (self=<optimized out>, args=<optimized out>, nargsf=<optimized out>, kwnames=<optimized out>) at ./Modules/_functoolsmodule.c:467
#18 0x0000555555abc520 in _PyVectorcall_Call (tstate=<optimized out>, func=<optimized out>, callable=<optimized out>, tuple=0x5555568c0b50 <_PyRuntime+135440>, kwargs=<optimized out>)
    at Objects/call.c:285
#19 0x0000555555e904e2 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:2937
#20 0x0000555555e57778 in _PyEval_EvalFrame (tstate=0x5555568f7b18 <_PyRuntime+360664>, frame=0x7e8ff6fe5220, throwflag=0) at ./Include/internal/pycore_ceval.h:118
#21 _PyEval_Vector (tstate=<optimized out>, func=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0) at Python/ceval.c:2134
#22 0x0000555555e57195 in PyEval_EvalCode (co=<optimized out>, globals=<optimized out>, locals=0x7c7ff70884c0) at Python/ceval.c:681
#23 0x0000555556061fb0 in run_eval_code_obj (tstate=tstate@entry=0x5555568f7b18 <_PyRuntime+360664>, co=co@entry=0x7d2ff6ffdfd0, globals=globals@entry=0x7c7ff70884c0,
    locals=locals@entry=0x7c7ff70884c0) at Python/pythonrun.c:1368
#24 0x000055555606117c in run_mod (mod=<optimized out>, filename=<optimized out>, globals=<optimized out>, locals=<optimized out>, flags=<optimized out>, arena=<optimized out>,
    interactive_src=<optimized out>, generate_new_source=<optimized out>) at Python/pythonrun.c:1471

Found using cpython-review-toolkit with Claude Opus 4.6, using the /cpython-review-toolkit:explore Modules/_functoolsmodule.c all deep command.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.15.0a7+ (heads/main:99e2c5eccd2, Mar 17 2026, 08:26:50) [Clang 21.1.2 (2ubuntu6)]

Linked PRs

Activity

  1. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Mar 17, 2026
  2. FocusMode-coder commented on Mar 17, 2026

    @FocusMode-coder
  3. added a commit that references this issue on Mar 17, 2026
  4. brijkapadia commented on Mar 17, 2026

    @brijkapadia
    Contributor

    Created a PR as it is quite a simple fix and I am already working on a much larger change to functools.partial

  5. added a commit that references this issue on Mar 18, 2026
  6. vstinner commented on Mar 18, 2026

    @vstinner
    Member

    The initial issue has been fixed by #146078, so I close the issue.

  7. added a commit that references this issue on Apr 25, 2026
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

    3.15bugs and security fixesextension-modulesC modules in the Modules dirtype-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