Source file src/syscall/exec_pdeathsig_test.go

     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  
     7  package syscall_test
     8  
     9  import (
    10  	"bufio"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"io"
    14  	"os"
    15  	"os/exec"
    16  	"os/signal"
    17  	"os/user"
    18  	"path/filepath"
    19  	"strconv"
    20  	"strings"
    21  	"syscall"
    22  	"testing"
    23  )
    24  
    25  // TestDeathSignalSetuid verifies that a command run with a different UID still
    26  // receives PDeathsig; it is a regression test for https://go.dev/issue/9686.
    27  func TestDeathSignalSetuid(t *testing.T) {
    28  	if testing.Short() {
    29  		t.Skipf("skipping test that copies its binary into temp dir")
    30  	}
    31  
    32  	// Copy the test binary to a location that another user can read/execute
    33  	// after we drop privileges.
    34  	//
    35  	// TODO(bcmills): Why do we believe that another users will be able to
    36  	// execute a binary in this directory? (It could be mounted noexec.)
    37  	tempDir := t.TempDir()
    38  	os.Chmod(tempDir, 0755)
    39  
    40  	tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
    41  
    42  	src, err := os.Open(os.Args[0])
    43  	if err != nil {
    44  		t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
    45  	}
    46  	defer src.Close()
    47  
    48  	dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
    49  	if err != nil {
    50  		t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
    51  	}
    52  	if _, err := io.Copy(dst, src); err != nil {
    53  		t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
    54  	}
    55  	err = dst.Close()
    56  	if err != nil {
    57  		t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
    58  	}
    59  
    60  	cmd := testenv.Command(t, tmpBinary)
    61  	cmd.Env = append(cmd.Environ(), "GO_DEATHSIG_PARENT=1")
    62  	chldStdin, err := cmd.StdinPipe()
    63  	if err != nil {
    64  		t.Fatalf("failed to create new stdin pipe: %v", err)
    65  	}
    66  	chldStdout, err := cmd.StdoutPipe()
    67  	if err != nil {
    68  		t.Fatalf("failed to create new stdout pipe: %v", err)
    69  	}
    70  	stderr := new(strings.Builder)
    71  	cmd.Stderr = stderr
    72  
    73  	err = cmd.Start()
    74  	defer func() {
    75  		chldStdin.Close()
    76  		cmd.Wait()
    77  		if stderr.Len() > 0 {
    78  			t.Logf("stderr:\n%s", stderr)
    79  		}
    80  	}()
    81  	if err != nil {
    82  		t.Fatalf("failed to start first child process: %v", err)
    83  	}
    84  
    85  	chldPipe := bufio.NewReader(chldStdout)
    86  
    87  	if got, err := chldPipe.ReadString('\n'); got == "start\n" {
    88  		syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
    89  
    90  		want := "ok\n"
    91  		if got, err = chldPipe.ReadString('\n'); got != want {
    92  			t.Fatalf("expected %q, received %q, %v", want, got, err)
    93  		}
    94  	} else if got == "skip\n" {
    95  		t.Skipf("skipping: parent could not run child program as selected user")
    96  	} else {
    97  		t.Fatalf("did not receive start from child, received %q, %v", got, err)
    98  	}
    99  }
   100  
   101  func deathSignalParent() {
   102  	var (
   103  		u   *user.User
   104  		err error
   105  	)
   106  	if os.Getuid() == 0 {
   107  		tryUsers := []string{"nobody"}
   108  		if testenv.Builder() != "" {
   109  			tryUsers = append(tryUsers, "gopher")
   110  		}
   111  		for _, name := range tryUsers {
   112  			u, err = user.Lookup(name)
   113  			if err == nil {
   114  				break
   115  			}
   116  			fmt.Fprintf(os.Stderr, "Lookup(%q): %v\n", name, err)
   117  		}
   118  	}
   119  	if u == nil {
   120  		// If we couldn't find an unprivileged user to run as, try running as
   121  		// the current user. (Empirically this still causes the call to Start to
   122  		// fail with a permission error if running as a non-root user on Linux.)
   123  		u, err = user.Current()
   124  		if err != nil {
   125  			fmt.Fprintln(os.Stderr, err)
   126  			os.Exit(1)
   127  		}
   128  	}
   129  
   130  	uid, err := strconv.ParseUint(u.Uid, 10, 32)
   131  	if err != nil {
   132  		fmt.Fprintf(os.Stderr, "invalid UID: %v\n", err)
   133  		os.Exit(1)
   134  	}
   135  	gid, err := strconv.ParseUint(u.Gid, 10, 32)
   136  	if err != nil {
   137  		fmt.Fprintf(os.Stderr, "invalid GID: %v\n", err)
   138  		os.Exit(1)
   139  	}
   140  
   141  	cmd := exec.Command(os.Args[0])
   142  	cmd.Env = append(os.Environ(),
   143  		"GO_DEATHSIG_PARENT=",
   144  		"GO_DEATHSIG_CHILD=1",
   145  	)
   146  	cmd.Stdin = os.Stdin
   147  	cmd.Stdout = os.Stdout
   148  	attrs := syscall.SysProcAttr{
   149  		Pdeathsig:  syscall.SIGUSR1,
   150  		Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)},
   151  	}
   152  	cmd.SysProcAttr = &attrs
   153  
   154  	fmt.Fprintf(os.Stderr, "starting process as user %q\n", u.Username)
   155  	if err := cmd.Start(); err != nil {
   156  		fmt.Fprintln(os.Stderr, err)
   157  		if testenv.SyscallIsNotSupported(err) {
   158  			fmt.Println("skip")
   159  			os.Exit(0)
   160  		}
   161  		os.Exit(1)
   162  	}
   163  	cmd.Wait()
   164  	os.Exit(0)
   165  }
   166  
   167  func deathSignalChild() {
   168  	c := make(chan os.Signal, 1)
   169  	signal.Notify(c, syscall.SIGUSR1)
   170  	go func() {
   171  		<-c
   172  		fmt.Println("ok")
   173  		os.Exit(0)
   174  	}()
   175  	fmt.Println("start")
   176  
   177  	buf := make([]byte, 32)
   178  	os.Stdin.Read(buf)
   179  
   180  	// We expected to be signaled before stdin closed
   181  	fmt.Println("not ok")
   182  	os.Exit(1)
   183  }
   184  

View as plain text