Skip to content

Commit a1a71ef

Browse files
cmaloneyashm-devvstinner
authored
[3.13] pythongh-140607: Validate returned byte count in RawIOBase.read (pythonGH-140611) (python#140730)
* [3.13] pythongh-140607: Validate returned byte count in RawIOBase.read (pythonGH-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 0f0a362) 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]>
1 parent f3476c6 commit a1a71ef

File tree

4 files changed

+30
-3
lines changed

4 files changed

+30
-3
lines changed

Lib/_pyio.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,8 @@ def read(self, size=-1):
623623
n = self.readinto(b)
624624
if n is None:
625625
return None
626+
if n < 0 or n > len(b):
627+
raise ValueError(f"readinto returned {n} outside buffer size {len(b)}")
626628
del b[n:]
627629
return bytes(b)
628630

Lib/test/test_io.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,22 @@ def test_RawIOBase_read(self):
869869
self.assertEqual(rawio.read(2), None)
870870
self.assertEqual(rawio.read(2), b"")
871871

872+
def test_RawIOBase_read_bounds_checking(self):
873+
# Make sure a `.readinto` call which returns a value outside
874+
# (0, len(buffer)) raises.
875+
class Misbehaved(self.RawIOBase):
876+
def __init__(self, readinto_return) -> None:
877+
self._readinto_return = readinto_return
878+
def readinto(self, b):
879+
return self._readinto_return
880+
881+
with self.assertRaises(ValueError) as cm:
882+
Misbehaved(2).read(1)
883+
self.assertEqual(str(cm.exception), "readinto returned 2 outside buffer size 1")
884+
for bad_size in (2147483647, sys.maxsize, -1, -1000):
885+
with self.assertRaises(ValueError):
886+
Misbehaved(bad_size).read()
887+
872888
def test_types_have_dict(self):
873889
test = (
874890
self.IOBase(),
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Inside :meth:`io.RawIOBase.read`, validate that the count of bytes returned by
2+
:meth:`io.RawIOBase.readinto` is valid (inside the provided buffer).

Modules/_io/iobase.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -931,14 +931,21 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n)
931931
return res;
932932
}
933933

934-
n = PyNumber_AsSsize_t(res, PyExc_ValueError);
934+
Py_ssize_t bytes_filled = PyNumber_AsSsize_t(res, PyExc_ValueError);
935935
Py_DECREF(res);
936-
if (n == -1 && PyErr_Occurred()) {
936+
if (bytes_filled == -1 && PyErr_Occurred()) {
937937
Py_DECREF(b);
938938
return NULL;
939939
}
940+
if (bytes_filled < 0 || bytes_filled > n) {
941+
Py_DECREF(b);
942+
PyErr_Format(PyExc_ValueError,
943+
"readinto returned %zd outside buffer size %zd",
944+
bytes_filled, n);
945+
return NULL;
946+
}
940947

941-
res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), n);
948+
res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), bytes_filled);
942949
Py_DECREF(b);
943950
return res;
944951
}

0 commit comments

Comments
 (0)