8000 gh-95380: Remove the 1024 bytes limit in fcntl.fcntl() and fcntl.ioctl() by serhiy-storchaka · Pull Request #132907 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-95380: Remove the 1024 bytes limit in fcntl.fcntl() and fcntl.ioctl() #132907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Doc/library/fcntl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,16 @@ 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*
and 1024 bytes),
this is most likely to result in a segmentation violation or
a more subtle data corruption.

Expand All @@ -120,6 +121,7 @@ The module defines the following functions:
.. versionchanged:: next
Add support of arbitrary :term:`bytes-like objects <bytes-like object>`,
not only :class:`bytes`.
The size of bytes-like objects is no longer limited to 1024 bytes.


.. function:: ioctl(fd, request, arg=0, mutate_flag=True, /)
Expand Down Expand Up @@ -157,8 +159,8 @@ 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*
and 1024 bytes),
this is most likely to result in a segmentation violation or
a more subtle data corruption.

Expand All @@ -180,6 +182,8 @@ The module defines the following functions:
.. versionchanged:: next
The GIL is always released during a system call.
System calls failing with EINTR are automatically retried.
The size of not mutated bytes-like objects is no longer
limited to 1024 bytes.

.. function:: flock(fd, operation, /)

Expand Down
46 changes: 46 additions & 0 deletions Lib/test/test_fcntl.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,52 @@ def test_fcntl_f_pipesize(self):
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()
3 changes: 1 addition & 2 deletions Lib/test/test_ioctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,8 @@ def test_ioctl_mutate_1024(self):
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()")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove the 1024 bytes limit on the size of not mutated bytes-like argument
in :func:`fcntl.fcntl` nad :func:`fcntl.ioctl`.
108 changes: 78 additions & 30 deletions Modules/fcntlmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,49 @@
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);
buf[len] = '\0';
PyBuffer_Release(&view);
return 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;
}
return PyBytes_FromStringAndSize(buf, len);
}
memcpy(buf, view.buf, len);
buf[len] = '\0';
PyBuffer_Release(&view);
else {
PyObject *result = PyBytes_FromStringAndSize(NULL, len);
if (result == NULL) {
PyBuffer_Release(&view);
return NULL;
}
void *ptr = PyBytes_AsString(result);
memcpy(ptr, view.buf, len);
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, 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') {

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

dereferencing ‘void *’ pointer

Check failure on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

void value not ignored as it ought to be

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

dereferencing ‘void *’ pointer

Check failure on line 128 in Module F438 s/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

void value not ignored as it ought to be

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04-arm)

dereferencing ‘void *’ pointer

Check failure on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04-arm)

void value not ignored as it ought to be

Check warning on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

dereferencing ‘void *’ pointer

Check failure on line 128 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

void value not ignored as it ought to be
PyErr_SetString(PyExc_SystemError, "buffer overflow");
return NULL;
}
return result;
}
return PyBytes_FromStringAndSize(buf, len);
#undef FCNTL_BUFSZ
}
PyErr_Format(PyExc_TypeError,
Expand Down Expand Up @@ -239,25 +263,49 @@
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);
buf[len] = '\0';
PyBuffer_Release(&view);
return 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;
}
return PyBytes_FromStringAndSize(buf, len);
}
memcpy(buf, view.buf, len);
buf[len] = '\0';
PyBuffer_Release(&view);
else {
PyObject *result = PyBytes_FromStringAndSize(NULL, len);
if (result == NULL) {
PyBuffer_Release(&view);
return NULL;
}
void *ptr = PyBytes_AsString(result);
memcpy(ptr, view.buf, len);
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, 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') {

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

void value not ignored as it ought to be

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

void value not ignored as it ought to be

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

void value not ignored as it ought to be

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04-arm)

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04-arm)

void value not ignored as it ought to be

Check warning on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

dereferencing ‘void *’ pointer

Check failure on line 303 in Modules/fcntlmodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

void value not ignored as it ought to be
PyErr_SetString(PyExc_SystemError, "buffer overflow");
return NULL;
}
return result;
}
return PyBytes_FromStringAndSize(buf, len);
#undef IOCTL_BUFSZ
}
PyErr_Format(PyExc_TypeError,
Expand Down
Loading
0