Skip to content

Use-after-free in TextIOWrapper.tell() with a reentrant decoder #153539

Description

@tonghuaroot

Bug report

Bug description:

The C implementation of TextIOWrapper.tell() uses a borrowed reference to the snapshot's next_input bytes across the decoder's getstate()/decode()/setstate() calls. A decoder whose getstate() re-enters seek() clears the snapshot and frees next_input, so tell() then reads freed memory (a use-after-free; segfaults on a debug build).

import codecs, io

armed = False
class D(codecs.IncrementalDecoder):
    def decode(self, b, final=False):
        return bytes(b).decode("latin-1")
    def getstate(self):
        global armed
        if armed:
            armed = False
            w.seek(0)   # reentrant seek clears the snapshot tell() is using
        return (b"", 0)
    def setstate(self, s): pass

codecs.register(lambda n: codecs.CodecInfo(name="d",
    encode=lambda s, e=0: (s.encode("latin-1"), len(s)),
    decode=lambda b, e=0: (bytes(b).decode("latin-1"), len(b)),
    incrementaldecoder=D) if n == "d" else None)

w = io.TextIOWrapper(io.BufferedReader(io.BytesIO(b"x" * 64)),
                     encoding="d", newline="")
w._CHUNK_SIZE = 8
w.read(5)
armed = True
w.tell()   # use-after-free

textiowrapper_read_chunk already keeps a strong reference to the same borrowed items; tell() does not.

CPython versions tested on:

3.13, 3.14, 3.15, CPython main branch

Operating systems tested on:

macOS

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions