]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139184: Set O_CLOEXEC for master_fd when calling os.forkpty() (#139408)
authorNadeshiko Manju <me@manjusaka.me>
Fri, 10 Oct 2025 08:56:10 +0000 (16:56 +0800)
committerGitHub <noreply@github.com>
Fri, 10 Oct 2025 08:56:10 +0000 (10:56 +0200)
Signed-off-by: Manjusaka <me@manjusaka.me>
Co-authored-by: Shamil <ashm.tech@proton.me>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Doc/library/os.rst
Doc/library/pty.rst
Lib/test/test_pty.py
Misc/NEWS.d/next/Library/2025-09-29-14-15-20.gh-issue-139184.dNl9O4.rst [new file with mode: 0644]
Modules/clinic/posixmodule.c.h
Modules/posixmodule.c

index ba3d189454941fe0afdc57991c782519432f1638..540eaa09d0e32a532e09e35dafd4fb3f7eac9fb3 100644 (file)
@@ -4592,6 +4592,8 @@ written in Python, such as a mail server's external command delivery program.
    master end of the pseudo-terminal.  For a more portable approach, use the
    :mod:`pty` module.  If an error occurs :exc:`OSError` is raised.
 
+   The returned file descriptor *fd* is :ref:`non-inheritable <fd_inheritance>`.
+
    .. audit-event:: os.forkpty "" os.forkpty
 
    .. warning::
@@ -4608,6 +4610,9 @@ written in Python, such as a mail server's external command delivery program.
       threads, this now raises a :exc:`DeprecationWarning`. See the
       longer explanation on :func:`os.fork`.
 
+   .. versionchanged:: next
+      The returned file descriptor is now made non-inheritable.
+
    .. availability:: Unix, not WASI, not Android, not iOS.
 
 
index 1a44bb13a841de6db0c44f1a04aa81a540b8072e..9fef8760b627a59291cf1dac679a2a7948c2ec16 100644 (file)
@@ -33,9 +33,14 @@ The :mod:`pty` module defines the following functions:
    file descriptor connected to the child's controlling terminal (and also to the
    child's standard input and output).
 
+   The returned file descriptor *fd* is :ref:`non-inheritable <fd_inheritance>`.
+
    .. warning:: On macOS the use of this function is unsafe when mixed with using
       higher-level system APIs, and that includes using :mod:`urllib.request`.
 
+   .. versionchanged:: next
+      The returned file descriptor is now made non-inheritable.
+
 
 .. function:: openpty()
 
index fbba7025ac4abfd6461e98e4fe78b08a18fa963d..a2018e864445e1a2dff5c3141065aa689bd86cd3 100644 (file)
@@ -230,6 +230,7 @@ class PtyTest(unittest.TestCase):
                 os._exit(2)
             os._exit(4)
         else:
+            self.assertFalse(os.get_inheritable(master_fd))
             debug("Waiting for child (%d) to finish." % pid)
             # In verbose mode, we have to consume the debug output from the
             # child or the child will block, causing this test to hang in the
diff --git a/Misc/NEWS.d/next/Library/2025-09-29-14-15-20.gh-issue-139184.dNl9O4.rst b/Misc/NEWS.d/next/Library/2025-09-29-14-15-20.gh-issue-139184.dNl9O4.rst
new file mode 100644 (file)
index 0000000..d50cdea
--- /dev/null
@@ -0,0 +1 @@
+:func:`os.forkpty` does now make the returned file descriptor non-inheritable.
index dddf98d127c15f4993f2dc10edf5c3d14272cfea..3d9863ad179d3c9619a2ba3cea31e8c1e9791912 100644 (file)
@@ -5035,7 +5035,8 @@ PyDoc_STRVAR(os_forkpty__doc__,
 "Returns a tuple of (pid, master_fd).\n"
 "Like fork(), return pid of 0 to the child process,\n"
 "and pid of child to the parent process.\n"
-"To both, return fd of newly opened pseudo-terminal.");
+"To both, return fd of newly opened pseudo-terminal.\n"
+"The master_fd is non-inheritable.");
 
 #define OS_FORKPTY_METHODDEF    \
     {"forkpty", (PyCFunction)os_forkpty, METH_NOARGS, os_forkpty__doc__},
@@ -13446,4 +13447,4 @@ exit:
 #ifndef OS__EMSCRIPTEN_LOG_METHODDEF
     #define OS__EMSCRIPTEN_LOG_METHODDEF
 #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */
-/*[clinic end generated code: output=b5b370c499174f85 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=47ace1528820858b input=a9049054013a1b77]*/
index 4189d300856068b42eb1c5486b55c7a4d072f7f7..7a2e36bf29420561f6a0100b231c9b8832f19f29 100644 (file)
@@ -9018,11 +9018,12 @@ Returns a tuple of (pid, master_fd).
 Like fork(), return pid of 0 to the child process,
 and pid of child to the parent process.
 To both, return fd of newly opened pseudo-terminal.
+The master_fd is non-inheritable.
 [clinic start generated code]*/
 
 static PyObject *
 os_forkpty_impl(PyObject *module)
-/*[clinic end generated code: output=60d0a5c7512e4087 input=f1f7f4bae3966010]*/
+/*[clinic end generated code: output=60d0a5c7512e4087 input=24765e0f33275b3b]*/
 {
     int master_fd = -1;
     pid_t pid;
@@ -9048,6 +9049,12 @@ os_forkpty_impl(PyObject *module)
     } else {
         /* parent: release the import lock. */
         PyOS_AfterFork_Parent();
+        /* set O_CLOEXEC on master_fd */
+        if (_Py_set_inheritable(master_fd, 0, NULL) < 0) {
+            PyErr_FormatUnraisable("Exception ignored when setting master_fd "
+                                   "non-inheritable in forkpty()");
+        }
+
         // After PyOS_AfterFork_Parent() starts the world to avoid deadlock.
         if (warn_about_fork_with_threads("forkpty") < 0)
             return NULL;
@@ -9055,6 +9062,7 @@ os_forkpty_impl(PyObject *module)
     if (pid == -1) {
         return posix_error();
     }
+
     return Py_BuildValue("(Ni)", PyLong_FromPid(pid), master_fd);
 }
 #endif /* HAVE_FORKPTY */