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

View as plain text