Skip to content

Commit 0e3012f

Browse files
committed
syscall: add support for SysProcAttr.Pdeathsig on FreeBSD
Fixes golang#46258 Change-Id: I63f70e67274a9df39c757243b99b12e50a9e4784
1 parent b41a9d7 commit 0e3012f

File tree

5 files changed

+178
-119
lines changed

5 files changed

+178
-119
lines changed

src/syscall/exec_freebsd.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,16 @@ type SysProcAttr struct {
2929
// Unlike Setctty, in this case Ctty must be a descriptor
3030
// number in the parent process.
3131
Foreground bool
32-
Pgid int // Child's process group ID if Setpgid.
32+
Pgid int // Child's process group ID if Setpgid.
33+
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux and FreeBSD only)
3334
}
3435

36+
const (
37+
_P_PID = 0
38+
39+
_PROC_PDEATHSIG_CTL = 11
40+
)
41+
3542
// Implemented in runtime package.
3643
func runtime_BeforeFork()
3744
func runtime_AfterFork()
@@ -57,6 +64,9 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
5764
i int
5865
)
5966

67+
// Record parent PID so child can test if it has died.
68+
ppid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
69+
6070
// guard against side effects of shuffling fds below.
6171
// Make sure that nextfd is beyond any currently open files so
6272
// that we can't run the risk of overwriting any of them.
@@ -175,6 +185,26 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
175185
}
176186
}
177187

188+
// Parent death signal
189+
if sys.Pdeathsig != 0 {
190+
_, _, err1 = RawSyscall6(SYS_PROCCTL, _P_PID, _PROC_PDEATHSIG_CTL, uintptr(unsafe.Pointer(&sys.Pdeathsig)), 0, 0, 0)
191+
if err1 != 0 {
192+
goto childerror
193+
}
194+
195+
// Signal self if parent is already dead. This might cause a
196+
// duplicate signal in rare cases, but it won't matter when
197+
// using SIGKILL.
198+
r1, _, _ = RawSyscall(SYS_GETPPID, 0, 0, 0)
199+
if r1 != ppid {
200+
pid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
201+
_, _, err1 := RawSyscall(SYS_KILL, pid, uintptr(sys.Pdeathsig), 0)
202+
if err1 != 0 {
203+
goto childerror
204+
}
205+
}
206+
}
207+
178208
// Pass 1: look for fd[i] < i and move those up above len(fd)
179209
// so that pass 2 won't stomp on an fd it needs later.
180210
if pipe < nextfd {

src/syscall/exec_linux.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type SysProcAttr struct {
4646
// number in the parent process.
4747
Foreground bool
4848
Pgid int // Child's process group ID if Setpgid.
49-
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
49+
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux and FreeBSD only)
5050
Cloneflags uintptr // Flags for clone calls (Linux only)
5151
Unshareflags uintptr // Flags for unshare calls (Linux only)
5252
UidMappings []SysProcIDMap // User ID mappings for user namespaces.

src/syscall/exec_pdeathsig_test.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2015 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+
//go:build freebsd || linux
6+
// +build freebsd linux
7+
8+
package syscall_test
9+
10+
import (
11+
"bufio"
12+
"fmt"
13+
"io"
14+
"os"
15+
"os/exec"
16+
"os/signal"
17+
"path/filepath"
18+
"syscall"
19+
"testing"
20+
"time"
21+
)
22+
23+
func TestDeathSignal(t *testing.T) {
24+
if os.Getuid() != 0 {
25+
t.Skip("skipping root only test")
26+
}
27+
28+
// Copy the test binary to a location that a non-root user can read/execute
29+
// after we drop privileges
30+
tempDir, err := os.MkdirTemp("", "TestDeathSignal")
31+
if err != nil {
32+
t.Fatalf("cannot create temporary directory: %v", err)
33+
}
34+
defer os.RemoveAll(tempDir)
35+
os.Chmod(tempDir, 0755)
36+
37+
tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
38+
39+
src, err := os.Open(os.Args[0])
40+
if err != nil {
41+
t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
42+
}
43+
defer src.Close()
44+
45+
dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
46+
if err != nil {
47+
t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
48+
}
49+
if _, err := io.Copy(dst, src); err != nil {
50+
t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
51+
}
52+
err = dst.Close()
53+
if err != nil {
54+
t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
55+
}
56+
57+
cmd := exec.Command(tmpBinary)
58+
cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
59+
chldStdin, err := cmd.StdinPipe()
60+
if err != nil {
61+
t.Fatalf("failed to create new stdin pipe: %v", err)
62+
}
63+
chldStdout, err := cmd.StdoutPipe()
64+
if err != nil {
65+
t.Fatalf("failed to create new stdout pipe: %v", err)
66+
}
67+
cmd.Stderr = os.Stderr
68+
69+
err = cmd.Start()
70+
defer cmd.Wait()
71+
if err != nil {
72+
t.Fatalf("failed to start first child process: %v", err)
73+
}
74+
75+
chldPipe := bufio.NewReader(chldStdout)
76+
77+
if got, err := chldPipe.ReadString('\n'); got == "start\n" {
78+
syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
79+
80+
go func() {
81+
time.Sleep(5 * time.Second)
82+
chldStdin.Close()
83+
}()
84+
85+
want := "ok\n"
86+
if got, err = chldPipe.ReadString('\n'); got != want {
87+
t.Fatalf("expected %q, received %q, %v", want, got, err)
88+
}
89+
} else {
90+
t.Fatalf("did not receive start from child, received %q, %v", got, err)
91+
}
92+
}
93+
94+
func deathSignalParent() {
95+
cmd := exec.Command(os.Args[0])
96+
cmd.Env = append(os.Environ(),
97+
"GO_DEATHSIG_PARENT=",
98+
"GO_DEATHSIG_CHILD=1",
99+
)
100+
cmd.Stdin = os.Stdin
101+
cmd.Stdout = os.Stdout
102+
attrs := syscall.SysProcAttr{
103+
Pdeathsig: syscall.SIGUSR1,
104+
// UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
105+
// unused on Ubuntu
106+
Credential: &syscall.Credential{Uid: 99, Gid: 99},
107+
}
108+
cmd.SysProcAttr = &attrs
109+
110+
err := cmd.Start()
111+
if err != nil {
112+
fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
113+
os.Exit(1)
114+
}
115+
cmd.Wait()
116+
os.Exit(0)
117+
}
118+
119+
func deathSignalChild() {
120+
c := make(chan os.Signal, 1)
121+
signal.Notify(c, syscall.SIGUSR1)
122+
go func() {
123+
<-c
124+
fmt.Println("ok")
125+
os.Exit(0)
126+
}()
127+
fmt.Println("start")
128+
129+
buf := make([]byte, 32)
130+
os.Stdin.Read(buf)
131+
132+
// We expected to be signaled before stdin closed
133+
fmt.Println("not ok")
134+
os.Exit(1)
135+
}

src/syscall/syscall_freebsd_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package syscall_test
99

1010
import (
1111
"fmt"
12+
"os"
1213
"syscall"
1314
"testing"
1415
"unsafe"
@@ -53,3 +54,13 @@ func TestConvertFromDirent11(t *testing.T) {
5354
}
5455
}
5556
}
57+
58+
func TestMain(m *testing.M) {
59+
if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
60+
deathSignalParent()
61+
} else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
62+
deathSignalChild()
63+
}
64+
65+
os.Exit(m.Run())
66+
}

src/syscall/syscall_linux_test.go

-117
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@
55
package syscall_test
66

77
import (
8-
"bufio"
98
"fmt"
109
"io"
1110
"io/fs"
1211
"os"
1312
"os/exec"
14-
"os/signal"
1513
"path/filepath"
1614
"runtime"
1715
"sort"
1816
"strconv"
1917
"strings"
2018
"syscall"
2119
"testing"
22-
"time"
2320
"unsafe"
2421
)
2522

@@ -153,120 +150,6 @@ func TestMain(m *testing.M) {
153150
os.Exit(m.Run())
154151
}
155152

156-
func TestLinuxDeathSignal(t *testing.T) {
157-
if os.Getuid() != 0 {
158-
t.Skip("skipping root only test")
159-
}
160-
161-
// Copy the test binary to a location that a non-root user can read/execute
162-
// after we drop privileges
163-
tempDir, err := os.MkdirTemp("", "TestDeathSignal")
164-
if err != nil {
165-
t.Fatalf("cannot create temporary directory: %v", err)
166-
}
167-
defer os.RemoveAll(tempDir)
168-
os.Chmod(tempDir, 0755)
169-
170-
tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
171-
172-
src, err := os.Open(os.Args[0])
173-
if err != nil {
174-
t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
175-
}
176-
defer src.Close()
177-
178-
dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
179-
if err != nil {
180-
t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
181-
}
182-
if _, err := io.Copy(dst, src); err != nil {
183-
t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
184-
}
185-
err = dst.Close()
186-
if err != nil {
187-
t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
188-
}
189-
190-
cmd := exec.Command(tmpBinary)
191-
cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
192-
chldStdin, err := cmd.StdinPipe()
193-
if err != nil {
194-
t.Fatalf("failed to create new stdin pipe: %v", err)
195-
}
196-
chldStdout, err := cmd.StdoutPipe()
197-
if err != nil {
198-
t.Fatalf("failed to create new stdout pipe: %v", err)
199-
}
200-
cmd.Stderr = os.Stderr
201-
202-
err = cmd.Start()
203-
defer cmd.Wait()
204-
if err != nil {
205-
t.Fatalf("failed to start first child process: %v", err)
206-
}
207-
208-
chldPipe := bufio.NewReader(chldStdout)
209-
210-
if got, err := chldPipe.ReadString('\n'); got == "start\n" {
211-
syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
212-
213-
go func() {
214-
time.Sleep(5 * time.Second)
215-
chldStdin.Close()
216-
}()
217-
218-
want := "ok\n"
219-
if got, err = chldPipe.ReadString('\n'); got != want {
220-
t.Fatalf("expected %q, received %q, %v", want, got, err)
221-
}
222-
} else {
223-
t.Fatalf("did not receive start from child, received %q, %v", got, err)
224-
}
225-
}
226-
227-
func deathSignalParent() {
228-
cmd := exec.Command(os.Args[0])
229-
cmd.Env = append(os.Environ(),
230-
"GO_DEATHSIG_PARENT=",
231-
"GO_DEATHSIG_CHILD=1",
232-
)
233-
cmd.Stdin = os.Stdin
234-
cmd.Stdout = os.Stdout
235-
attrs := syscall.SysProcAttr{
236-
Pdeathsig: syscall.SIGUSR1,
237-
// UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
238-
// unused on Ubuntu
239-
Credential: &syscall.Credential{Uid: 99, Gid: 99},
240-
}
241-
cmd.SysProcAttr = &attrs
242-
243-
err := cmd.Start()
244-
if err != nil {
245-
fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
246-
os.Exit(1)
247-
}
248-
cmd.Wait()
249-
os.Exit(0)
250-
}
251-
252-
func deathSignalChild() {
253-
c := make(chan os.Signal, 1)
254-
signal.Notify(c, syscall.SIGUSR1)
255-
go func() {
256-
<-c
257-
fmt.Println("ok")
258-
os.Exit(0)
259-
}()
260-
fmt.Println("start")
261-
262-
buf := make([]byte, 32)
263-
os.Stdin.Read(buf)
264-
265-
// We expected to be signaled before stdin closed
266-
fmt.Println("not ok")
267-
os.Exit(1)
268-
}
269-
270153
func TestParseNetlinkMessage(t *testing.T) {
271154
for i, b := range [][]byte{
272155
{103, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 11, 0, 1, 0, 0, 0, 0, 5, 8, 0, 3,

0 commit comments

Comments
 (0)