Skip to content
Open
66 changes: 66 additions & 0 deletions Lib/test/test_io/test_bufferedio.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,28 @@ def test_read1_error_does_not_cause_reentrant_failure(self):
# Used to crash before gh-143689:
self.assertEqual(bufio.read1(1), b"h")

def test_concurrent_close(self):
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm not sure whether it's better keep the tests duplicated for each stream type or to use CommonBufferedTests (but that also runs for the Python versions, so we'd need some extra logic to skip those) or even to create CommonCBufferedTests just for the C versions?

bufio = self.tp(self.MockRawIO())

class EvilIndex:
def __index__(self):
bufio.close()
return 0

with self.assertRaisesRegex(ValueError, "seek of closed file"):
bufio.seek(EvilIndex())

def test_concurrent_detach(self):
bufio = self.tp(self.MockRawIO())

class EvilIndex:
def __index__(self):
bufio.detach()
return 0

with self.assertRaisesRegex(ValueError, "raw stream has been detached"):
bufio.seek(EvilIndex())


class PyBufferedReaderTest(BufferedReaderTest, PyTestCase):
tp = pyio.BufferedReader
Expand Down Expand Up @@ -1002,6 +1024,28 @@ def closed(self):
self.assertRaisesRegex(ValueError, "test", bufio.flush)
self.assertRaisesRegex(ValueError, "test", bufio.close)

def test_concurrent_close(self):
bufio = self.tp(self.MockRawIO())

class EvilIndex:
def __index__(self):
bufio.close()
return 0

with self.assertRaisesRegex(ValueError, "seek of closed file"):
bufio.seek(EvilIndex())

def test_concurrent_detach(self):
bufio = self.tp(self.MockRawIO())

class EvilIndex:
def __index__(self):
bufio.detach()
return 0

with self.assertRaisesRegex(ValueError, "raw stream has been detached"):
bufio.seek(EvilIndex())


class PyBufferedWriterTest(BufferedWriterTest, PyTestCase):
tp = pyio.BufferedWriter
Expand Down Expand Up @@ -1493,6 +1537,28 @@ def test_args_error(self):
with self.assertRaisesRegex(TypeError, "BufferedRandom"):
self.tp(self.BytesIO(), 1024, 1024, 1024)

def test_concurrent_close(self):
bufio = self.tp(self.MockRawIO())

class EvilIndex:
def __index__(self):
bufio.close()
return 0

with self.assertRaisesRegex(ValueError, "seek of closed file"):
bufio.seek(EvilIndex())

def test_concurrent_detach(self):
bufio = self.tp(self.MockRawIO())

class EvilIndex:
def __index__(self):
bufio.detach()
return 0

with self.assertRaisesRegex(ValueError, "raw stream has been detached"):
bufio.seek(EvilIndex())


class PyBufferedRandomTest(BufferedRandomTest, PyTestCase):
tp = pyio.BufferedRandom
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a crash in the ``seek`` method of :class:`~io.BufferedWriter`,
:class:`~io.BufferedReader` and :class:`~io.BufferedRandom` when the
stream is concurrently closed or detached.
8 changes: 4 additions & 4 deletions Modules/_io/bufferedio.c
Original file line number Diff line number Diff line change
Expand Up @@ -1383,17 +1383,17 @@ _io__Buffered_seek_impl(buffered *self, PyObject *targetobj, int whence)
return NULL;
}

target = PyNumber_AsOff_t(targetobj, PyExc_ValueError);
if (target == -1 && PyErr_Occurred())
return NULL;

CHECK_CLOSED(self, "seek of closed file")

_PyIO_State *state = find_io_state_by_def(Py_TYPE(self));
if (_PyIOBase_check_seekable(state, self->raw, Py_True) == NULL) {
return NULL;
}

target = PyNumber_AsOff_t(targetobj, PyExc_ValueError);
if (target == -1 && PyErr_Occurred())
return NULL;

/* SEEK_SET and SEEK_CUR are special because we could seek inside the
buffer. Other whence values must be managed without this optimization.
Some Operating Systems can provide additional values, like
Expand Down
Loading