Unverified Kaydet (Commit) 9d572732 authored tarafından Serhiy Storchaka's avatar Serhiy Storchaka Kaydeden (comit) GitHub

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.
üst f1d36d8e
...@@ -2532,12 +2532,14 @@ class SendfileTestServer(asyncore.dispatcher, threading.Thread): ...@@ -2532,12 +2532,14 @@ class SendfileTestServer(asyncore.dispatcher, threading.Thread):
def __init__(self, conn): def __init__(self, conn):
asynchat.async_chat.__init__(self, conn) asynchat.async_chat.__init__(self, conn)
self.in_buffer = [] self.in_buffer = []
self.accumulate = True
self.closed = False self.closed = False
self.push(b"220 ready\r\n") self.push(b"220 ready\r\n")
def handle_read(self): def handle_read(self):
data = self.recv(4096) data = self.recv(4096)
self.in_buffer.append(data) if self.accumulate:
self.in_buffer.append(data)
def get_data(self): def get_data(self):
return b''.join(self.in_buffer) return b''.join(self.in_buffer)
...@@ -2618,6 +2620,8 @@ class TestSendfile(unittest.TestCase): ...@@ -2618,6 +2620,8 @@ class TestSendfile(unittest.TestCase):
not sys.platform.startswith("sunos") not sys.platform.startswith("sunos")
requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS, requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS,
'requires headers and trailers support') 'requires headers and trailers support')
requires_32b = unittest.skipUnless(sys.maxsize < 2**32,
'test is only meaningful on 32-bit builds')
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
...@@ -2648,17 +2652,13 @@ class TestSendfile(unittest.TestCase): ...@@ -2648,17 +2652,13 @@ class TestSendfile(unittest.TestCase):
self.server.stop() self.server.stop()
self.server = None self.server = None
def sendfile_wrapper(self, sock, file, offset, nbytes, headers=[], trailers=[]): def sendfile_wrapper(self, *args, **kwargs):
"""A higher level wrapper representing how an application is """A higher level wrapper representing how an application is
supposed to use sendfile(). supposed to use sendfile().
""" """
while 1: while True:
try: try:
if self.SUPPORT_HEADERS_TRAILERS: return os.sendfile(*args, **kwargs)
return os.sendfile(sock, file, offset, nbytes, headers,
trailers)
else:
return os.sendfile(sock, file, offset, nbytes)
except OSError as err: except OSError as err:
if err.errno == errno.ECONNRESET: if err.errno == errno.ECONNRESET:
# disconnected # disconnected
...@@ -2749,20 +2749,22 @@ class TestSendfile(unittest.TestCase): ...@@ -2749,20 +2749,22 @@ class TestSendfile(unittest.TestCase):
@requires_headers_trailers @requires_headers_trailers
def test_headers(self): def test_headers(self):
total_sent = 0 total_sent = 0
expected_data = b"x" * 512 + b"y" * 256 + self.DATA[:-1]
sent = os.sendfile(self.sockno, self.fileno, 0, 4096, sent = os.sendfile(self.sockno, self.fileno, 0, 4096,
headers=[b"x" * 512]) headers=[b"x" * 512, b"y" * 256])
self.assertLessEqual(sent, 512 + 256 + 4096)
total_sent += sent total_sent += sent
offset = 4096 offset = 4096
nbytes = 4096 while total_sent < len(expected_data):
while 1: nbytes = min(len(expected_data) - total_sent, 4096)
sent = self.sendfile_wrapper(self.sockno, self.fileno, sent = self.sendfile_wrapper(self.sockno, self.fileno,
offset, nbytes) offset, nbytes)
if sent == 0: if sent == 0:
break break
self.assertLessEqual(sent, nbytes)
total_sent += sent total_sent += sent
offset += sent offset += sent
expected_data = b"x" * 512 + self.DATA
self.assertEqual(total_sent, len(expected_data)) self.assertEqual(total_sent, len(expected_data))
self.client.close() self.client.close()
self.server.wait() self.server.wait()
...@@ -2778,12 +2780,30 @@ class TestSendfile(unittest.TestCase): ...@@ -2778,12 +2780,30 @@ class TestSendfile(unittest.TestCase):
create_file(TESTFN2, file_data) create_file(TESTFN2, file_data)
with open(TESTFN2, 'rb') as f: with open(TESTFN2, 'rb') as f:
os.sendfile(self.sockno, f.fileno(), 0, len(file_data), os.sendfile(self.sockno, f.fileno(), 0, 5,
trailers=[b"1234"]) trailers=[b"123456", b"789"])
self.client.close() self.client.close()
self.server.wait() self.server.wait()
data = self.server.handler_instance.get_data() data = self.server.handler_instance.get_data()
self.assertEqual(data, b"abcdef1234") self.assertEqual(data, b"abcde123456789")
@requires_headers_trailers
@requires_32b
def test_headers_overflow_32bits(self):
self.server.handler_instance.accumulate = False
with self.assertRaises(OSError) as cm:
os.sendfile(self.sockno, self.fileno, 0, 0,
headers=[b"x" * 2**16] * 2**15)
self.assertEqual(cm.exception.errno, errno.EINVAL)
@requires_headers_trailers
@requires_32b
def test_trailers_overflow_32bits(self):
self.server.handler_instance.accumulate = False
with self.assertRaises(OSError) as cm:
os.sendfile(self.sockno, self.fileno, 0, 0,
trailers=[b"x" * 2**16] * 2**15)
self.assertEqual(cm.exception.errno, errno.EINVAL)
@requires_headers_trailers @requires_headers_trailers
@unittest.skipUnless(hasattr(os, 'SF_NODISKIO'), @unittest.skipUnless(hasattr(os, 'SF_NODISKIO'),
......
...@@ -20,6 +20,9 @@ import warnings ...@@ -20,6 +20,9 @@ import warnings
_DUMMY_SYMLINK = os.path.join(tempfile.gettempdir(), _DUMMY_SYMLINK = os.path.join(tempfile.gettempdir(),
support.TESTFN + '-dummy-symlink') support.TESTFN + '-dummy-symlink')
requires_32b = unittest.skipUnless(sys.maxsize < 2**32,
'test is only meaningful on 32-bit builds')
class PosixTester(unittest.TestCase): class PosixTester(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -284,6 +287,7 @@ class PosixTester(unittest.TestCase): ...@@ -284,6 +287,7 @@ class PosixTester(unittest.TestCase):
finally: finally:
os.close(fd) os.close(fd)
@unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()")
@unittest.skipUnless(hasattr(posix, 'RWF_HIPRI'), "test needs posix.RWF_HIPRI") @unittest.skipUnless(hasattr(posix, 'RWF_HIPRI'), "test needs posix.RWF_HIPRI")
def test_preadv_flags(self): def test_preadv_flags(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT) fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
...@@ -295,6 +299,19 @@ class PosixTester(unittest.TestCase): ...@@ -295,6 +299,19 @@ class PosixTester(unittest.TestCase):
finally: finally:
os.close(fd) os.close(fd)
@unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()")
@requires_32b
def test_preadv_overflow_32bits(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
buf = [bytearray(2**16)] * 2**15
with self.assertRaises(OSError) as cm:
os.preadv(fd, buf, 0)
self.assertEqual(cm.exception.errno, errno.EINVAL)
self.assertEqual(bytes(buf[0]), b'\0'* 2**16)
finally:
os.close(fd)
@unittest.skipUnless(hasattr(posix, 'pwrite'), "test needs posix.pwrite()") @unittest.skipUnless(hasattr(posix, 'pwrite'), "test needs posix.pwrite()")
def test_pwrite(self): def test_pwrite(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT) fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
...@@ -320,6 +337,7 @@ class PosixTester(unittest.TestCase): ...@@ -320,6 +337,7 @@ class PosixTester(unittest.TestCase):
finally: finally:
os.close(fd) os.close(fd)
@unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()")
@unittest.skipUnless(hasattr(posix, 'os.RWF_SYNC'), "test needs os.RWF_SYNC") @unittest.skipUnless(hasattr(posix, 'os.RWF_SYNC'), "test needs os.RWF_SYNC")
def test_pwritev_flags(self): def test_pwritev_flags(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT) fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
...@@ -334,6 +352,17 @@ class PosixTester(unittest.TestCase): ...@@ -334,6 +352,17 @@ class PosixTester(unittest.TestCase):
finally: finally:
os.close(fd) os.close(fd)
@unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()")
@requires_32b
def test_pwritev_overflow_32bits(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
with self.assertRaises(OSError) as cm:
os.pwritev(fd, [b"x" * 2**16] * 2**15, 0)
self.assertEqual(cm.exception.errno, errno.EINVAL)
finally:
os.close(fd)
@unittest.skipUnless(hasattr(posix, 'posix_fallocate'), @unittest.skipUnless(hasattr(posix, 'posix_fallocate'),
"test needs posix.posix_fallocate()") "test needs posix.posix_fallocate()")
def test_posix_fallocate(self): def test_posix_fallocate(self):
...@@ -435,6 +464,17 @@ class PosixTester(unittest.TestCase): ...@@ -435,6 +464,17 @@ class PosixTester(unittest.TestCase):
finally: finally:
os.close(fd) os.close(fd)
@unittest.skipUnless(hasattr(posix, 'writev'), "test needs posix.writev()")
@requires_32b
def test_writev_overflow_32bits(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
with self.assertRaises(OSError) as cm:
os.writev(fd, [b"x" * 2**16] * 2**15)
self.assertEqual(cm.exception.errno, errno.EINVAL)
finally:
os.close(fd)
@unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()") @unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()")
def test_readv(self): def test_readv(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT) fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
...@@ -457,6 +497,19 @@ class PosixTester(unittest.TestCase): ...@@ -457,6 +497,19 @@ class PosixTester(unittest.TestCase):
finally: finally:
os.close(fd) os.close(fd)
@unittest.skipUnless(hasattr(posix, 'readv'), "test needs posix.readv()")
@requires_32b
def test_readv_overflow_32bits(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
buf = [bytearray(2**16)] * 2**15
with self.assertRaises(OSError) as cm:
os.readv(fd, buf)
self.assertEqual(cm.exception.errno, errno.EINVAL)
self.assertEqual(bytes(buf[0]), b'\0'* 2**16)
finally:
os.close(fd)
@unittest.skipUnless(hasattr(posix, 'dup'), @unittest.skipUnless(hasattr(posix, 'dup'),
'test needs posix.dup()') 'test needs posix.dup()')
def test_dup(self): def test_dup(self):
......
Fixed integer overflow in :func:`os.readv`, :func:`os.writev`,
:func:`os.preadv` and :func:`os.pwritev` and in :func:`os.sendfile` with
*headers* or *trailers* arguments (on BSD-based OSes and macOS).
Fixed sending the part of the file in :func:`os.sendfile` on macOS. Using
the *trailers* argument could cause sending more bytes from the input file
than was specified.
...@@ -8321,12 +8321,13 @@ os_read_impl(PyObject *module, int fd, Py_ssize_t length) ...@@ -8321,12 +8321,13 @@ os_read_impl(PyObject *module, int fd, Py_ssize_t length)
} }
#if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \ #if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \
|| defined(__APPLE__))) || defined(HAVE_READV) || defined(HAVE_WRITEV) || defined(__APPLE__))) \
static Py_ssize_t || defined(HAVE_READV) || defined(HAVE_PREADV) || defined (HAVE_PREADV2) \
|| defined(HAVE_WRITEV) || defined(HAVE_PWRITEV) || defined (HAVE_PWRITEV2)
static int
iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, int type) iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, int type)
{ {
Py_ssize_t i, j; Py_ssize_t i, j;
Py_ssize_t blen, total = 0;
*iov = PyMem_New(struct iovec, cnt); *iov = PyMem_New(struct iovec, cnt);
if (*iov == NULL) { if (*iov == NULL) {
...@@ -8351,11 +8352,9 @@ iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, in ...@@ -8351,11 +8352,9 @@ iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, in
} }
Py_DECREF(item); Py_DECREF(item);
(*iov)[i].iov_base = (*buf)[i].buf; (*iov)[i].iov_base = (*buf)[i].buf;
blen = (*buf)[i].len; (*iov)[i].iov_len = (*buf)[i].len;
(*iov)[i].iov_len = blen;
total += blen;
} }
return total; return 0;
fail: fail:
PyMem_Del(*iov); PyMem_Del(*iov);
...@@ -8652,12 +8651,20 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) ...@@ -8652,12 +8651,20 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
} }
if (i > 0) { if (i > 0) {
sf.hdr_cnt = (int)i; sf.hdr_cnt = (int)i;
i = iov_setup(&(sf.headers), &hbuf, if (iov_setup(&(sf.headers), &hbuf,
headers, sf.hdr_cnt, PyBUF_SIMPLE); headers, sf.hdr_cnt, PyBUF_SIMPLE) < 0)
if (i < 0)
return NULL; return NULL;
#ifdef __APPLE__ #ifdef __APPLE__
sbytes += i; for (i = 0; i < sf.hdr_cnt; i++) {
Py_ssize_t blen = sf.headers[i].iov_len;
# define OFF_T_MAX 0x7fffffffffffffff
if (sbytes >= OFF_T_MAX - blen) {
PyErr_SetString(PyExc_OverflowError,
"sendfile() header is too large");
return NULL;
}
sbytes += blen;
}
#endif #endif
} }
} }
...@@ -8678,13 +8685,9 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) ...@@ -8678,13 +8685,9 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
} }
if (i > 0) { if (i > 0) {
sf.trl_cnt = (int)i; sf.trl_cnt = (int)i;
i = iov_setup(&(sf.trailers), &tbuf, if (iov_setup(&(sf.trailers), &tbuf,
trailers, sf.trl_cnt, PyBUF_SIMPLE); trailers, sf.trl_cnt, PyBUF_SIMPLE) < 0)
if (i < 0)
return NULL; return NULL;
#ifdef __APPLE__
sbytes += i;
#endif
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment