]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-140650: Fix write(), flush() and close() methods of io.BufferedWriter (GH-140653)
authorSachin Shah <39803835+inventshah@users.noreply.github.com>
Wed, 5 Nov 2025 19:15:27 +0000 (14:15 -0500)
committerGitHub <noreply@github.com>
Wed, 5 Nov 2025 19:15:27 +0000 (21:15 +0200)
They could raise SystemError or crash when getting the "closed" attribute
or converting it to boolean raises an exception.

Lib/test/test_io/test_bufferedio.py
Misc/NEWS.d/next/Library/2025-10-27-00-40-49.gh-issue-140650.DYJPJ9.rst [new file with mode: 0644]
Modules/_io/bufferedio.c

index 6e9e96b0e55262faa5f3f36394cf4e4b0c053204..30c34e818b1572845a4ad28411e370ec7b9dab63 100644 (file)
@@ -962,6 +962,27 @@ class CBufferedWriterTest(BufferedWriterTest, SizeofTest, CTestCase):
         with self.assertRaisesRegex(TypeError, "BufferedWriter"):
             self.tp(self.BytesIO(), 1024, 1024, 1024)
 
+    def test_non_boolean_closed_attr(self):
+        # gh-140650: check TypeError is raised
+        class MockRawIOWithoutClosed(self.MockRawIO):
+            closed = NotImplemented
+
+        bufio = self.tp(MockRawIOWithoutClosed())
+        self.assertRaises(TypeError, bufio.write, b"")
+        self.assertRaises(TypeError, bufio.flush)
+        self.assertRaises(TypeError, bufio.close)
+
+    def test_closed_attr_raises(self):
+        class MockRawIOClosedRaises(self.MockRawIO):
+            @property
+            def closed(self):
+                raise ValueError("test")
+
+        bufio = self.tp(MockRawIOClosedRaises())
+        self.assertRaisesRegex(ValueError, "test", bufio.write, b"")
+        self.assertRaisesRegex(ValueError, "test", bufio.flush)
+        self.assertRaisesRegex(ValueError, "test", bufio.close)
+
 
 class PyBufferedWriterTest(BufferedWriterTest, PyTestCase):
     tp = pyio.BufferedWriter
diff --git a/Misc/NEWS.d/next/Library/2025-10-27-00-40-49.gh-issue-140650.DYJPJ9.rst b/Misc/NEWS.d/next/Library/2025-10-27-00-40-49.gh-issue-140650.DYJPJ9.rst
new file mode 100644 (file)
index 0000000..2ae153a
--- /dev/null
@@ -0,0 +1,3 @@
+Fix an issue where closing :class:`io.BufferedWriter` could crash if
+the closed attribute raised an exception on access or could not be
+converted to a boolean.
index 0a2b35025321cf54693f20b926e49822208f44a9..0b4bc4c6b8ad421c32f97f9b1d99827ca391b342 100644 (file)
@@ -362,16 +362,24 @@ _enter_buffered_busy(buffered *self)
     }
 
 #define IS_CLOSED(self) \
-    (!self->buffer || \
+    (!self->buffer ? 1 : \
     (self->fast_closed_checks \
      ? _PyFileIO_closed(self->raw) \
      : buffered_closed(self)))
 
 #define CHECK_CLOSED(self, error_msg) \
-    if (IS_CLOSED(self) && (Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t) == 0)) { \
-        PyErr_SetString(PyExc_ValueError, error_msg); \
-        return NULL; \
-    } \
+    do { \
+        int _closed = IS_CLOSED(self); \
+        if (_closed < 0) { \
+            return NULL; \
+        } \
+        if (_closed && \
+            (Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t) == 0)) \
+        { \
+            PyErr_SetString(PyExc_ValueError, error_msg); \
+            return NULL; \
+        } \
+    } while (0);
 
 #define VALID_READ_BUFFER(self) \
     (self->readable && self->read_end != -1)
@@ -2079,6 +2087,7 @@ _io_BufferedWriter_write_impl(buffered *self, Py_buffer *buffer)
     PyObject *res = NULL;
     Py_ssize_t written, avail, remaining;
     Py_off_t offset;
+    int r;
 
     CHECK_INITIALIZED(self)
 
@@ -2087,7 +2096,11 @@ _io_BufferedWriter_write_impl(buffered *self, Py_buffer *buffer)
 
     /* Issue #31976: Check for closed file after acquiring the lock. Another
        thread could be holding the lock while closing the file. */
-    if (IS_CLOSED(self)) {
+    r = IS_CLOSED(self);
+    if (r < 0) {
+        goto error;
+    }
+    if (r > 0) {
         PyErr_SetString(PyExc_ValueError, "write to closed file");
         goto error;
     }