Skip to content

Commit 1eb62a4

Browse files
committed
fix: crash .TextIOWrapper.seek with a custom __int__ method detaches
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
1 parent 8b64dd8 commit 1eb62a4

File tree

3 files changed

+30
-2
lines changed

3 files changed

+30
-2
lines changed

Lib/test/test_io/test_textio.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,23 @@ def closed(self):
15601560
wrapper = self.TextIOWrapper(raw)
15611561
wrapper.close() # should not crash
15621562

1563+
def test_issue143007(self):
1564+
# gh-143007: Null pointer dereference in TextIOWrapper.seek
1565+
# via re-entrant __int__
1566+
wrapper = self.TextIOWrapper(self.BytesIO(b"x"))
1567+
1568+
class Cookie(int):
1569+
def __new__(cls, wrapper):
1570+
obj = int.__new__(cls, 0)
1571+
obj.wrapper = wrapper
1572+
return obj
1573+
def __int__(self):
1574+
self.wrapper.detach()
1575+
return 0
1576+
1577+
with self.assertRaises(ValueError):
1578+
wrapper.seek(Cookie(wrapper), 0) # should not crash
1579+
15631580

15641581
class PyTextIOWrapperTest(TextIOWrapperTest, PyTestCase):
15651582
shutdown_error = "LookupError: unknown encoding: ascii"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash in :meth:`io.TextIOWrapper.seek` when a custom cookie's
2+
``__int__`` method detaches the underlying buffer.

Modules/_io/textio.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2416,13 +2416,22 @@ typedef struct {
24162416
#endif
24172417

24182418
static int
2419-
textiowrapper_parse_cookie(cookie_type *cookie, PyObject *cookieObj)
2419+
textiowrapper_parse_cookie(textio *self, cookie_type *cookie, PyObject *cookieObj)
24202420
{
24212421
unsigned char buffer[COOKIE_BUF_LEN];
24222422
PyLongObject *cookieLong = (PyLongObject *)PyNumber_Long(cookieObj);
24232423
if (cookieLong == NULL)
24242424
return -1;
24252425

2426+
// gh-143007: PyNumber_Long can call arbitrary code through __int__
2427+
// which may detach the underlying buffer.
2428+
if (self->detached) {
2429+
Py_DECREF(cookieLong);
2430+
PyErr_SetString(PyExc_ValueError,
2431+
"underlying buffer has been detached");
2432+
return -1;
2433+
}
2434+
24262435
if (_PyLong_AsByteArray(cookieLong, buffer, sizeof(buffer),
24272436
PY_LITTLE_ENDIAN, 0, 1) < 0) {
24282437
Py_DECREF(cookieLong);
@@ -2637,7 +2646,7 @@ _io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)
26372646
/* The strategy of seek() is to go back to the safe start point
26382647
* and replay the effect of read(chars_to_skip) from there.
26392648
*/
2640-
if (textiowrapper_parse_cookie(&cookie, cookieObj) < 0)
2649+
if (textiowrapper_parse_cookie(self, &cookie, cookieObj) < 0)
26412650
goto fail;
26422651

26432652
/* Seek back to the safe start point. */

0 commit comments

Comments
 (0)