GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Use-after-free in `_io.BytesIO.writelines` via re-entrant `__buffer__` close · Issue #143378 · python/cpython

Use-after-free in _io.BytesIO.writelines via re-entrant __buffer__ close #143378

New issue
Copy link
New issue
Copy link
Closed
Closed
Use-after-free in _io.BytesIO.writelines via re-entrant __buffer__ close#143378
Copy link
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtopic-IOtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump

Description

@jackfromeast
jackfromeast
opened on Jan 3, 2026
Issue body actions

What happened?

_io.BytesIO.writelines pulls buffers from each iterable element inside write_bytes_lock_held, but a crafted object can close the target in __buffer__, freeing self->buf while the write path still runs, which makes write_bytes_lock_held dereference freed memory.

Proof of Concept:

from _io import BytesIO


class Evil:
    def __init__(self, target):
        self.target = target

    def __buffer__(self, flags):
        self.target.close()
        return memoryview(b"A")


bio = BytesIO()
bio.writelines([Evil(bio)])

Vulnerable Code Snippet:

Click to expand
static PyObject *
_io_BytesIO_writelines_impl(bytesio *self, PyObject *lines)
/*[clinic end generated code: output=03a43a75773bc397 input=5d6a616ae39dc9ca]*/
{
    PyObject *it, *item;

    CHECK_CLOSED(self);

    it = PyObject_GetIter(lines);
    if (it == NULL)
        return NULL;

    while ((item = PyIter_Next(it)) != NULL) {
        Py_ssize_t ret = write_bytes_lock_held(self, item);
        Py_DECREF(item);
        if (ret < 0) {
            Py_DECREF(it);
            return NULL;
        }
    }
    Py_DECREF(it);

    /* See if PyIter_Next failed */
    if (PyErr_Occurred())
        return NULL;

    Py_RETURN_NONE;
}

/* Buggy Re-entrant Path */
Py_NO_INLINE static Py_ssize_t
write_bytes_lock_held(bytesio *self, PyObject *b)
{
    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);

    Py_buffer buf;
    if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0) {  /* Reentrant call site */
        return -1;
    }
    Py_ssize_t len = buf.len;
    /* ... */

    memcpy(PyBytes_AS_STRING(self->buf) + self->pos,  /* crashing pointer derived */ /* Crash site */
           buf.buf, len);
    /* ... */
    return len;
}

/* Clobbering Path */
static PyObject *
_io_BytesIO_close_impl(bytesio *self)
{
    CHECK_EXPORTS(self);
    Py_CLEAR(self->buf);  /* state mutate site */
    Py_RETURN_NONE;
}

Sanitizer Output:

Click to expand
AddressSanitizer:DEADLYSIGNAL
=================================================================
==2192404==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000010 (pc 0x5d4bc5d1b62d bp 0x7fff6c6ec680 sp 0x7fff6c6ec560 T0)
==2192404==The signal is caused by a READ memory access.
==2192404==Hint: address points to the zero page.
    #0 0x5d4bc5d1b62d in write_bytes_lock_held Modules/_io/bytesio.c:215
    #1 0x5d4bc5d1c0cf in _io_BytesIO_writelines_impl Modules/_io/bytesio.c:802
    #2 0x5d4bc5d1c0cf in _io_BytesIO_writelines Modules/_io/clinic/bytesio.c.h:547
    #3 0x5d4bc57213e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #4 0x5d4bc57213e7 in PyObject_Vectorcall Objects/call.c:327
    #5 0x5d4bc55d55a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #6 0x5d4bc5a9fad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #7 0x5d4bc5a9fad6 in _PyEval_Vector Python/ceval.c:2001
    #8 0x5d4bc5a9fad6 in PyEval_EvalCode Python/ceval.c:884
    #9 0x5d4bc5be516e in run_eval_code_obj Python/pythonrun.c:1365
    #10 0x5d4bc5be516e in run_mod Python/pythonrun.c:1459
    #11 0x5d4bc5be9e17 in pyrun_file Python/pythonrun.c:1293
    #12 0x5d4bc5be9e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #13 0x5d4bc5bea93c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #14 0x5d4bc5c5de3c in pymain_run_file_obj Modules/main.c:410
    #15 0x5d4bc5c5de3c in pymain_run_file Modules/main.c:429
    #16 0x5d4bc5c5de3c in pymain_run_python Modules/main.c:691
    #17 0x5d4bc5c5f71e in Py_RunMain Modules/main.c:772
    #18 0x5d4bc5c5f71e in pymain_main Modules/main.c:802
    #19 0x5d4bc5c5f71e in Py_BytesMain Modules/main.c:826
    #20 0x7d5d7d22a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #21 0x7d5d7d22a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #22 0x5d4bc55f9634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV Modules/_io/bytesio.c:215 in write_bytes_lock_held
==2192404==ABORTING

CPython versions tested on:

3.12, 3.13, 3.14, 3.15

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

Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]

Linked PRs

  • gh-143378: Fix UAF when BytesIO is concurrently mutated during write operations #143408
  • [3.14] gh-143378: Fix use-after-free when BytesIO is concurrently mutated during write operations (GH-143408) #143599
  • [3.13] gh-143378: Fix use-after-free when BytesIO is concurrently mutated during write operations (GH-143408) #143600

Activity

  1. StanFromIreland
    added
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    topic-IO
    type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
    on Jan 3, 2026
  2. superboy-zjc
    added a commit that references this issue on Jan 4, 2026

    pythongh-143378: Fix use-after-free BytesIO write via re-entrant buff…

    2409319
  3. superboy-zjc commented on Jan 4, 2026

    @superboy-zjc
    superboy-zjc
    on Jan 4, 2026
    Contributor
    More actions

    PyObject_GetBuffer() can execute user code (e.g. via __buffer__), which may close or otherwise mutate a BytesIO object while write() or writelines() is in progress. This could invalidate the internal buffer and lead to a use-after-free.

    I've proposed a PR here: #143408. Temporarily bump the exports counter while acquiring the input buffer to block re-entrant mutation, and add regression tests to ensure such cases raise BufferError instead of crashing.

  4. superboy-zjc
    added 8 commits that reference this issue on Jan 4, 2026

    pythongh-143378: Fix Sphinx references in NEWS entry

    2c3035c

    pythongh-143378: reduce duplication in unit tests per review

    27c99da

    pythongh-143378: address review comments in NEWS entry

    586fbdc

    pythongh-143378: clean up unit tests

    89b2cb7

    pythongh-143378: refactor the buffer check and unit tests for C and P…

    7805fa1

    pythongh-143378: clean up test comments

    6ac90be

    pythongh-143378: clean up duplicated shape check

    2155e48

    pythongh-143378: clean up duplicated shape check in Python implementa…

    4f0b8b6
  5. serhiy-storchaka
    added a commit that references this issue on Jan 9, 2026

    gh-143378: Fix use-after-free when BytesIO is concurrently mutated du…

    6d54b6a
  6. miss-islington
    added a commit that references this issue on Jan 9, 2026

    pythongh-143378: Fix use-after-free when BytesIO is concurrently muta…

    f4c7ace
  7. serhiy-storchaka
    added a commit that references this issue on Jan 9, 2026

    [3.13] pythongh-143378: Fix use-after-free when BytesIO is concurrent…

    d2e381b
  8. serhiy-storchaka
    closed this as completedon Jan 9, 2026
  9. serhiy-storchaka
    added 2 commits that reference this issue on Jan 9, 2026

    [3.14] gh-143378: Fix use-after-free when BytesIO is concurrently mut…

    f264f10

    [3.13] gh-143378: Fix use-after-free when BytesIO is concurrently mut…

    dcc6117
  10. reidenong
    added a commit that references this issue on Jan 12, 2026

    pythongh-143378: Fix use-after-free when BytesIO is concurrently muta…

    9689c2e
  11. thunder-coding
    added a commit that references this issue on Feb 15, 2026

    pythongh-143378: Fix use-after-free when BytesIO is concurrently muta…

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirC modules in the Modules dirtopic-IOtype-crashA hard crash of the interpreter, possibly with a core dumpA 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

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.