GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-143008: Fix Null pointer dereferences in TextIOWrapper underlying stream access by cmaloney · Pull Request #145957 · python/cpython

Conversation

@cmaloney

@cmaloney cmaloney commented Mar 15, 2026 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

TextIOWrapper keeps its underlying stream in a member called self->buffer. That stream can be detached by user code, such as custom .flush() implementations, resulting in self->buffer being set to NULL. The implementation often checked at the start of functions if self->buffer is in a good state but did not always recheck after other Python code which could modify self->buffer.

The cases which need to be re-checked are hard to spot so rather than rely on reviewer effort make a better safety net by changing all self->buffer access to go through helper functions.

Thank you @yihong0618 for the test, NEWS and an initial implementation in gh-143041.

  • Issue: Null pointer dereference in TextIOWrapper.truncate via re-entrant flush #143008

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@cmaloney @yihong0618
pythongh-143008: Safer TextIOWrapper underlying stream access
42caa08
TextIOWrapper keeps its underlying stream in a member called
`self->buffer`. That stream can be detached by user code, such as custom
`.flush` implementations resulting in `self->buffer` being set to NULL.
The implementation often checked at the start of functions if
`self->buffer` is in a good state, but did not always recheck after
other Python code was called which could modify `self->buffer`.

The cases which need to be re-checked are hard to spot so rather than
rely on reviewer effort create better safety by making all self->buffer
access go through helper functions.

Thank you yihong0618 for the test, NEWS and initial implementation in
pythongh-143041.

Co-authored-by: yihong0618 <[email protected]>
@bedevere-app bedevere-app Bot mentioned this pull request Mar 15, 2026
Null pointer dereference in TextIOWrapper.truncate via re-entrant flush #143008
Closed
@bedevere-app bedevere-app Bot added the awaiting review label Mar 15, 2026
@yihong0618

yihong0618 commented Mar 15, 2026

Copy link
Copy Markdown
Contributor

wow that is cool thanks

cmaloney reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@cmaloney cmaloney added needs backport to 3.13 only security fixes needs backport to 3.14 bugs and security fixes labels Mar 15, 2026
cmaloney added 2 commits March 14, 2026 19:12
@cmaloney
fixup: Refer to existing helpers
551c3eb
@cmaloney
fixup: More precise recursion errorcatching
4f455f4
@cmaloney cmaloney changed the title gh-143008: Safer TextIOWrapper underlying stream access gh-143008: Fix Null Pointer Dereferences in TextIOWrapper underlying stream access Mar 17, 2026
@cmaloney cmaloney changed the title gh-143008: Fix Null Pointer Dereferences in TextIOWrapper underlying stream access gh-143008: Fix Null pointer dereferences in TextIOWrapper underlying stream access Mar 17, 2026
cmaloney added 2 commits March 16, 2026 23:08
@cmaloney
fixup: Formatting fixes, simplify test
ec39a61
@cmaloney
fixup: correct commment
98bc865
yihong0618
yihong0618 reviewed Mar 17, 2026
View reviewed changes
Comment thread Lib/test/test_io/test_textio.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

cmaloney and others added 2 commits March 30, 2026 00:33
@cmaloney
PEP 7 fixes; simplify some comments
c52559b
@cmaloney
Merge branch 'main' into textio_nullbuffer
210b21b
@cmaloney

cmaloney commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

@colesbury could you take a look as you've done some recent work on TextIO safety around threading. These cases are generally single threaded but generally the pattern "Implementation usually checked state; then did an operation which could modify it and didn't recheck".

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@colesbury

colesbury commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

I'm a bit confused about the relationship here between _textiowrapper_buffer_safe and CHECK_ATTACHED. When can buffer be NULL and CHECK_ATTACHED not trigger?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@colesbury
colesbury self-requested a review April 17, 2026 17:57
@colesbury

colesbury commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Less important, but please match the CPython style for C code, e.g.:

  • { on new lines for functions definitions
  • when wrapping function calls, indent continuation lines to align with the call site
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@cmaloney

cmaloney commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

I'm a bit confused about the relationship here between _textiowrapper_buffer_safe and CHECK_ATTACHED. When can buffer be NULL and CHECK_ATTACHED not trigger?

CHECK_ATTACHED, by way of CHECK_INITIALIZED, checks self->ok first. The self->ok flag is set to 0 at start of __init__, 1 at the end, and cleared/reset to 0 during .clear() as well as at the start of the Py_tp_dealloc. During __init__ there are calls to check the file is in a good state and get some attributes (remember: self->buf is the underlying file-like object not an internal buffer).

The more complicated case is during deletion where there is an interaction with the base class. TextIOWrapper implements Py_tp_dealloc and its base class (TextIOBase which has a base class IOBase) implements a Py_tp_finalize with the function iobase_finalize. That function tries to ensure all data is written in the full I/O "stack" before any part of it is closed out of order. It tries to have Text I/O flush and close, then Buffered I/O (self->buf), then the Raw I/O. If there is an underlying file (self->buf != NULL) during those operations need to try and write data in the TextIOWrapper even though self->ok is 0.

Adding to the complexity here all the objects involved and the multiple operations (flush(), close(), .closed) can be overridden and could call TextIOWrapper.clear() or TextIOWrapper.detach() causing self->buf to become NULL.

Less important, but please match the CPython style for C code, e.g.:

Sorry for missing those bits, doing a review of that and will update for it shortly.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@cmaloney
Improve PEP-7
150fcee
@cmaloney

cmaloney commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

There are a couple cases I'm not sure how to PEP 7, in particular:

/* hits 81 cols */
        PyObject *bytes = _textiowrapper_buffer_callmethod_noargs(self,
                                                                  &_Py_ID(read));
/* hits 81 cols */
            res = _textiowrapper_buffer_callmethod_onearg(self,
                                                          &_Py_ID(_dealloc_warn),
                                                          (PyObject *)self);


PyObject *input_chunk = _textiowrapper_buffer_callmethod_onearg(self,
            &_Py_ID(read), bytes_to_feed);

A couple directions I looked at but don't know how to decide between:

  1. Make the variable name shorter (increases diff)
  2. Make the function name shorter (ex. _textiowrapper_buf_... or _textio_buffer_, _textiowrapper_buf_callmeth_noargs)
  3. Whats the right indentation to just put on the next line?
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@colesbury

colesbury commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

I think it's fine to make the function names shorter, i.e., buffer_callmethod_onearg instead of _textiowrapper_buffer_callmethod_onearg. File-scope static functions don't need a prefix or underscore.

In general the preference is for wrapping function calls like the following (from PEP 7 example):

PyErr_Format(PyExc_TypeError,
             "cannot create '%.100s' instances",
             type->tp_name);

But if that's not practical due to overly long lines, than just try to fit the style of surround code. For example, from elsewhere in textio.c:

        PyObject *incrementalDecoder = PyObject_CallFunctionObjArgs(
            (PyObject *)state->PyIncrementalNewlineDecoder_Type,
            self->decoder, self->readtranslate ? Py_True : Py_False, NULL);
cmaloney reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cmaloney added 4 commits April 21, 2026 00:06
@cmaloney
PEP-7: Move to shorter function names, reformat arg lists
d7f14fc
@cmaloney
Improve NEWS grammer
df212a5
@cmaloney
Simplify comment
d1a1a98
@cmaloney
Fix comment referring to function name
009cf12
@cmaloney

cmaloney commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

This should also resolve gh-143007

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@cmaloney

cmaloney commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

I think thish is ready for re-review @colesbury

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed May 28, 2026
View reviewed changes
Comment thread Modules/_io/textio.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_io/textio.c
leading to NULL pointer dereferences (see gh-143008, gh-142594). Protect against
that by using helpers to check self->buffer validity at callsites. */
static PyObject *
buffer_access_safe(textio *self)

@vstinner vstinner May 28, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to rename buffer_xxx() functions to textiowrapper_buffer_xxx(), and rename this function to textiowrapper_buffer_get().

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@cmaloney cmaloney Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started like that (see: d7f14fc) but it made some of the PEP-7 formatting really bad so moved to a shorter name

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Modules/_io/textio.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_io/textio.c Outdated
Comment on lines +3324 to +3327
/* During destruction self->ok is 0 but self->buffer is non-NULL and this
needs to error in that case which the safe buffer wrapper does not.

Match original behavior by calling CHECK_ATTACHED explicitly. */

@vstinner vstinner May 28, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

textiowrapper_dealloc() sets ok to 0 and almost immediately sets buffer to NULL. So I don't understand well when it's possible that ok is 0 and buffer is non-NULL.

This comment is unclear to me. I suggest rephrasing it to something like:

"Call CHECK_ATTACHED() to raise an exception if ..., when ... happens."

The "Match original behavior" part is unclear to me.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@cmaloney cmaloney Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to checking self->ok directly and just returning true in that case which differs more in this bug fix but I think is a lot more understandable. Described a couple cases this is hit by the test suite.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
cmaloney added 2 commits June 4, 2026 19:48
@cmaloney
Fix clinic merge
2581e6c
@cmaloney
Fix blurb
feafb90
vstinner
vstinner approved these changes Jun 5, 2026
View reviewed changes

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

I don't think that it's worth it to backport the "check buffer" fixes. #143008 issue was found by fuzzing. Regular Python code should never meet this bug in practice. Triggering the code requires to design a very precise class which misbehaves.

But IMO the critical section on TextIOWrapper.__init__ should be backported. Once this PR is merged, you can create a fix for 3.15 (and then we can backport it).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cmaloney reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/test/test_io/test_textio.py Outdated
('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)),
]
for name, method in tests:
with self.subTest(name), set_recursion_limit(100):

@vstinner vstinner Jun 5, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you reduce the recursion limit?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka Jun 6, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use unlimited recursion at all? Call the method, detach the buffer, set the flag, call the method yet one time if the flag was not set.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cmaloney reacted with eyes emoji
All reactions
  • 👀 1 reaction

@cmaloney cmaloney Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the recursion

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/test/test_io/test_textio.py Outdated
Comment on lines +1602 to +1603
wrapper_ref = lambda: None
del wrapper

@vstinner vstinner Jun 5, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it work to just assign wrapper to None (and leave wrapper_ref unchanged)?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@cmaloney cmaloney Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed wrapper_ref entirely and reworked how the classes work to simplify

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Modules/_io/textio.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_io/textio.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting review labels Jun 5, 2026
serhiy-storchaka
serhiy-storchaka reviewed Jun 6, 2026
View reviewed changes

@serhiy-storchaka serhiy-storchaka left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, would not it be simpler if set buffer to None instead of NULL? How does it work in the Python implementation?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_io/test_textio.py Outdated
from collections import UserList
from test import support
from test.support import os_helper, threading_helper
from test.support import (

@serhiy-storchaka serhiy-storchaka Jun 6, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to not mix import of module attributes and submodules in one statement.

Complex multiline import is less readable than separate oneline imports.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@cmaloney cmaloney Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed set_recursion_limit entirely

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/test/test_io/test_textio.py Outdated
('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)),
]
for name, method in tests:
with self.subTest(name), set_recursion_limit(100):

@serhiy-storchaka serhiy-storchaka Jun 6, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use unlimited recursion at all? Call the method, detach the buffer, set the flag, call the method yet one time if the flag was not set.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cmaloney reacted with eyes emoji
All reactions
  • 👀 1 reaction
@cmaloney @vstinner
Apply suggestions from code review
c0bdd84
Co-authored-by: Victor Stinner <[email protected]>
@cmaloney

cmaloney commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

_pyio uses None to indicate no buffer. Can definitely migrate _io.TextIOWrapper to do that but that's a substantial code delta for an edge case and doesn't simplify fixing the time of check time of use issue here. Moving to None would make it calling methods on None rather than a null pointer de-reference. Would still need most this change of "always check just before using".

re: Recursion most the tests don't rely on it. The one that does is when flush() calls detach itself which results in another call to flush(). gh-143041 resolved that by making a new "state" effectively of "are detaching, don't detach again". Can do a similar pattern here if want to eliminate the recursion potentially: In .detach() change self->detached and/or self->ok before calling _PyFile_Flush(self) and making sure .flush() handles that case appropriately. I'm hesitant to do that as it changes the set of states a TextIOWrapper can be in a bit and I don't have those well mapped out mentally. Limiting recursion means the test exits after some number of recursion instances which validates it no longer segfaults / NULL pointer dereference is resolved.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cmaloney added 4 commits June 8, 2026 23:06
@cmaloney
Remove wrapper_ref, use simpler classes, wrapper local only
d7bf513
@cmaloney
Separate attribute and submodule imports
a24957d
@cmaloney
Fix NULL with no exception after flush in detach
c2cb455
@cmaloney
remove recursion. Add make_text helper and DetachOnce
cbcd21a
cmaloney
cmaloney commented Jun 9, 2026
View reviewed changes

@cmaloney cmaloney left a comment

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a refactoring that removed the recursion while not making too much complex

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_io/test_textio.py Outdated
from collections import UserList
from test import support
from test.support import os_helper, threading_helper
from test.support import (

@cmaloney cmaloney Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed set_recursion_limit entirely

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/test/test_io/test_textio.py Outdated
('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)),
]
for name, method in tests:
with self.subTest(name), set_recursion_limit(100):

@cmaloney cmaloney Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the recursion

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@cmaloney cmaloney removed needs backport to 3.13 only security fixes needs backport to 3.14 bugs and security fixes needs backport to 3.15 bugs and security fixes labels Jun 9, 2026
@vstinner
vstinner merged commit db4b194 into python:main Jun 9, 2026
61 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting merge label Jun 9, 2026
@vstinner

vstinner commented Jun 9, 2026

Copy link
Copy Markdown
Member

Merged, thanks @cmaloney for the fix.

No one should meet this bug in practice, since it requires a custom buffer object which detach() when the stream is not supposed to be detached. But well, it's good to fix the code to support this corner case.

@cmaloney: You can create a change for the 3.15 branch to backport the critical section if you want.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner vstinner mentioned this pull request Jun 9, 2026
gh-143008: fix TextIOWrapper.truncate via re-entrant flush #143041
Closed
@cmaloney
cmaloney deleted the textio_nullbuffer branch June 9, 2026 16:59
@cmaloney

cmaloney commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Critical section only backport: gh-151203

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Jun 9, 2026
[3.15] gh-143008: Fix race re-initializing TextIOWrapper #151203
Merged
gh-143007: fix crash .TextIOWrapper.seek with a custom __int__ method detaches #143043
Closed
philthompson10 pushed a commit to philthompson10/cpython that referenced this pull request Jun 17, 2026
@cmaloney @yihong0618
@vstinner @philthompson10
pythongh-143008: Fix Null pointer dereferences in TextIOWrapper under…
c24e0bb
…lying stream access (python#145957)

TextIOWrapper keeps its underlying stream in a member called
`self->buffer`. That stream can be detached by user code, such as custom
`.flush` implementations resulting in `self->buffer` being set to NULL.
The implementation often checked at the start of functions if
`self->buffer` is in a good state, but did not always recheck after
other Python code was called which could modify `self->buffer`.

The cases which need to be re-checked are hard to spot so rather than
rely on reviewer effort create better safety by making all self->buffer
access go through helper functions.

Thank you yihong0618 for the test, NEWS and initial implementation in
pythongh-143041.

Co-authored-by: yihong0618 <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>
@BHUVANSH855 BHUVANSH855 mentioned this pull request Jul 31, 2026
NULL pointer dereference in BufferedIO methods after re-entrant detach() #154997
Open
vstinner added a commit that referenced this pull request Oct 16, 2026
@vstinner @cmaloney
@yihong0618 @brijkapadia @ashm-dev
[3.15] gh-157364: Fix use-after-free in io.TextIOWrapper during reent…
ea1312b
…rant detach (#159591)

* gh-143008: Fix Null pointer dereferences in TextIOWrapper underlying stream access (#145957)

TextIOWrapper keeps its underlying stream in a member called
`self->buffer`. That stream can be detached by user code, such as custom
`.flush` implementations resulting in `self->buffer` being set to NULL.
The implementation often checked at the start of functions if
`self->buffer` is in a good state, but did not always recheck after
other Python code was called which could modify `self->buffer`.

The cases which need to be re-checked are hard to spot so rather than
rely on reviewer effort create better safety by making all self->buffer
access go through helper functions.

Thank you yihong0618 for the test, NEWS and initial implementation in
gh-143041.

Co-authored-by: yihong0618 <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>
(cherry picked from commit db4b194)

* gh-154523: Fix data-race in `TextIOWrapper.detach()` (#154565)

(cherry picked from commit 8440a6a)

* gh-157364: Fix use-after-free in io.TextIOWrapper during reentrant detach (#157370)

Co-authored-by: Victor Stinner <[email protected]>
(cherry picked from commit e19dc47)

---------

Co-authored-by: Cody Maloney <[email protected]>
Co-authored-by: yihong0618 <[email protected]>
Co-authored-by: Brij Kapadia <[email protected]>
Co-authored-by: Shamil <[email protected]>
@vstinner vstinner mentioned this pull request Oct 16, 2026
[3.14] gh-157364: Fix use-after-free in io.TextIOWrapper during reentrant detach #159600
Merged
vstinner added a commit that referenced this pull request Oct 16, 2026
@vstinner @cmaloney
@yihong0618 @brijkapadia @ashm-dev
[3.14] gh-157364: Fix use-after-free in io.TextIOWrapper during reent…
13fc16c
…rant detach (#159600)

[3.15] gh-157364: Fix use-after-free in io.TextIOWrapper during reentrant detach (#159591)

* gh-143008: Fix Null pointer dereferences in TextIOWrapper underlying stream access (#145957)

TextIOWrapper keeps its underlying stream in a member called
`self->buffer`. That stream can be detached by user code, such as custom
`.flush` implementations resulting in `self->buffer` being set to NULL.
The implementation often checked at the start of functions if
`self->buffer` is in a good state, but did not always recheck after
other Python code was called which could modify `self->buffer`.

The cases which need to be re-checked are hard to spot so rather than
rely on reviewer effort create better safety by making all self->buffer
access go through helper functions.

Thank you yihong0618 for the test, NEWS and initial implementation in
gh-143041.



(cherry picked from commit db4b194)

* gh-154523: Fix data-race in `TextIOWrapper.detach()` (#154565)

(cherry picked from commit 8440a6a)

* gh-157364: Fix use-after-free in io.TextIOWrapper during reentrant detach (#157370)


(cherry picked from commit e19dc47)

---------





(cherry picked from commit ea1312b)

Co-authored-by: Cody Maloney <[email protected]>
Co-authored-by: yihong0618 <[email protected]>
Co-authored-by: Brij Kapadia <[email protected]>
Co-authored-by: Shamil <[email protected]>
@chrullrich chrullrich mentioned this pull request Oct 24, 2026
Bug: Cannot import petl with Python 3.15.1 petl-developers/petl#768
Closed
1 task done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@serhiy-storchaka serhiy-storchaka serhiy-storchaka left review comments
@vstinner vstinner vstinner approved these changes
@colesbury colesbury Awaiting requested review from colesbury
+1 more reviewer
@yihong0618 yihong0618 yihong0618 left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@cmaloney @yihong0618 @colesbury @vstinner @serhiy-storchaka
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.