aboutsummaryrefslogtreecommitdiff
path: root/src/syscall
diff options
context:
space:
mode:
authorTobias Klauser <tklauser@distanz.ch>2021-10-13 16:30:12 +0200
committerTobias Klauser <tobias.klauser@gmail.com>2021-10-14 07:19:29 +0000
commit2feb2cc450e1925b9359957c90bae27e01662171 (patch)
tree10c10cde1bffe926a4a89f0a91d797110f25d618 /src/syscall
parent1349c6eb1e3faf8b83db9a1ea30764d56b40c7dc (diff)
downloadgo-2feb2cc450e1925b9359957c90bae27e01662171.tar.gz
go-2feb2cc450e1925b9359957c90bae27e01662171.zip
syscall: add support for SysProcAttr.Pdeathsig on FreeBSD
Fixes #46258 Change-Id: I63f70e67274a9df39c757243b99b12e50a9e4784 Reviewed-on: https://go-review.googlesource.com/c/go/+/355570 Trust: Tobias Klauser <tobias.klauser@gmail.com> Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'src/syscall')
-rw-r--r--src/syscall/exec_freebsd.go38
-rw-r--r--src/syscall/exec_linux.go2
-rw-r--r--src/syscall/exec_pdeathsig_test.go135
-rw-r--r--src/syscall/syscall_freebsd_test.go11
-rw-r--r--src/syscall/syscall_linux_test.go117
5 files changed, 184 insertions, 119 deletions
diff --git a/src/syscall/exec_freebsd.go b/src/syscall/exec_freebsd.go
index 393b3d485b..a7410db4b6 100644
--- a/src/syscall/exec_freebsd.go
+++ b/src/syscall/exec_freebsd.go
@@ -5,6 +5,7 @@
package syscall
import (
+ "runtime"
"unsafe"
)
@@ -29,9 +30,16 @@ type SysProcAttr struct {
// Unlike Setctty, in this case Ctty must be a descriptor
// number in the parent process.
Foreground bool
- Pgid int // Child's process group ID if Setpgid.
+ Pgid int // Child's process group ID if Setpgid.
+ Pdeathsig Signal // Signal that the process will get when its parent dies (Linux and FreeBSD only)
}
+const (
+ _P_PID = 0
+
+ _PROC_PDEATHSIG_CTL = 11
+)
+
// Implemented in runtime package.
func runtime_BeforeFork()
func runtime_AfterFork()
@@ -57,6 +65,9 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
i int
)
+ // Record parent PID so child can test if it has died.
+ ppid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
+
// guard against side effects of shuffling fds below.
// Make sure that nextfd is beyond any currently open files so
// that we can't run the risk of overwriting any of them.
@@ -175,6 +186,31 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
}
}
+ // Parent death signal
+ if sys.Pdeathsig != 0 {
+ switch runtime.GOARCH {
+ case "386", "arm":
+ _, _, err1 = RawSyscall6(SYS_PROCCTL, _P_PID, 0, 0, _PROC_PDEATHSIG_CTL, uintptr(unsafe.Pointer(&sys.Pdeathsig)), 0)
+ default:
+ _, _, err1 = RawSyscall6(SYS_PROCCTL, _P_PID, 0, _PROC_PDEATHSIG_CTL, uintptr(unsafe.Pointer(&sys.Pdeathsig)), 0, 0)
+ }
+ if err1 != 0 {
+ goto childerror
+ }
+
+ // Signal self if parent is already dead. This might cause a
+ // duplicate signal in rare cases, but it won't matter when
+ // using SIGKILL.
+ r1, _, _ = RawSyscall(SYS_GETPPID, 0, 0, 0)
+ if r1 != ppid {
+ pid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
+ _, _, err1 := RawSyscall(SYS_KILL, pid, uintptr(sys.Pdeathsig), 0)
+ if err1 != 0 {
+ goto childerror
+ }
+ }
+ }
+
// Pass 1: look for fd[i] < i and move those up above len(fd)
// so that pass 2 won't stomp on an fd it needs later.
if pipe < nextfd {
diff --git a/src/syscall/exec_linux.go b/src/syscall/exec_linux.go
index dfc5228545..e1506e1e2b 100644
--- a/src/syscall/exec_linux.go
+++ b/src/syscall/exec_linux.go
@@ -46,7 +46,7 @@ type SysProcAttr struct {
// number in the parent process.
Foreground bool
Pgid int // Child's process group ID if Setpgid.
- Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
+ Pdeathsig Signal // Signal that the process will get when its parent dies (Linux and FreeBSD only)
Cloneflags uintptr // Flags for clone calls (Linux only)
Unshareflags uintptr // Flags for unshare calls (Linux only)
UidMappings []SysProcIDMap // User ID mappings for user namespaces.
diff --git a/src/syscall/exec_pdeathsig_test.go b/src/syscall/exec_pdeathsig_test.go
new file mode 100644
index 0000000000..6533d3a138
--- /dev/null
+++ b/src/syscall/exec_pdeathsig_test.go
@@ -0,0 +1,135 @@
+// Copyright 2015 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.
+
+//go:build freebsd || linux
+// +build freebsd linux
+
+package syscall_test
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "os/signal"
+ "path/filepath"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func TestDeathSignal(t *testing.T) {
+ if os.Getuid() != 0 {
+ t.Skip("skipping root only test")
+ }
+
+ // Copy the test binary to a location that a non-root user can read/execute
+ // after we drop privileges
+ tempDir, err := os.MkdirTemp("", "TestDeathSignal")
+ if err != nil {
+ t.Fatalf("cannot create temporary directory: %v", err)
+ }
+ defer os.RemoveAll(tempDir)
+ os.Chmod(tempDir, 0755)
+
+ tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
+
+ src, err := os.Open(os.Args[0])
+ if err != nil {
+ t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
+ }
+ defer src.Close()
+
+ dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
+ if err != nil {
+ t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
+ }
+ if _, err := io.Copy(dst, src); err != nil {
+ t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
+ }
+ err = dst.Close()
+ if err != nil {
+ t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
+ }
+
+ cmd := exec.Command(tmpBinary)
+ cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
+ chldStdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatalf("failed to create new stdin pipe: %v", err)
+ }
+ chldStdout, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatalf("failed to create new stdout pipe: %v", err)
+ }
+ cmd.Stderr = os.Stderr
+
+ err = cmd.Start()
+ defer cmd.Wait()
+ if err != nil {
+ t.Fatalf("failed to start first child process: %v", err)
+ }
+
+ chldPipe := bufio.NewReader(chldStdout)
+
+ if got, err := chldPipe.ReadString('\n'); got == "start\n" {
+ syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
+
+ go func() {
+ time.Sleep(5 * time.Second)
+ chldStdin.Close()
+ }()
+
+ want := "ok\n"
+ if got, err = chldPipe.ReadString('\n'); got != want {
+ t.Fatalf("expected %q, received %q, %v", want, got, err)
+ }
+ } else {
+ t.Fatalf("did not receive start from child, received %q, %v", got, err)
+ }
+}
+
+func deathSignalParent() {
+ cmd := exec.Command(os.Args[0])
+ cmd.Env = append(os.Environ(),
+ "GO_DEATHSIG_PARENT=",
+ "GO_DEATHSIG_CHILD=1",
+ )
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ attrs := syscall.SysProcAttr{
+ Pdeathsig: syscall.SIGUSR1,
+ // UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
+ // unused on Ubuntu
+ Credential: &syscall.Credential{Uid: 99, Gid: 99},
+ }
+ cmd.SysProcAttr = &attrs
+
+ err := cmd.Start()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
+ os.Exit(1)
+ }
+ cmd.Wait()
+ os.Exit(0)
+}
+
+func deathSignalChild() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGUSR1)
+ go func() {
+ <-c
+ fmt.Println("ok")
+ os.Exit(0)
+ }()
+ fmt.Println("start")
+
+ buf := make([]byte, 32)
+ os.Stdin.Read(buf)
+
+ // We expected to be signaled before stdin closed
+ fmt.Println("not ok")
+ os.Exit(1)
+}
diff --git a/src/syscall/syscall_freebsd_test.go b/src/syscall/syscall_freebsd_test.go
index 89c7959d0c..72cd133b85 100644
--- a/src/syscall/syscall_freebsd_test.go
+++ b/src/syscall/syscall_freebsd_test.go
@@ -9,6 +9,7 @@ package syscall_test
import (
"fmt"
+ "os"
"syscall"
"testing"
"unsafe"
@@ -53,3 +54,13 @@ func TestConvertFromDirent11(t *testing.T) {
}
}
}
+
+func TestMain(m *testing.M) {
+ if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
+ deathSignalParent()
+ } else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
+ deathSignalChild()
+ }
+
+ os.Exit(m.Run())
+}
diff --git a/src/syscall/syscall_linux_test.go b/src/syscall/syscall_linux_test.go
index 442dc9f10e..8d828be015 100644
--- a/src/syscall/syscall_linux_test.go
+++ b/src/syscall/syscall_linux_test.go
@@ -5,13 +5,11 @@
package syscall_test
import (
- "bufio"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
- "os/signal"
"path/filepath"
"runtime"
"sort"
@@ -19,7 +17,6 @@ import (
"strings"
"syscall"
"testing"
- "time"
"unsafe"
)
@@ -153,120 +150,6 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
-func TestLinuxDeathSignal(t *testing.T) {
- if os.Getuid() != 0 {
- t.Skip("skipping root only test")
- }
-
- // Copy the test binary to a location that a non-root user can read/execute
- // after we drop privileges
- tempDir, err := os.MkdirTemp("", "TestDeathSignal")
- if err != nil {
- t.Fatalf("cannot create temporary directory: %v", err)
- }
- defer os.RemoveAll(tempDir)
- os.Chmod(tempDir, 0755)
-
- tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
-
- src, err := os.Open(os.Args[0])
- if err != nil {
- t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
- }
- defer src.Close()
-
- dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
- if err != nil {
- t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
- }
- if _, err := io.Copy(dst, src); err != nil {
- t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
- }
- err = dst.Close()
- if err != nil {
- t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
- }
-
- cmd := exec.Command(tmpBinary)
- cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
- chldStdin, err := cmd.StdinPipe()
- if err != nil {
- t.Fatalf("failed to create new stdin pipe: %v", err)
- }
- chldStdout, err := cmd.StdoutPipe()
- if err != nil {
- t.Fatalf("failed to create new stdout pipe: %v", err)
- }
- cmd.Stderr = os.Stderr
-
- err = cmd.Start()
- defer cmd.Wait()
- if err != nil {
- t.Fatalf("failed to start first child process: %v", err)
- }
-
- chldPipe := bufio.NewReader(chldStdout)
-
- if got, err := chldPipe.ReadString('\n'); got == "start\n" {
- syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
-
- go func() {
- time.Sleep(5 * time.Second)
- chldStdin.Close()
- }()
-
- want := "ok\n"
- if got, err = chldPipe.ReadString('\n'); got != want {
- t.Fatalf("expected %q, received %q, %v", want, got, err)
- }
- } else {
- t.Fatalf("did not receive start from child, received %q, %v", got, err)
- }
-}
-
-func deathSignalParent() {
- cmd := exec.Command(os.Args[0])
- cmd.Env = append(os.Environ(),
- "GO_DEATHSIG_PARENT=",
- "GO_DEATHSIG_CHILD=1",
- )
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- attrs := syscall.SysProcAttr{
- Pdeathsig: syscall.SIGUSR1,
- // UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
- // unused on Ubuntu
- Credential: &syscall.Credential{Uid: 99, Gid: 99},
- }
- cmd.SysProcAttr = &attrs
-
- err := cmd.Start()
- if err != nil {
- fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
- os.Exit(1)
- }
- cmd.Wait()
- os.Exit(0)
-}
-
-func deathSignalChild() {
- c := make(chan os.Signal, 1)
- signal.Notify(c, syscall.SIGUSR1)
- go func() {
- <-c
- fmt.Println("ok")
- os.Exit(0)
- }()
- fmt.Println("start")
-
- buf := make([]byte, 32)
- os.Stdin.Read(buf)
-
- // We expected to be signaled before stdin closed
- fmt.Println("not ok")
- os.Exit(1)
-}
-
func TestParseNetlinkMessage(t *testing.T) {
for i, b := range [][]byte{
{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,