Skip to content

Commit 7323aac

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

File tree

4 files changed

+189
-141
lines changed

4 files changed

+189
-141
lines changed

src/syscall/exec_freebsd.go

Lines changed: 31 additions & 1 deletion
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, _ := rawSyscallNoError(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, _ = rawSyscallNoError(SYS_GETPPID, 0, 0, 0)
199+
if r1 != ppid {
200+
pid, _ := rawSyscallNoError(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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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 TestMain(m *testing.M) {
24+
if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
25+
deathSignalParent()
26+
} else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
27+
deathSignalChild()
28+
} else if os.Getenv("GO_SYSCALL_NOERROR") == "1" {
29+
syscallNoError()
30+
}
31+
32+
os.Exit(m.Run())
33+
}
34+
35+
func TestDeathSignal(t *testing.T) {
36+
if os.Getuid() != 0 {
37+
t.Skip("skipping root only test")
38+
}
39+
40+
// Copy the test binary to a location that a non-root user can read/execute
41+
// after we drop privileges
42+
tempDir, err := os.MkdirTemp("", "TestDeathSignal")
43+
if err != nil {
44+
t.Fatalf("cannot create temporary directory: %v", err)
45+
}
46+
defer os.RemoveAll(tempDir)
47+
os.Chmod(tempDir, 0755)
48+
49+
tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
50+
51+
src, err := os.Open(os.Args[0])
52+
if err != nil {
53+
t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
54+
}
55+
defer src.Close()
56+
57+
dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
58+
if err != nil {
59+
t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
60+
}
61+
if _, err := io.Copy(dst, src); err != nil {
62+
t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
63+
}
64+
err = dst.Close()
65+
if err != nil {
66+
t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
67+
}
68+
69+
cmd := exec.Command(tmpBinary)
70+
cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
71+
chldStdin, err := cmd.StdinPipe()
72+
if err != nil {
73+
t.Fatalf("failed to create new stdin pipe: %v", err)
74+
}
75+
chldStdout, err := cmd.StdoutPipe()
76+
if err != nil {
77+
t.Fatalf("failed to create new stdout pipe: %v", err)
78+
}
79+
cmd.Stderr = os.Stderr
80+
81+
err = cmd.Start()
82+
defer cmd.Wait()
83+
if err != nil {
84+
t.Fatalf("failed to start first child process: %v", err)
85+
}
86+
87+
chldPipe := bufio.NewReader(chldStdout)
88+
89+
if got, err := chldPipe.ReadString('\n'); got == "start\n" {
90+
syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
91+
92+
go func() {
93+
time.Sleep(5 * time.Second)
94+
chldStdin.Close()
95+
}()
96+
97+
want := "ok\n"
98+
if got, err = chldPipe.ReadString('\n'); got != want {
99+
t.Fatalf("expected %q, received %q, %v", want, got, err)
100+
}
101+
} else {
102+
t.Fatalf("did not receive start from child, received %q, %v", got, err)
103+
}
104+
}
105+
106+
func deathSignalParent() {
107+
cmd := exec.Command(os.Args[0])
108+
cmd.Env = append(os.Environ(),
109+
"GO_DEATHSIG_PARENT=",
110+
"GO_DEATHSIG_CHILD=1",
111+
)
112+
cmd.Stdin = os.Stdin
113+
cmd.Stdout = os.Stdout
114+
attrs := syscall.SysProcAttr{
115+
Pdeathsig: syscall.SIGUSR1,
116+
// UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
117+
// unused on Ubuntu
118+
Credential: &syscall.Credential{Uid: 99, Gid: 99},
119+
}
120+
cmd.SysProcAttr = &attrs
121+
122+
err := cmd.Start()
123+
if err != nil {
124+
fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
125+
os.Exit(1)
126+
}
127+
cmd.Wait()
128+
os.Exit(0)
129+
}
130+
131+
func deathSignalChild() {
132+
c := make(chan os.Signal, 1)
133+
signal.Notify(c, syscall.SIGUSR1)
134+
go func() {
135+
<-c
136+
fmt.Println("ok")
137+
os.Exit(0)
138+
}()
139+
fmt.Println("start")
140+
141+
buf := make([]byte, 32)
142+
os.Stdin.Read(buf)
143+
144+
// We expected to be signaled before stdin closed
145+
fmt.Println("not ok")
146+
os.Exit(1)
147+
}
148+
149+
func syscallNoError() {
150+
// Test that the return value from SYS_GETEUID32 (which cannot fail)
151+
// doesn't get treated as an error (see https://golang.org/issue/22924)
152+
euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
153+
euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
154+
155+
fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
156+
os.Exit(0)
157+
}

src/syscall/syscall_linux_test.go

Lines changed: 0 additions & 139 deletions
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

@@ -141,132 +138,6 @@ func TestFchmodat(t *testing.T) {
141138
}
142139
}
143140

144-
func TestMain(m *testing.M) {
145-
if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
146-
deathSignalParent()
147-
} else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
148-
deathSignalChild()
149-
} else if os.Getenv("GO_SYSCALL_NOERROR") == "1" {
150-
syscallNoError()
151-
}
152-
153-
os.Exit(m.Run())
154-
}
155-
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-
270141
func TestParseNetlinkMessage(t *testing.T) {
271142
for i, b := range [][]byte{
272143
{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,
@@ -390,16 +261,6 @@ func filesystemIsNoSUID(path string) bool {
390261
return st.Flags&syscall.MS_NOSUID != 0
391262
}
392263

393-
func syscallNoError() {
394-
// Test that the return value from SYS_GETEUID32 (which cannot fail)
395-
// doesn't get treated as an error (see https://golang.org/issue/22924)
396-
euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
397-
euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
398-
399-
fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
400-
os.Exit(0)
401-
}
402-
403264
// reference uapi/linux/prctl.h
404265
const (
405266
PR_GET_KEEPCAPS uintptr = 7

0 commit comments

Comments
 (0)