Skip to content

Commit

Permalink
os: on GNU/Linux use waitid to avoid wait/kill race
Browse files Browse the repository at this point in the history
On systems that support the POSIX.1-2008 waitid function, we can use it
to block until a wait will succeed. This avoids a possible race
condition: if a program calls p.Kill/p.Signal and p.Wait from two
different goroutines, then it is possible for the wait to complete just
before the signal is sent. In that case, it is possible that the system
will start a new process using the same PID between the wait and the
signal, causing the signal to be sent to the wrong process. The
Process.isdone field attempts to avoid that race, but there is a small
gap of time between when wait returns and isdone is set when the race
can occur.

This CL avoids that race by using waitid to wait until the process has
exited without actually collecting the PID. Then it sets isdone, then
waits for any active signals to complete, and only then collects the PID.

No test because any plausible test would require starting enough
processes to recycle all the process IDs.

Update #13987.
Update #16028.

Change-Id: Id2939431991d3b355dfb22f08793585fc0568ce8
Reviewed-on: https://go-review.googlesource.com/23967
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
  • Loading branch information
ianlancetaylor committed Jun 10, 2016
1 parent e980a3d commit cea29c4
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 2 deletions.
6 changes: 4 additions & 2 deletions src/os/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ package os

import (
"runtime"
"sync"
"sync/atomic"
"syscall"
)

// Process stores the information about a process created by StartProcess.
type Process struct {
Pid int
handle uintptr // handle is accessed atomically on Windows
isdone uint32 // process has been successfully waited on, non zero if true
handle uintptr // handle is accessed atomically on Windows
isdone uint32 // process has been successfully waited on, non zero if true
sigMu sync.RWMutex // avoid race between wait and signal
}

func newProcess(pid int, handle uintptr) *Process {
Expand Down
18 changes: 18 additions & 0 deletions src/os/exec_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ func (p *Process) wait() (ps *ProcessState, err error) {
if p.Pid == -1 {
return nil, syscall.EINVAL
}

// If we can block until Wait4 will succeed immediately, do so.
ready, err := p.blockUntilWaitable()
if err != nil {
return nil, err
}
if ready {
// Mark the process done now, before the call to Wait4,
// so that Process.signal will not send a signal.
p.setDone()
// Acquire a write lock on sigMu to wait for any
// active call to the signal method to complete.
p.sigMu.Lock()
p.sigMu.Unlock()
}

var status syscall.WaitStatus
var rusage syscall.Rusage
pid1, e := syscall.Wait4(p.Pid, &status, 0, &rusage)
Expand All @@ -43,6 +59,8 @@ func (p *Process) signal(sig Signal) error {
if p.Pid == 0 {
return errors.New("os: process not initialized")
}
p.sigMu.RLock()
defer p.sigMu.RUnlock()
if p.done() {
return errFinished
}
Expand Down
29 changes: 29 additions & 0 deletions src/os/wait_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package os

import (
"runtime"
"syscall"
"unsafe"
)

const _P_PID = 1

// blockUntilWaitable attempts to block until a call to p.Wait will
// succeed immediately, and returns whether it has done so.
// It does not actually call p.Wait.
func (p *Process) blockUntilWaitable() (bool, error) {
// waitid expects a pointer to a siginfo_t, which is 128 bytes
// on all systems. We don't care about the values it returns.
var siginfo [128]byte
psig := &siginfo[0]
_, _, e := syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
runtime.KeepAlive(psig)
if e != 0 {
return false, NewSyscallError("waitid", e)
}
return true, nil
}
16 changes: 16 additions & 0 deletions src/os/wait_unimp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd nacl netbsd openbsd solaris

package os

// blockUntilWaitable attempts to block until a call to p.Wait will
// succeed immediately, and returns whether it has done so.
// It does not actually call p.Wait.
// This version is used on systems that do not implement waitid,
// or where we have not implemented it yet.
func (p *Process) blockUntilWaitable() (bool, error) {
return false, nil
}

0 comments on commit cea29c4

Please sign in to comment.