Supporting atomic noninheritability of FDs
Date: Tue, 3 Dec 2019 03:38:46 +0000
By default, file handles are inheritable. This means that if you have a server using libcurl and call fork() + exec(), the file handles used by libcurl are inherited by the child process. This can have security implications in some cases, but more commonly just results in unreliability: close() won't disconnect the TCP connection because the socket is still open in an unaware child process for who knows how long.
Fixing this is complicated, though. Many UNIX systems have a way to mark a socket as close-on-exec. It has to be atomic, though, because in a multi-threaded application, a fork() may come at any moment.
In Linux, open() and socket() have platform-specific flags to make the handle close-on-exec atomically. For open(), it's O_CLOEXEC, and for socket(), it's SOCK_CLOEXEC bitwise-or'd into the "type" parameter. accept() has no room for such a parameter, so Linux defines an alternate version, accept4(), that takes a 4th parameter to which SOCK_CLOEXEC can be passed. (Linux also supports O_NONBLOCK/SOCK_NONBLOCK to set non-blocking mode without another system call, but this is just a performance improvement.)
In Windows, an extended version of socket() exists, WSASocketW(). One of its extra parameters is a flags field, to which WSA_FLAG_NO_HANDLE_INHERIT can be passed for the desired effect. It's only supported starting with Windows 7 Service Pack 1, though. There is no usable extension of accept() for this, but in Windows, the no-inherit flag on the accepted socket is apparently copied from the listener socket in my tests. This behavior is not documented.
In most other platforms for which this is a problem, there are no ways to do atomic setting of close-on-exec. For example, macOS doesn't seem to have a way to atomically set the flag with FD creation. You'd have to just call fcntl() and hope a fork() didn't happen in the meantime. (In Windows, the non-atomic way is SetHandleInformation(), because IP sockets are kernel handles.)
Even with all this, the problem can't fully be solved on Linux, because calls to fopen() by anything create an inheritable handle. OpenSSL reading the certificate file would leak a handle in parallel with a fork(), as a simple example.
Is it worth trying?
Received on 2019-12-03