Skip to content

Commit 3e4b688

Browse files
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 47957da commit 3e4b688

File tree

5 files changed

+114
-32
lines changed

5 files changed

+114
-32
lines changed

Lib/test/test_os.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,12 +2532,14 @@ class Handler(asynchat.async_chat):
25322532
def __init__(self, conn):
25332533
asynchat.async_chat.__init__(self, conn)
25342534
self.in_buffer = []
2535+
self.accumulate = True
25352536
self.closed = False
25362537
self.push(b"220 ready\r\n")
25372538

25382539
def handle_read(self):
25392540
data = self.recv(4096)
2540-
self.in_buffer.append(data)
2541+
if self.accumulate:
2542+
self.in_buffer.append(data)
25412543

25422544
def get_data(self):
25432545
return b''.join(self.in_buffer)
@@ -2618,6 +2620,8 @@ class TestSendfile(unittest.TestCase):
26182620
not sys.platform.startswith("sunos")
26192621
requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS,
26202622
'requires headers and trailers support')
2623+
requires_32b = unittest.skipUnless(sys.maxsize < 2**32,
2624+
'test is only meaningful on 32-bit builds')
26212625

26222626
@classmethod
26232627
def setUpClass(cls):
@@ -2648,17 +2652,13 @@ def tearDown(self):
26482652
self.server.stop()
26492653
self.server = None
26502654

2651-
def sendfile_wrapper(self, sock, file, offset, nbytes, headers=[], trailers=[]):
2655+
def sendfile_wrapper(self, *args, **kwargs):
26522656
"""A higher level wrapper representing how an application is
26532657
supposed to use sendfile().
26542658
"""
2655-
while 1:
2659+
while True:
26562660
try:
2657-
if self.SUPPORT_HEADERS_TRAILERS:
2658-
return os.sendfile(sock, file, offset, nbytes, headers,
2659-
trailers)
2660-
else:
2661-
return os.sendfile(sock, file, offset, nbytes)
2661+
return os.sendfile(*args, **kwargs)
26622662
except OSError as err:
26632663
if err.errno == errno.ECONNRESET:
26642664
# disconnected
@@ -2749,20 +2749,22 @@ def test_keywords(self):
27492749
@requires_headers_trailers
27502750
def test_headers(self):
27512751
total_sent = 0
2752+
expected_data = b"x" * 512 + b"y" * 256 + self.DATA[:-1]
27522753
sent = os.sendfile(self.sockno, self.fileno, 0, 4096,
2753-
headers=[b"x" * 512])
2754+
headers=[b"x" * 512, b"y" * 256])
2755+
self.assertLessEqual(sent, 512 + 256 + 4096)
27542756
total_sent += sent
27552757
offset = 4096
2756-
nbytes = 4096
2757-
while 1:
2758+
while total_sent < len(expected_data):
2759+
nbytes = min(len(expected_data) - total_sent, 4096)
27582760
sent = self.sendfile_wrapper(self.sockno, self.fileno,
27592761
offset, nbytes)
27602762
if sent == 0:
27612763
break
2764+
self.assertLessEqual(sent, nbytes)
27622765
total_sent += sent
27632766
offset += sent
27642767

2765-
expected_data = b"x" * 512 + self.DATA
27662768
self.assertEqual(total_sent, len(expected_data))
27672769
self.client.close()
27682770
self.server.wait()
@@ -2778,12 +2780,30 @@ def test_trailers(self):
27782780
create_file(TESTFN2, file_data)
27792781

27802782
with open(TESTFN2, 'rb') as f:
2781-
os.sendfile(self.sockno, f.fileno(), 0, len(file_data),
2782-
trailers=[b"1234"])
2783+
os.sendfile(self.sockno, f.fileno(), 0, 5,
2784+
trailers=[b"123456", b"789"])
27832785
self.client.close()
27842786
self.server.wait()
27852787
data = self.server.handler_instance.get_data()
2786-
self.assertEqual(data, b"abcdef1234")
2788+
self.assertEqual(data, b"abcde123456789")
2789+
2790+
@requires_headers_trailers
2791+
@requires_32b
2792+
def test_headers_overflow_32bits(self):
2793+
self.server.handler_instance.accumulate = False
2794+
with self.assertRaises(OSError) as cm:
2795+
os.sendfile(self.sockno, self.fileno, 0, 0,
2796+
headers=[b"x" * 2**16] * 2**15)
2797+
self.assertEqual(cm.exception.errno, errno.EINVAL)
2798+
2799+
@requires_headers_trailers
2800+
@requires_32b
2801+
def test_trailers_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+
trailers=[b"x" * 2**16] * 2**15)
2806+
self.assertEqual(cm.exception.errno, errno.EINVAL)
27872807

27882808
@requires_headers_trailers
27892809
@unittest.skipUnless(hasattr(os, 'SF_NODISKIO'),

Lib/test/test_posix.py

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

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

2528
def setUp(self):
@@ -284,6 +287,7 @@ def test_preadv(self):
284287
finally:
285288
os.close(fd)
286289

290+
@unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()")
287291
@unittest.skipUnless(hasattr(posix, 'RWF_HIPRI'), "test needs posix.RWF_HIPRI")
288292
def test_preadv_flags(self):
289293
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
@@ -295,6 +299,19 @@ def test_preadv_flags(self):
295299
finally:
296300
os.close(fd)
297301

302+
@unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()")
303+
@requires_32b
304+
def test_preadv_overflow_32bits(self):
305+
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
306+
try:
307+
buf = [bytearray(2**16)] * 2**15
308+
with self.assertRaises(OSError) as cm:
309+
os.preadv(fd, buf, 0)
310+
self.assertEqual(cm.exception.errno, errno.EINVAL)
311+
self.assertEqual(bytes(buf[0]), b'\0'* 2**16)
312+
finally:
313+
os.close(fd)
314+
298315
@unittest.skipUnless(hasattr(posix, 'pwrite'), "test needs posix.pwrite()")
299316
def test_pwrite(self):
300317
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
@@ -320,6 +337,7 @@ def test_pwritev(self):
320337
finally:
321338
os.close(fd)
322339

340+
@unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()")
323341
@unittest.skipUnless(hasattr(posix, 'os.RWF_SYNC'), "test needs os.RWF_SYNC")
324342
def test_pwritev_flags(self):
325343
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
@@ -334,6 +352,17 @@ def test_pwritev_flags(self):
334352
finally:
335353
os.close(fd)
336354

355+
@unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()")
356+
@requires_32b
357+
def test_pwritev_overflow_32bits(self):
358+
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
359+
try:
360+
with self.assertRaises(OSError) as cm:
361+
os.pwritev(fd, [b"x" * 2**16] * 2**15, 0)
362+
self.assertEqual(cm.exception.errno, errno.EINVAL)
363+
finally:
364+
os.close(fd)
365+
337366
@unittest.skipUnless(hasattr(posix, 'posix_fallocate'),
338367
"test needs posix.posix_fallocate()")
339368
def test_posix_fallocate(self):
@@ -435,6 +464,17 @@ def test_writev(self):
435464
finally:
436465
os.close(fd)
437466

467+
@unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()")
468+
@requires_32b
469+
def test_writev_overflow_32bits(self):
470+
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
471+
try:
472+
with self.assertRaises(OSError) as cm:
473+
os.writev(fd, [b"x" * 2**16] * 2**15)
474+
self.assertEqual(cm.exception.errno, errno.EINVAL)
475+
finally:
476+
os.close(fd)
477+
438478
@unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()")
439479
def test_readv(self):
440480
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
@@ -457,6 +497,19 @@ def test_readv(self):
457497
finally:
458498
os.close(fd)
459499

500+
@unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()")
501+
@requires_32b
502+
def test_readv_overflow_32bits(self):
503+
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
504+
try:
505+
buf = [bytearray(2**16)] * 2**15
506+
with self.assertRaises(OSError) as cm:
507+
os.readv(fd, buf)
508+
self.assertEqual(cm.exception.errno, errno.EINVAL)
509+
self.assertEqual(bytes(buf[0]), b'\0'* 2**16)
510+
finally:
511+
os.close(fd)
512+
460513
@unittest.skipUnless(hasattr(posix, 'dup'),
461514
'test needs posix.dup()')
462515
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
@@ -8026,12 +8026,13 @@ os_read_impl(PyObject *module, int fd, Py_ssize_t length)
80268026
}
80278027

80288028
#if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \
8029-
|| defined(__APPLE__))) || defined(HAVE_READV) || defined(HAVE_WRITEV)
8030-
static Py_ssize_t
8029+
|| defined(__APPLE__))) \
8030+
|| defined(HAVE_READV) || defined(HAVE_PREADV) || defined (HAVE_PREADV2) \
8031+
|| defined(HAVE_WRITEV) || defined(HAVE_PWRITEV) || defined (HAVE_PWRITEV2)
8032+
static int
80318033
iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, int type)
80328034
{
80338035
Py_ssize_t i, j;
8034-
Py_ssize_t blen, total = 0;
80358036

80368037
*iov = PyMem_New(struct iovec, cnt);
80378038
if (*iov == NULL) {
@@ -8056,11 +8057,9 @@ iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, in
80568057
}
80578058
Py_DECREF(item);
80588059
(*iov)[i].iov_base = (*buf)[i].buf;
8059-
blen = (*buf)[i].len;
8060-
(*iov)[i].iov_len = blen;
8061-
total += blen;
8060+
(*iov)[i].iov_len = (*buf)[i].len;
80628061
}
8063-
return total;
8062+
return 0;
80648063

80658064
fail:
80668065
PyMem_Del(*iov);
@@ -8357,12 +8356,20 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
83578356
}
83588357
if (i > 0) {
83598358
sf.hdr_cnt = (int)i;
8360-
i = iov_setup(&(sf.headers), &hbuf,
8361-
headers, sf.hdr_cnt, PyBUF_SIMPLE);
8362-
if (i < 0)
8359+
if (iov_setup(&(sf.headers), &hbuf,
8360+
headers, sf.hdr_cnt, PyBUF_SIMPLE) < 0)
83638361
return NULL;
83648362
#ifdef __APPLE__
8365-
sbytes += i;
8363+
for (i = 0; i < sf.hdr_cnt; i++) {
8364+
Py_ssize_t blen = sf.headers[i].iov_len;
8365+
# define OFF_T_MAX 0x7fffffffffffffff
8366+
if (sbytes >= OFF_T_MAX - blen) {
8367+
PyErr_SetString(PyExc_OverflowError,
8368+
"sendfile() header is too large");
8369+
return NULL;
8370+
}
8371+
sbytes += blen;
8372+
}
83668373
#endif
83678374
}
83688375
}
@@ -8383,13 +8390,9 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
83838390
}
83848391
if (i > 0) {
83858392
sf.trl_cnt = (int)i;
8386-
i = iov_setup(&(sf.trailers), &tbuf,
8387-
trailers, sf.trl_cnt, PyBUF_SIMPLE);
8388-
if (i < 0)
8393+
if (iov_setup(&(sf.trailers), &tbuf,
8394+
trailers, sf.trl_cnt, PyBUF_SIMPLE) < 0)
83898395
return NULL;
8390-
#ifdef __APPLE__
8391-
sbytes += i;
8392-
#endif
83938396
}
83948397
}
83958398
}

0 commit comments

Comments
 (0)