Black Lives Matter. Support the Equal Justice Initiative.

Source file src/syscall/syscall_linux_test.go

Documentation: syscall

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

View as plain text