Skip to content

Commit 30e889e

Browse files
[3.6] bpo-33871: Fix os.sendfile(), os.writev(), os.readv(), etc. (GH-7931)
* Fix integer overflow in os.readv(), os.writev(), os.preadv() and os.pwritev() and in os.sendfile() with headers or trailers arguments (on BSD-based OSes and MacOS). * Fix sending the part of the file in os.sendfile() on MacOS. Using the trailers argument could cause sending more bytes from the input file than was specified. Thanks Ned Deily for testing on 32-bit MacOS.. (cherry picked from commit 9d57273) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 0b376eb commit 30e889e

File tree

5 files changed

+88
-32
lines changed

5 files changed

+88
-32
lines changed

Lib/test/test_os.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,12 +2540,14 @@ class Handler(asynchat.async_chat):
25402540
def __init__(self, conn):
25412541
asynchat.async_chat.__init__(self, conn)
25422542
self.in_buffer = []
2543+
self.accumulate = True
25432544
self.closed = False
25442545
self.push(b"220 ready\r\n")
25452546

25462547
def handle_read(self):
25472548
data = self.recv(4096)
2548-
self.in_buffer.append(data)
2549+
if self.accumulate:
2550+
self.in_buffer.append(data)
25492551

25502552
def get_data(self):
25512553
return b''.join(self.in_buffer)
@@ -2627,6 +2629,8 @@ class TestSendfile(unittest.TestCase):
26272629
not sys.platform.startswith("sunos")
26282630
requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS,
26292631
'requires headers and trailers support')
2632+
requires_32b = unittest.skipUnless(sys.maxsize < 2**32,
2633+
'test is only meaningful on 32-bit builds')
26302634

26312635
@classmethod
26322636
def setUpClass(cls):
@@ -2657,17 +2661,13 @@ def tearDown(self):
26572661
self.server.stop()
26582662
self.server = None
26592663

2660-
def sendfile_wrapper(self, sock, file, offset, nbytes, headers=[], trailers=[]):
2664+
def sendfile_wrapper(self, *args, **kwargs):
26612665
"""A higher level wrapper representing how an application is
26622666
supposed to use sendfile().
26632667
"""
2664-
while 1:
2668+
while True:
26652669
try:
2666-
if self.SUPPORT_HEADERS_TRAILERS:
2667-
return os.sendfile(sock, file, offset, nbytes, headers,
2668-
trailers)
2669-
else:
2670-
return os.sendfile(sock, file, offset, nbytes)
2670+
return os.sendfile(*args, **kwargs)
26712671
except OSError as err:
26722672
if err.errno == errno.ECONNRESET:
26732673
# disconnected
@@ -2758,20 +2758,22 @@ def test_keywords(self):
27582758
@requires_headers_trailers
27592759
def test_headers(self):
27602760
total_sent = 0
2761+
expected_data = b"x" * 512 + b"y" * 256 + self.DATA[:-1]
27612762
sent = os.sendfile(self.sockno, self.fileno, 0, 4096,
2762-
headers=[b"x" * 512])
2763+
headers=[b"x" * 512, b"y" * 256])
2764+
self.assertLessEqual(sent, 512 + 256 + 4096)
27632765
total_sent += sent
27642766
offset = 4096
2765-
nbytes = 4096
2766-
while 1:
2767+
while total_sent < len(expected_data):
2768+
nbytes = min(len(expected_data) - total_sent, 4096)
27672769
sent = self.sendfile_wrapper(self.sockno, self.fileno,
27682770
offset, nbytes)
27692771
if sent == 0:
27702772
break
2773+
self.assertLessEqual(sent, nbytes)
27712774
total_sent += sent
27722775
offset += sent
27732776

2774-
expected_data = b"x" * 512 + self.DATA
27752777
self.assertEqual(total_sent, len(expected_data))
27762778
self.client.close()
27772779
self.server.wait()
@@ -2787,12 +2789,30 @@ def test_trailers(self):
27872789
create_file(TESTFN2, file_data)
27882790

27892791
with open(TESTFN2, 'rb') as f:
2790-
os.sendfile(self.sockno, f.fileno(), 0, len(file_data),
2791-
trailers=[b"1234"])
2792+
os.sendfile(self.sockno, f.fileno(), 0, 5,
2793+
trailers=[b"123456", b"789"])
27922794
self.client.close()
27932795
self.server.wait()
27942796
data = self.server.handler_instance.get_data()
2795-
self.assertEqual(data, b"abcdef1234")
2797+
self.assertEqual(data, b"abcde123456789")
2798+
2799+
@requires_headers_trailers
2800+
@requires_32b
2801+
def test_headers_overflow_32bits(self):
2802+
self.server.handler_instance.accumulate = False
2803+
with self.assertRaises(OSError) as cm:
2804+
os.sendfile(self.sockno, self.fileno, 0, 0,
2805+
headers=[b"x" * 2**16] * 2**15)
2806+
self.assertEqual(cm.exception.errno, errno.EINVAL)
2807+
2808+
@requires_headers_trailers
2809+
@requires_32b
2810+
def test_trailers_overflow_32bits(self):
2811+
self.server.handler_instance.accumulate = False
2812+
with self.assertRaises(OSError) as cm:
2813+
os.sendfile(self.sockno, self.fileno, 0, 0,
2814+
trailers=[b"x" * 2**16] * 2**15)
2815+
self.assertEqual(cm.exception.errno, errno.EINVAL)
27962816

27972817
@requires_headers_trailers
27982818
@unittest.skipUnless(hasattr(os, 'SF_NODISKIO'),

Lib/test/test_posix.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
_DUMMY_SYMLINK = os.path.join(tempfile.gettempdir(),
2020
support.TESTFN + '-dummy-symlink')
2121

22+
requires_32b = unittest.skipUnless(sys.maxsize < 2**32,
23+
'test is only meaningful on 32-bit builds')
24+
2225
class PosixTester(unittest.TestCase):
2326

2427
def setUp(self):
@@ -322,6 +325,17 @@ def test_writev(self):
322325
finally:
323326
os.close(fd)
324327

328+
@unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()")
329+
@requires_32b
330+
def test_writev_overflow_32bits(self):
331+
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
332+
try:
333+
with self.assertRaises(OSError) as cm:
334+
os.writev(fd, [b"x" * 2**16] * 2**15)
335+
self.assertEqual(cm.exception.errno, errno.EINVAL)
336+
finally:
337+
os.close(fd)
338+
325339
@unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()")
326340
def test_readv(self):
327341
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
@@ -344,6 +358,19 @@ def test_readv(self):
344358
finally:
345359
os.close(fd)
346360

361+
@unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()")
362+
@requires_32b
363+
def test_readv_overflow_32bits(self):
364+
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
365+
try:
366+
buf = [bytearray(2**16)] * 2**15
367+
with self.assertRaises(OSError) as cm:
368+
os.readv(fd, buf)
369+
self.assertEqual(cm.exception.errno, errno.EINVAL)
370+
self.assertEqual(bytes(buf[0]), b'\0'* 2**16)
371+
finally:
372+
os.close(fd)
373+
347374
@unittest.skipUnless(hasattr(posix, 'dup'),
348375
'test needs posix.dup()')
349376
def test_dup(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed integer overflow in :func:`os.readv`, :func:`os.writev`,
2+
:func:`os.preadv` and :func:`os.pwritev` and in :func:`os.sendfile` with
3+
*headers* or *trailers* arguments (on BSD-based OSes and macOS).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed sending the part of the file in :func:`os.sendfile` on macOS. Using
2+
the *trailers* argument could cause sending more bytes from the input file
3+
than was specified.

Modules/posixmodule.c

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7923,12 +7923,13 @@ os_read_impl(PyObject *module, int fd, Py_ssize_t length)
79237923
}
79247924

79257925
#if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \
7926-
|| defined(__APPLE__))) || defined(HAVE_READV) || defined(HAVE_WRITEV)
7927-
static Py_ssize_t
7926+
|| defined(__APPLE__))) \
7927+
|| defined(HAVE_READV) || defined(HAVE_PREADV) || defined (HAVE_PREADV2) \
7928+
|| defined(HAVE_WRITEV) || defined(HAVE_PWRITEV) || defined (HAVE_PWRITEV2)
7929+
static int
79287930
iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, int type)
79297931
{
79307932
Py_ssize_t i, j;
7931-
Py_ssize_t blen, total = 0;
79327933

79337934
*iov = PyMem_New(struct iovec, cnt);
79347935
if (*iov == NULL) {
@@ -7953,11 +7954,9 @@ iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, in
79537954
}
79547955
Py_DECREF(item);
79557956
(*iov)[i].iov_base = (*buf)[i].buf;
7956-
blen = (*buf)[i].len;
7957-
(*iov)[i].iov_len = blen;
7958-
total += blen;
7957+
(*iov)[i].iov_len = (*buf)[i].len;
79597958
}
7960-
return total;
7959+
return 0;
79617960

79627961
fail:
79637962
PyMem_Del(*iov);
@@ -8166,12 +8165,20 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
81668165
}
81678166
if (i > 0) {
81688167
sf.hdr_cnt = (int)i;
8169-
i = iov_setup(&(sf.headers), &hbuf,
8170-
headers, sf.hdr_cnt, PyBUF_SIMPLE);
8171-
if (i < 0)
8168+
if (iov_setup(&(sf.headers), &hbuf,
8169+
headers, sf.hdr_cnt, PyBUF_SIMPLE) < 0)
81728170
return NULL;
81738171
#ifdef __APPLE__
8174-
sbytes += i;
8172+
for (i = 0; i < sf.hdr_cnt; i++) {
8173+
Py_ssize_t blen = sf.headers[i].iov_len;
8174+
# define OFF_T_MAX 0x7fffffffffffffff
8175+
if (sbytes >= OFF_T_MAX - blen) {
8176+
PyErr_SetString(PyExc_OverflowError,
8177+
"sendfile() header is too large");
8178+
return NULL;
8179+
}
8180+
sbytes += blen;
8181+
}
81758182
#endif
81768183
}
81778184
}
@@ -8192,13 +8199,9 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
81928199
}
81938200
if (i > 0) {
81948201
sf.trl_cnt = (int)i;
8195-
i = iov_setup(&(sf.trailers), &tbuf,
8196-
trailers, sf.trl_cnt, PyBUF_SIMPLE);
8197-
if (i < 0)
8202+
if (iov_setup(&(sf.trailers), &tbuf,
8203+
trailers, sf.trl_cnt, PyBUF_SIMPLE) < 0)
81988204
return NULL;
8199-
#ifdef __APPLE__
8200-
sbytes += i;
8201-
#endif
82028205
}
82038206
}
82048207
}

0 commit comments

Comments
 (0)