mirror of
https://github.com/python/cpython.git
synced 2026-10-28 18:35:21 +08:00
gh-151818: Fix double-free in _CALL_LIST_APPEND on allocation failure (#151826)
_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.
This commit is contained in:
1 parent
3e13407763
commit
4ecf6ca1b3
8 files changed
+133
-7
No files matched your search
+1
-1
@@ -1146,7 +1146,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
|
||||
[CALL_KW_NON_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
|
||||
[CALL_KW_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_SYNC_SP_FLAG | HAS_NEEDS_GUARD_IP_FLAG | HAS_RECORDS_VALUE_FLAG },
|
||||
[CALL_LEN] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
|
||||
[CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
|
||||
[CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
|
||||
[CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
|
||||
[CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG },
|
||||
[CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG },
|
||||
|
||||
Generated
+1
-1
@@ -338,7 +338,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = {
|
||||
[_GUARD_CALLABLE_ISINSTANCE] = HAS_EXIT_FLAG,
|
||||
[_CALL_ISINSTANCE] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
|
||||
[_GUARD_CALLABLE_LIST_APPEND] = HAS_EXIT_FLAG,
|
||||
[_CALL_LIST_APPEND] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG,
|
||||
[_CALL_LIST_APPEND] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG,
|
||||
[_GUARD_CALLABLE_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_EXIT_FLAG,
|
||||
[_CALL_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG,
|
||||
[_CHECK_RECURSION_LIMIT] = HAS_EXIT_FLAG,
|
||||
|
||||
@@ -2,6 +2,7 @@ import collections
|
||||
import copy
|
||||
import pickle
|
||||
import dis
|
||||
import sys
|
||||
import threading
|
||||
import types
|
||||
import unittest
|
||||
@@ -2171,6 +2172,39 @@ class TestSpecializer(TestBase):
|
||||
self.assert_no_opcode(my_list_append, "CALL_LIST_APPEND")
|
||||
self.assert_no_opcode(my_list_append, "CALL")
|
||||
|
||||
@cpython_only
|
||||
@requires_specialization
|
||||
def test_call_list_append_memory_error(self):
|
||||
# gh-151818: if CALL_LIST_APPEND fails to resize the list,
|
||||
# the argument must be released exactly once.
|
||||
_testcapi = import_module("_testcapi")
|
||||
|
||||
class Holder:
|
||||
pass
|
||||
|
||||
def list_append(l, holder):
|
||||
# holder.item is pushed as a new reference, which
|
||||
# CALL_LIST_APPEND steals.
|
||||
l.append(holder.item)
|
||||
|
||||
holder = Holder()
|
||||
holder.item = item = object()
|
||||
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
|
||||
list_append([], holder)
|
||||
self.assert_specialized(list_append, "CALL_LIST_APPEND")
|
||||
|
||||
refcount = sys.getrefcount(item)
|
||||
# An empty list has no spare capacity, so append() must resize it.
|
||||
lst = []
|
||||
with self.assertRaises(MemoryError):
|
||||
_testcapi.set_nomemory(0)
|
||||
try:
|
||||
list_append(lst, holder)
|
||||
finally:
|
||||
_testcapi.remove_mem_hooks()
|
||||
self.assertEqual(lst, [])
|
||||
self.assertEqual(sys.getrefcount(item), refcount)
|
||||
|
||||
@cpython_only
|
||||
@requires_specialization
|
||||
def test_call_c_function_extra_flags(self):
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Fix a double-free crash in ``list.append()`` (the specialized
|
||||
``CALL_LIST_APPEND`` instruction) when growing the list fails with
|
||||
:exc:`MemoryError`.
|
||||
Generated
+16
@@ -3984,6 +3984,22 @@
|
||||
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
|
||||
UNLOCK_OBJECT(self_o);
|
||||
if (err) {
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
_PyStackRef tmp = self;
|
||||
self = PyStackRef_NULL;
|
||||
stack_pointer[-2] = self;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
assert(stack_pointer == _PyFrame_GetStackPointer(frame));
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
tmp = callable;
|
||||
callable = PyStackRef_NULL;
|
||||
stack_pointer[-3] = callable;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
stack_pointer += -3;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
JUMP_TO_LABEL(error);
|
||||
}
|
||||
c = callable;
|
||||
|
||||
+4
-1
@@ -5038,7 +5038,10 @@ dummy_func(
|
||||
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
|
||||
UNLOCK_OBJECT(self_o);
|
||||
if (err) {
|
||||
ERROR_NO_POP();
|
||||
// arg is consumed even on failure: close the other inputs
|
||||
// and pop them all, so that arg is not closed a second time.
|
||||
DECREF_INPUTS();
|
||||
ERROR_IF(true);
|
||||
}
|
||||
c = callable;
|
||||
s = self;
|
||||
|
||||
Generated
+58
-4
@@ -19083,6 +19083,22 @@
|
||||
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
|
||||
UNLOCK_OBJECT(self_o);
|
||||
if (err) {
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
_PyStackRef tmp = self;
|
||||
self = PyStackRef_NULL;
|
||||
stack_pointer[-2] = self;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
assert(stack_pointer == _PyFrame_GetStackPointer(frame));
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
tmp = callable;
|
||||
callable = PyStackRef_NULL;
|
||||
stack_pointer[-3] = callable;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
stack_pointer += -3;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
SET_CURRENT_CACHED_VALUES(0);
|
||||
JUMP_TO_ERROR();
|
||||
}
|
||||
@@ -19125,7 +19141,21 @@
|
||||
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
|
||||
UNLOCK_OBJECT(self_o);
|
||||
if (err) {
|
||||
stack_pointer += 1;
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
_PyStackRef tmp = self;
|
||||
self = PyStackRef_NULL;
|
||||
stack_pointer[-1] = self;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
assert(stack_pointer == _PyFrame_GetStackPointer(frame));
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
tmp = callable;
|
||||
callable = PyStackRef_NULL;
|
||||
stack_pointer[-2] = callable;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
stack_pointer += -2;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
SET_CURRENT_CACHED_VALUES(0);
|
||||
JUMP_TO_ERROR();
|
||||
@@ -19171,8 +19201,21 @@
|
||||
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
|
||||
UNLOCK_OBJECT(self_o);
|
||||
if (err) {
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
_PyStackRef tmp = self;
|
||||
self = PyStackRef_NULL;
|
||||
stack_pointer[0] = self;
|
||||
stack_pointer += 2;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
assert(stack_pointer == _PyFrame_GetStackPointer(frame));
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
tmp = callable;
|
||||
callable = PyStackRef_NULL;
|
||||
stack_pointer[-1] = callable;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
stack_pointer += -1;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
SET_CURRENT_CACHED_VALUES(0);
|
||||
JUMP_TO_ERROR();
|
||||
@@ -19220,10 +19263,21 @@
|
||||
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
|
||||
UNLOCK_OBJECT(self_o);
|
||||
if (err) {
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
_PyStackRef tmp = self;
|
||||
self = PyStackRef_NULL;
|
||||
stack_pointer[0] = callable;
|
||||
stack_pointer[1] = self;
|
||||
stack_pointer += 3;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
assert(stack_pointer == _PyFrame_GetStackPointer(frame));
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
tmp = callable;
|
||||
callable = PyStackRef_NULL;
|
||||
stack_pointer[0] = callable;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
SET_CURRENT_CACHED_VALUES(0);
|
||||
JUMP_TO_ERROR();
|
||||
}
|
||||
|
||||
Generated
+16
@@ -3984,6 +3984,22 @@
|
||||
int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg));
|
||||
UNLOCK_OBJECT(self_o);
|
||||
if (err) {
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
_PyStackRef tmp = self;
|
||||
self = PyStackRef_NULL;
|
||||
stack_pointer[-2] = self;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
assert(stack_pointer == _PyFrame_GetStackPointer(frame));
|
||||
_PyFrame_StackPointerValidate(frame);
|
||||
tmp = callable;
|
||||
callable = PyStackRef_NULL;
|
||||
stack_pointer[-3] = callable;
|
||||
PyStackRef_CLOSE(tmp);
|
||||
_PyFrame_StackPointerInvalidate(frame);
|
||||
stack_pointer += -3;
|
||||
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
|
||||
JUMP_TO_LABEL(error);
|
||||
}
|
||||
c = callable;
|
||||
|
||||
Reference in new issue
Block a user