From: Nadeshiko Manju Date: Fri, 10 Oct 2025 08:56:10 +0000 (+0800) Subject: gh-139184: Set O_CLOEXEC for master_fd when calling os.forkpty() (#139408) X-Git-Tag: v3.15.0a1~54 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7cafd76a7fc6e52d93dd11e454a49c4dbebdf080;p=thirdparty%2FPython%2Fcpython.git gh-139184: Set O_CLOEXEC for master_fd when calling os.forkpty() (#139408) Signed-off-by: Manjusaka Co-authored-by: Shamil Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- diff --git a/Doc/library/os.rst b/Doc/library/os.rst index ba3d18945494..540eaa09d0e3 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -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 `. + .. 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. diff --git a/Doc/library/pty.rst b/Doc/library/pty.rst index 1a44bb13a841..9fef8760b627 100644 --- a/Doc/library/pty.rst +++ b/Doc/library/pty.rst @@ -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 `. + .. 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() diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index fbba7025ac4a..a2018e864445 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -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 index 000000000000..d50cdeaadc45 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-29-14-15-20.gh-issue-139184.dNl9O4.rst @@ -0,0 +1 @@ +:func:`os.forkpty` does now make the returned file descriptor non-inheritable. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index dddf98d127c1..3d9863ad179d 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -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]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 4189d3008560..7a2e36bf2942 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -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 */