Skip to content

Segfault from calling TextIOWrapper.flush in threads on 3.13 free-threaded build #140846

@devdanzin

Description

@devdanzin

Crash report

What happened?

The code below will segfault on a free-threaded 3.13 build, but not on main. This makes me suspicious it's a known issue that wasn't fixed in 3.13 on purpose, but I couldn't find any previous reports of this.

import io
from threading import Thread

class Wrapper(io.TextIOWrapper):
    def __init__(self):
        super().__init__(Writer())

class Writer(io.RawIOBase):
    def writable(self): return True
    def write(self, b): pass

s = Wrapper()

def stress_flush():
    for _ in range(100):
        s.flush()
        s.write("aaa")

for x in range(500):
    try:
        alive = [Thread(target=stress_flush) for x in range(30)]
        for t in alive:
            t.start()

        s.detach()

        for t in alive:
            t.join()
    except: pass

Backtrace from MRE:

Thread 7 "python" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffae8ce6c0 (LWP 29263)]
0x0000555555a1821e in Py_TYPE (ob=0x0) at ./Include/object.h:336
336         return ob->ob_type;
(gdb) bt
#0  0x0000555555a1821e in Py_TYPE (ob=0x0) at ./Include/object.h:336
#1  _PyObject_GetMethod (obj=0x0, name=name@entry=0x5555563a2e08 <_PyRuntime+109832>, method=method@entry=0x7fffad5e68a0) at Objects/object.c:1544
#2  0x0000555555959d41 in PyObject_VectorcallMethod (name=name@entry=0x5555563a2e08 <_PyRuntime+109832>, args=args@entry=0x7fffad5e6860,
    nargsf=<optimized out>, nargsf@entry=9223372036854775810, kwnames=kwnames@entry=0x0) at Objects/call.c:839
#3  0x0000555555da87e3 in PyObject_CallMethodOneArg (self=0x0, name=<optimized out>, arg=arg@entry=0x7fffc0050370)
    at ./Include/cpython/abstract.h:74
#4  0x0000555555dae861 in _textiowrapper_writeflush (self=self@entry=0x7fffb435a210) at ./Modules/_io/textio.c:1629
#5  0x0000555555daf023 in _io_TextIOWrapper_flush_impl (self=self@entry=0x7fffb435a210) at ./Modules/_io/textio.c:3107
#6  0x0000555555daf318 in _io_TextIOWrapper_flush (self=0x7fffb435a210, _unused_ignored=<optimized out>) at ./Modules/_io/clinic/textio.c.h:1108
#7  0x0000555555977567 in method_vectorcall_NOARGS (func=0x7fffb444a180, args=0x52900005f3e8, nargsf=<optimized out>, kwnames=<optimized out>)
    at Objects/descrobject.c:447
#8  0x00005555559583a8 in _PyObject_VectorcallTstate (tstate=0x52a000024210, callable=callable@entry=0x7fffb444a180,
    args=args@entry=0x52900005f3e8, nargsf=9223372036854775809, kwnames=kwnames@entry=0x0) at ./Include/internal/pycore_call.h:168
#9  0x000055555595849b in PyObject_Vectorcall (callable=callable@entry=0x7fffb444a180, args=args@entry=0x52900005f3e8, nargsf=<optimized out>,
    kwnames=kwnames@entry=0x0) at Objects/call.c:327
#10 0x0000555555bc7f05 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=0x52900005f388, throwflag=<optimized out>)
    at Python/generated_cases.c.h:813
#11 0x0000555555befce7 in _PyEval_EvalFrame (tstate=tstate@entry=0x52a000024210, frame=<optimized out>, throwflag=throwflag@entry=0)
    at ./Include/internal/pycore_ceval.h:121
#12 0x0000555555befe56 in _PyEval_Vector (tstate=0x52a000024210, func=0x7fffb47e7b10, locals=locals@entry=0x0, args=0x7fffad7ce030, argcount=1,
    kwnames=0x0) at Python/ceval.c:1820

I wasn't able to reduce the fuzzer's output, so I re-created the crash from first principles, meaning I might have create a MRE for a slightly different issue.

Backtrace as seen in raw fuzzing output (points to _io_TextIOWrapper_close_impl instead):

Thread 1149 "python" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffb0ddb6c0 (LWP 110209)]
0x0000555555a1821e in Py_TYPE (ob=0x0) at ./Include/object.h:336
336         return ob->ob_type;

#0  0x0000555555a1821e in Py_TYPE (ob=0x0) at ./Include/object.h:336
#1  _PyObject_GetMethod (obj=0x0, name=name@entry=0x55555639b818 <_PyRuntime+79768>, method=method@entry=0x7fffafb962e0) at Objects/object.c:1544
#2  0x0000555555959d41 in PyObject_VectorcallMethod (name=<optimized out>, args=args@entry=0x7fffafb93ea0, nargsf=<optimized out>, 
    nargsf@entry=9223372036854775809, kwnames=kwnames@entry=0x0) at Objects/call.c:839
#3  0x0000555555db259f in PyObject_CallMethodNoArgs (name=<optimized out>, self=<optimized out>) at ./Include/cpython/abstract.h:65
#4  _io_TextIOWrapper_close_impl (self=self@entry=0x7fffb4ba8710) at ./Modules/_io/textio.c:3152
#5  0x0000555555db271d in _io_TextIOWrapper_close (self=0x7fffb4ba8710, _unused_ignored=<optimized out>) at ./Modules/_io/clinic/textio.c.h:1131
#6  0x0000555555977567 in method_vectorcall_NOARGS (func=0x7fffb444a1f0, args=0x7fffafbf9240, nargsf=<optimized out>, kwnames=<optimized out>)
    at Objects/descrobject.c:447
#7  0x00005555559583a8 in _PyObject_VectorcallTstate (tstate=tstate@entry=0x52a001ae2210, callable=callable@entry=0x7fffb444a1f0, 
    args=args@entry=0x7fffafbf9240, nargsf=nargsf@entry=1, kwnames=kwnames@entry=0x0) at ./Include/internal/pycore_call.h:168
#8  0x0000555555959d98 in PyObject_VectorcallMethod (name=<optimized out>, args=args@entry=0x7fffafbf9240, nargsf=1, nargsf@entry=9223372036854775809, 
    kwnames=kwnames@entry=0x0) at Objects/call.c:856
#9  0x0000555555d8ab04 in PyObject_CallMethodNoArgs (name=<optimized out>, self=<optimized out>) at ./Include/cpython/abstract.h:65
#10 iobase_finalize (self=self@entry=0x7fffb4ba8710) at ./Modules/_io/iobase.c:316
#11 0x0000555555a68a30 in wrap_del (self=0x7fffb4ba8710, args=<optimized out>, wrapped=0x555555d8a98c <iobase_finalize>) at Objects/typeobject.c:8878
#12 0x00005555559764eb in wrapperdescr_raw_call (descr=0x7fffb44451e0, self=0x7fffb4ba8710, args=args@entry=0x5555563a76a0 <_PyRuntime+128544>, 
    kwds=kwds@entry=0x0) at Objects/descrobject.c:531
#13 0x00005555559765da in wrapper_call (self=self@entry=0x7fffc2c0d300, args=args@entry=0x5555563a76a0 <_PyRuntime+128544>, kwds=kwds@entry=0x0)
    at Objects/descrobject.c:1438
#14 0x000055555595812a in _PyObject_MakeTpCall (tstate=tstate@entry=0x52a001ae2210, callable=callable@entry=0x7fffc2c0d300, args=args@entry=0x52900169e520, 
    nargs=<optimized out>, keywords=keywords@entry=0x0) at Objects/call.c:242
#15 0x0000555555958442 in _PyObject_VectorcallTstate (tstate=0x52a001ae2210, callable=callable@entry=0x7fffc2c0d300, args=args@entry=0x52900169e520, 
    nargsf=<optimized out>, kwnames=kwnames@entry=0x0) at ./Include/internal/pycore_call.h:166
#16 0x000055555595849b in PyObject_Vectorcall (callable=callable@entry=0x7fffc2c0d300, args=args@entry=0x52900169e520, nargsf=<optimized out>, 
    kwnames=kwnames@entry=0x0) at Objects/call.c:327

Found using fusil by @vstinner.

CPython versions tested on:

3.13

Operating systems tested on:

Linux

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

Python 3.13.9+ experimental free-threading build (heads/3.13:30c2661b6e2, Oct 30 2025, 19:36:10) [GCC 13.3.0]

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13bugs and security fixesextension-modulesC modules in the Modules dirtopic-free-threadingtype-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