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

View as plain text