https://github.com/python/cpython/commit/a1a71efa6e596363e6c42071f97b73a6b9e847f6
commit: a1a71efa6e596363e6c42071f97b73a6b9e847f6
branch: 3.13
author: Cody Maloney <[email protected]>
committer: vstinner <[email protected]>
date: 2025-10-29T13:31:57+01:00
summary:

[3.13] gh-140607: Validate returned byte count in RawIOBase.read (GH-140611) 
(#140730)

* [3.13] gh-140607: Validate returned byte count in RawIOBase.read (GH-140611)

While `RawIOBase.readinto` should return a count of bytes between 0 and
the length of the given buffer, it is not required to. Add validation
inside RawIOBase.read() that the returned byte count is valid.
(cherry picked from commit 0f0a362768aecb4c791724cce486d8317533a94d)

Co-authored-by: Cody Maloney <[email protected]>
Co-authored-by: Shamil <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>

* fixup: Use older attribute name

---------

Co-authored-by: Shamil <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-10-25-21-04-00.gh-issue-140607.oOZGxS.rst
M Lib/_pyio.py
M Lib/test/test_io.py
M Modules/_io/iobase.c

diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index d65e32252123ca..48c8f770f81f1b 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -623,6 +623,8 @@ def read(self, size=-1):
         n = self.readinto(b)
         if n is None:
             return None
+        if n < 0 or n > len(b):
+            raise ValueError(f"readinto returned {n} outside buffer size 
{len(b)}")
         del b[n:]
         return bytes(b)
 
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 0da611a23cf070..5d83464a0dd440 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -869,6 +869,22 @@ def test_RawIOBase_read(self):
         self.assertEqual(rawio.read(2), None)
         self.assertEqual(rawio.read(2), b"")
 
+    def test_RawIOBase_read_bounds_checking(self):
+        # Make sure a `.readinto` call which returns a value outside
+        # (0, len(buffer)) raises.
+        class Misbehaved(self.RawIOBase):
+            def __init__(self, readinto_return) -> None:
+                self._readinto_return = readinto_return
+            def readinto(self, b):
+                return self._readinto_return
+
+        with self.assertRaises(ValueError) as cm:
+            Misbehaved(2).read(1)
+        self.assertEqual(str(cm.exception), "readinto returned 2 outside 
buffer size 1")
+        for bad_size in (2147483647, sys.maxsize, -1, -1000):
+            with self.assertRaises(ValueError):
+                Misbehaved(bad_size).read()
+
     def test_types_have_dict(self):
         test = (
             self.IOBase(),
diff --git 
a/Misc/NEWS.d/next/Library/2025-10-25-21-04-00.gh-issue-140607.oOZGxS.rst 
b/Misc/NEWS.d/next/Library/2025-10-25-21-04-00.gh-issue-140607.oOZGxS.rst
new file mode 100644
index 00000000000000..cc33217c9f563e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-10-25-21-04-00.gh-issue-140607.oOZGxS.rst
@@ -0,0 +1,2 @@
+Inside :meth:`io.RawIOBase.read`, validate that the count of bytes returned by
+:meth:`io.RawIOBase.readinto` is valid (inside the provided buffer).
diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c
index 12f8b430f9ceb1..618652b371abe2 100644
--- a/Modules/_io/iobase.c
+++ b/Modules/_io/iobase.c
@@ -931,14 +931,21 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n)
         return res;
     }
 
-    n = PyNumber_AsSsize_t(res, PyExc_ValueError);
+    Py_ssize_t bytes_filled = PyNumber_AsSsize_t(res, PyExc_ValueError);
     Py_DECREF(res);
-    if (n == -1 && PyErr_Occurred()) {
+    if (bytes_filled == -1 && PyErr_Occurred()) {
         Py_DECREF(b);
         return NULL;
     }
+    if (bytes_filled < 0 || bytes_filled > n) {
+        Py_DECREF(b);
+        PyErr_Format(PyExc_ValueError,
+                     "readinto returned %zd outside buffer size %zd",
+                     bytes_filled, n);
+        return NULL;
+    }
 
-    res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), n);
+    res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), bytes_filled);
     Py_DECREF(b);
     return res;
 }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to