Skip to content

gh-151818: Fix double-free in _CALL_LIST_APPEND on allocation failure#151826

Open
timurmamedov1 wants to merge 2 commits into
python:mainfrom
timurmamedov1:gh-151818-fix-list-append-double-free
Open

gh-151818: Fix double-free in _CALL_LIST_APPEND on allocation failure#151826
timurmamedov1 wants to merge 2 commits into
python:mainfrom
timurmamedov1:gh-151818-fix-list-append-double-free

Conversation

@timurmamedov1

@timurmamedov1 timurmamedov1 commented Jun 21, 2026

Copy link
Copy Markdown

_CALL_LIST_APPEND stole the arg stackref via PyStackRef_AsPyObjectSteal and passed it to _PyList_AppendTakeRef. When the list's backing array failed to grow, _PyList_AppendTakeRef decref'd the item, but the stale stackref remained on the value stack. exception_unwind then closed it a second time. Double-free on debug builds (_Py_NegativeRefcount), segfault on release builds.

Fix: Give _PyList_AppendTakeRef a separate reference via Py_NewRef and close the stackref explicitly on success. On the error path the stackref still holds a valid reference, so the exception unwinder can safely close it.

This trades one extra Py_INCREF on the hot path for correct error handling. The sibling ops (LIST_APPEND, SET_ADD, MAP_ADD) avoid this by using ERROR_IF which drops dead inputs, but _CALL_LIST_APPEND has additional live inputs (callable, self) that prevent using ERROR_IF directly.

Related to gh-151119 / gh-151538 (different bug in the same op - missing eval-stack sync), but this is a distinct bug as noted by @devdanzin.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a reference-management bug in the specialized CALL_LIST_APPEND fast path that could double-close/decref an argument on list-resize allocation failure, leading to a double-free/crash. The change adjusts ownership so the list append consumes an independent reference while the eval-stack PyStackRef remains valid for unwinding.

Changes:

  • Update CALL_LIST_APPEND implementations to pass Py_NewRef(PyStackRef_AsPyObjectBorrow(arg)) to _PyList_AppendTakeRef() and explicitly PyStackRef_CLOSE(arg) only on success.
  • Adjust stack-pointer synchronization in generated interpreter/test-case code to correctly pop/close arg on the success path.
  • Simplify Tier 2 uop variants for _CALL_LIST_APPEND (retain only _CALL_LIST_APPEND_r33) and update uop metadata/IDs accordingly; add HAS_ESCAPES_FLAG for _CALL_LIST_APPEND.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Python/generated_cases.c.h Fix ref ownership for _CALL_LIST_APPEND and adjust stack-pointer updates/cleanup to avoid double-close on error.
Python/executor_cases.c.h Fix Tier 2 _CALL_LIST_APPEND_r33 to avoid stealing arg and restore stack correctly on error; remove other variants.
Python/bytecodes.c Update _CALL_LIST_APPEND uop definition to use an independent ref for list append and close arg only on success.
Modules/_testinternalcapi/test_cases.c.h Mirrors interpreter generated-case fix for internal testing harness.
Include/internal/pycore_uop_metadata.h Mark _CALL_LIST_APPEND as escaping and restrict caching configuration to the remaining _r33 variant.
Include/internal/pycore_uop_ids.h Remove _CALL_LIST_APPEND_r03/r13/r23 IDs and renumber subsequent uops.
Misc/NEWS.d/next/Core_and_Builtins/2026-06-20-18-00-00.gh-issue-151818.LsApDf.rst Adds NEWS entry documenting the crash fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Python/bytecodes.c
Comment on lines +5108 to +5114
PyObject *arg_o = PyStackRef_AsPyObjectBorrow(arg);
int err = _PyList_AppendTakeRef((PyListObject *)self_o, Py_NewRef(arg_o));
UNLOCK_OBJECT(self_o);
if (err) {
ERROR_NO_POP();
}
PyStackRef_CLOSE(arg);
@timurmamedov1 timurmamedov1 force-pushed the gh-151818-fix-list-append-double-free branch from b054bf2 to f3a6786 Compare June 21, 2026 05:20
…ailure

_CALL_LIST_APPEND stole the arg stackref via PyStackRef_AsPyObjectSteal
and passed it to _PyList_AppendTakeRef. When the list's backing array
failed to grow, _PyList_AppendTakeRef decreffed the item, but the stale
stackref remained on the value stack. The exception unwinder then closed
it a second time, causing a double-free / use-after-free.

Fix by giving _PyList_AppendTakeRef a separate reference via Py_NewRef
and closing the stackref explicitly on success. On the error path the
stackref still holds a valid reference, so the exception unwinder can
safely close it.
CALL_LIST_APPEND is not in the Sphinx opcode domain, so use double
backticks instead of :opcode: to avoid a broken cross-reference.
@timurmamedov1 timurmamedov1 force-pushed the gh-151818-fix-list-append-double-free branch from c9176ae to ca1f525 Compare June 21, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants