Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Lib/_pyio.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,12 +941,12 @@ def read1(self, size=-1):
return self.read(size)

def write(self, b):
if self.closed:
raise ValueError("write to closed file")
if isinstance(b, str):
raise TypeError("can't write str to binary stream")
with memoryview(b) as view:
n = view.nbytes # Size of any bytes-like object
if self.closed:
raise ValueError("write to closed file")
if n == 0:
return 0
pos = self._pos
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_memoryio.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,48 @@ def test_issue5449(self):
self.ioclass(initial_bytes=buf)
self.assertRaises(TypeError, self.ioclass, buf, foo=None)

def test_write_concurrent_close(self):
class B:
def __buffer__(self, flags):
memio.close()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(ValueError, memio.write, B())

# Prevent crashes when memio.write() or memio.writelines()
# concurrently mutates (e.g., closes or exports) 'memio'.
# See: https://github.com/python/cpython/issues/143378.

def test_writelines_concurrent_close(self):
class B:
def __buffer__(self, flags):
memio.close()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(ValueError, memio.writelines, [B()])

def test_write_concurrent_export(self):
class B:
buf = None
def __buffer__(self, flags):
self.buf = memio.getbuffer()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(BufferError, memio.write, B())

def test_writelines_concurrent_export(self):
class B:
buf = None
def __buffer__(self, flags):
self.buf = memio.getbuffer()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(BufferError, memio.writelines, [B()])


class TextIOTestMixin:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix use-after-free crashes when a :class:`~io.BytesIO` object is concurrently mutated during :meth:`~io.RawIOBase.write` or :meth:`~io.IOBase.writelines`.
16 changes: 8 additions & 8 deletions Modules/_io/bytesio.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,18 @@ resize_buffer(bytesio *self, size_t size)
Py_NO_INLINE static Py_ssize_t
write_bytes(bytesio *self, PyObject *b)
{
if (check_closed(self)) {
return -1;
}
if (check_exports(self)) {
return -1;
}

Py_buffer buf;
Py_ssize_t len;
if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0) {
return -1;
}
Py_ssize_t len = buf.len;

if (check_closed(self) || check_exports(self)) {
len = -1;
goto done;
}

len = buf.len;
if (len == 0) {
goto done;
}
Expand Down
Loading