diff options
-rw-r--r-- | Doc/library/fcntl.rst | 18 | ||||
-rw-r--r-- | Lib/test/test_fcntl.py | 46 | ||||
-rw-r--r-- | Lib/test/test_ioctl.py | 3 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst | 2 | ||||
-rw-r--r-- | Modules/fcntlmodule.c | 124 |
5 files changed, 147 insertions, 46 deletions
diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index c8ce86cc7af..5c078df44ff 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -107,15 +107,15 @@ The module defines the following functions: passed to the C :c:func:`fcntl` call. The return value after a successful call is the contents of the buffer, converted to a :class:`bytes` object. The length of the returned object will be the same as the length of the - *arg* argument. This is limited to 1024 bytes. + *arg* argument. If the :c:func:`fcntl` call fails, an :exc:`OSError` is raised. .. note:: - If the type or the size of *arg* does not match the type or size - of the argument of the operation (for example, if an integer is + If the type or size of *arg* does not match the type or size + of the operation's argument (for example, if an integer is passed when a pointer is expected, or the information returned in - the buffer by the operating system is larger than 1024 bytes), + the buffer by the operating system is larger than the size of *arg*), this is most likely to result in a segmentation violation or a more subtle data corruption. @@ -125,6 +125,9 @@ The module defines the following functions: Add support of arbitrary :term:`bytes-like objects <bytes-like object>`, not only :class:`bytes`. + .. versionchanged:: next + The size of bytes-like objects is no longer limited to 1024 bytes. + .. function:: ioctl(fd, request, arg=0, mutate_flag=True, /) @@ -161,8 +164,7 @@ The module defines the following functions: If the type or size of *arg* does not match the type or size of the operation's argument (for example, if an integer is passed when a pointer is expected, or the information returned in - the buffer by the operating system is larger than 1024 bytes, - or the size of the mutable bytes-like object is too small), + the buffer by the operating system is larger than the size of *arg*), this is most likely to result in a segmentation violation or a more subtle data corruption. @@ -185,6 +187,10 @@ The module defines the following functions: The GIL is always released during a system call. System calls failing with EINTR are automatically retried. + .. versionchanged:: next + The size of not mutated bytes-like objects is no longer + limited to 1024 bytes. + .. function:: flock(fd, operation, /) Perform the lock operation *operation* on file descriptor *fd* (file objects providing diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index b84c98ef3a2..e0e6782258f 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -228,6 +228,52 @@ class TestFcntl(unittest.TestCase): os.close(test_pipe_r) os.close(test_pipe_w) + def _check_fcntl_not_mutate_len(self, nbytes=None): + self.f = open(TESTFN, 'wb') + buf = struct.pack('ii', fcntl.F_OWNER_PID, os.getpid()) + if nbytes is not None: + buf += b' ' * (nbytes - len(buf)) + else: + nbytes = len(buf) + save_buf = bytes(buf) + r = fcntl.fcntl(self.f, fcntl.F_SETOWN_EX, buf) + self.assertIsInstance(r, bytes) + self.assertEqual(len(r), len(save_buf)) + self.assertEqual(buf, save_buf) + type, pid = memoryview(r).cast('i')[:2] + self.assertEqual(type, fcntl.F_OWNER_PID) + self.assertEqual(pid, os.getpid()) + + buf = b' ' * nbytes + r = fcntl.fcntl(self.f, fcntl.F_GETOWN_EX, buf) + self.assertIsInstance(r, bytes) + self.assertEqual(len(r), len(save_buf)) + self.assertEqual(buf, b' ' * nbytes) + type, pid = memoryview(r).cast('i')[:2] + self.assertEqual(type, fcntl.F_OWNER_PID) + self.assertEqual(pid, os.getpid()) + + buf = memoryview(b' ' * nbytes) + r = fcntl.fcntl(self.f, fcntl.F_GETOWN_EX, buf) + self.assertIsInstance(r, bytes) + self.assertEqual(len(r), len(save_buf)) + self.assertEqual(bytes(buf), b' ' * nbytes) + type, pid = memoryview(r).cast('i')[:2] + self.assertEqual(type, fcntl.F_OWNER_PID) + self.assertEqual(pid, os.getpid()) + + @unittest.skipUnless( + hasattr(fcntl, "F_SETOWN_EX") and hasattr(fcntl, "F_GETOWN_EX"), + "requires F_SETOWN_EX and F_GETOWN_EX") + def test_fcntl_small_buffer(self): + self._check_fcntl_not_mutate_len() + + @unittest.skipUnless( + hasattr(fcntl, "F_SETOWN_EX") and hasattr(fcntl, "F_GETOWN_EX"), + "requires F_SETOWN_EX and F_GETOWN_EX") + def test_fcntl_large_buffer(self): + self._check_fcntl_not_mutate_len(2024) + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_ioctl.py b/Lib/test/test_ioctl.py index 7a986048bda..3c7a58aa2bc 100644 --- a/Lib/test/test_ioctl.py +++ b/Lib/test/test_ioctl.py @@ -127,9 +127,8 @@ class IoctlTestsTty(unittest.TestCase): self._check_ioctl_not_mutate_len(1024) def test_ioctl_mutate_2048(self): - # Test with a larger buffer, just for the record. self._check_ioctl_mutate_len(2048) - self.assertRaises(ValueError, self._check_ioctl_not_mutate_len, 2048) + self._check_ioctl_not_mutate_len(1024) @unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()") diff --git a/Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst b/Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst new file mode 100644 index 00000000000..8dcc6190cfd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-25-11-53-37.gh-issue-95380.7dvPe-.rst @@ -0,0 +1,2 @@ +:func:`fcntl.fcntl` and :func:`fcntl.ioctl`: Remove the 1024 bytes limit +on the size of not mutated bytes-like argument. diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 220ee9ecdff..8b6379f1e65 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -93,29 +93,53 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg) return NULL; } Py_ssize_t len = view.len; - if (len > FCNTL_BUFSZ) { - PyErr_SetString(PyExc_ValueError, - "fcntl argument 3 is too long"); + if (len <= FCNTL_BUFSZ) { + memcpy(buf, view.buf, len); + memcpy(buf + len, guard, GUARDSZ); PyBuffer_Release(&view); - return NULL; - } - memcpy(buf, view.buf, len); - memcpy(buf + len, guard, GUARDSZ); - PyBuffer_Release(&view); - do { - Py_BEGIN_ALLOW_THREADS - ret = fcntl(fd, code, buf); - Py_END_ALLOW_THREADS - } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); - if (ret < 0) { - return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL; + do { + Py_BEGIN_ALLOW_THREADS + ret = fcntl(fd, code, buf); + Py_END_ALLOW_THREADS + } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (ret < 0) { + return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL; + } + if (memcmp(buf + len, guard, GUARDSZ) != 0) { + PyErr_SetString(PyExc_SystemError, "buffer overflow"); + return NULL; + } + return PyBytes_FromStringAndSize(buf, len); } - if (memcmp(buf + len, guard, GUARDSZ) != 0) { - PyErr_SetString(PyExc_SystemError, "buffer overflow"); - return NULL; + else { + PyObject *result = PyBytes_FromStringAndSize(NULL, len); + if (result == NULL) { + PyBuffer_Release(&view); + return NULL; + } + char *ptr = PyBytes_AsString(result); + memcpy(ptr, view.buf, len); + PyBuffer_Release(&view); + + do { + Py_BEGIN_ALLOW_THREADS + ret = fcntl(fd, code, ptr); + Py_END_ALLOW_THREADS + } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (ret < 0) { + if (async_err) { + PyErr_SetFromErrno(PyExc_OSError); + } + Py_DECREF(result); + return NULL; + } + if (ptr[len] != '\0') { + PyErr_SetString(PyExc_SystemError, "buffer overflow"); + return NULL; + } + return result; } - return PyBytes_FromStringAndSize(buf, len); #undef FCNTL_BUFSZ } PyErr_Format(PyExc_TypeError, @@ -251,29 +275,53 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long code, PyObject *arg, return NULL; } Py_ssize_t len = view.len; - if (len > IOCTL_BUFSZ) { - PyErr_SetString(PyExc_ValueError, - "ioctl argument 3 is too long"); + if (len <= IOCTL_BUFSZ) { + memcpy(buf, view.buf, len); + memcpy(buf + len, guard, GUARDSZ); PyBuffer_Release(&view); - return NULL; - } - memcpy(buf, view.buf, len); - memcpy(buf + len, guard, GUARDSZ); - PyBuffer_Release(&view); - do { - Py_BEGIN_ALLOW_THREADS - ret = ioctl(fd, code, buf); - Py_END_ALLOW_THREADS - } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); - if (ret < 0) { - return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL; + do { + Py_BEGIN_ALLOW_THREADS + ret = ioctl(fd, code, buf); + Py_END_ALLOW_THREADS + } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (ret < 0) { + return !async_err ? PyErr_SetFromErrno(PyExc_OSError) : NULL; + } + if (memcmp(buf + len, guard, GUARDSZ) != 0) { + PyErr_SetString(PyExc_SystemError, "buffer overflow"); + return NULL; + } + return PyBytes_FromStringAndSize(buf, len); } - if (memcmp(buf + len, guard, GUARDSZ) != 0) { - PyErr_SetString(PyExc_SystemError, "buffer overflow"); - return NULL; + else { + PyObject *result = PyBytes_FromStringAndSize(NULL, len); + if (result == NULL) { + PyBuffer_Release(&view); + return NULL; + } + char *ptr = PyBytes_AsString(result); + memcpy(ptr, view.buf, len); + PyBuffer_Release(&view); + + do { + Py_BEGIN_ALLOW_THREADS + ret = ioctl(fd, code, ptr); + Py_END_ALLOW_THREADS + } while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (ret < 0) { + if (async_err) { + PyErr_SetFromErrno(PyExc_OSError); + } + Py_DECREF(result); + return NULL; + } + if (ptr[len] != '\0') { + PyErr_SetString(PyExc_SystemError, "buffer overflow"); + return NULL; + } + return result; } - return PyBytes_FromStringAndSize(buf, len); #undef IOCTL_BUFSZ } PyErr_Format(PyExc_TypeError, |