From 1d8930957a8c0777f9d6b13fcb1b296df029c132 Mon Sep 17 00:00:00 2001 From: Manjusaka Date: Mon, 29 Sep 2025 14:14:40 +0800 Subject: [PATCH 1/3] gh-139184: set O_CLOEXEC for master_fd when call os.forkpty Signed-off-by: Manjusaka --- Lib/test/test_pty.py | 4 ++++ .../2025-09-29-14-15-20.gh-issue-139184.dNl9O4.rst | 1 + Modules/posixmodule.c | 8 ++++++++ 3 files changed, 13 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-09-29-14-15-20.gh-issue-139184.dNl9O4.rst diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index fbba7025ac4abf..65185613fae337 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -12,6 +12,7 @@ raise unittest.SkipTest("pty is not available on this platform") import errno +import fcntl import os import pty import tty @@ -230,6 +231,9 @@ def test_fork(self): os._exit(2) os._exit(4) else: + flags = fcntl.fcntl(master_fd, fcntl.F_GETFD) + cloexec_set = bool(flags & fcntl.FD_CLOEXEC) + self.assertEqual(cloexec_set, True) 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 00000000000000..7388afaa94ad48 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-29-14-15-20.gh-issue-139184.dNl9O4.rst @@ -0,0 +1 @@ +Set O_CLOEXEC for master_fd when call os.forkpty diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b7a0110226590e..be2b501e4fa889 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -9059,6 +9059,9 @@ 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) + goto error; // After PyOS_AfterFork_Parent() starts the world to avoid deadlock. if (warn_about_fork_with_threads("forkpty") < 0) return NULL; @@ -9066,7 +9069,12 @@ os_forkpty_impl(PyObject *module) if (pid == -1) { return posix_error(); } + return Py_BuildValue("(Ni)", PyLong_FromPid(pid), master_fd); +error: + if (master_fd != -1) + close(master_fd); + return NULL; } #endif /* HAVE_FORKPTY */ From b0cf98a317f733bbb3231327858bcd22ab005247 Mon Sep 17 00:00:00 2001 From: Nadeshiko Manju Date: Mon, 29 Sep 2025 17:42:34 +0800 Subject: [PATCH 2/3] Update Lib/test/test_pty.py Co-authored-by: Shamil --- Lib/test/test_pty.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 65185613fae337..98e8c28d605d45 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -233,7 +233,8 @@ def test_fork(self): else: flags = fcntl.fcntl(master_fd, fcntl.F_GETFD) cloexec_set = bool(flags & fcntl.FD_CLOEXEC) - self.assertEqual(cloexec_set, True) + flags = fcntl.fcntl(master_fd, fcntl.F_GETFD) + self.assertTrue(flags & fcntl.FD_CLOEXEC) 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 From d9be40e62f10776951a4e52a692d30c20122143e Mon Sep 17 00:00:00 2001 From: Manjusaka Date: Mon, 29 Sep 2025 17:44:05 +0800 Subject: [PATCH 3/3] fix test Signed-off-by: Manjusaka --- Lib/test/test_pty.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 98e8c28d605d45..385169efcd5b1d 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -231,8 +231,6 @@ def test_fork(self): os._exit(2) os._exit(4) else: - flags = fcntl.fcntl(master_fd, fcntl.F_GETFD) - cloexec_set = bool(flags & fcntl.FD_CLOEXEC) flags = fcntl.fcntl(master_fd, fcntl.F_GETFD) self.assertTrue(flags & fcntl.FD_CLOEXEC) debug("Waiting for child (%d) to finish." % pid)