Skip to content

Commit cea29c4

Browse files
os: on GNU/Linux use waitid to avoid wait/kill race
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 <[email protected]> Reviewed-by: Austin Clements <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent e980a3d commit cea29c4

File tree

4 files changed

+67
-2
lines changed

4 files changed

+67
-2
lines changed

src/os/exec.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ package os
66

77
import (
88
"runtime"
9+
"sync"
910
"sync/atomic"
1011
"syscall"
1112
)
1213

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

2022
func newProcess(pid int, handle uintptr) *Process {

src/os/exec_unix.go

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ func (p *Process) wait() (ps *ProcessState, err error) {
1717
if p.Pid == -1 {
1818
return nil, syscall.EINVAL
1919
}
20+
21+
// If we can block until Wait4 will succeed immediately, do so.
22+
ready, err := p.blockUntilWaitable()
23+
if err != nil {
24+
return nil, err
25+
}
26+
if ready {
27+
// Mark the process done now, before the call to Wait4,
28+
// so that Process.signal will not send a signal.
29+
p.setDone()
30+
// Acquire a write lock on sigMu to wait for any
31+
// active call to the signal method to complete.
32+
p.sigMu.Lock()
33+
p.sigMu.Unlock()
34+
}
35+
2036
var status syscall.WaitStatus
2137
var rusage syscall.Rusage
2238
pid1, e := syscall.Wait4(p.Pid, &status, 0, &rusage)
@@ -43,6 +59,8 @@ func (p *Process) signal(sig Signal) error {
4359
if p.Pid == 0 {
4460
return errors.New("os: process not initialized")
4561
}
62+
p.sigMu.RLock()
63+
defer p.sigMu.RUnlock()
4664
if p.done() {
4765
return errFinished
4866
}

src/os/wait_linux.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os
6+
7+
import (
8+
"runtime"
9+
"syscall"
10+
"unsafe"
11+
)
12+
13+
const _P_PID = 1
14+
15+
// blockUntilWaitable attempts to block until a call to p.Wait will
16+
// succeed immediately, and returns whether it has done so.
17+
// It does not actually call p.Wait.
18+
func (p *Process) blockUntilWaitable() (bool, error) {
19+
// waitid expects a pointer to a siginfo_t, which is 128 bytes
20+
// on all systems. We don't care about the values it returns.
21+
var siginfo [128]byte
22+
psig := &siginfo[0]
23+
_, _, e := syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
24+
runtime.KeepAlive(psig)
25+
if e != 0 {
26+
return false, NewSyscallError("waitid", e)
27+
}
28+
return true, nil
29+
}

src/os/wait_unimp.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build darwin dragonfly freebsd nacl netbsd openbsd solaris
6+
7+
package os
8+
9+
// blockUntilWaitable attempts to block until a call to p.Wait will
10+
// succeed immediately, and returns whether it has done so.
11+
// It does not actually call p.Wait.
12+
// This version is used on systems that do not implement waitid,
13+
// or where we have not implemented it yet.
14+
func (p *Process) blockUntilWaitable() (bool, error) {
15+
return false, nil
16+
}

0 commit comments

Comments
 (0)