Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 433 – Easier suppression of file descriptor inheritance

PEP 433 – Easier suppression of file descriptor inheritance

Author:
Victor Stinner <vstinner at python.org>
Status:
Superseded
Type:
Standards Track
Created:
10-Jan-2013
Python-Version:
3.4
Superseded-By:
446

Table of Contents

Abstract

Add a new optionalcloexec parameter on functions creating filedescriptors, add different ways to change default values of thisparameter, and add four new functions:

  • os.get_cloexec(fd)
  • os.set_cloexec(fd,cloexec=True)
  • sys.getdefaultcloexec()
  • sys.setdefaultcloexec(cloexec)

Rationale

A file descriptor has a close-on-exec flag which indicates if the filedescriptor will be inherited or not.

On UNIX, if the close-on-exec flag is set, the file descriptor is notinherited: it will be closed at the execution of child processes;otherwise the file descriptor is inherited by child processes.

On Windows, if the close-on-exec flag is set, the file descriptor is notinherited; the file descriptor is inherited by child processes if theclose-on-exec flag is cleared and ifCreateProcess() is called withthebInheritHandles parameter set toTRUE (whensubprocess.Popen is created withclose_fds=False for example).Windows does not have “close-on-exec” flag but an inheritance flag whichis just the opposite value. For example, setting close-on-exec flagmeans clearing theHANDLE_FLAG_INHERIT flag of a handle.

Status in Python 3.3

On UNIX, the subprocess module closes file descriptors greater than 2 bydefault since Python 3.2[1]. All file descriptorscreated by the parent process are automatically closed in the childprocess.

xmlrpc.server.SimpleXMLRPCServer sets the close-on-exec flag ofthe listening socket, the parent classsocketserver.TCPServerdoes not set this flag.

There are other cases creating a subprocess or executing a new programwhere file descriptors are not closed: functions of theos.spawn*()and theos.exec*() families and third party modules callingexec() orfork() +exec(). In this case, file descriptorsare shared between the parent and the child processes which is usuallyunexpected and causes various issues.

This PEP proposes to continue the work started with the change in thesubprocess in Python 3.2, to fix the issue in any code, and not justcode using subprocess.

Inherited file descriptors issues

Closing the file descriptor in the parent process does not close therelated resource (file, socket, …) because it is still open in thechild process.

The listening socket of TCPServer is not closed onexec(): the childprocess is able to get connection from new clients; if the parent closesthe listening socket and create a new listening socket on the sameaddress, it would get an “address already is used” error.

Not closing file descriptors can lead to resource exhaustion: even ifthe parent closes all files, creating a new file descriptor may failwith “too many files” because files are still open in the child process.

See also the following issues:

Security

Leaking file descriptors is a major security vulnerability. Anuntrusted child process can read sensitive data like passwords andtake control of the parent process though leaked file descriptors. Itis for example a known vulnerability to escape from a chroot.

See also the CERT recommendation:FIO42-C. Ensure files are properly closed when they are no longer needed.

Example of vulnerabilities:

Atomicity

Usingfcntl() to set the close-on-exec flag is not safe in amultithreaded application. If a thread callsfork() andexec()between the creation of the file descriptor and the call tofcntl(fd,F_SETFD,new_flags): the file descriptor will beinherited by the child process. Modern operating systems offerfunctions to set the flag during the creation of the file descriptor,which avoids the race condition.

Portability

Python 3.2 addedsocket.SOCK_CLOEXEC flag, Python 3.3 addedos.O_CLOEXEC flag andos.pipe2() function. It is alreadypossible to set atomically close-on-exec flag in Python 3.3 whenopening a file and creating a pipe or socket.

The problem is that these flags and functions are not portable: onlyrecent versions of operating systems support them.O_CLOEXEC andSOCK_CLOEXEC flags are ignored by old Linux versions and soFD_CLOEXEC flag must be checked usingfcntl(fd,F_GETFD). Ifthe kernel ignoresO_CLOEXEC orSOCK_CLOEXEC flag, a call tofcntl(fd,F_SETFD,flags) is required to set close-on-exec flag.

Note

OpenBSD older 5.2 does not close the file descriptor withclose-on-exec flag set iffork() is used beforeexec(), butit works correctly ifexec() is called withoutfork(). Tryopenbsd_bug.py.

Scope

Applications still have to close explicitly file descriptors after afork(). The close-on-exec flag only closes file descriptors afterexec(), and so afterfork() +exec().

This PEP only change the close-on-exec flag of file descriptorscreated by the Python standard library, or by modules using thestandard library. Third party modules not using the standard libraryshould be modified to conform to this PEP. The newos.set_cloexec() function can be used for example.

Note

SeeClose file descriptors after fork for a possible solutionforfork() withoutexec().

Proposal

Add a new optionalcloexec parameter on functions creating filedescriptors and different ways to change default value of thisparameter.

Add new functions:

  • os.get_cloexec(fd:int)->bool: get theclose-on-exec flag of a file descriptor. Not available on allplatforms.
  • os.set_cloexec(fd:int,cloexec:bool=True): set or clear theclose-on-exec flag on a file descriptor. Not available on allplatforms.
  • sys.getdefaultcloexec()->bool: get the current default valueof thecloexec parameter
  • sys.setdefaultcloexec(cloexec:bool): set the default valueof thecloexec parameter

Add a new optionalcloexec parameter to:

  • asyncore.dispatcher.create_socket()
  • io.FileIO
  • io.open()
  • open()
  • os.dup()
  • os.dup2()
  • os.fdopen()
  • os.open()
  • os.openpty()
  • os.pipe()
  • select.devpoll()
  • select.epoll()
  • select.kqueue()
  • socket.socket()
  • socket.socket.accept()
  • socket.socket.dup()
  • socket.socket.fromfd
  • socket.socketpair()

The default value of thecloexec parameter issys.getdefaultcloexec().

Add a new command line option-e and an environment variablePYTHONCLOEXEC to the set close-on-exec flag by default.

subprocess clears the close-on-exec flag of file descriptors of thepass_fds parameter.

All functions creating file descriptors in the standard library mustrespect the default value of thecloexec parameter:sys.getdefaultcloexec().

File descriptors 0 (stdin), 1 (stdout) and 2 (stderr) are expected to beinherited, but Python does not handle them differently. Whenos.dup2() is used to replace standard streams,cloexec=Falsemust be specified explicitly.

Drawbacks of the proposal:

  • It is not more possible to know if the close-on-exec flag will beset or not on a newly created file descriptor just by reading thesource code.
  • If the inheritance of a file descriptor matters, thecloexecparameter must now be specified explicitly, or the library or theapplication will not work depending on the default value of thecloexec parameter.

Alternatives

Inheritance enabled by default, default no configurable

Add a new optional parametercloexec on functions creating filedescriptors. The default value of thecloexec parameter isFalse,and this default cannot be changed. File descriptor inheritance enabled bydefault is also the default on POSIX and on Windows. This alternative isthe most conservative option.

This option does not solve issues listed in theRationalesection, it only provides a helper to fix them. All functions creatingfile descriptors have to be modified to setcloexec=True in eachmodule used by an application to fix all these issues.

Inheritance enabled by default, default can only be set to True

This alternative is based on the proposal: the only difference is thatsys.setdefaultcloexec() does not take any argument, it can only beused to set the default value of thecloexec parameter toTrue.

Disable inheritance by default

This alternative is based on the proposal: the only difference is thatthe default value of thecloexec parameter isTrue (instead ofFalse).

If a file must be inherited by child processes,cloexec=Falseparameter can be used.

Advantages of setting close-on-exec flag by default:

Drawbacks of setting close-on-exec flag by default:

  • It violates the principle of least surprise. Developers using theos module may expect that Python respects the POSIX standard and sothat close-on-exec flag is not set by default.
  • The os module is written as a thin wrapper to system calls (tofunctions of the C standard library). If atomic flags to setclose-on-exec flag are not supported (seeAppendix: Operatingsystem support), a single Python function call may call 2 or 3system calls (seePerformances section).
  • Extra system calls, if any, may slow down Python: seePerformances.

Backward compatibility: only a few programs rely on inheritance of filedescriptors, and they only pass a few file descriptors, usually justone. These programs will fail immediately withEBADF error, and itwill be simple to fix them: addcloexec=False parameter or useos.set_cloexec(fd,False).

Thesubprocess module will be changed anyway to clearclose-on-exec flag on file descriptors listed in thepass_fdsparameter of Popen constructor. So it possible that these programs willnot need any fix if they use thesubprocess module.

Close file descriptors after fork

This PEP does not fix issues with applications usingfork()withoutexec(). Python needs a generic process to registercallbacks which would be called after a fork, see#16500:Add an atfork module. Such registry could be used to close filedescriptors just after afork().

Drawbacks:

  • It does not solve the problem on Windows:fork() does not existon Windows
  • This alternative does not solve the problem for programs usingexec() withoutfork().
  • A third party module may call directly the C functionfork()which will not call “atfork” callbacks.
  • All functions creating file descriptors must be changed to registera callback and then unregister their callback when the file isclosed. Or a list ofall open file descriptors must bemaintained.
  • The operating system is a better place than Python to closeautomatically file descriptors. For example, it is not easy toavoid a race condition between closing the file and unregisteringthe callback closing the file.

open(): add “e” flag to mode

A new “e” mode would set close-on-exec flag (best-effort).

This alternative only solves the problem foropen().socket.socket() and os.pipe() do not have amode parameter forexample.

Since its version 2.7, the GNU libc supports"e" flag forfopen(). It usesO_CLOEXEC if available, or usefcntl(fd,F_SETFD,FD_CLOEXEC). With Visual Studio, fopen() accepts a “N”flag which usesO_NOINHERIT.

Bikeshedding on the name of the new parameter

  • inherit,inherited: closer to Windows definition
  • sensitive
  • sterile: “Does not produce offspring.”

Applications using inheritance of file descriptors

Most developers don’t know that file descriptors are inherited bydefault. Most programs do not rely on inheritance of file descriptors.For example,subprocess.Popen was changed in Python 3.2 to closeall file descriptors greater than 2 in the child process by default.No user complained about this behavior change yet.

Network servers using fork may want to pass the client socket to thechild process. For example, on UNIX a CGI server pass the socketclient through file descriptors 0 (stdin) and 1 (stdout) usingdup2().

To access a restricted resource like creating a socket listening on aTCP port lower than 1024 or reading a file containing sensitive datalike passwords, a common practice is: start as the root user, create afile descriptor, create a child process, drop privileges (ex: change thecurrent user), pass the file descriptor to the child process and exitthe parent process.

Security is very important in such use case: leaking another filedescriptor would be a critical security vulnerability (seeSecurity).The root process may not exit but monitors the child process instead,and restarts a new child process and pass the same file descriptor ifthe previous child process crashed.

Example of programs taking file descriptors from the parent processusing a command line option:

  • gpg:--status-fd<fd>,--logger-fd<fd>, etc.
  • openssl:-passfd:<fd>
  • qemu:-add-fd<fd>
  • valgrind:--log-fd=<fd>,--input-fd=<fd>, etc.
  • xterm:-S<fd>

On Linux, it is possible to use"/dev/fd/<fd>" filename to pass afile descriptor to a program expecting a filename.

Performances

Setting close-on-exec flag may require additional system calls foreach creation of new file descriptors. The number of additional systemcalls depends on the method used to set the flag:

  • O_NOINHERIT: no additional system call
  • O_CLOEXEC: one additional system call, but only at the creationof the first file descriptor, to check if the flag is supported. Ifthe flag is not supported, Python has to fallback to the next method.
  • ioctl(fd,FIOCLEX): one additional system call per filedescriptor
  • fcntl(fd,F_SETFD,flags): two additional system calls per filedescriptor, one to get old flags and one to set new flags

On Linux, setting the close-on-flag has a low overhead on performances.Results ofbench_cloexec.pyon Linux 3.6:

  • close-on-flag not set: 7.8 us
  • O_CLOEXEC: 1% slower (7.9 us)
  • ioctl(): 3% slower (8.0 us)
  • fcntl(): 3% slower (8.0 us)

Implementation

os.get_cloexec(fd)

Get the close-on-exec flag of a file descriptor.

Pseudo-code:

ifos.name=='nt':defget_cloexec(fd):handle=_winapi._get_osfhandle(fd);flags=_winapi.GetHandleInformation(handle)returnnot(flags&_winapi.HANDLE_FLAG_INHERIT)else:try:importfcntlexceptImportError:passelse:defget_cloexec(fd):flags=fcntl.fcntl(fd,fcntl.F_GETFD)returnbool(flags&fcntl.FD_CLOEXEC)

os.set_cloexec(fd, cloexec=True)

Set or clear the close-on-exec flag on a file descriptor. The flagis set after the creation of the file descriptor and so it is notatomic.

Pseudo-code:

ifos.name=='nt':defset_cloexec(fd,cloexec=True):handle=_winapi._get_osfhandle(fd);mask=_winapi.HANDLE_FLAG_INHERITifcloexec:flags=0else:flags=mask_winapi.SetHandleInformation(handle,mask,flags)else:fnctl=Noneioctl=Nonetry:importioctlexceptImportError:try:importfcntlexceptImportError:passifioctlisnotNoneandhasattr('FIOCLEX',ioctl):defset_cloexec(fd,cloexec=True):ifcloexec:ioctl.ioctl(fd,ioctl.FIOCLEX)else:ioctl.ioctl(fd,ioctl.FIONCLEX)eliffnctlisnotNone:defset_cloexec(fd,cloexec=True):flags=fcntl.fcntl(fd,fcntl.F_GETFD)ifcloexec:flags|=FD_CLOEXECelse:flags&=~FD_CLOEXECfcntl.fcntl(fd,fcntl.F_SETFD,flags)

ioctl is preferred over fcntl because it requires only one syscall,instead of two syscalls for fcntl.

Note

fcntl(fd,F_SETFD,flags) only supports one flag(FD_CLOEXEC), so it would be possible to avoidfcntl(fd,F_GETFD). But it may drop other flags in the future, and so it issafer to keep the two functions calls.

Note

fopen() function of the GNU libc ignores the error iffcntl(fd,F_SETFD,flags) failed.

open()

  • Windows:open() withO_NOINHERIT flag [atomic]
  • open() withO_CLOEXECflag [atomic]
  • open() +os.set_cloexec(fd,True) [best-effort]

os.dup()

  • Windows:DuplicateHandle() [atomic]
  • fcntl(fd,F_DUPFD_CLOEXEC) [atomic]
  • dup() +os.set_cloexec(fd,True) [best-effort]

os.dup2()

  • fcntl(fd,F_DUP2FD_CLOEXEC,fd2) [atomic]
  • dup3() withO_CLOEXEC flag [atomic]
  • dup2() +os.set_cloexec(fd,True) [best-effort]

os.pipe()

  • Windows:CreatePipe() withSECURITY_ATTRIBUTES.bInheritHandle=TRUE, or_pipe() withO_NOINHERIT flag [atomic]
  • pipe2() withO_CLOEXEC flag [atomic]
  • pipe() +os.set_cloexec(fd,True) [best-effort]

socket.socket()

  • Windows:WSASocket() withWSA_FLAG_NO_HANDLE_INHERIT flag[atomic]
  • socket() withSOCK_CLOEXEC flag [atomic]
  • socket() +os.set_cloexec(fd,True) [best-effort]

socket.socketpair()

  • socketpair() withSOCK_CLOEXEC flag [atomic]
  • socketpair() +os.set_cloexec(fd,True) [best-effort]

socket.socket.accept()

  • accept4() withSOCK_CLOEXEC flag [atomic]
  • accept() +os.set_cloexec(fd,True) [best-effort]

Backward compatibility

There is no backward incompatible change. The default behaviour isunchanged: the close-on-exec flag is not set by default.

Appendix: Operating system support

Windows

Windows has anO_NOINHERIT flag: “Do not inherit in childprocesses”.

For example, it is supported byopen() and_pipe().

The flag can be cleared usingSetHandleInformation(fd,HANDLE_FLAG_INHERIT,0).

CreateProcess() has anbInheritHandles parameter: if it isFALSE, the handles are not inherited. If it isTRUE, handleswithHANDLE_FLAG_INHERIT flag set are inherited.subprocess.Popen usesclose_fds option to definebInheritHandles.

ioctl

Functions:

  • ioctl(fd,FIOCLEX,0): set the close-on-exec flag
  • ioctl(fd,FIONCLEX,0): clear the close-on-exec flag

Availability: Linux, Mac OS X, QNX, NetBSD, OpenBSD, FreeBSD.

fcntl

Functions:

  • flags=fcntl(fd,F_GETFD);fcntl(fd,F_SETFD,flags|FD_CLOEXEC):set the close-on-exec flag
  • flags=fcntl(fd,F_GETFD);fcntl(fd,F_SETFD,flags&~FD_CLOEXEC):clear the close-on-exec flag

Availability: AIX, Digital UNIX, FreeBSD, HP-UX, IRIX, Linux, Mac OSX, OpenBSD, Solaris, SunOS, Unicos.

Atomic flags

New flags:

  • O_CLOEXEC: available on Linux (2.6.23), FreeBSD (8.3),OpenBSD 5.0, Solaris 11, QNX, BeOS, next NetBSD release (6.1?).This flag is part of POSIX.1-2008.
  • SOCK_CLOEXEC flag forsocket() andsocketpair(),available on Linux 2.6.27, OpenBSD 5.2, NetBSD 6.0.
  • WSA_FLAG_NO_HANDLE_INHERIT flag forWSASocket(): supportedon Windows 7 with SP1, Windows Server 2008 R2 with SP1, and later
  • fcntl():F_DUPFD_CLOEXEC flag, available on Linux 2.6.24,OpenBSD 5.0, FreeBSD 9.1, NetBSD 6.0, Solaris 11. This flag is partof POSIX.1-2008.
  • fcntl():F_DUP2FD_CLOEXEC flag, available on FreeBSD 9.1and Solaris 11.
  • recvmsg():MSG_CMSG_CLOEXEC, available on Linux 2.6.23,NetBSD 6.0.

On Linux older than 2.6.23,O_CLOEXEC flag is simply ignored. Sowe have to check that the flag is supported by callingfcntl(). Ifit does not work, we have to set the flag usingioctl() orfcntl().

On Linux older than 2.6.27, if theSOCK_CLOEXEC flag is set in thesocket type,socket() orsocketpair() fail anderrno is settoEINVAL.

On Windows XPS3,WSASocket() withWSAEPROTOTYPE whenWSA_FLAG_NO_HANDLE_INHERIT flag is used.

New functions:

  • dup3(): available on Linux 2.6.27 (and glibc 2.9)
  • pipe2(): available on Linux 2.6.27 (and glibc 2.9)
  • accept4(): available on Linux 2.6.28 (and glibc 2.10)

Ifaccept4() is called on Linux older than 2.6.28,accept4()returns-1 (fail) anderrno is set toENOSYS.

Links

Links:

Python issues:

Other languages:

Footnotes

[1]
On UNIX since Python 3.2, subprocess.Popen()closes all file descriptors by default:close_fds=True. Itcloses file descriptors in range 3 inclusive tolocal_max_fdexclusive, wherelocal_max_fd isfcntl(0,F_MAXFD) onNetBSD, orsysconf(_SC_OPEN_MAX) otherwise. If the error pipehas a descriptor smaller than 3,ValueError is raised.

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-0433.rst

Last modified:2025-02-01 08:59:27 GMT


[8]ページ先頭

©2009-2026 Movatter.jp