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
Bug report
Bug description:
The C implementation of
TextIOWrapper.tell()uses a borrowed reference to the snapshot'snext_inputbytes across the decoder'sgetstate()/decode()/setstate()calls. A decoder whosegetstate()re-entersseek()clears the snapshot and freesnext_input, sotell()then reads freed memory (a use-after-free; segfaults on a debug build).textiowrapper_read_chunkalready 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