-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
Closed
Labels
3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
What happened?
During SETITEM handling, _pickle.do_setitems inserts stacked key/value pairs into the target mapping and then clears the shared unpickling stack. A crafted key runs Unpickler.load() from its __hash__, re-entering the unpickler while the outer do_setitems still holds borrowed pointers. The inner load drains the stack and decrefs the pending key/dict, so the outer PyObject_SetItem resumes with freed objects, crashing inside setitem_take2_lock_held.
Proof of Concept:
import io, _pickle
CURRENT = None
class Victim(dict):
pass
class TriggerKey:
armed = True
def __hash__(self):
if TriggerKey.armed:
TriggerKey.armed = False
try:
CURRENT.load()
except Exception:
pass
return 1337
class Payload:
pass
DATA = (b"\x80\x05c__main__\nVictim\n)\x81"
b"c__main__\nTriggerKey\n)\x81"
b"c__main__\nPayload\n)\x81s"
b"\x80\x05N..")
class Recorder(_pickle.Unpickler):
def find_class(self, module, name):
global CURRENT
CURRENT = self
return super().find_class(module, name)
Recorder(io.BytesIO(DATA)).load()Vulnerable Code Snippet:
Click to expand
/* Buggy Re-entrant Path */
static int
load_setitem(PickleState *state, UnpicklerObject *self)
{
return do_setitems(state, self, Py_SIZE(self->stack) - 2);
}
static int
do_setitems(PickleState *st, UnpicklerObject *self, Py_ssize_t x)
{
PyObject *value, *key;
PyObject *dict;
Py_ssize_t len, i;
int status = 0;
len = Py_SIZE(self->stack);
/* ... */
dict = self->stack->data[x - 1];
for (i = x + 1; i < len; i += 2) {
key = self->stack->data[i - 1]; /* crashing pointer derived */
value = self->stack->data[i];
if (PyObject_SetItem(dict, key, value) < 0) { /* Reentrant call site */
status = -1;
break;
}
}
/* ... */
return status;
}
/* Objects/dictobject.c */
static int
setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
{
Py_hash_t hash = _PyObject_HashFast(key); /* Crash site */
/* key.__hash__ can re-enter _pickle.Unpickler.load() and clear the stack */
/* ... */
return insertdict(interp, mp, key, hash, value);
}
/* Clobbering Path */
static PyObject *
load(PickleState *st, UnpicklerObject *self)
{
self->num_marks = 0;
self->stack->mark_set = 0;
self->stack->fence = 0;
self->proto = 0;
if (Py_SIZE(self->stack))
Pdata_clear(self->stack, 0); /* state mutate site */
/* ... */
return NULL;
}
static int
Pdata_clear(Pdata *self, Py_ssize_t clearto)
{
Py_ssize_t i = Py_SIZE(self);
/* ... */
while (--i >= clearto) {
Py_CLEAR(self->data[i]); /* state mutate site */
}
Py_SET_SIZE(self, clearto);
return 0;
}Sanitizer Output:
Click to expand
=================================================================
==332913==ERROR: AddressSanitizer: heap-use-after-free on address 0x51300002ae80 at pc 0x57bb210be7fb bp 0x7ffe60361260 sp 0x7ffe60361250
READ of size 8 at 0x51300002ae80 thread T0
#0 0x57bb210be7fa in setitem_take2_lock_held Objects/dictobject.c:2671
#1 0x57bb210be7fa in _PyDict_SetItem_Take2 Objects/dictobject.c:2683
#2 0x57bb210be7fa in PyDict_SetItem Objects/dictobject.c:2703
#3 0x57bb210be7fa in dict_ass_sub Objects/dictobject.c:3509
#4 0x76c94d71c291 in do_setitems Modules/_pickle.c:6590
#5 0x76c94d72c054 in load_setitem Modules/_pickle.c:6603
#6 0x76c94d72c054 in load Modules/_pickle.c:6972
#7 0x57bb20fdf3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#8 0x57bb20fdf3e7 in PyObject_Vectorcall Objects/call.c:327
#9 0x57bb20e9bf33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#10 0x57bb2135dad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#11 0x57bb2135dad6 in _PyEval_Vector Python/ceval.c:2001
#12 0x57bb2135dad6 in PyEval_EvalCode Python/ceval.c:884
#13 0x57bb214a316e in run_eval_code_obj Python/pythonrun.c:1365
#14 0x57bb214a316e in run_mod Python/pythonrun.c:1459
#15 0x57bb214a7e17 in pyrun_file Python/pythonrun.c:1293
#16 0x57bb214a7e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#17 0x57bb214a893c in _PyRun_AnyFileObject Python/pythonrun.c:81
#18 0x57bb2151be3c in pymain_run_file_obj Modules/main.c:410
#19 0x57bb2151be3c in pymain_run_file Modules/main.c:429
#20 0x57bb2151be3c in pymain_run_python Modules/main.c:691
#21 0x57bb2151d71e in Py_RunMain Modules/main.c:772
#22 0x57bb2151d71e in pymain_main Modules/main.c:802
#23 0x57bb2151d71e in Py_BytesMain Modules/main.c:826
#24 0x76c94dc2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#25 0x76c94dc2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#26 0x57bb20eb7634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
0x51300002ae80 is located 64 bytes inside of 360-byte region [0x51300002ae40,0x51300002afa8)
freed by thread T0 here:
#0 0x76c94e0fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x57bb211708f3 in subtype_dealloc Objects/typeobject.c:2852
#2 0x57bb210fc1d8 in _Py_Dealloc Objects/object.c:3200
#3 0x76c94d72a54b in Py_DECREF Include/refcount.h:418
#4 0x76c94d72a54b in Pdata_clear Modules/_pickle.c:480
#5 0x76c94d72a54b in load Modules/_pickle.c:6892
#6 0x57bb20fdf3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#7 0x57bb20fdf3e7 in PyObject_Vectorcall Objects/call.c:327
#8 0x57bb20e9bf33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#9 0x57bb2135e2a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#10 0x57bb2135e2a5 in _PyEval_Vector Python/ceval.c:2001
#11 0x57bb20fdf72d in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#12 0x57bb20fdf72d in PyObject_CallOneArg Objects/call.c:395
#13 0x57bb21193512 in call_unbound_noarg Objects/typeobject.c:3040
#14 0x57bb21193512 in maybe_call_special_no_args Objects/typeobject.c:3153
#15 0x57bb21193512 in slot_tp_hash Objects/typeobject.c:10564
#16 0x57bb210be638 in _PyObject_HashFast Include/internal/pycore_object.h:872
#17 0x57bb210be638 in setitem_take2_lock_held Objects/dictobject.c:2661
#18 0x57bb210be638 in _PyDict_SetItem_Take2 Objects/dictobject.c:2683
#19 0x57bb210be638 in PyDict_SetItem Objects/dictobject.c:2703
#20 0x57bb210be638 in dict_ass_sub Objects/dictobject.c:3509
#21 0x76c94d71c291 in do_setitems Modules/_pickle.c:6590
#22 0x76c94d72c054 in load_setitem Modules/_pickle.c:6603
#23 0x76c94d72c054 in load Modules/_pickle.c:6972
#24 0x57bb20fdf3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#25 0x57bb20fdf3e7 in PyObject_Vectorcall Objects/call.c:327
#26 0x57bb20e9bf33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#27 0x57bb2135dad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#28 0x57bb2135dad6 in _PyEval_Vector Python/ceval.c:2001
#29 0x57bb2135dad6 in PyEval_EvalCode Python/ceval.c:884
#30 0x57bb214a316e in run_eval_code_obj Python/pythonrun.c:1365
#31 0x57bb214a316e in run_mod Python/pythonrun.c:1459
#32 0x57bb214a7e17 in pyrun_file Python/pythonrun.c:1293
#33 0x57bb214a7e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#34 0x57bb214a893c in _PyRun_AnyFileObject Python/pythonrun.c:81
#35 0x57bb2151be3c in pymain_run_file_obj Modules/main.c:410
#36 0x57bb2151be3c in pymain_run_file Modules/main.c:429
#37 0x57bb2151be3c in pymain_run_python Modules/main.c:691
#38 0x57bb2151d71e in Py_RunMain Modules/main.c:772
#39 0x57bb2151d71e in pymain_main Modules/main.c:802
#40 0x57bb2151d71e in Py_BytesMain Modules/main.c:826
#41 0x76c94dc2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#42 0x76c94dc2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#43 0x57bb20eb7634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
previously allocated by thread T0 here:
#0 0x76c94e0fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x57bb2118488e in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
#2 0x57bb2118488e in _PyType_AllocNoTrack Objects/typeobject.c:2504
#3 0x57bb21184af4 in PyType_GenericAlloc Objects/typeobject.c:2535
#4 0x57bb210a1aea in dict_new Objects/dictobject.c:4904
#5 0x76c94d727b8b in load_newobj Modules/_pickle.c:6021
#6 0x76c94d72b460 in load Modules/_pickle.c:6954
#7 0x57bb20fdf3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#8 0x57bb20fdf3e7 in PyObject_Vectorcall Objects/call.c:327
#9 0x57bb20e9bf33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#10 0x57bb2135dad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#11 0x57bb2135dad6 in _PyEval_Vector Python/ceval.c:2001
#12 0x57bb2135dad6 in PyEval_EvalCode Python/ceval.c:884
#13 0x57bb214a316e in run_eval_code_obj Python/pythonrun.c:1365
#14 0x57bb214a316e in run_mod Python/pythonrun.c:1459
#15 0x57bb214a7e17 in pyrun_file Python/pythonrun.c:1293
#16 0x57bb214a7e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#17 0x57bb214a893c in _PyRun_AnyFileObject Python/pythonrun.c:81
#18 0x57bb2151be3c in pymain_run_file_obj Modules/main.c:410
#19 0x57bb2151be3c in pymain_run_file Modules/main.c:429
#20 0x57bb2151be3c in pymain_run_python Modules/main.c:691
#21 0x57bb2151d71e in Py_RunMain Modules/main.c:772
#22 0x57bb2151d71e in pymain_main Modules/main.c:802
#23 0x57bb2151d71e in Py_BytesMain Modules/main.c:826
#24 0x76c94dc2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#25 0x76c94dc2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#26 0x57bb20eb7634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
SUMMARY: AddressSanitizer: heap-use-after-free Objects/dictobject.c:2671 in setitem_take2_lock_held
Shadow bytes around the buggy address:
0x51300002ac00: fd fd fd fd fd fa fa fa fa fa fa fa fa fa fa fa
0x51300002ac80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51300002ad00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51300002ad80: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa
0x51300002ae00: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
=>0x51300002ae80:[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51300002af00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51300002af80: fd fd fd fd fd fa fa fa fa fa fa fa fa fa fa fa
0x51300002b000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x51300002b080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x51300002b100: 00 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==332913==ABORTING
CPython versions tested on:
Details
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) |
ASAN | 1 |
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 16:55:18) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 16:56:16) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0] |
ASAN | 1 |
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Projects
Status
Done