What happened?
TextIOWrapper.close() queries the stream’s closed property. A user-defined RawIOBase.closed can re-enter and call wrapper.detach(), clearing self->buffer. The function then continues and calls self->buffer.close() on a NULL pointer, leading to a null-pointer dereference in _PyObject_GetMethod/_Py_TYPE.
Proof of Concept:
import io class EvilBuffer(io.RawIOBase): def __init__(self): super().__init__() self.wrapper = None self.detached = False @property def closed(self): if self.wrapper is not None and not self.detached: self.detached = True self.wrapper.detach() return False raw = EvilBuffer() wrapper = io.TextIOWrapper(raw) raw.wrapper = wrapper wrapper.close()
Affected Versions:
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:9c4638d, Oct 17 2025, 11:19:30) |
ASAN | 1 |
Python 3.10.19+ (heads/3.10:0142619, Oct 17 2025, 11:20:05) [GCC 13.3.0] |
ASAN | 1 |
Python 3.11.14+ (heads/3.11:88f3f5b, Oct 17 2025, 11:20:44) [GCC 13.3.0] |
ASAN | 1 |
Python 3.12.12+ (heads/3.12:8cb2092, Oct 17 2025, 11:21:35) [GCC 13.3.0] |
ASAN | 1 |
Python 3.13.9+ (heads/3.13:0760a57, Oct 17 2025, 11:22:25) [GCC 13.3.0] |
ASAN | 1 |
Python 3.14.0+ (heads/3.14:889e918, Oct 17 2025, 11:23:02) [GCC 13.3.0] |
ASAN | 1 |
Python 3.15.0a1+ (heads/main:fbf0843, Oct 17 2025, 11:23:37) [GCC 13.3.0] |
ASAN | 1 |
Related Code Snippet
/*[clinic input] @critical_section _io.TextIOWrapper.close [clinic start generated code]*/ static PyObject * _io_TextIOWrapper_close_impl(textio *self) /*[clinic end generated code: output=056ccf8b4876e4f4 input=8e12d7079d5ac5c1]*/ { PyObject *res; int r; CHECK_ATTACHED(self); // Bug: Re-enter the closed property method which detached the textio buffer res = _io_TextIOWrapper_closed_get_impl(self); if (res == NULL) return NULL; r = PyObject_IsTrue(res); Py_DECREF(res); if (r < 0) return NULL; if (r > 0) { Py_RETURN_NONE; /* stream already closed */ } else { PyObject *exc = NULL; if (self->finalizing) { res = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(_dealloc_warn), (PyObject *)self); if (res) { Py_DECREF(res); } else { PyErr_Clear(); } } if (_PyFile_Flush((PyObject *)self) < 0) { exc = PyErr_GetRaisedException(); } // Crash: self->buffer=0x0 after detached. res = PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(close)); if (exc != NULL) { _PyErr_ChainExceptions1(exc); Py_CLEAR(res); } return res; } }
Sanitizer Output:
=================================================================
==1558952==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x64c4d0fea39a bp 0x7ffe22fcb5f0 sp 0x7ffe22fcb530 T0)
==1558952==The signal is caused by a READ memory access.
==1558952==Hint: address points to the zero page.
#0 0x64c4d0fea39a in _Py_TYPE Include/object.h:277
#1 0x64c4d0fea39a in _PyObject_GetMethodStackRef Objects/object.c:1698
#2 0x64c4d0f2a796 in PyObject_VectorcallMethod Objects/call.c:840
#3 0x64c4d13e717d in PyObject_CallMethodNoArgs Include/cpython/abstract.h:65
#4 0x64c4d13e717d in _io_TextIOWrapper_close_impl Modules/_io/textio.c:3169
#5 0x64c4d13e7277 in _io_TextIOWrapper_close Modules/_io/clinic/textio.c.h:1160
#6 0x64c4d0f48571 in method_vectorcall_NOARGS Objects/descrobject.c:448
#7 0x64c4d0f28e7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#8 0x64c4d0f28f72 in PyObject_Vectorcall Objects/call.c:327
#9 0x64c4d11a7056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#10 0x64c4d11eae54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#11 0x64c4d11eb148 in _PyEval_Vector Python/ceval.c:2001
#12 0x64c4d11eb3f8 in PyEval_EvalCode Python/ceval.c:884
#13 0x64c4d12e2507 in run_eval_code_obj Python/pythonrun.c:1365
#14 0x64c4d12e2723 in run_mod Python/pythonrun.c:1459
#15 0x64c4d12e357a in pyrun_file Python/pythonrun.c:1293
#16 0x64c4d12e6220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#17 0x64c4d12e64f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
#18 0x64c4d133774d in pymain_run_file_obj Modules/main.c:410
#19 0x64c4d13379b4 in pymain_run_file Modules/main.c:429
#20 0x64c4d13391b2 in pymain_run_python Modules/main.c:691
#21 0x64c4d1339842 in Py_RunMain Modules/main.c:772
#22 0x64c4d1339a2e in pymain_main Modules/main.c:802
#23 0x64c4d1339db3 in Py_BytesMain Modules/main.c:826
#24 0x64c4d0dbd645 in main Programs/python.c:15
#25 0x7cb5cbe2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#26 0x7cb5cbe2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#27 0x64c4d0dbd574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV Include/object.h:277 in _Py_TYPE
==1558952==ABORTING