You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
UAF when writing to a bytearray with an element implementing __index__ with side-effects #91153
$ cat basneak.py
ba = bytearray([0 for _ in range(10000)])
class sneaky:
def __index__(self):
ba.clear()
return 1
ba[-1] = sneaky()
$ valgrind ./python basneak.py
==87894== Memcheck, a memory error detector
==87894== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==87894== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==87894== Command: ./python basneak.py
==87894==
==87894== Invalid write of size 1
==87894== at 0x49B70F: bytearray_ass_subscript (bytearrayobject.c:632)
==87894== by 0x488E03: PyObject_SetItem (abstract.c:211)
<snip>
In bytearray_setitem(), we first do bounds checking, and then call _getbytevalue() to get the numeric value of the argument.
I think there's a similar bug in bytearray_ass_subscript().
proof of concept that this bug can be used to corrupt memory in a malicious/useful way
tested on Python 3.12.0a0 built with ./configure --enable-optimizations
===================================================================17089==ERROR: AddressSanitizer: heap-use-after-free on address 0x62600000580f at pc 0x55f0875e0b57 bp 0x7fff4c7822e0 sp 0x7fff4c7822d0WRITE of size 1 at 0x62600000580f thread T0 #0 0x55f0875e0b56 in bytearray_ass_subscript Objects/bytearrayobject.c:618 #1 0x55f08758e10f in PyObject_SetItem Objects/abstract.c:212 #2 0x55f087bc13ec in _PyEval_EvalFrameDefault Python/ceval.c:2333 #3 0x55f087c304a3 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:74 #4 0x55f087c304a3 in _PyEval_Vector Python/ceval.c:6472 #5 0x55f087c309c9 in PyEval_EvalCode Python/ceval.c:1161 #6 0x55f087daf6b2 in run_eval_code_obj Python/pythonrun.c:1713 #7 0x55f087db12ac in run_mod Python/pythonrun.c:1734 #8 0x55f087db1667 in pyrun_file Python/pythonrun.c:1629 #9 0x55f087dbf998 in _PyRun_SimpleFileObject Python/pythonrun.c:439 #10 0x55f087dc0151 in _PyRun_AnyFileObject Python/pythonrun.c:78 #11 0x55f087e616b7 in pymain_run_file_obj Modules/main.c:360 #12 0x55f087e6234c in pymain_run_file Modules/main.c:379 #13 0x55f087e66d2a in pymain_run_python Modules/main.c:610 #14 0x55f087e66fa5 in Py_RunMain Modules/main.c:689 #15 0x55f087e67195 in pymain_main Modules/main.c:719 #16 0x55f087e674fc in Py_BytesMain Modules/main.c:743 #17 0x55f0873362c5 in main Programs/python.c:15 #18 0x7f73bfe63082 in __libc_start_main ../csu/libc-start.c:308 #19 0x55f0873361fd in _start (/workspaces/cpython/python+0x18491fd)0x62600000580f is located 9999 bytes inside of 10001-byte region [0x626000003100,0x626000005811)freed by thread T0 here: #0 0x7f73c0c2ac3e in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:163 #1 0x55f0878484bb in _PyMem_RawRealloc Objects/obmalloc.c:123 #2 0x55f08784cb06 in PyObject_Realloc Objects/obmalloc.c:735 #3 0x55f0875d5540 in PyByteArray_Resize Objects/bytearrayobject.c:233 #4 0x55f0875dfdcc in bytearray_clear_impl Objects/bytearrayobject.c:1134 #5 0x55f0875dfec4 in bytearray_clear Objects/clinic/bytearrayobject.c.h:89 #6 0x55f087689006 in method_vectorcall_NOARGS Objects/descrobject.c:454 #7 0x55f08763a245 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:92 #8 0x55f08763a245 in PyObject_Vectorcall Objects/call.c:300 #9 0x55f087c101b1 in _PyEval_EvalFrameDefault Python/ceval.c:4841 #10 0x55f087c304a3 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:74 #11 0x55f087c304a3 in _PyEval_Vector Python/ceval.c:6472 #12 0x55f087636edd in _PyFunction_Vectorcall Objects/call.c:396 #13 0x55f0878ee41e in _PyObject_VectorcallTstate Include/internal/pycore_call.h:92 #14 0x55f0878ee41e in vectorcall_unbound Objects/typeobject.c:1651 #15 0x55f0878ee41e in vectorcall_method Objects/typeobject.c:1682 #16 0x55f0878efe5a in slot_nb_index Objects/typeobject.c:7643 #17 0x55f087585d45 in _PyNumber_Index Objects/abstract.c:1418 #18 0x55f08776dd33 in PyLong_AsLongAndOverflow Objects/longobject.c:503 #19 0x55f0875a70bc in _getbytevalue Objects/bytearrayobject.c:27 #20 0x55f0875e0765 in bytearray_ass_subscript Objects/bytearrayobject.c:616 #21 0x55f08758e10f in PyObject_SetItem Objects/abstract.c:212 #22 0x55f087bc13ec in _PyEval_EvalFrameDefault Python/ceval.c:2333 #23 0x55f087c304a3 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:74 #24 0x55f087c304a3 in _PyEval_Vector Python/ceval.c:6472 #25 0x55f087c309c9 in PyEval_EvalCode Python/ceval.c:1161 #26 0x55f087daf6b2 in run_eval_code_obj Python/pythonrun.c:1713 #27 0x55f087db12ac in run_mod Python/pythonrun.c:1734 #28 0x55f087db1667 in pyrun_file Python/pythonrun.c:1629 #29 0x55f087dbf998 in _PyRun_SimpleFileObject Python/pythonrun.c:439 #30 0x55f087dc0151 in _PyRun_AnyFileObject Python/pythonrun.c:78 #31 0x55f087e616b7 in pymain_run_file_obj Modules/main.c:360 #32 0x55f087e6234c in pymain_run_file Modules/main.c:379 #33 0x55f087e66d2a in pymain_run_python Modules/main.c:610 #34 0x55f087e66fa5 in Py_RunMain Modules/main.c:689previously allocated by thread T0 here: #0 0x7f73c0c2ac3e in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:163 #1 0x55f0878484bb in _PyMem_RawRealloc Objects/obmalloc.c:123 #2 0x55f08784cb06 in PyObject_Realloc Objects/obmalloc.c:735 #3 0x55f0875d5540 in PyByteArray_Resize Objects/bytearrayobject.c:233 #4 0x55f0875d6f5c in bytearray___init___impl Objects/bytearrayobject.c:835 #5 0x55f0875d8f83 in bytearray___init__ Objects/clinic/bytearrayobject.c.h:68 #6 0x55f0878d9f4e in type_call Objects/typeobject.c:1112 #7 0x55f087637615 in _PyObject_MakeTpCall Objects/call.c:215 #8 0x55f08763a2a6 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:90 #9 0x55f08763a2a6 in PyObject_Vectorcall Objects/call.c:300 #10 0x55f087c101b1 in _PyEval_EvalFrameDefault Python/ceval.c:4841 #11 0x55f087c304a3 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:74 #12 0x55f087c304a3 in _PyEval_Vector Python/ceval.c:6472 #13 0x55f087c309c9 in PyEval_EvalCode Python/ceval.c:1161 #14 0x55f087daf6b2 in run_eval_code_obj Python/pythonrun.c:1713 #15 0x55f087db12ac in run_mod Python/pythonrun.c:1734 #16 0x55f087db1667 in pyrun_file Python/pythonrun.c:1629 #17 0x55f087dbf998 in _PyRun_SimpleFileObject Python/pythonrun.c:439 #18 0x55f087dc0151 in _PyRun_AnyFileObject Python/pythonrun.c:78 #19 0x55f087e616b7 in pymain_run_file_obj Modules/main.c:360 #20 0x55f087e6234c in pymain_run_file Modules/main.c:379 #21 0x55f087e66d2a in pymain_run_python Modules/main.c:610 #22 0x55f087e66fa5 in Py_RunMain Modules/main.c:689 #23 0x55f087e67195 in pymain_main Modules/main.c:719 #24 0x55f087e674fc in Py_BytesMain Modules/main.c:743 #25 0x55f0873362c5 in main Programs/python.c:15 #26 0x7f73bfe63082 in __libc_start_main ../csu/libc-start.c:308SUMMARY: AddressSanitizer: heap-use-after-free Objects/bytearrayobject.c:618 in bytearray_ass_subscriptShadow bytes around the buggy address: 0x0c4c7fff8ab0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c4c7fff8ac0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c4c7fff8ad0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c4c7fff8ae0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c4c7fff8af0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd=>0x0c4c7fff8b00: fd[fd]fd fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c4c7fff8b10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c4c7fff8b20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c4c7fff8b30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c4c7fff8b40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c4c7fff8b50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa faShadow 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 Shadow gap: cc==17089==ABORTING
Activity
JelleZijlstra commentedon Mar 13, 2022
Inspired by Guido's comment in https://github.com/python/cpython/pull/31834/files#r825352900, I found that there are some places in bytearrayobject.c where we can write to free'd memory if we encounter an object with a sneaky __index__ method:
In bytearray_setitem(), we first do bounds checking, and then call _getbytevalue() to get the numeric value of the argument.
I think there's a similar bug in bytearray_ass_subscript().
JelleZijlstra commentedon Mar 13, 2022
3.9 segfaults.
(gdb) bt
#0 bytearray_ass_subscript (self=<optimized out>, index=0x7ffff7e118f0, values=0x7ffff6f918b0) at Objects/bytearrayobject.c:640
#1 0x0000000000536302 in _PyEval_EvalFrameDefault (tstate=<optimized out>, f=0x999a80, throwflag=<optimized out>) at Python/ceval.c:1990
#2 0x0000000000534775 in _PyEval_EvalFrame (throwflag=0, f=0x999a80, tstate=0x93de90) at ./Include/internal/pycore_ceval.h:40
chilaxan commentedon May 19, 2022
proof of concept that this bug can be used to corrupt memory in a malicious/useful way
tested on Python 3.12.0a0 built with
./configure --enable-optimizationskumaraditya303 commentedon Jul 13, 2022
ASAN report:
GH-91153: Handle mutating __index__ methods in bytearray item assignm…
pythonGH-91153: Handle mutating __index__ methods in bytearray item a…
pythonGH-91153: Handle mutating __index__ methods in bytearray item a…
32 remaining items
Merge branch 'main' into bast0006/pythongh-91153
gh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` when `…
pythongh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` …
[3.13] pythongh-91153: prevent a crash in `bytearray.__setitem__(ind,…
[3.13] gh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)`…
[3.14] gh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)`…
pythongh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` …
pythongh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` …
pythongh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` …
pythongh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` …
[3.14] pythongh-91153: prevent a crash in `bytearray.__setitem__(ind,…
gh-153570: Fix use-after-free in bytearray.take_bytes() with a reentr…
[3.15] gh-153570: Fix use-after-free in bytearray.take_bytes() with a…
pythongh-153570: Fix use-after-free in bytearray.take_bytes() with a …