From a54c913a6a99e6079aa093b6fee82cf5865e8089 Mon Sep 17 00:00:00 2001 From: Carl Jackson Date: Thu, 12 Jun 2014 04:03:32 -0400 Subject: [PATCH] Fix GC race in bind with Einhorn This fixes a race condition between package bind and the garbage collector, where if the garbage collector ran between einhornInit and einhornBind, bind would fatal with the error "dup: bad file descriptor" The core of the bug is that Go's os.File uses runtime.SetFinalizer to register a callback to close the underlying file descriptor an os.File points at when the os.File itself is being garbage collected. However, the Einhorn initialization code in bind, in the process of ensuring that every Einhorn-passed socket had CloseOnExec set on it, allocated os.File's pointing at each of these passed file descriptors, but did not keep references to these os.File's, allowing them to be garbage collected. Subsequently, if you attempted to bind one of these sockets, you'd find that it was no longer open. This is the simplest fix to the bug, which is to only allocate an os.File when we actually attempt to bind the socket. Note that there's still a race condition here if you attempt to bind the same file descriptor twice, since a GC between the two binds will likely cause the file to be collected. Fortunately, that one can be worked around by simply not allowing such silly behavior :). Another patch that makes this more clear will follow. Closes #29. --- bind/einhorn.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bind/einhorn.go b/bind/einhorn.go index 3f7635f..8c11c75 100644 --- a/bind/einhorn.go +++ b/bind/einhorn.go @@ -38,8 +38,7 @@ func einhornInit() { // Prevent einhorn's fds from leaking to our children for i := 0; i < einhornNumFds; i++ { - fd := int(einhornFd(i).Fd()) - syscall.CloseOnExec(fd) + syscall.CloseOnExec(einhornFdMap(i)) } } @@ -47,13 +46,13 @@ func usingEinhorn() bool { return einhornNumFds > 0 } -func einhornFd(n int) *os.File { +func einhornFdMap(n int) int { name := fmt.Sprintf("EINHORN_FD_%d", n) fno, err := envInt(name) if err != nil { log.Fatal(einhornErr) } - return os.NewFile(uintptr(fno), name) + return fno } func einhornBind(n int) (net.Listener, error) { @@ -64,7 +63,8 @@ func einhornBind(n int) (net.Listener, error) { return nil, fmt.Errorf(tooBigErr, n, einhornNumFds) } - f := einhornFd(n) + fno := einhornFdMap(n) + f := os.NewFile(uintptr(fno), fmt.Sprintf("einhorn@%d", n)) return net.FileListener(f) }