Source file src/syscall/syscall_linux_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  package syscall_test
     6  
     7  import (
     8  	"fmt"
     9  	"internal/testenv"
    10  	"io"
    11  	"io/fs"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"runtime"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"syscall"
    21  	"testing"
    22  	"unsafe"
    23  )
    24  
    25  // chtmpdir changes the working directory to a new temporary directory and
    26  // provides a cleanup function. Used when PWD is read-only.
    27  func chtmpdir(t *testing.T) func() {
    28  	oldwd, err := os.Getwd()
    29  	if err != nil {
    30  		t.Fatalf("chtmpdir: %v", err)
    31  	}
    32  	d, err := os.MkdirTemp("", "test")
    33  	if err != nil {
    34  		t.Fatalf("chtmpdir: %v", err)
    35  	}
    36  	if err := os.Chdir(d); err != nil {
    37  		t.Fatalf("chtmpdir: %v", err)
    38  	}
    39  	return func() {
    40  		if err := os.Chdir(oldwd); err != nil {
    41  			t.Fatalf("chtmpdir: %v", err)
    42  		}
    43  		os.RemoveAll(d)
    44  	}
    45  }
    46  
    47  func touch(t *testing.T, name string) {
    48  	f, err := os.Create(name)
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	if err := f.Close(); err != nil {
    53  		t.Fatal(err)
    54  	}
    55  }
    56  
    57  const (
    58  	_AT_SYMLINK_NOFOLLOW = 0x100
    59  	_AT_FDCWD            = -0x64
    60  	_AT_EACCESS          = 0x200
    61  	_F_OK                = 0
    62  	_R_OK                = 4
    63  )
    64  
    65  func TestFaccessat(t *testing.T) {
    66  	defer chtmpdir(t)()
    67  	touch(t, "file1")
    68  
    69  	err := syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 0)
    70  	if err != nil {
    71  		t.Errorf("Faccessat: unexpected error: %v", err)
    72  	}
    73  
    74  	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 2)
    75  	if err != syscall.EINVAL {
    76  		t.Errorf("Faccessat: unexpected error: %v, want EINVAL", err)
    77  	}
    78  
    79  	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_EACCESS)
    80  	if err != nil {
    81  		t.Errorf("Faccessat: unexpected error: %v", err)
    82  	}
    83  
    84  	err = os.Symlink("file1", "symlink1")
    85  	if err != nil {
    86  		t.Fatal(err)
    87  	}
    88  
    89  	err = syscall.Faccessat(_AT_FDCWD, "symlink1", _R_OK, _AT_SYMLINK_NOFOLLOW)
    90  	if err != nil {
    91  		t.Errorf("Faccessat SYMLINK_NOFOLLOW: unexpected error %v", err)
    92  	}
    93  
    94  	// We can't really test _AT_SYMLINK_NOFOLLOW, because there
    95  	// doesn't seem to be any way to change the mode of a symlink.
    96  	// We don't test _AT_EACCESS because such tests are only
    97  	// meaningful if run as root.
    98  
    99  	err = syscall.Fchmodat(_AT_FDCWD, "file1", 0, 0)
   100  	if err != nil {
   101  		t.Errorf("Fchmodat: unexpected error %v", err)
   102  	}
   103  
   104  	err = syscall.Faccessat(_AT_FDCWD, "file1", _F_OK, _AT_SYMLINK_NOFOLLOW)
   105  	if err != nil {
   106  		t.Errorf("Faccessat: unexpected error: %v", err)
   107  	}
   108  
   109  	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_SYMLINK_NOFOLLOW)
   110  	if err != syscall.EACCES {
   111  		if syscall.Getuid() != 0 {
   112  			t.Errorf("Faccessat: unexpected error: %v, want EACCES", err)
   113  		}
   114  	}
   115  }
   116  
   117  func TestFchmodat(t *testing.T) {
   118  	defer chtmpdir(t)()
   119  
   120  	touch(t, "file1")
   121  	os.Symlink("file1", "symlink1")
   122  
   123  	err := syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, 0)
   124  	if err != nil {
   125  		t.Fatalf("Fchmodat: unexpected error: %v", err)
   126  	}
   127  
   128  	fi, err := os.Stat("file1")
   129  	if err != nil {
   130  		t.Fatal(err)
   131  	}
   132  
   133  	if fi.Mode() != 0444 {
   134  		t.Errorf("Fchmodat: failed to change mode: expected %v, got %v", 0444, fi.Mode())
   135  	}
   136  
   137  	err = syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, _AT_SYMLINK_NOFOLLOW)
   138  	if err != syscall.EOPNOTSUPP {
   139  		t.Fatalf("Fchmodat: unexpected error: %v, expected EOPNOTSUPP", err)
   140  	}
   141  }
   142  
   143  func TestMain(m *testing.M) {
   144  	if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
   145  		deathSignalParent()
   146  	} else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
   147  		deathSignalChild()
   148  	} else if os.Getenv("GO_SYSCALL_NOERROR") == "1" {
   149  		syscallNoError()
   150  	}
   151  
   152  	os.Exit(m.Run())
   153  }
   154  
   155  func TestParseNetlinkMessage(t *testing.T) {
   156  	for i, b := range [][]byte{
   157  		{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,
   158  			0, 8, 0, 6, 0, 0, 0, 0, 1, 63, 0, 10, 0, 69, 16, 0, 59, 39, 82, 64, 0, 64, 6, 21, 89, 127, 0, 0,
   159  			1, 127, 0, 0, 1, 230, 228, 31, 144, 32, 186, 155, 211, 185, 151, 209, 179, 128, 24, 1, 86,
   160  			53, 119, 0, 0, 1, 1, 8, 10, 0, 17, 234, 12, 0, 17, 189, 126, 107, 106, 108, 107, 106, 13, 10,
   161  		},
   162  		{106, 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, 3, 8, 0, 3,
   163  			0, 8, 0, 6, 0, 0, 0, 0, 1, 66, 0, 10, 0, 69, 0, 0, 62, 230, 255, 64, 0, 64, 6, 85, 184, 127, 0, 0,
   164  			1, 127, 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 65, 250, 60, 192, 97, 128, 24, 1, 86, 253, 21, 0,
   165  			0, 1, 1, 8, 10, 0, 51, 106, 89, 0, 51, 102, 198, 108, 104, 106, 108, 107, 104, 108, 107, 104, 10,
   166  		},
   167  		{102, 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, 1, 8, 0, 3, 0,
   168  			8, 0, 6, 0, 0, 0, 0, 1, 62, 0, 10, 0, 69, 0, 0, 58, 231, 2, 64, 0, 64, 6, 85, 185, 127, 0, 0, 1, 127,
   169  			0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 86, 250, 60, 192, 97, 128, 24, 1, 86, 104, 64, 0, 0, 1, 1, 8,
   170  			10, 0, 52, 198, 200, 0, 51, 135, 232, 101, 115, 97, 103, 103, 10,
   171  		},
   172  	} {
   173  		m, err := syscall.ParseNetlinkMessage(b)
   174  		if err != syscall.EINVAL {
   175  			t.Errorf("#%d: got %v; want EINVAL", i, err)
   176  		}
   177  		if m != nil {
   178  			t.Errorf("#%d: got %v; want nil", i, m)
   179  		}
   180  	}
   181  }
   182  
   183  func TestSyscallNoError(t *testing.T) {
   184  	// On Linux there are currently no syscalls which don't fail and return
   185  	// a value larger than 0xfffffffffffff001 so we could test RawSyscall
   186  	// vs. RawSyscallNoError on 64bit architectures.
   187  	if unsafe.Sizeof(uintptr(0)) != 4 {
   188  		t.Skip("skipping on non-32bit architecture")
   189  	}
   190  
   191  	// See https://golang.org/issue/35422
   192  	// On MIPS, Linux returns whether the syscall had an error in a separate
   193  	// register (R7), not using a negative return value as on other
   194  	// architectures.
   195  	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
   196  		t.Skipf("skipping on %s", runtime.GOARCH)
   197  	}
   198  
   199  	if os.Getuid() != 0 {
   200  		t.Skip("skipping root only test")
   201  	}
   202  	if testing.Short() && testenv.Builder() != "" && os.Getenv("USER") == "swarming" {
   203  		// The Go build system's swarming user is known not to be root.
   204  		// Unfortunately, it sometimes appears as root due the current
   205  		// implementation of a no-network check using 'unshare -n -r'.
   206  		// Since this test does need root to work, we need to skip it.
   207  		t.Skip("skipping root only test on a non-root builder")
   208  	}
   209  
   210  	if runtime.GOOS == "android" {
   211  		t.Skip("skipping on rooted android, see issue 27364")
   212  	}
   213  
   214  	// Copy the test binary to a location that a non-root user can read/execute
   215  	// after we drop privileges
   216  	tempDir, err := os.MkdirTemp("", "TestSyscallNoError")
   217  	if err != nil {
   218  		t.Fatalf("cannot create temporary directory: %v", err)
   219  	}
   220  	defer os.RemoveAll(tempDir)
   221  	os.Chmod(tempDir, 0755)
   222  
   223  	tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
   224  
   225  	src, err := os.Open(os.Args[0])
   226  	if err != nil {
   227  		t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
   228  	}
   229  	defer src.Close()
   230  
   231  	dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
   232  	if err != nil {
   233  		t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
   234  	}
   235  	if _, err := io.Copy(dst, src); err != nil {
   236  		t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
   237  	}
   238  	err = dst.Close()
   239  	if err != nil {
   240  		t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
   241  	}
   242  
   243  	uid := uint32(0xfffffffe)
   244  	err = os.Chown(tmpBinary, int(uid), -1)
   245  	if err != nil {
   246  		t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err)
   247  	}
   248  
   249  	err = os.Chmod(tmpBinary, 0755|fs.ModeSetuid)
   250  	if err != nil {
   251  		t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err)
   252  	}
   253  
   254  	cmd := exec.Command(tmpBinary)
   255  	cmd.Env = append(os.Environ(), "GO_SYSCALL_NOERROR=1")
   256  
   257  	out, err := cmd.CombinedOutput()
   258  	if err != nil {
   259  		t.Fatalf("failed to start first child process: %v", err)
   260  	}
   261  
   262  	got := strings.TrimSpace(string(out))
   263  	want := strconv.FormatUint(uint64(uid)+1, 10) + " / " +
   264  		strconv.FormatUint(uint64(-uid), 10) + " / " +
   265  		strconv.FormatUint(uint64(uid), 10)
   266  	if got != want {
   267  		if filesystemIsNoSUID(tmpBinary) {
   268  			t.Skip("skipping test when temp dir is mounted nosuid")
   269  		}
   270  		// formatted so the values are aligned for easier comparison
   271  		t.Errorf("expected %s,\ngot      %s", want, got)
   272  	}
   273  }
   274  
   275  // filesystemIsNoSUID reports whether the filesystem for the given
   276  // path is mounted nosuid.
   277  func filesystemIsNoSUID(path string) bool {
   278  	var st syscall.Statfs_t
   279  	if syscall.Statfs(path, &st) != nil {
   280  		return false
   281  	}
   282  	return st.Flags&syscall.MS_NOSUID != 0
   283  }
   284  
   285  func syscallNoError() {
   286  	// Test that the return value from SYS_GETEUID32 (which cannot fail)
   287  	// doesn't get treated as an error (see https://golang.org/issue/22924)
   288  	euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
   289  	euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
   290  
   291  	fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
   292  	os.Exit(0)
   293  }
   294  
   295  // reference uapi/linux/prctl.h
   296  const (
   297  	PR_GET_KEEPCAPS uintptr = 7
   298  	PR_SET_KEEPCAPS         = 8
   299  )
   300  
   301  // TestAllThreadsSyscall tests that the go runtime can perform
   302  // syscalls that execute on all OSThreads - with which to support
   303  // POSIX semantics for security state changes.
   304  func TestAllThreadsSyscall(t *testing.T) {
   305  	if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
   306  		t.Skip("AllThreadsSyscall disabled with cgo")
   307  	}
   308  
   309  	fns := []struct {
   310  		label string
   311  		fn    func(uintptr) error
   312  	}{
   313  		{
   314  			label: "prctl<3-args>",
   315  			fn: func(v uintptr) error {
   316  				_, _, e := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0)
   317  				if e != 0 {
   318  					return e
   319  				}
   320  				return nil
   321  			},
   322  		},
   323  		{
   324  			label: "prctl<6-args>",
   325  			fn: func(v uintptr) error {
   326  				_, _, e := syscall.AllThreadsSyscall6(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0, 0, 0, 0)
   327  				if e != 0 {
   328  					return e
   329  				}
   330  				return nil
   331  			},
   332  		},
   333  	}
   334  
   335  	waiter := func(q <-chan uintptr, r chan<- uintptr, once bool) {
   336  		for x := range q {
   337  			runtime.LockOSThread()
   338  			v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0)
   339  			if e != 0 {
   340  				t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) failed: %v", syscall.Gettid(), e)
   341  			} else if x != v {
   342  				t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) mismatch: got=%d want=%d", syscall.Gettid(), v, x)
   343  			}
   344  			r <- v
   345  			if once {
   346  				break
   347  			}
   348  			runtime.UnlockOSThread()
   349  		}
   350  	}
   351  
   352  	// launches per fns member.
   353  	const launches = 11
   354  	question := make(chan uintptr)
   355  	response := make(chan uintptr)
   356  	defer close(question)
   357  
   358  	routines := 0
   359  	for i, v := range fns {
   360  		for j := 0; j < launches; j++ {
   361  			// Add another goroutine - the closest thing
   362  			// we can do to encourage more OS thread
   363  			// creation - while the test is running.  The
   364  			// actual thread creation may or may not be
   365  			// needed, based on the number of available
   366  			// unlocked OS threads at the time waiter
   367  			// calls runtime.LockOSThread(), but the goal
   368  			// of doing this every time through the loop
   369  			// is to race thread creation with v.fn(want)
   370  			// being executed. Via the once boolean we
   371  			// also encourage one in 5 waiters to return
   372  			// locked after participating in only one
   373  			// question response sequence. This allows the
   374  			// test to race thread destruction too.
   375  			once := routines%5 == 4
   376  			go waiter(question, response, once)
   377  
   378  			// Keep a count of how many goroutines are
   379  			// going to participate in the
   380  			// question/response test. This will count up
   381  			// towards 2*launches minus the count of
   382  			// routines that have been invoked with
   383  			// once=true.
   384  			routines++
   385  
   386  			// Decide what value we want to set the
   387  			// process-shared KEEPCAPS. Note, there is
   388  			// an explicit repeat of 0 when we change the
   389  			// variant of the syscall being used.
   390  			want := uintptr(j & 1)
   391  
   392  			// Invoke the AllThreadsSyscall* variant.
   393  			if err := v.fn(want); err != nil {
   394  				t.Errorf("[%d,%d] %s(PR_SET_KEEPCAPS, %d, ...): %v", i, j, v.label, j&1, err)
   395  			}
   396  
   397  			// At this point, we want all launched Go
   398  			// routines to confirm that they see the
   399  			// wanted value for KEEPCAPS.
   400  			for k := 0; k < routines; k++ {
   401  				question <- want
   402  			}
   403  
   404  			// At this point, we should have a large
   405  			// number of locked OS threads all wanting to
   406  			// reply.
   407  			for k := 0; k < routines; k++ {
   408  				if got := <-response; got != want {
   409  					t.Errorf("[%d,%d,%d] waiter result got=%d, want=%d", i, j, k, got, want)
   410  				}
   411  			}
   412  
   413  			// Provide an explicit opportunity for this Go
   414  			// routine to change Ms.
   415  			runtime.Gosched()
   416  
   417  			if once {
   418  				// One waiter routine will have exited.
   419  				routines--
   420  			}
   421  
   422  			// Whatever M we are now running on, confirm
   423  			// we see the wanted value too.
   424  			if v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0); e != 0 {
   425  				t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) failed: %v", i, j, e)
   426  			} else if v != want {
   427  				t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) gave wrong value: got=%v, want=1", i, j, v)
   428  			}
   429  		}
   430  	}
   431  }
   432  
   433  // compareStatus is used to confirm the contents of the thread
   434  // specific status files match expectations.
   435  func compareStatus(filter, expect string) error {
   436  	expected := filter + expect
   437  	pid := syscall.Getpid()
   438  	fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
   439  	if err != nil {
   440  		return fmt.Errorf("unable to find %d tasks: %v", pid, err)
   441  	}
   442  	expectedProc := fmt.Sprintf("Pid:\t%d", pid)
   443  	foundAThread := false
   444  	for _, f := range fs {
   445  		tf := fmt.Sprintf("/proc/%s/status", f.Name())
   446  		d, err := os.ReadFile(tf)
   447  		if err != nil {
   448  			// There are a surprising number of ways this
   449  			// can error out on linux.  We've seen all of
   450  			// the following, so treat any error here as
   451  			// equivalent to the "process is gone":
   452  			//    os.IsNotExist(err),
   453  			//    "... : no such process",
   454  			//    "... : bad file descriptor.
   455  			continue
   456  		}
   457  		lines := strings.Split(string(d), "\n")
   458  		for _, line := range lines {
   459  			// Different kernel vintages pad differently.
   460  			line = strings.TrimSpace(line)
   461  			if strings.HasPrefix(line, "Pid:\t") {
   462  				// On loaded systems, it is possible
   463  				// for a TID to be reused really
   464  				// quickly. As such, we need to
   465  				// validate that the thread status
   466  				// info we just read is a task of the
   467  				// same process PID as we are
   468  				// currently running, and not a
   469  				// recently terminated thread
   470  				// resurfaced in a different process.
   471  				if line != expectedProc {
   472  					break
   473  				}
   474  				// Fall through in the unlikely case
   475  				// that filter at some point is
   476  				// "Pid:\t".
   477  			}
   478  			if strings.HasPrefix(line, filter) {
   479  				if line == expected {
   480  					foundAThread = true
   481  					break
   482  				}
   483  				if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") {
   484  					// https://github.com/golang/go/issues/46145
   485  					// Containers don't reliably output this line in sorted order so manually sort and compare that.
   486  					a := strings.Split(line[8:], " ")
   487  					sort.Strings(a)
   488  					got := strings.Join(a, " ")
   489  					if got == expected[8:] {
   490  						foundAThread = true
   491  						break
   492  					}
   493  
   494  				}
   495  				return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
   496  			}
   497  		}
   498  	}
   499  	if !foundAThread {
   500  		return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
   501  	}
   502  	return nil
   503  }
   504  
   505  // killAThread locks the goroutine to an OS thread and exits; this
   506  // causes an OS thread to terminate.
   507  func killAThread(c <-chan struct{}) {
   508  	runtime.LockOSThread()
   509  	<-c
   510  	return
   511  }
   512  
   513  // TestSetuidEtc performs tests on all of the wrapped system calls
   514  // that mirror to the 9 glibc syscalls with POSIX semantics. The test
   515  // here is considered authoritative and should compile and run
   516  // CGO_ENABLED=0 or 1. Note, there is an extended copy of this same
   517  // test in ../../misc/cgo/test/issue1435.go which requires
   518  // CGO_ENABLED=1 and launches pthreads from C that run concurrently
   519  // with the Go code of the test - and the test validates that these
   520  // pthreads are also kept in sync with the security state changed with
   521  // the syscalls. Care should be taken to mirror any enhancements to
   522  // this test here in that file too.
   523  func TestSetuidEtc(t *testing.T) {
   524  	if syscall.Getuid() != 0 {
   525  		t.Skip("skipping root only test")
   526  	}
   527  	if testing.Short() && testenv.Builder() != "" && os.Getenv("USER") == "swarming" {
   528  		// The Go build system's swarming user is known not to be root.
   529  		// Unfortunately, it sometimes appears as root due the current
   530  		// implementation of a no-network check using 'unshare -n -r'.
   531  		// Since this test does need root to work, we need to skip it.
   532  		t.Skip("skipping root only test on a non-root builder")
   533  	}
   534  	if _, err := os.Stat("/etc/alpine-release"); err == nil {
   535  		t.Skip("skipping glibc test on alpine - go.dev/issue/19938")
   536  	}
   537  	vs := []struct {
   538  		call           string
   539  		fn             func() error
   540  		filter, expect string
   541  	}{
   542  		{call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
   543  		{call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   544  
   545  		{call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
   546  		{call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
   547  
   548  		{call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
   549  		{call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   550  
   551  		{call: "Setgroups([]int{0,1,2,3})", fn: func() error { return syscall.Setgroups([]int{0, 1, 2, 3}) }, filter: "Groups:", expect: "\t0 1 2 3"},
   552  		{call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
   553  		{call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
   554  
   555  		{call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
   556  		{call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
   557  		{call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   558  
   559  		{call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
   560  		{call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
   561  		{call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
   562  
   563  		{call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
   564  		{call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
   565  		{call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   566  
   567  		{call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
   568  		{call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
   569  		{call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
   570  	}
   571  
   572  	for i, v := range vs {
   573  		// Generate some thread churn as we execute the tests.
   574  		c := make(chan struct{})
   575  		go killAThread(c)
   576  		close(c)
   577  
   578  		if err := v.fn(); err != nil {
   579  			t.Errorf("[%d] %q failed: %v", i, v.call, err)
   580  			continue
   581  		}
   582  		if err := compareStatus(v.filter, v.expect); err != nil {
   583  			t.Errorf("[%d] %q comparison: %v", i, v.call, err)
   584  		}
   585  	}
   586  }
   587  
   588  // TestAllThreadsSyscallError verifies that errors are properly returned when
   589  // the syscall fails on the original thread.
   590  func TestAllThreadsSyscallError(t *testing.T) {
   591  	// SYS_CAPGET takes pointers as the first two arguments. Since we pass
   592  	// 0, we expect to get EFAULT back.
   593  	r1, r2, err := syscall.AllThreadsSyscall(syscall.SYS_CAPGET, 0, 0, 0)
   594  	if err == syscall.ENOTSUP {
   595  		t.Skip("AllThreadsSyscall disabled with cgo")
   596  	}
   597  	if err != syscall.EFAULT {
   598  		t.Errorf("AllThreadSyscall(SYS_CAPGET) got %d, %d, %v, want err %v", r1, r2, err, syscall.EFAULT)
   599  	}
   600  }
   601  
   602  // TestAllThreadsSyscallBlockedSyscall confirms that AllThreadsSyscall
   603  // can interrupt threads in long-running system calls. This test will
   604  // deadlock if this doesn't work correctly.
   605  func TestAllThreadsSyscallBlockedSyscall(t *testing.T) {
   606  	if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
   607  		t.Skip("AllThreadsSyscall disabled with cgo")
   608  	}
   609  
   610  	rd, wr, err := os.Pipe()
   611  	if err != nil {
   612  		t.Fatalf("unable to obtain a pipe: %v", err)
   613  	}
   614  
   615  	// Perform a blocking read on the pipe.
   616  	var wg sync.WaitGroup
   617  	ready := make(chan bool)
   618  	wg.Add(1)
   619  	go func() {
   620  		data := make([]byte, 1)
   621  
   622  		// To narrow the window we have to wait for this
   623  		// goroutine to block in read, synchronize just before
   624  		// calling read.
   625  		ready <- true
   626  
   627  		// We use syscall.Read directly to avoid the poller.
   628  		// This will return when the write side is closed.
   629  		n, err := syscall.Read(int(rd.Fd()), data)
   630  		if !(n == 0 && err == nil) {
   631  			t.Errorf("expected read to return 0, got %d, %s", n, err)
   632  		}
   633  
   634  		// Clean up rd and also ensure rd stays reachable so
   635  		// it doesn't get closed by GC.
   636  		rd.Close()
   637  		wg.Done()
   638  	}()
   639  	<-ready
   640  
   641  	// Loop here to give the goroutine more time to block in read.
   642  	// Generally this will trigger on the first iteration anyway.
   643  	pid := syscall.Getpid()
   644  	for i := 0; i < 100; i++ {
   645  		if id, _, e := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0); e != 0 {
   646  			t.Errorf("[%d] getpid failed: %v", i, e)
   647  		} else if int(id) != pid {
   648  			t.Errorf("[%d] getpid got=%d, want=%d", i, id, pid)
   649  		}
   650  		// Provide an explicit opportunity for this goroutine
   651  		// to change Ms.
   652  		runtime.Gosched()
   653  	}
   654  	wr.Close()
   655  	wg.Wait()
   656  }
   657  

View as plain text