GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-143008: Fix Null pointer dereferences in TextIOWrapper underlying stream access by cmaloney · Pull Request #145957 · python/cpython Conversation
|
|
wow that is cool thanks |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@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". |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I'm a bit confused about the relationship here between |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Less important, but please match the CPython style for C code, e.g.:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
The more complicated case is during deletion where there is an interaction with the base class. Adding to the complexity here all the objects involved and the multiple operations (
Sorry for missing those bits, doing a review of that and will update for it shortly. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
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:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I think it's fine to make the function names shorter, i.e., 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 PyObject *incrementalDecoder = PyObject_CallFunctionObjArgs( (PyObject *)state->PyIncrementalNewlineDecoder_Type, self->decoder, self->readtranslate ? Py_True : Py_False, NULL); |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
This should also resolve gh-143007 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I think thish is ready for re-review @colesbury |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| 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) |
There was a problem 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.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| /* 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. */ |
There was a problem 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.
There was an error while loading. Please reload this page.
There was a problem 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.
There was an error while loading. Please reload this page.
There was a problem 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.
There was an error while loading. Please reload this page.
| ('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)), | ||
| ] | ||
| for name, method in tests: | ||
| with self.subTest(name), set_recursion_limit(100): |
There was a problem 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.
There was an error while loading. Please reload this page.
There was a problem 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.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Removed the recursion
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| wrapper_ref = lambda: None | ||
| del wrapper |
There was a problem 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.
There was an error while loading. Please reload this page.
There was a problem 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.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem 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.
There was an error while loading. Please reload this page.
| from collections import UserList | ||
| from test import support | ||
| from test.support import os_helper, threading_helper | ||
| from test.support import ( |
There was a problem 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.
There was an error while loading. Please reload this page.
There was a problem 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.
There was an error while loading. Please reload this page.
| ('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)), | ||
| ] | ||
| for name, method in tests: | ||
| with self.subTest(name), set_recursion_limit(100): |
There was a problem 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.
There was an error while loading. Please reload this page.
Co-authored-by: Victor Stinner <[email protected]>
|
re: Recursion most the tests don't rely on it. The one that does is when |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem 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.
There was an error while loading. Please reload this page.
| from collections import UserList | ||
| from test import support | ||
| from test.support import os_helper, threading_helper | ||
| from test.support import ( |
There was a problem 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.
There was an error while loading. Please reload this page.
| ('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)), | ||
| ] | ||
| for name, method in tests: | ||
| with self.subTest(name), set_recursion_limit(100): |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Removed the recursion
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
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. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Critical section only backport: gh-151203 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…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]>
…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]>
…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]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.