Skip to content

Commit 1d75fa4

Browse files
gh-77046: os.pipe() sets _O_NOINHERIT flag on fds (#113817)
On Windows, set _O_NOINHERIT flag on file descriptors created by os.pipe() and io.WindowsConsoleIO. Add test_pipe_spawnl() to test_os. Co-authored-by: Zackery Spytz <[email protected]>
1 parent e82b096 commit 1d75fa4

File tree

5 files changed

+68
-6
lines changed

5 files changed

+68
-6
lines changed

Doc/library/msvcrt.rst

+6-2
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,14 @@ File Operations
7575
.. function:: open_osfhandle(handle, flags)
7676

7777
Create a C runtime file descriptor from the file handle *handle*. The *flags*
78-
parameter should be a bitwise OR of :const:`os.O_APPEND`, :const:`os.O_RDONLY`,
79-
and :const:`os.O_TEXT`. The returned file descriptor may be used as a parameter
78+
parameter should be a bitwise OR of :const:`os.O_APPEND`,
79+
:const:`os.O_RDONLY`, :const:`os.O_TEXT` and :const:`os.O_NOINHERIT`.
80+
The returned file descriptor may be used as a parameter
8081
to :func:`os.fdopen` to create a file object.
8182

83+
The file descriptor is inheritable by default. Pass :const:`os.O_NOINHERIT`
84+
flag to make it non inheritable.
85+
8286
.. audit-event:: msvcrt.open_osfhandle handle,flags msvcrt.open_osfhandle
8387

8488

Lib/test/test_os.py

+55
Original file line numberDiff line numberDiff line change
@@ -4485,6 +4485,61 @@ def test_openpty(self):
44854485
self.assertEqual(os.get_inheritable(master_fd), False)
44864486
self.assertEqual(os.get_inheritable(slave_fd), False)
44874487

4488+
@unittest.skipUnless(hasattr(os, 'spawnl'), "need os.openpty()")
4489+
def test_pipe_spawnl(self):
4490+
# gh-77046: On Windows, os.pipe() file descriptors must be created with
4491+
# _O_NOINHERIT to make them non-inheritable. UCRT has no public API to
4492+
# get (_osfile(fd) & _O_NOINHERIT), so use a functional test.
4493+
#
4494+
# Make sure that fd is not inherited by a child process created by
4495+
# os.spawnl(): get_osfhandle() and dup() must fail with EBADF.
4496+
4497+
fd, fd2 = os.pipe()
4498+
self.addCleanup(os.close, fd)
4499+
self.addCleanup(os.close, fd2)
4500+
4501+
code = textwrap.dedent(f"""
4502+
import errno
4503+
import os
4504+
import test.support
4505+
try:
4506+
import msvcrt
4507+
except ImportError:
4508+
msvcrt = None
4509+
4510+
fd = {fd}
4511+
4512+
with test.support.SuppressCrashReport():
4513+
if msvcrt is not None:
4514+
try:
4515+
handle = msvcrt.get_osfhandle(fd)
4516+
except OSError as exc:
4517+
if exc.errno != errno.EBADF:
4518+
raise
4519+
# get_osfhandle(fd) failed with EBADF as expected
4520+
else:
4521+
raise Exception("get_osfhandle() must fail")
4522+
4523+
try:
4524+
fd3 = os.dup(fd)
4525+
except OSError as exc:
4526+
if exc.errno != errno.EBADF:
4527+
raise
4528+
# os.dup(fd) failed with EBADF as expected
4529+
else:
4530+
os.close(fd3)
4531+
raise Exception("dup must fail")
4532+
""")
4533+
4534+
filename = os_helper.TESTFN
4535+
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
4536+
with open(filename, "w") as fp:
4537+
print(code, file=fp, end="")
4538+
4539+
cmd = [sys.executable, filename]
4540+
exitcode = os.spawnl(os.P_WAIT, cmd[0], *cmd)
4541+
self.assertEqual(exitcode, 0)
4542+
44884543

44894544
class PathTConverterTests(unittest.TestCase):
44904545
# tuples of (function name, allows fd arguments, additional arguments to
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
On Windows, file descriptors wrapping Windows handles are now created non
2+
inheritable by default (:pep:`446`). Patch by Zackery Spytz and Victor
3+
Stinner.

Modules/_io/winconsoleio.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,9 @@ _io__WindowsConsoleIO___init___impl(winconsoleio *self, PyObject *nameobj,
391391
}
392392

393393
if (self->writable)
394-
self->fd = _Py_open_osfhandle_noraise(handle, _O_WRONLY | _O_BINARY);
394+
self->fd = _Py_open_osfhandle_noraise(handle, _O_WRONLY | _O_BINARY | _O_NOINHERIT);
395395
else
396-
self->fd = _Py_open_osfhandle_noraise(handle, _O_RDONLY | _O_BINARY);
396+
self->fd = _Py_open_osfhandle_noraise(handle, _O_RDONLY | _O_BINARY | _O_NOINHERIT);
397397
if (self->fd < 0) {
398398
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj);
399399
CloseHandle(handle);

Modules/posixmodule.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -11578,8 +11578,8 @@ os_pipe_impl(PyObject *module)
1157811578
Py_BEGIN_ALLOW_THREADS
1157911579
ok = CreatePipe(&read, &write, &attr, 0);
1158011580
if (ok) {
11581-
fds[0] = _Py_open_osfhandle_noraise(read, _O_RDONLY);
11582-
fds[1] = _Py_open_osfhandle_noraise(write, _O_WRONLY);
11581+
fds[0] = _Py_open_osfhandle_noraise(read, _O_RDONLY | _O_NOINHERIT);
11582+
fds[1] = _Py_open_osfhandle_noraise(write, _O_WRONLY | _O_NOINHERIT);
1158311583
if (fds[0] == -1 || fds[1] == -1) {
1158411584
CloseHandle(read);
1158511585
CloseHandle(write);

0 commit comments

Comments
 (0)