Set O_CLOEXEC on received file descriptors #537
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
By default Go sets
O_CLOEXEC
on file descriptors it creates. This ensures that they aren't accidentally inherited over fork+exec.When binding a port on the host we first try
net.Listen
. On modern macOS this works forINADDR_ANY
(all IPs)Unfortunately it doesn't work for privileged ports on specific IPs, such as
127.0.0.1:80
. For these cases we contact a helper process which runs as root, which binds the port and then usessendmsg
to transmit it to us. Unfortunately when werecvmsg
a file descriptor, the resulting descriptor does not haveO_CLOEXEC
set, so it is captured by future subprocesses. Even if the parent process callsclose()
, the port is still in use by the subprocesses and can't be re-bound. This caused the failure seen in docker/for-mac#5649The solution is to ensure all file descriptors have
O_CLOEXEC
set. On Linux there's asendmsg
flag to set the flag atomically on received fds, but this is missing on Darwin. The best we can do is to callfcntl
to set the flag ourselves after receiving the fd. Unfortunately this means there's a small race window where a fork would inherit the fd, but this shouldn't happen very often in practice.Go will call
fcntl
for us if we "adopt" the file descriptor usingos.NewFile
and then usenet.FileListener
andnet.FilePacketConn
. This allows us to remove a lot of complicated wrapping code and allows us to remove a workaround forClose
hanging when blocked inAccept
, caused by the state of the file descriptor not being correct. This required some minor changes to types (net.Listener
rather than*net.TCPListener
) but it's worth it.It's possible to work around this problem in other ways in the client code, for example:
fork
but beforeexec
. This can be expensive with highulimit
values, if there is no API to enumerate open fds. I'm not sure it can be done easily in Go where theexec.Command
API is high-level.Signed-off-by: David Scott dave@recoil.org