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  	"slices"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"syscall"
    21  	"testing"
    22  	"unsafe"
    23  )
    24  
    25  func touch(t *testing.T, name string) {
    26  	f, err := os.Create(name)
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	if err := f.Close(); err != nil {
    31  		t.Fatal(err)
    32  	}
    33  }
    34  
    35  const (
    36  	_AT_SYMLINK_NOFOLLOW = 0x100
    37  	_AT_FDCWD            = -0x64
    38  	_AT_EACCESS          = 0x200
    39  	_F_OK                = 0
    40  	_R_OK                = 4
    41  )
    42  
    43  func TestFaccessat(t *testing.T) {
    44  	t.Chdir(t.TempDir())
    45  	touch(t, "file1")
    46  
    47  	err := syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 0)
    48  	if err != nil {
    49  		t.Errorf("Faccessat: unexpected error: %v", err)
    50  	}
    51  
    52  	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 2)
    53  	if err != syscall.EINVAL {
    54  		t.Errorf("Faccessat: unexpected error: %v, want EINVAL", err)
    55  	}
    56  
    57  	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_EACCESS)
    58  	if err != nil {
    59  		t.Errorf("Faccessat: unexpected error: %v", err)
    60  	}
    61  
    62  	err = os.Symlink("file1", "symlink1")
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  
    67  	err = syscall.Faccessat(_AT_FDCWD, "symlink1", _R_OK, _AT_SYMLINK_NOFOLLOW)
    68  	if err != nil {
    69  		t.Errorf("Faccessat SYMLINK_NOFOLLOW: unexpected error %v", err)
    70  	}
    71  
    72  	// We can't really test _AT_SYMLINK_NOFOLLOW, because there
    73  	// doesn't seem to be any way to change the mode of a symlink.
    74  	// We don't test _AT_EACCESS because such tests are only
    75  	// meaningful if run as root.
    76  
    77  	err = syscall.Fchmodat(_AT_FDCWD, "file1", 0, 0)
    78  	if err != nil {
    79  		t.Errorf("Fchmodat: unexpected error %v", err)
    80  	}
    81  
    82  	err = syscall.Faccessat(_AT_FDCWD, "file1", _F_OK, _AT_SYMLINK_NOFOLLOW)
    83  	if err != nil {
    84  		t.Errorf("Faccessat: unexpected error: %v", err)
    85  	}
    86  
    87  	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_SYMLINK_NOFOLLOW)
    88  	if err != syscall.EACCES {
    89  		if syscall.Getuid() != 0 {
    90  			t.Errorf("Faccessat: unexpected error: %v, want EACCES", err)
    91  		}
    92  	}
    93  }
    94  
    95  func TestFchmodat(t *testing.T) {
    96  	t.Chdir(t.TempDir())
    97  
    98  	touch(t, "file1")
    99  	os.Symlink("file1", "symlink1")
   100  
   101  	err := syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, 0)
   102  	if err != nil {
   103  		t.Fatalf("Fchmodat: unexpected error: %v", err)
   104  	}
   105  
   106  	fi, err := os.Stat("file1")
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  
   111  	if fi.Mode() != 0444 {
   112  		t.Errorf("Fchmodat: failed to change mode: expected %v, got %v", 0444, fi.Mode())
   113  	}
   114  
   115  	err = syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, _AT_SYMLINK_NOFOLLOW)
   116  	if err != syscall.EOPNOTSUPP {
   117  		t.Fatalf("Fchmodat: unexpected error: %v, expected EOPNOTSUPP", err)
   118  	}
   119  }
   120  
   121  func TestMain(m *testing.M) {
   122  	if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
   123  		deathSignalParent()
   124  	} else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
   125  		deathSignalChild()
   126  	} else if os.Getenv("GO_SYSCALL_NOERROR") == "1" {
   127  		syscallNoError()
   128  	}
   129  
   130  	os.Exit(m.Run())
   131  }
   132  
   133  func TestParseNetlinkMessage(t *testing.T) {
   134  	for i, b := range [][]byte{
   135  		{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,
   136  			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,
   137  			1, 127, 0, 0, 1, 230, 228, 31, 144, 32, 186, 155, 211, 185, 151, 209, 179, 128, 24, 1, 86,
   138  			53, 119, 0, 0, 1, 1, 8, 10, 0, 17, 234, 12, 0, 17, 189, 126, 107, 106, 108, 107, 106, 13, 10,
   139  		},
   140  		{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,
   141  			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,
   142  			1, 127, 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 65, 250, 60, 192, 97, 128, 24, 1, 86, 253, 21, 0,
   143  			0, 1, 1, 8, 10, 0, 51, 106, 89, 0, 51, 102, 198, 108, 104, 106, 108, 107, 104, 108, 107, 104, 10,
   144  		},
   145  		{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,
   146  			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,
   147  			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,
   148  			10, 0, 52, 198, 200, 0, 51, 135, 232, 101, 115, 97, 103, 103, 10,
   149  		},
   150  	} {
   151  		m, err := syscall.ParseNetlinkMessage(b)
   152  		if err != syscall.EINVAL {
   153  			t.Errorf("#%d: got %v; want EINVAL", i, err)
   154  		}
   155  		if m != nil {
   156  			t.Errorf("#%d: got %v; want nil", i, m)
   157  		}
   158  	}
   159  }
   160  
   161  func TestSyscallNoError(t *testing.T) {
   162  	// On Linux there are currently no syscalls which don't fail and return
   163  	// a value larger than 0xfffffffffffff001 so we could test RawSyscall
   164  	// vs. RawSyscallNoError on 64bit architectures.
   165  	if unsafe.Sizeof(uintptr(0)) != 4 {
   166  		t.Skip("skipping on non-32bit architecture")
   167  	}
   168  
   169  	// See https://golang.org/issue/35422
   170  	// On MIPS, Linux returns whether the syscall had an error in a separate
   171  	// register (R7), not using a negative return value as on other
   172  	// architectures.
   173  	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
   174  		t.Skipf("skipping on %s", runtime.GOARCH)
   175  	}
   176  
   177  	if os.Getuid() != 0 {
   178  		t.Skip("skipping root only test")
   179  	}
   180  	if testing.Short() && testenv.Builder() != "" && os.Getenv("USER") == "swarming" {
   181  		// The Go build system's swarming user is known not to be root.
   182  		// Unfortunately, it sometimes appears as root due the current
   183  		// implementation of a no-network check using 'unshare -n -r'.
   184  		// Since this test does need root to work, we need to skip it.
   185  		t.Skip("skipping root only test on a non-root builder")
   186  	}
   187  
   188  	if runtime.GOOS == "android" {
   189  		t.Skip("skipping on rooted android, see issue 27364")
   190  	}
   191  
   192  	// Copy the test binary to a location that a non-root user can read/execute
   193  	// after we drop privileges.
   194  	tempDir := t.TempDir()
   195  	os.Chmod(tempDir, 0755)
   196  
   197  	tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
   198  
   199  	src, err := os.Open(os.Args[0])
   200  	if err != nil {
   201  		t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
   202  	}
   203  	defer src.Close()
   204  
   205  	dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
   206  	if err != nil {
   207  		t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
   208  	}
   209  	if _, err := io.Copy(dst, src); err != nil {
   210  		t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
   211  	}
   212  	err = dst.Close()
   213  	if err != nil {
   214  		t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
   215  	}
   216  
   217  	uid := uint32(0xfffffffe)
   218  	err = os.Chown(tmpBinary, int(uid), -1)
   219  	if err != nil {
   220  		t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err)
   221  	}
   222  
   223  	err = os.Chmod(tmpBinary, 0755|fs.ModeSetuid)
   224  	if err != nil {
   225  		t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err)
   226  	}
   227  
   228  	cmd := exec.Command(tmpBinary)
   229  	cmd.Env = append(os.Environ(), "GO_SYSCALL_NOERROR=1")
   230  
   231  	out, err := cmd.CombinedOutput()
   232  	if err != nil {
   233  		t.Fatalf("failed to start first child process: %v", err)
   234  	}
   235  
   236  	got := strings.TrimSpace(string(out))
   237  	want := strconv.FormatUint(uint64(uid)+1, 10) + " / " +
   238  		strconv.FormatUint(uint64(-uid), 10) + " / " +
   239  		strconv.FormatUint(uint64(uid), 10)
   240  	if got != want {
   241  		if filesystemIsNoSUID(tmpBinary) {
   242  			t.Skip("skipping test when temp dir is mounted nosuid")
   243  		}
   244  		// formatted so the values are aligned for easier comparison
   245  		t.Errorf("expected %s,\ngot      %s", want, got)
   246  	}
   247  }
   248  
   249  // filesystemIsNoSUID reports whether the filesystem for the given
   250  // path is mounted nosuid.
   251  func filesystemIsNoSUID(path string) bool {
   252  	var st syscall.Statfs_t
   253  	if syscall.Statfs(path, &st) != nil {
   254  		return false
   255  	}
   256  	return st.Flags&syscall.MS_NOSUID != 0
   257  }
   258  
   259  func syscallNoError() {
   260  	// Test that the return value from SYS_GETEUID32 (which cannot fail)
   261  	// doesn't get treated as an error (see https://golang.org/issue/22924)
   262  	euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
   263  	euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
   264  
   265  	fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
   266  	os.Exit(0)
   267  }
   268  
   269  // reference uapi/linux/prctl.h
   270  const (
   271  	PR_GET_KEEPCAPS uintptr = 7
   272  	PR_SET_KEEPCAPS         = 8
   273  )
   274  
   275  // TestAllThreadsSyscall tests that the go runtime can perform
   276  // syscalls that execute on all OSThreads - with which to support
   277  // POSIX semantics for security state changes.
   278  func TestAllThreadsSyscall(t *testing.T) {
   279  	if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
   280  		t.Skip("AllThreadsSyscall disabled with cgo")
   281  	}
   282  
   283  	fns := []struct {
   284  		label string
   285  		fn    func(uintptr) error
   286  	}{
   287  		{
   288  			label: "prctl<3-args>",
   289  			fn: func(v uintptr) error {
   290  				_, _, e := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0)
   291  				if e != 0 {
   292  					return e
   293  				}
   294  				return nil
   295  			},
   296  		},
   297  		{
   298  			label: "prctl<6-args>",
   299  			fn: func(v uintptr) error {
   300  				_, _, e := syscall.AllThreadsSyscall6(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0, 0, 0, 0)
   301  				if e != 0 {
   302  					return e
   303  				}
   304  				return nil
   305  			},
   306  		},
   307  	}
   308  
   309  	waiter := func(q <-chan uintptr, r chan<- uintptr, once bool) {
   310  		for x := range q {
   311  			runtime.LockOSThread()
   312  			v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0)
   313  			if e != 0 {
   314  				t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) failed: %v", syscall.Gettid(), e)
   315  			} else if x != v {
   316  				t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) mismatch: got=%d want=%d", syscall.Gettid(), v, x)
   317  			}
   318  			r <- v
   319  			if once {
   320  				break
   321  			}
   322  			runtime.UnlockOSThread()
   323  		}
   324  	}
   325  
   326  	// launches per fns member.
   327  	const launches = 11
   328  	question := make(chan uintptr)
   329  	response := make(chan uintptr)
   330  	defer close(question)
   331  
   332  	routines := 0
   333  	for i, v := range fns {
   334  		for j := 0; j < launches; j++ {
   335  			// Add another goroutine - the closest thing
   336  			// we can do to encourage more OS thread
   337  			// creation - while the test is running.  The
   338  			// actual thread creation may or may not be
   339  			// needed, based on the number of available
   340  			// unlocked OS threads at the time waiter
   341  			// calls runtime.LockOSThread(), but the goal
   342  			// of doing this every time through the loop
   343  			// is to race thread creation with v.fn(want)
   344  			// being executed. Via the once boolean we
   345  			// also encourage one in 5 waiters to return
   346  			// locked after participating in only one
   347  			// question response sequence. This allows the
   348  			// test to race thread destruction too.
   349  			once := routines%5 == 4
   350  			go waiter(question, response, once)
   351  
   352  			// Keep a count of how many goroutines are
   353  			// going to participate in the
   354  			// question/response test. This will count up
   355  			// towards 2*launches minus the count of
   356  			// routines that have been invoked with
   357  			// once=true.
   358  			routines++
   359  
   360  			// Decide what value we want to set the
   361  			// process-shared KEEPCAPS. Note, there is
   362  			// an explicit repeat of 0 when we change the
   363  			// variant of the syscall being used.
   364  			want := uintptr(j & 1)
   365  
   366  			// Invoke the AllThreadsSyscall* variant.
   367  			if err := v.fn(want); err != nil {
   368  				t.Errorf("[%d,%d] %s(PR_SET_KEEPCAPS, %d, ...): %v", i, j, v.label, j&1, err)
   369  			}
   370  
   371  			// At this point, we want all launched Go
   372  			// routines to confirm that they see the
   373  			// wanted value for KEEPCAPS.
   374  			for k := 0; k < routines; k++ {
   375  				question <- want
   376  			}
   377  
   378  			// At this point, we should have a large
   379  			// number of locked OS threads all wanting to
   380  			// reply.
   381  			for k := 0; k < routines; k++ {
   382  				if got := <-response; got != want {
   383  					t.Errorf("[%d,%d,%d] waiter result got=%d, want=%d", i, j, k, got, want)
   384  				}
   385  			}
   386  
   387  			// Provide an explicit opportunity for this Go
   388  			// routine to change Ms.
   389  			runtime.Gosched()
   390  
   391  			if once {
   392  				// One waiter routine will have exited.
   393  				routines--
   394  			}
   395  
   396  			// Whatever M we are now running on, confirm
   397  			// we see the wanted value too.
   398  			if v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0); e != 0 {
   399  				t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) failed: %v", i, j, e)
   400  			} else if v != want {
   401  				t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) gave wrong value: got=%v, want=1", i, j, v)
   402  			}
   403  		}
   404  	}
   405  }
   406  
   407  // compareStatus is used to confirm the contents of the thread
   408  // specific status files match expectations.
   409  func compareStatus(filter, expect string) error {
   410  	expected := filter + expect
   411  	pid := syscall.Getpid()
   412  	fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
   413  	if err != nil {
   414  		return fmt.Errorf("unable to find %d tasks: %v", pid, err)
   415  	}
   416  	expectedProc := fmt.Sprintf("Pid:\t%d", pid)
   417  	foundAThread := false
   418  	for _, f := range fs {
   419  		tf := fmt.Sprintf("/proc/%s/status", f.Name())
   420  		d, err := os.ReadFile(tf)
   421  		if err != nil {
   422  			// There are a surprising number of ways this
   423  			// can error out on linux.  We've seen all of
   424  			// the following, so treat any error here as
   425  			// equivalent to the "process is gone":
   426  			//    os.IsNotExist(err),
   427  			//    "... : no such process",
   428  			//    "... : bad file descriptor.
   429  			continue
   430  		}
   431  		lines := strings.Split(string(d), "\n")
   432  		for _, line := range lines {
   433  			// Different kernel vintages pad differently.
   434  			line = strings.TrimSpace(line)
   435  			if strings.HasPrefix(line, "Pid:\t") {
   436  				// On loaded systems, it is possible
   437  				// for a TID to be reused really
   438  				// quickly. As such, we need to
   439  				// validate that the thread status
   440  				// info we just read is a task of the
   441  				// same process PID as we are
   442  				// currently running, and not a
   443  				// recently terminated thread
   444  				// resurfaced in a different process.
   445  				if line != expectedProc {
   446  					break
   447  				}
   448  				// Fall through in the unlikely case
   449  				// that filter at some point is
   450  				// "Pid:\t".
   451  			}
   452  			if strings.HasPrefix(line, filter) {
   453  				if line == expected {
   454  					foundAThread = true
   455  					break
   456  				}
   457  				if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") {
   458  					// https://github.com/golang/go/issues/46145
   459  					// Containers don't reliably output this line in sorted order so manually sort and compare that.
   460  					a := strings.Split(line[8:], " ")
   461  					slices.Sort(a)
   462  					got := strings.Join(a, " ")
   463  					if got == expected[8:] {
   464  						foundAThread = true
   465  						break
   466  					}
   467  
   468  				}
   469  				return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
   470  			}
   471  		}
   472  	}
   473  	if !foundAThread {
   474  		return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
   475  	}
   476  	return nil
   477  }
   478  
   479  // killAThread locks the goroutine to an OS thread and exits; this
   480  // causes an OS thread to terminate.
   481  func killAThread(c <-chan struct{}) {
   482  	runtime.LockOSThread()
   483  	<-c
   484  	return
   485  }
   486  
   487  // TestSetuidEtc performs tests on all of the wrapped system calls
   488  // that mirror to the 9 glibc syscalls with POSIX semantics. The test
   489  // here is considered authoritative and should compile and run
   490  // CGO_ENABLED=0 or 1. Note, there is an extended copy of this same
   491  // test in ../../misc/cgo/test/issue1435.go which requires
   492  // CGO_ENABLED=1 and launches pthreads from C that run concurrently
   493  // with the Go code of the test - and the test validates that these
   494  // pthreads are also kept in sync with the security state changed with
   495  // the syscalls. Care should be taken to mirror any enhancements to
   496  // this test here in that file too.
   497  func TestSetuidEtc(t *testing.T) {
   498  	if syscall.Getuid() != 0 {
   499  		t.Skip("skipping root only test")
   500  	}
   501  	if syscall.Getgid() != 0 {
   502  		t.Skip("skipping the test when root's gid is not default value 0")
   503  	}
   504  	if testing.Short() && testenv.Builder() != "" && os.Getenv("USER") == "swarming" {
   505  		// The Go build system's swarming user is known not to be root.
   506  		// Unfortunately, it sometimes appears as root due the current
   507  		// implementation of a no-network check using 'unshare -n -r'.
   508  		// Since this test does need root to work, we need to skip it.
   509  		t.Skip("skipping root only test on a non-root builder")
   510  	}
   511  	if _, err := os.Stat("/etc/alpine-release"); err == nil {
   512  		t.Skip("skipping glibc test on alpine - go.dev/issue/19938")
   513  	}
   514  	vs := []struct {
   515  		call           string
   516  		fn             func() error
   517  		filter, expect string
   518  	}{
   519  		{call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
   520  		{call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   521  
   522  		{call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
   523  		{call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
   524  
   525  		{call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
   526  		{call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   527  
   528  		{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"},
   529  		{call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
   530  		{call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
   531  
   532  		{call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
   533  		{call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
   534  		{call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   535  
   536  		{call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
   537  		{call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
   538  		{call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
   539  
   540  		{call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
   541  		{call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
   542  		{call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
   543  
   544  		{call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
   545  		{call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
   546  		{call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
   547  	}
   548  
   549  	for i, v := range vs {
   550  		// Generate some thread churn as we execute the tests.
   551  		c := make(chan struct{})
   552  		go killAThread(c)
   553  		close(c)
   554  
   555  		if err := v.fn(); err != nil {
   556  			t.Errorf("[%d] %q failed: %v", i, v.call, err)
   557  			continue
   558  		}
   559  		if err := compareStatus(v.filter, v.expect); err != nil {
   560  			t.Errorf("[%d] %q comparison: %v", i, v.call, err)
   561  		}
   562  	}
   563  }
   564  
   565  // TestAllThreadsSyscallError verifies that errors are properly returned when
   566  // the syscall fails on the original thread.
   567  func TestAllThreadsSyscallError(t *testing.T) {
   568  	// SYS_CAPGET takes pointers as the first two arguments. Since we pass
   569  	// 0, we expect to get EFAULT back.
   570  	r1, r2, err := syscall.AllThreadsSyscall(syscall.SYS_CAPGET, 0, 0, 0)
   571  	if err == syscall.ENOTSUP {
   572  		t.Skip("AllThreadsSyscall disabled with cgo")
   573  	}
   574  	if err != syscall.EFAULT {
   575  		t.Errorf("AllThreadSyscall(SYS_CAPGET) got %d, %d, %v, want err %v", r1, r2, err, syscall.EFAULT)
   576  	}
   577  }
   578  
   579  // TestAllThreadsSyscallBlockedSyscall confirms that AllThreadsSyscall
   580  // can interrupt threads in long-running system calls. This test will
   581  // deadlock if this doesn't work correctly.
   582  func TestAllThreadsSyscallBlockedSyscall(t *testing.T) {
   583  	if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
   584  		t.Skip("AllThreadsSyscall disabled with cgo")
   585  	}
   586  
   587  	rd, wr, err := os.Pipe()
   588  	if err != nil {
   589  		t.Fatalf("unable to obtain a pipe: %v", err)
   590  	}
   591  
   592  	// Perform a blocking read on the pipe.
   593  	var wg sync.WaitGroup
   594  	ready := make(chan bool)
   595  	wg.Add(1)
   596  	go func() {
   597  		data := make([]byte, 1)
   598  
   599  		// To narrow the window we have to wait for this
   600  		// goroutine to block in read, synchronize just before
   601  		// calling read.
   602  		ready <- true
   603  
   604  		// We use syscall.Read directly to avoid the poller.
   605  		// This will return when the write side is closed.
   606  		n, err := syscall.Read(int(rd.Fd()), data)
   607  		if !(n == 0 && err == nil) {
   608  			t.Errorf("expected read to return 0, got %d, %s", n, err)
   609  		}
   610  
   611  		// Clean up rd and also ensure rd stays reachable so
   612  		// it doesn't get closed by GC.
   613  		rd.Close()
   614  		wg.Done()
   615  	}()
   616  	<-ready
   617  
   618  	// Loop here to give the goroutine more time to block in read.
   619  	// Generally this will trigger on the first iteration anyway.
   620  	pid := syscall.Getpid()
   621  	for i := 0; i < 100; i++ {
   622  		if id, _, e := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0); e != 0 {
   623  			t.Errorf("[%d] getpid failed: %v", i, e)
   624  		} else if int(id) != pid {
   625  			t.Errorf("[%d] getpid got=%d, want=%d", i, id, pid)
   626  		}
   627  		// Provide an explicit opportunity for this goroutine
   628  		// to change Ms.
   629  		runtime.Gosched()
   630  	}
   631  	wr.Close()
   632  	wg.Wait()
   633  }
   634  
   635  func TestPrlimitSelf(t *testing.T) {
   636  	origLimit := syscall.OrigRlimitNofile()
   637  	origRlimitNofile := syscall.GetInternalOrigRlimitNofile()
   638  
   639  	if origLimit == nil {
   640  		defer origRlimitNofile.Store(origLimit)
   641  		origRlimitNofile.Store(&syscall.Rlimit{
   642  			Cur: 1024,
   643  			Max: 65536,
   644  		})
   645  	}
   646  
   647  	// Get current process's nofile limit
   648  	var lim syscall.Rlimit
   649  	if err := syscall.Prlimit(0, syscall.RLIMIT_NOFILE, nil, &lim); err != nil {
   650  		t.Fatalf("Failed to get the current nofile limit: %v", err)
   651  	}
   652  	// Set current process's nofile limit through prlimit
   653  	if err := syscall.Prlimit(0, syscall.RLIMIT_NOFILE, &lim, nil); err != nil {
   654  		t.Fatalf("Prlimit self failed: %v", err)
   655  	}
   656  
   657  	rlimLater := origRlimitNofile.Load()
   658  	if rlimLater != nil {
   659  		t.Fatalf("origRlimitNofile got=%v, want=nil", rlimLater)
   660  	}
   661  }
   662  
   663  func TestPrlimitOtherProcess(t *testing.T) {
   664  	origLimit := syscall.OrigRlimitNofile()
   665  	origRlimitNofile := syscall.GetInternalOrigRlimitNofile()
   666  
   667  	if origLimit == nil {
   668  		defer origRlimitNofile.Store(origLimit)
   669  		origRlimitNofile.Store(&syscall.Rlimit{
   670  			Cur: 1024,
   671  			Max: 65536,
   672  		})
   673  	}
   674  	rlimOrig := origRlimitNofile.Load()
   675  
   676  	// Start a child process firstly,
   677  	// so we can use Prlimit to set it's nofile limit.
   678  	cmd := exec.Command("sleep", "infinity")
   679  	cmd.Start()
   680  	defer func() {
   681  		cmd.Process.Kill()
   682  		cmd.Process.Wait()
   683  	}()
   684  
   685  	// Get child process's current nofile limit
   686  	var lim syscall.Rlimit
   687  	if err := syscall.Prlimit(cmd.Process.Pid, syscall.RLIMIT_NOFILE, nil, &lim); err != nil {
   688  		t.Fatalf("Failed to get the current nofile limit: %v", err)
   689  	}
   690  	// Set child process's nofile rlimit through prlimit
   691  	if err := syscall.Prlimit(cmd.Process.Pid, syscall.RLIMIT_NOFILE, &lim, nil); err != nil {
   692  		t.Fatalf("Prlimit(%d) failed: %v", cmd.Process.Pid, err)
   693  	}
   694  
   695  	rlimLater := origRlimitNofile.Load()
   696  	if rlimLater != rlimOrig {
   697  		t.Fatalf("origRlimitNofile got=%v, want=%v", rlimLater, rlimOrig)
   698  	}
   699  }
   700  
   701  const magicRlimitValue = 42
   702  
   703  // TestPrlimitFileLimit tests that we can start a Go program, use
   704  // prlimit to change its NOFILE limit, and have that updated limit be
   705  // seen by children. See issue #66797.
   706  func TestPrlimitFileLimit(t *testing.T) {
   707  	switch os.Getenv("GO_WANT_HELPER_PROCESS") {
   708  	case "prlimit1":
   709  		testPrlimitFileLimitHelper1(t)
   710  		return
   711  	case "prlimit2":
   712  		testPrlimitFileLimitHelper2(t)
   713  		return
   714  	}
   715  
   716  	origRlimitNofile := syscall.GetInternalOrigRlimitNofile()
   717  	defer origRlimitNofile.Store(origRlimitNofile.Load())
   718  
   719  	// Set our rlimit to magic+1/max.
   720  	// That will also become the rlimit of the child.
   721  
   722  	var lim syscall.Rlimit
   723  	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
   724  		t.Fatal(err)
   725  	}
   726  	max := lim.Max
   727  
   728  	lim = syscall.Rlimit{
   729  		Cur: magicRlimitValue + 1,
   730  		Max: max,
   731  	}
   732  	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
   733  		t.Fatal(err)
   734  	}
   735  
   736  	exe, err := os.Executable()
   737  	if err != nil {
   738  		t.Fatal(err)
   739  	}
   740  
   741  	r1, w1, err := os.Pipe()
   742  	if err != nil {
   743  		t.Fatal(err)
   744  	}
   745  	defer r1.Close()
   746  	defer w1.Close()
   747  
   748  	r2, w2, err := os.Pipe()
   749  	if err != nil {
   750  		t.Fatal(err)
   751  	}
   752  	defer r2.Close()
   753  	defer w2.Close()
   754  
   755  	var output strings.Builder
   756  
   757  	const arg = "-test.run=^TestPrlimitFileLimit$"
   758  	cmd := testenv.CommandContext(t, t.Context(), exe, arg, "-test.v")
   759  	cmd = testenv.CleanCmdEnv(cmd)
   760  	cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=prlimit1")
   761  	cmd.ExtraFiles = []*os.File{r1, w2}
   762  	cmd.Stdout = &output
   763  	cmd.Stderr = &output
   764  
   765  	t.Logf("running %s %s", exe, arg)
   766  
   767  	if err := cmd.Start(); err != nil {
   768  		t.Fatal(err)
   769  	}
   770  
   771  	// Wait for the child to start.
   772  	b := make([]byte, 1)
   773  	if n, err := r2.Read(b); err != nil {
   774  		t.Fatal(err)
   775  	} else if n != 1 {
   776  		t.Fatalf("read %d bytes, want 1", n)
   777  	}
   778  
   779  	// Set the child's prlimit.
   780  	lim = syscall.Rlimit{
   781  		Cur: magicRlimitValue,
   782  		Max: max,
   783  	}
   784  	if err := syscall.Prlimit(cmd.Process.Pid, syscall.RLIMIT_NOFILE, &lim, nil); err != nil {
   785  		t.Fatalf("Prlimit failed: %v", err)
   786  	}
   787  
   788  	// Tell the child to continue.
   789  	if n, err := w1.Write(b); err != nil {
   790  		t.Fatal(err)
   791  	} else if n != 1 {
   792  		t.Fatalf("wrote %d bytes, want 1", n)
   793  	}
   794  
   795  	err = cmd.Wait()
   796  	if output.Len() > 0 {
   797  		t.Logf("%s", output.String())
   798  	}
   799  
   800  	if err != nil {
   801  		t.Errorf("child failed: %v", err)
   802  	}
   803  }
   804  
   805  // testPrlimitFileLimitHelper1 is run by TestPrlimitFileLimit.
   806  func testPrlimitFileLimitHelper1(t *testing.T) {
   807  	var lim syscall.Rlimit
   808  	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
   809  		t.Fatal(err)
   810  	}
   811  	t.Logf("helper1 rlimit is %v", lim)
   812  	t.Logf("helper1 cached rlimit is %v", syscall.OrigRlimitNofile())
   813  
   814  	// Tell the parent that we are ready.
   815  	b := []byte{0}
   816  	if n, err := syscall.Write(4, b); err != nil {
   817  		t.Fatal(err)
   818  	} else if n != 1 {
   819  		t.Fatalf("wrote %d bytes, want 1", n)
   820  	}
   821  
   822  	// Wait for the parent to tell us that prlimit was used.
   823  	if n, err := syscall.Read(3, b); err != nil {
   824  		t.Fatal(err)
   825  	} else if n != 1 {
   826  		t.Fatalf("read %d bytes, want 1", n)
   827  	}
   828  
   829  	if err := syscall.Close(3); err != nil {
   830  		t.Errorf("Close(3): %v", err)
   831  	}
   832  	if err := syscall.Close(4); err != nil {
   833  		t.Errorf("Close(4): %v", err)
   834  	}
   835  
   836  	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
   837  		t.Fatal(err)
   838  	}
   839  	t.Logf("after prlimit helper1 rlimit is %v", lim)
   840  	t.Logf("after prlimit helper1 cached rlimit is %v", syscall.OrigRlimitNofile())
   841  
   842  	// Start the grandchild, which should see the rlimit
   843  	// set by the prlimit called by the parent.
   844  
   845  	exe, err := os.Executable()
   846  	if err != nil {
   847  		t.Fatal(err)
   848  	}
   849  
   850  	const arg = "-test.run=^TestPrlimitFileLimit$"
   851  	cmd := testenv.CommandContext(t, t.Context(), exe, arg, "-test.v")
   852  	cmd = testenv.CleanCmdEnv(cmd)
   853  	cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=prlimit2")
   854  	t.Logf("running %s %s", exe, arg)
   855  	out, err := cmd.CombinedOutput()
   856  	if len(out) > 0 {
   857  		t.Logf("%s", out)
   858  	}
   859  	if err != nil {
   860  		t.Errorf("grandchild failed: %v", err)
   861  	} else {
   862  		fmt.Println("OK")
   863  	}
   864  }
   865  
   866  // testPrlimitFileLimitHelper2 is run by testPrlimitFileLimit1.
   867  func testPrlimitFileLimitHelper2(t *testing.T) {
   868  	var lim syscall.Rlimit
   869  	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
   870  		t.Fatal(err)
   871  	}
   872  
   873  	t.Logf("helper2 rlimit is %v", lim)
   874  	cached := syscall.OrigRlimitNofile()
   875  	t.Logf("helper2 cached rlimit is %v", cached)
   876  
   877  	// The value return by Getrlimit will have been adjusted.
   878  	// We should have cached the value set by prlimit called by the parent.
   879  
   880  	if cached == nil {
   881  		t.Fatal("no cached rlimit")
   882  	} else if cached.Cur != magicRlimitValue {
   883  		t.Fatalf("cached rlimit is %d, want %d", cached.Cur, magicRlimitValue)
   884  	}
   885  
   886  	fmt.Println("OK")
   887  }
   888  

View as plain text