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

Null pointer dereference in `TextIOWrapper.close()` via re-entrant `closed` property that detaches `buffer` · Issue #142594 · python/cpython · GitHub

Repository navigation

Null pointer dereference in TextIOWrapper.close() via re-entrant closed property that detaches buffer #142594

Closed

Description

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

Linked PRs

Activity

  1. cmaloney commented on Dec 12, 2025

    Contributor

    This has some similarities to gh-140650 (checking closed can result in an error/exception)

  2. yihong0618 commented on Dec 13, 2025

    Contributor

    The fix seems simple

    diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
    index 65da300abc..d458109ff7 100644
    --- a/Modules/_io/textio.c
    +++ b/Modules/_io/textio.c
    @@ -3150,6 +3150,9 @@ _io_TextIOWrapper_close_impl(textio *self)
         if (r > 0) {
             Py_RETURN_NONE; /* stream already closed */
         }
    +    if (self->detached) {
    +        Py_RETURN_NONE;
    +    }
         else {
             PyObject *exc = NULL;
             if (self->finalizing) {
    
  3. added a commit that references this issue on Dec 15, 2025
  4. added 2 commits that reference this issue on Dec 15, 2025
  5. added a commit that references this issue on Dec 16, 2025
  6. added 2 commits that reference this issue on Dec 16, 2025
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 dirtopic-IOtype-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