https://github.com/python/cpython/commit/c2eaeee3dc3306ca486b0377b07b1a957584b691
commit: c2eaeee3dc3306ca486b0377b07b1a957584b691
branch: main
author: Serhiy Storchaka <storch...@gmail.com>
committer: serhiy-storchaka <storch...@gmail.com>
date: 2025-04-28T17:56:10+03:00
summary:

gh-132915: Try to detect a buffer overflow in fcntl() and ioctl() (GH-132919)

SystemError is raised when buffer overflow is detected.
The stack and memory can already be corrupted, so treat this error as fatal.

files:
A Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst
M Modules/fcntlmodule.c

diff --git 
a/Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst 
b/Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst
new file mode 100644
index 00000000000000..95a7d9e9159d59
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-25-12-55-06.gh-issue-132915.XuKCXn.rst
@@ -0,0 +1,3 @@
+:func:`fcntl.fcntl` and :func:`fcntl.ioctl` can now detect a buffer overflow
+and raise :exc:`SystemError`. The stack and memory can be corrupted in such
+case, so treat this error as fatal.
diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c
index 7e14e6bc3a525d..ebcacd2fb0ece1 100644
--- a/Modules/fcntlmodule.c
+++ b/Modules/fcntlmodule.c
@@ -22,6 +22,10 @@
 #  include <stropts.h>            // I_FLUSHBAND
 #endif
 
+#define GUARDSZ 8
+// NUL followed by random bytes.
+static const char guard[GUARDSZ] = "\x00\xfa\x69\xc4\x67\xa3\x6c\x58";
+
 /*[clinic input]
 module fcntl
 [clinic start generated code]*/
@@ -80,9 +84,10 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, 
PyObject *arg)
         return PyLong_FromLong(ret);
     }
     if (PyUnicode_Check(arg) || PyObject_CheckBuffer(arg)) {
-#define FCNTL_BUFSZ 1024
         Py_buffer view;
-        char buf[FCNTL_BUFSZ+1];  /* argument plus NUL byte */
+#define FCNTL_BUFSZ 1024
+        /* argument plus NUL byte plus guard to detect a buffer overflow */
+        char buf[FCNTL_BUFSZ+GUARDSZ];
 
         if (!PyArg_Parse(arg, "s*", &view)) {
             return NULL;
@@ -95,7 +100,7 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, 
PyObject *arg)
             return NULL;
         }
         memcpy(buf, view.buf, len);
-        buf[len] = '\0';
+        memcpy(buf + len, guard, GUARDSZ);
         PyBuffer_Release(&view);
 
         do {
@@ -106,6 +111,10 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, 
PyObject *arg)
         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);
 #undef FCNTL_BUFSZ
     }
@@ -199,26 +208,22 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long 
code, PyObject *arg,
     if (PyUnicode_Check(arg) || PyObject_CheckBuffer(arg)) {
         Py_buffer view;
 #define IOCTL_BUFSZ 1024
-        char buf[IOCTL_BUFSZ+1];  /* argument plus NUL byte */
+        /* argument plus NUL byte plus guard to detect a buffer overflow */
+        char buf[IOCTL_BUFSZ+GUARDSZ];
         if (mutate_arg && !PyBytes_Check(arg) && !PyUnicode_Check(arg)) {
             if (PyObject_GetBuffer(arg, &view, PyBUF_WRITABLE) == 0) {
-                if (view.len <= IOCTL_BUFSZ) {
-                    memcpy(buf, view.buf, view.len);
-                    buf[view.len] = '\0';
-                    do {
-                        Py_BEGIN_ALLOW_THREADS
-                        ret = ioctl(fd, code, buf);
-                        Py_END_ALLOW_THREADS
-                    } while (ret == -1 && errno == EINTR && !(async_err = 
PyErr_CheckSignals()));
-                    memcpy(view.buf, buf, view.len);
-                }
-                else {
-                    do {
-                        Py_BEGIN_ALLOW_THREADS
-                        ret = ioctl(fd, code, view.buf);
-                        Py_END_ALLOW_THREADS
-                    } while (ret == -1 && errno == EINTR && !(async_err = 
PyErr_CheckSignals()));
+                Py_ssize_t len = view.len;
+                void *ptr = view.buf;
+                if (len <= IOCTL_BUFSZ) {
+                    memcpy(buf, ptr, len);
+                    memcpy(buf + len, guard, GUARDSZ);
+                    ptr = buf;
                 }
+                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);
@@ -226,7 +231,14 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long 
code, PyObject *arg,
                     PyBuffer_Release(&view);
                     return NULL;
                 }
+                if (ptr == buf) {
+                    memcpy(view.buf, buf, len);
+                }
                 PyBuffer_Release(&view);
+                if (ptr == buf && memcmp(buf + len, guard, GUARDSZ) != 0) {
+                    PyErr_SetString(PyExc_SystemError, "buffer overflow");
+                    return NULL;
+                }
                 return PyLong_FromLong(ret);
             }
             if (!PyErr_ExceptionMatches(PyExc_BufferError)) {
@@ -246,7 +258,7 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long 
code, PyObject *arg,
             return NULL;
         }
         memcpy(buf, view.buf, len);
-        buf[len] = '\0';
+        memcpy(buf + len, guard, GUARDSZ);
         PyBuffer_Release(&view);
 
         do {
@@ -257,6 +269,10 @@ fcntl_ioctl_impl(PyObject *module, int fd, unsigned long 
code, PyObject *arg,
         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);
 #undef IOCTL_BUFSZ
     }

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to