Skip to content

Commit 9a7dccd

Browse files
cmaloneyashm-devvstinner
authored
[3.14] pythongh-140607: Validate returned byte count in RawIOBase.read (pythonGH-140611) (python#140728)
* [3.14] 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 7c4a8e5 commit 9a7dccd

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
@@ -617,6 +617,8 @@ def read(self, size=-1):
617617
n = self.readinto(b)
618618
if n is None:
619619
return None
620+
if n < 0 or n > len(b):
621+
raise ValueError(f"readinto returned {n} outside buffer size {len(b)}")
620622
del b[n:]
621623
return bytes(b)
622624

Lib/test/test_io.py

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

896+
def test_RawIOBase_read_bounds_checking(self):
897+
# Make sure a `.readinto` call which returns a value outside
898+
# (0, len(buffer)) raises.
899+
class Misbehaved(self.RawIOBase):
900+
def __init__(self, readinto_return) -> None:
901+
self._readinto_return = readinto_return
902+
def readinto(self, b):
903+
return self._readinto_return
904+
905+
with self.assertRaises(ValueError) as cm:
906+
Misbehaved(2).read(1)
907+
self.assertEqual(str(cm.exception), "readinto returned 2 outside buffer size 1")
908+
for bad_size in (2147483647, sys.maxsize, -1, -1000):
909+
with self.assertRaises(ValueError):
910+
Misbehaved(bad_size).read()
911+
896912
def test_types_have_dict(self):
897913
test = (
898914
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
@@ -938,14 +938,21 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n)
938938
return res;
939939
}
940940

941-
n = PyNumber_AsSsize_t(res, PyExc_ValueError);
941+
Py_ssize_t bytes_filled = PyNumber_AsSsize_t(res, PyExc_ValueError);
942942
Py_DECREF(res);
943-
if (n == -1 && PyErr_Occurred()) {
943+
if (bytes_filled == -1 && PyErr_Occurred()) {
944944
Py_DECREF(b);
945945
return NULL;
946946
}
947+
if (bytes_filled < 0 || bytes_filled > n) {
948+
Py_DECREF(b);
949+
PyErr_Format(PyExc_ValueError,
950+
"readinto returned %zd outside buffer size %zd",
951+
bytes_filled, n);
952+
return NULL;
953+
}
947954

948-
res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), n);
955+
res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), bytes_filled);
949956
Py_DECREF(b);
950957
return res;
951958
}

0 commit comments

Comments
 (0)