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

@timurmamedov1timurmamedov1 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.

CopilotAI 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
FileDescription
Python/generated_cases.c.hFix ref ownership for _CALL_LIST_APPEND and adjust stack-pointer updates/cleanup to avoid double-close on error.
Python/executor_cases.c.hFix Tier 2 _CALL_LIST_APPEND_r33 to avoid stealing arg and restore stack correctly on error; remove other variants.
Python/bytecodes.cUpdate _CALL_LIST_APPEND uop definition to use an independent ref for list append and close arg only on success.
Modules/_testinternalcapi/test_cases.c.hMirrors interpreter generated-case fix for internal testing harness.
Include/internal/pycore_uop_metadata.hMark _CALL_LIST_APPEND as escaping and restrict caching configuration to the remaining _r33 variant.
Include/internal/pycore_uop_ids.hRemove _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.rstAdds NEWS entry documenting the crash fix.

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

Comment threadPython/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
timurmamedov1force-pushed the gh-151818-fix-list-append-double-free branch 2 times, most recently from c9176ae to ca1f525CompareJune 21, 2026 16:17

@vstinnervstinner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wrote a different fix using DECREF_INPUTS(): #152114.

…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.
Sign up for freeto 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.

3 participants

@timurmamedov1@vstinner