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

Null pointer dereference in `TextIOWrapper.seek` via re-entrant `__int__` · Issue #143007 · python/cpython · GitHub

Repository navigation

Null pointer dereference in TextIOWrapper.seek via re-entrant __int__ #143007

Closed

Description

What happened?

A crafted integer subclass overrides __int__ so when TextIOWrapper.seek converts the cookie with PyNumber_Long it detaches the wrapper and frees the buffer, yet the seek logic continues with the original self->buffer, leading _io_TextIOWrapper_seek_impl to call a method on a NULL pointer and crash the interpreter.

Proof of Concept:

import io

wrapper = io.TextIOWrapper(io.BytesIO(b"x"))

class Cookie(int):
    def __new__(cls, wrapper):
        obj = int.__new__(cls, 0)
        obj.wrapper = wrapper
        return obj
    def __int__(self):
        self.wrapper.detach()
        return 0

wrapper.seek(Cookie(wrapper), io.SEEK_SET)

Affected Versions:

Details
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) ASAN 1
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 16:55:18) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 16:56:16) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 16:57:16) [Clang 18.1.3 (1ubuntu1)] ASAN 1

Vulnerable Code:

Details
static int
textiowrapper_parse_cookie(cookie_type *cookie, PyObject *cookieObj)
{
    unsigned char buffer[COOKIE_BUF_LEN];
    PyLongObject *cookieLong = (PyLongObject *)PyNumber_Long(cookieObj);
    if (cookieLong == NULL)
        return -1;

    if (_PyLong_AsByteArray(cookieLong, buffer, sizeof(buffer),
                            PY_LITTLE_ENDIAN, 0, 1) < 0) {
        Py_DECREF(cookieLong);
        return -1;
    }
    Py_DECREF(cookieLong);

    memcpy(&cookie->start_pos, buffer + OFF_START_POS, sizeof(cookie->start_pos));
    memcpy(&cookie->dec_flags, buffer + OFF_DEC_FLAGS, sizeof(cookie->dec_flags));
    memcpy(&cookie->bytes_to_feed, buffer + OFF_BYTES_TO_FEED, sizeof(cookie->bytes_to_feed));
    memcpy(&cookie->chars_to_skip, buffer + OFF_CHARS_TO_SKIP, sizeof(cookie->chars_to_skip));
    memcpy(&cookie->need_eof, buffer + OFF_NEED_EOF, sizeof(cookie->need_eof));

    return 0;
}

static PyObject *
_io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)
/*[clinic end generated code: output=0a15679764e2d04d input=4bea78698be23d7e]*/
{
    PyObject *posobj;
    cookie_type cookie;
    PyObject *res;
    int cmp;
    PyObject *snapshot;

    CHECK_ATTACHED(self);
    CHECK_CLOSED(self);

    Py_INCREF(cookieObj);

    if (!self->seekable) {
        _unsupported(self->state, "underlying stream is not seekable");
        goto fail;
    }

    /* ... */

    /* The strategy of seek() is to go back to the safe start point
     * and replay the effect of read(chars_to_skip) from there.
     */
    if (textiowrapper_parse_cookie(&cookie, cookieObj) < 0)
        goto fail;

    /* Seek back to the safe start point. */
    posobj = PyLong_FromOff_t(cookie.start_pos);
    if (posobj == NULL)
        goto fail;

    // self->buf has been set to null
    res = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(seek), posobj);

Sanitizer Output:

Details
=================================================================
==1652913==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x5e52803ff39a bp 0x7fff1072a2c0 sp 0x7fff1072a200 T0)
==1652913==The signal is caused by a READ memory access.
==1652913==Hint: address points to the zero page.
    #0 0x5e52803ff39a in _Py_TYPE Include/object.h:277
    #1 0x5e52803ff39a in _PyObject_GetMethodStackRef Objects/object.c:1698
    #2 0x5e528033f796 in PyObject_VectorcallMethod Objects/call.c:840
    #3 0x5e52807f39a2 in PyObject_CallMethodOneArg Include/cpython/abstract.h:74
    #4 0x5e52807fb66b in _io_TextIOWrapper_seek_impl Modules/_io/textio.c:2647
    #5 0x5e52807fbe97 in _io_TextIOWrapper_seek Modules/_io/clinic/textio.c.h:938
    #6 0x5e528035d6ed in method_vectorcall_FASTCALL Objects/descrobject.c:402
    #7 0x5e528033de7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #8 0x5e528033df72 in PyObject_Vectorcall Objects/call.c:327
    #9 0x5e52805bc056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x5e52805ffe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x5e5280600148 in _PyEval_Vector Python/ceval.c:2001
    #12 0x5e52806003f8 in PyEval_EvalCode Python/ceval.c:884
    #13 0x5e52806f7507 in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x5e52806f7723 in run_mod Python/pythonrun.c:1459
    #15 0x5e52806f857a in pyrun_file Python/pythonrun.c:1293
    #16 0x5e52806fb220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x5e52806fb4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x5e528074c74d in pymain_run_file_obj Modules/main.c:410
    #19 0x5e528074c9b4 in pymain_run_file Modules/main.c:429
    #20 0x5e528074e1b2 in pymain_run_python Modules/main.c:691
    #21 0x5e528074e842 in Py_RunMain Modules/main.c:772
    #22 0x5e528074ea2e in pymain_main Modules/main.c:802
    #23 0x5e528074edb3 in Py_BytesMain Modules/main.c:826
    #24 0x5e52801d2645 in main Programs/python.c:15
    #25 0x7642c742a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #26 0x7642c742a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #27 0x5e52801d2574 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: 202d5dbb945f6d5f5a66ad50e2688d56affd6ecb)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV Include/object.h:277 in _Py_TYPE
==1652913==ABORTING

Linked PRs

Activity

  1. yihong0618 commented on Dec 20, 2025

    Contributor

    we can use the same fix as I metion in #143008 (comment)

    diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
    index f988195256..6bcf9877e8 100644
    --- a/Modules/_io/textio.c
    +++ b/Modules/_io/textio.c
    @@ -2644,6 +2644,7 @@ _io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)
         posobj = PyLong_FromOff_t(cookie.start_pos);
         if (posobj == NULL)
             goto fail;
    +    CHECK_ATTACHED(self);
         res = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(seek), posobj);
         Py_DECREF(posobj);
         if (res == NULL)
    @@ -2965,12 +2966,11 @@ static PyObject *
     _io_TextIOWrapper_truncate_impl(textio *self, PyObject *pos)
     /*[clinic end generated code: output=90ec2afb9bb7745f input=8bddb320834c93ee]*/
     {
    -    CHECK_ATTACHED(self)
     
         if (_PyFile_Flush((PyObject *)self) < 0) {
             return NULL;
         }
    -
    +    CHECK_ATTACHED(self)
         return PyObject_CallMethodOneArg(self->buffer, &_Py_ID(truncate), pos);
     }

    I think when call PyObject_CallMethodOneArg we check the CHECK_ATTACHED before that, can avoid these problem?

    @jackfromeast what do you think

  2. picnixz commented on Jan 1, 2026

  3. cmaloney commented on Jun 10, 2026

    Contributor

    Closing this as the reproducer script here doesn't crash anymore. In gh-143008 a general fix for this type of issue, null pointer dereference of self->buffer after detach, was added. Thanks for the report @jackfromeast!

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