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)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.
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.
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:
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:
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.
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.
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().
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 parametersys.setdefaultcloexec(cloexec:bool): set the default valueof thecloexec parameterAdd a new optionalcloexec parameter to:
asyncore.dispatcher.create_socket()io.FileIOio.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.fromfdsocket.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:
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.
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.
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:
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.
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:
fork() does not existon Windowsexec() withoutfork().fork()which will not call “atfork” callbacks.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.
inherit,inherited: closer to Windows definitionsensitivesterile: “Does not produce offspring.”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:
--status-fd<fd>,--logger-fd<fd>, etc.-passfd:<fd>-add-fd<fd>--log-fd=<fd>,--input-fd=<fd>, etc.-S<fd>On Linux, it is possible to use"/dev/fd/<fd>" filename to pass afile descriptor to a program expecting a filename.
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 callO_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 filedescriptorfcntl(fd,F_SETFD,flags): two additional system calls per filedescriptor, one to get old flags and one to set new flagsOn Linux, setting the close-on-flag has a low overhead on performances.Results ofbench_cloexec.pyon Linux 3.6:
O_CLOEXEC: 1% slower (7.9 us)ioctl(): 3% slower (8.0 us)fcntl(): 3% slower (8.0 us)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)
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() withO_NOINHERIT flag [atomic]open() withO_CLOEXECflag [atomic]open() +os.set_cloexec(fd,True) [best-effort]DuplicateHandle() [atomic]fcntl(fd,F_DUPFD_CLOEXEC) [atomic]dup() +os.set_cloexec(fd,True) [best-effort]fcntl(fd,F_DUP2FD_CLOEXEC,fd2) [atomic]dup3() withO_CLOEXEC flag [atomic]dup2() +os.set_cloexec(fd,True) [best-effort]CreatePipe() withSECURITY_ATTRIBUTES.bInheritHandle=TRUE, or_pipe() withO_NOINHERIT flag [atomic]pipe2() withO_CLOEXEC flag [atomic]pipe() +os.set_cloexec(fd,True) [best-effort]WSASocket() withWSA_FLAG_NO_HANDLE_INHERIT flag[atomic]socket() withSOCK_CLOEXEC flag [atomic]socket() +os.set_cloexec(fd,True) [best-effort]socketpair() withSOCK_CLOEXEC flag [atomic]socketpair() +os.set_cloexec(fd,True) [best-effort]accept4() withSOCK_CLOEXEC flag [atomic]accept() +os.set_cloexec(fd,True) [best-effort]There is no backward incompatible change. The default behaviour isunchanged: the close-on-exec flag is not set by default.
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.
Functions:
ioctl(fd,FIOCLEX,0): set the close-on-exec flagioctl(fd,FIONCLEX,0): clear the close-on-exec flagAvailability: Linux, Mac OS X, QNX, NetBSD, OpenBSD, FreeBSD.
Functions:
flags=fcntl(fd,F_GETFD);fcntl(fd,F_SETFD,flags|FD_CLOEXEC):set the close-on-exec flagflags=fcntl(fd,F_GETFD);fcntl(fd,F_SETFD,flags&~FD_CLOEXEC):clear the close-on-exec flagAvailability: AIX, Digital UNIX, FreeBSD, HP-UX, IRIX, Linux, Mac OSX, OpenBSD, Solaris, SunOS, Unicos.
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 laterfcntl():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:
SetHandleInformation(fd,HANDLE_FLAG_INHERIT,1)Python issues:
Other languages:
$SYSTEM_FD_MAX ($^F).See$SYSTEM_FD_MAX documentation. Perl doesthis since the creation of Perl (it was already present in Perl 1).Unix.set_close_on_exec function.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.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