mirror of
https://github.com/golang/go
synced 2024-11-18 17:34:51 -07:00
syscall: skip TestDeathSignalSetuid if exec fails with a permission error
Also explicitly look up user "nobody" (or "gopher" on the Go builders) if running as root, instead of hard-coding UID/GID 99. Fixes #62719. Change-Id: I9fa8955f2c239804fa775f2478a5274af9330822 Reviewed-on: https://go-review.googlesource.com/c/go/+/529795 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
517c66d4ee
commit
dd881027c3
@ -14,26 +14,26 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeathSignal(t *testing.T) {
|
// TestDeathSignalSetuid verifies that a command run with a different UID still
|
||||||
if os.Getuid() != 0 {
|
// receives PDeathsig; it is a regression test for https://go.dev/issue/9686.
|
||||||
t.Skip("skipping root only test")
|
func TestDeathSignalSetuid(t *testing.T) {
|
||||||
}
|
if testing.Short() {
|
||||||
if testing.Short() && testenv.Builder() != "" && os.Getenv("USER") == "swarming" {
|
t.Skipf("skipping test that copies its binary into temp dir")
|
||||||
// The Go build system's swarming user is known not to be root.
|
|
||||||
// Unfortunately, it sometimes appears as root due the current
|
|
||||||
// implementation of a no-network check using 'unshare -n -r'.
|
|
||||||
// Since this test does need root to work, we need to skip it.
|
|
||||||
t.Skip("skipping root only test on a non-root builder")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the test binary to a location that a non-root user can read/execute
|
// Copy the test binary to a location that another user can read/execute
|
||||||
// after we drop privileges
|
// after we drop privileges.
|
||||||
|
//
|
||||||
|
// TODO(bcmills): Why do we believe that another users will be able to
|
||||||
|
// execute a binary in this directory? (It could be mounted noexec.)
|
||||||
tempDir, err := os.MkdirTemp("", "TestDeathSignal")
|
tempDir, err := os.MkdirTemp("", "TestDeathSignal")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot create temporary directory: %v", err)
|
t.Fatalf("cannot create temporary directory: %v", err)
|
||||||
@ -61,8 +61,8 @@ func TestDeathSignal(t *testing.T) {
|
|||||||
t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
|
t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(tmpBinary)
|
cmd := testenv.Command(t, tmpBinary)
|
||||||
cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
|
cmd.Env = append(cmd.Environ(), "GO_DEATHSIG_PARENT=1")
|
||||||
chldStdin, err := cmd.StdinPipe()
|
chldStdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create new stdin pipe: %v", err)
|
t.Fatalf("failed to create new stdin pipe: %v", err)
|
||||||
@ -71,10 +71,17 @@ func TestDeathSignal(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create new stdout pipe: %v", err)
|
t.Fatalf("failed to create new stdout pipe: %v", err)
|
||||||
}
|
}
|
||||||
cmd.Stderr = os.Stderr
|
stderr := new(strings.Builder)
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
defer cmd.Wait()
|
defer func() {
|
||||||
|
chldStdin.Close()
|
||||||
|
cmd.Wait()
|
||||||
|
if stderr.Len() > 0 {
|
||||||
|
t.Logf("stderr:\n%s", stderr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to start first child process: %v", err)
|
t.Fatalf("failed to start first child process: %v", err)
|
||||||
}
|
}
|
||||||
@ -84,21 +91,57 @@ func TestDeathSignal(t *testing.T) {
|
|||||||
if got, err := chldPipe.ReadString('\n'); got == "start\n" {
|
if got, err := chldPipe.ReadString('\n'); got == "start\n" {
|
||||||
syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
|
syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
|
||||||
|
|
||||||
go func() {
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
chldStdin.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
want := "ok\n"
|
want := "ok\n"
|
||||||
if got, err = chldPipe.ReadString('\n'); got != want {
|
if got, err = chldPipe.ReadString('\n'); got != want {
|
||||||
t.Fatalf("expected %q, received %q, %v", want, got, err)
|
t.Fatalf("expected %q, received %q, %v", want, got, err)
|
||||||
}
|
}
|
||||||
|
} else if got == "skip\n" {
|
||||||
|
t.Skipf("skipping: parent could not run child program as selected user")
|
||||||
} else {
|
} else {
|
||||||
t.Fatalf("did not receive start from child, received %q, %v", got, err)
|
t.Fatalf("did not receive start from child, received %q, %v", got, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deathSignalParent() {
|
func deathSignalParent() {
|
||||||
|
var (
|
||||||
|
u *user.User
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
tryUsers := []string{"nobody"}
|
||||||
|
if testenv.Builder() != "" {
|
||||||
|
tryUsers = append(tryUsers, "gopher")
|
||||||
|
}
|
||||||
|
for _, name := range tryUsers {
|
||||||
|
u, err = user.Lookup(name)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Lookup(%q): %v\n", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if u == nil {
|
||||||
|
// If we couldn't find an unprivileged user to run as, try running as
|
||||||
|
// the current user. (Empirically this still causes the call to Start to
|
||||||
|
// fail with a permission error if running as a non-root user on Linux.)
|
||||||
|
u, err = user.Current()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := strconv.ParseUint(u.Uid, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "invalid UID: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
gid, err := strconv.ParseUint(u.Gid, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "invalid GID: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0])
|
cmd := exec.Command(os.Args[0])
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
"GO_DEATHSIG_PARENT=",
|
"GO_DEATHSIG_PARENT=",
|
||||||
@ -107,16 +150,18 @@ func deathSignalParent() {
|
|||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
attrs := syscall.SysProcAttr{
|
attrs := syscall.SysProcAttr{
|
||||||
Pdeathsig: syscall.SIGUSR1,
|
Pdeathsig: syscall.SIGUSR1,
|
||||||
// UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is
|
Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)},
|
||||||
// unused on Ubuntu
|
|
||||||
Credential: &syscall.Credential{Uid: 99, Gid: 99},
|
|
||||||
}
|
}
|
||||||
cmd.SysProcAttr = &attrs
|
cmd.SysProcAttr = &attrs
|
||||||
|
|
||||||
err := cmd.Start()
|
fmt.Fprintf(os.Stderr, "starting process as user %q\n", u.Username)
|
||||||
if err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
if testenv.SyscallIsNotSupported(err) {
|
||||||
|
fmt.Println("skip")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
|
Loading…
Reference in New Issue
Block a user