github.com/dorkamotorka/go/src@v0.0.0-20230614113921-187095f0e316/syscall/exec_linux_test.go (about)

     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  //go:build linux
     6  
     7  package syscall_test
     8  
     9  import (
    10  	"bytes"
    11  	"flag"
    12  	"fmt"
    13  	"internal/testenv"
    14  	"io"
    15  	"os"
    16  	"os/exec"
    17  	"os/user"
    18  	"path"
    19  	"path/filepath"
    20  	"runtime"
    21  	"strconv"
    22  	"strings"
    23  	"syscall"
    24  	"testing"
    25  	"unsafe"
    26  )
    27  
    28  // whoamiNEWUSER returns a command that runs "whoami" with CLONE_NEWUSER,
    29  // mapping uid and gid 0 to the actual uid and gid of the test.
    30  func whoamiNEWUSER(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd {
    31  	t.Helper()
    32  	testenv.MustHaveExecPath(t, "whoami")
    33  	cmd := testenv.Command(t, "whoami")
    34  	cmd.SysProcAttr = &syscall.SysProcAttr{
    35  		Cloneflags: syscall.CLONE_NEWUSER,
    36  		UidMappings: []syscall.SysProcIDMap{
    37  			{ContainerID: 0, HostID: uid, Size: 1},
    38  		},
    39  		GidMappings: []syscall.SysProcIDMap{
    40  			{ContainerID: 0, HostID: gid, Size: 1},
    41  		},
    42  		GidMappingsEnableSetgroups: setgroups,
    43  	}
    44  	return cmd
    45  }
    46  
    47  func TestCloneNEWUSERAndRemap(t *testing.T) {
    48  	for _, setgroups := range []bool{false, true} {
    49  		setgroups := setgroups
    50  		t.Run(fmt.Sprintf("setgroups=%v", setgroups), func(t *testing.T) {
    51  			uid := os.Getuid()
    52  			gid := os.Getgid()
    53  
    54  			cmd := whoamiNEWUSER(t, uid, gid, setgroups)
    55  			out, err := cmd.CombinedOutput()
    56  			t.Logf("%v: %v", cmd, err)
    57  
    58  			if uid != 0 && setgroups {
    59  				t.Logf("as non-root, expected permission error due to unprivileged gid_map")
    60  				if !os.IsPermission(err) {
    61  					if err == nil {
    62  						t.Skipf("unexpected success: probably old kernel without security fix?")
    63  					}
    64  					if testenv.SyscallIsNotSupported(err) {
    65  						t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported")
    66  					}
    67  					t.Fatalf("got non-permission error") // Already logged above.
    68  				}
    69  				return
    70  			}
    71  
    72  			if err != nil {
    73  				if testenv.SyscallIsNotSupported(err) {
    74  					// May be inside a container that disallows CLONE_NEWUSER.
    75  					t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported")
    76  				}
    77  				t.Fatalf("unexpected command failure; output:\n%s", out)
    78  			}
    79  
    80  			sout := strings.TrimSpace(string(out))
    81  			want := "root"
    82  			if sout != want {
    83  				t.Fatalf("whoami = %q; want %q", out, want)
    84  			}
    85  		})
    86  	}
    87  }
    88  
    89  func TestEmptyCredGroupsDisableSetgroups(t *testing.T) {
    90  	cmd := whoamiNEWUSER(t, os.Getuid(), os.Getgid(), false)
    91  	cmd.SysProcAttr.Credential = &syscall.Credential{}
    92  	if err := cmd.Run(); err != nil {
    93  		if testenv.SyscallIsNotSupported(err) {
    94  			t.Skipf("skipping: %v: %v", cmd, err)
    95  		}
    96  		t.Fatal(err)
    97  	}
    98  }
    99  
   100  func TestUnshare(t *testing.T) {
   101  	path := "/proc/net/dev"
   102  	if _, err := os.Stat(path); err != nil {
   103  		if os.IsNotExist(err) {
   104  			t.Skip("kernel doesn't support proc filesystem")
   105  		}
   106  		if os.IsPermission(err) {
   107  			t.Skip("unable to test proc filesystem due to permissions")
   108  		}
   109  		t.Fatal(err)
   110  	}
   111  
   112  	orig, err := os.ReadFile(path)
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	origLines := strings.Split(strings.TrimSpace(string(orig)), "\n")
   117  
   118  	cmd := testenv.Command(t, "cat", path)
   119  	cmd.SysProcAttr = &syscall.SysProcAttr{
   120  		Unshareflags: syscall.CLONE_NEWNET,
   121  	}
   122  	out, err := cmd.CombinedOutput()
   123  	if err != nil {
   124  		if testenv.SyscallIsNotSupported(err) {
   125  			// CLONE_NEWNET does not appear to be supported.
   126  			t.Skipf("skipping due to permission error: %v", err)
   127  		}
   128  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   129  	}
   130  
   131  	// Check there is only the local network interface
   132  	sout := strings.TrimSpace(string(out))
   133  	if !strings.Contains(sout, "lo:") {
   134  		t.Fatalf("Expected lo network interface to exist, got %s", sout)
   135  	}
   136  
   137  	lines := strings.Split(sout, "\n")
   138  	if len(lines) >= len(origLines) {
   139  		t.Fatalf("Got %d lines of output, want <%d", len(lines), len(origLines))
   140  	}
   141  }
   142  
   143  func TestGroupCleanup(t *testing.T) {
   144  	testenv.MustHaveExecPath(t, "id")
   145  	cmd := testenv.Command(t, "id")
   146  	cmd.SysProcAttr = &syscall.SysProcAttr{
   147  		Credential: &syscall.Credential{
   148  			Uid: 0,
   149  			Gid: 0,
   150  		},
   151  	}
   152  	out, err := cmd.CombinedOutput()
   153  	if err != nil {
   154  		if testenv.SyscallIsNotSupported(err) {
   155  			t.Skipf("skipping: %v: %v", cmd, err)
   156  		}
   157  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   158  	}
   159  	strOut := strings.TrimSpace(string(out))
   160  	t.Logf("id: %s", strOut)
   161  
   162  	expected := "uid=0(root) gid=0(root)"
   163  	// Just check prefix because some distros reportedly output a
   164  	// context parameter; see https://golang.org/issue/16224.
   165  	// Alpine does not output groups; see https://golang.org/issue/19938.
   166  	if !strings.HasPrefix(strOut, expected) {
   167  		t.Errorf("expected prefix: %q", expected)
   168  	}
   169  }
   170  
   171  func TestGroupCleanupUserNamespace(t *testing.T) {
   172  	testenv.MustHaveExecPath(t, "id")
   173  	cmd := testenv.Command(t, "id")
   174  	uid, gid := os.Getuid(), os.Getgid()
   175  	cmd.SysProcAttr = &syscall.SysProcAttr{
   176  		Cloneflags: syscall.CLONE_NEWUSER,
   177  		Credential: &syscall.Credential{
   178  			Uid: uint32(uid),
   179  			Gid: uint32(gid),
   180  		},
   181  		UidMappings: []syscall.SysProcIDMap{
   182  			{ContainerID: 0, HostID: uid, Size: 1},
   183  		},
   184  		GidMappings: []syscall.SysProcIDMap{
   185  			{ContainerID: 0, HostID: gid, Size: 1},
   186  		},
   187  	}
   188  	out, err := cmd.CombinedOutput()
   189  	if err != nil {
   190  		if testenv.SyscallIsNotSupported(err) {
   191  			t.Skipf("skipping: %v: %v", cmd, err)
   192  		}
   193  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   194  	}
   195  	strOut := strings.TrimSpace(string(out))
   196  	t.Logf("id: %s", strOut)
   197  
   198  	// As in TestGroupCleanup, just check prefix.
   199  	// The actual groups and contexts seem to vary from one distro to the next.
   200  	expected := "uid=0(root) gid=0(root) groups=0(root)"
   201  	if !strings.HasPrefix(strOut, expected) {
   202  		t.Errorf("expected prefix: %q", expected)
   203  	}
   204  }
   205  
   206  // Test for https://go.dev/issue/19661: unshare fails because systemd
   207  // has forced / to be shared
   208  func TestUnshareMountNameSpace(t *testing.T) {
   209  	testenv.MustHaveExec(t)
   210  
   211  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
   212  		dir := flag.Args()[0]
   213  		err := syscall.Mount("none", dir, "proc", 0, "")
   214  		if err != nil {
   215  			fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %#v", dir, err)
   216  			os.Exit(2)
   217  		}
   218  		os.Exit(0)
   219  	}
   220  
   221  	exe, err := os.Executable()
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  
   226  	d := t.TempDir()
   227  	t.Cleanup(func() {
   228  		// If the subprocess fails to unshare the parent directory, force-unmount it
   229  		// so that the test can clean it up.
   230  		if _, err := os.Stat(d); err == nil {
   231  			syscall.Unmount(d, syscall.MNT_FORCE)
   232  		}
   233  	})
   234  	cmd := testenv.Command(t, exe, "-test.run=TestUnshareMountNameSpace", d)
   235  	cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
   236  	cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS}
   237  
   238  	o, err := cmd.CombinedOutput()
   239  	if err != nil {
   240  		if testenv.SyscallIsNotSupported(err) {
   241  			t.Skipf("skipping: could not start process with CLONE_NEWNS: %v", err)
   242  		}
   243  		t.Fatalf("unshare failed: %v\n%s", err, o)
   244  	}
   245  
   246  	// How do we tell if the namespace was really unshared? It turns out
   247  	// to be simple: just try to remove the directory. If it's still mounted
   248  	// on the rm will fail with EBUSY.
   249  	if err := os.Remove(d); err != nil {
   250  		t.Errorf("rmdir failed on %v: %v", d, err)
   251  	}
   252  }
   253  
   254  // Test for Issue 20103: unshare fails when chroot is used
   255  func TestUnshareMountNameSpaceChroot(t *testing.T) {
   256  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
   257  		dir := flag.Args()[0]
   258  		err := syscall.Mount("none", dir, "proc", 0, "")
   259  		if err != nil {
   260  			fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %#v", dir, err)
   261  			os.Exit(2)
   262  		}
   263  		os.Exit(0)
   264  	}
   265  
   266  	d := t.TempDir()
   267  
   268  	// Since we are doing a chroot, we need the binary there,
   269  	// and it must be statically linked.
   270  	testenv.MustHaveGoBuild(t)
   271  	x := filepath.Join(d, "syscall.test")
   272  	t.Cleanup(func() {
   273  		// If the subprocess fails to unshare the parent directory, force-unmount it
   274  		// so that the test can clean it up.
   275  		if _, err := os.Stat(d); err == nil {
   276  			syscall.Unmount(d, syscall.MNT_FORCE)
   277  		}
   278  	})
   279  
   280  	cmd := testenv.Command(t, testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall")
   281  	cmd.Env = append(cmd.Environ(), "CGO_ENABLED=0")
   282  	if o, err := cmd.CombinedOutput(); err != nil {
   283  		t.Fatalf("Build of syscall in chroot failed, output %v, err %v", o, err)
   284  	}
   285  
   286  	cmd = testenv.Command(t, "/syscall.test", "-test.run=TestUnshareMountNameSpaceChroot", "/")
   287  	cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
   288  	cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS}
   289  
   290  	o, err := cmd.CombinedOutput()
   291  	if err != nil {
   292  		if testenv.SyscallIsNotSupported(err) {
   293  			t.Skipf("skipping: could not start process with CLONE_NEWNS and Chroot %q: %v", d, err)
   294  		}
   295  		t.Fatalf("unshare failed: %v\n%s", err, o)
   296  	}
   297  
   298  	// How do we tell if the namespace was really unshared? It turns out
   299  	// to be simple: just try to remove the executable. If it's still mounted
   300  	// on, the rm will fail.
   301  	if err := os.Remove(x); err != nil {
   302  		t.Errorf("rm failed on %v: %v", x, err)
   303  	}
   304  	if err := os.Remove(d); err != nil {
   305  		t.Errorf("rmdir failed on %v: %v", d, err)
   306  	}
   307  }
   308  
   309  // Test for Issue 29789: unshare fails when uid/gid mapping is specified
   310  func TestUnshareUidGidMapping(t *testing.T) {
   311  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
   312  		defer os.Exit(0)
   313  		if err := syscall.Chroot(os.TempDir()); err != nil {
   314  			fmt.Fprintln(os.Stderr, err)
   315  			os.Exit(2)
   316  		}
   317  	}
   318  
   319  	if os.Getuid() == 0 {
   320  		t.Skip("test exercises unprivileged user namespace, fails with privileges")
   321  	}
   322  
   323  	testenv.MustHaveExec(t)
   324  	exe, err := os.Executable()
   325  	if err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	cmd := testenv.Command(t, exe, "-test.run=TestUnshareUidGidMapping")
   330  	cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
   331  	cmd.SysProcAttr = &syscall.SysProcAttr{
   332  		Unshareflags:               syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
   333  		GidMappingsEnableSetgroups: false,
   334  		UidMappings: []syscall.SysProcIDMap{
   335  			{
   336  				ContainerID: 0,
   337  				HostID:      syscall.Getuid(),
   338  				Size:        1,
   339  			},
   340  		},
   341  		GidMappings: []syscall.SysProcIDMap{
   342  			{
   343  				ContainerID: 0,
   344  				HostID:      syscall.Getgid(),
   345  				Size:        1,
   346  			},
   347  		},
   348  	}
   349  	out, err := cmd.CombinedOutput()
   350  	if err != nil {
   351  		if testenv.SyscallIsNotSupported(err) {
   352  			t.Skipf("skipping: could not start process with CLONE_NEWNS and CLONE_NEWUSER: %v", err)
   353  		}
   354  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   355  	}
   356  }
   357  
   358  func prepareCgroupFD(t *testing.T) (int, string) {
   359  	t.Helper()
   360  
   361  	const O_PATH = 0x200000 // Same for all architectures, but for some reason not defined in syscall for 386||amd64.
   362  
   363  	// Requires cgroup v2.
   364  	const prefix = "/sys/fs/cgroup"
   365  	selfCg, err := os.ReadFile("/proc/self/cgroup")
   366  	if err != nil {
   367  		if os.IsNotExist(err) || os.IsPermission(err) {
   368  			t.Skip(err)
   369  		}
   370  		t.Fatal(err)
   371  	}
   372  
   373  	// Expect a single line like this:
   374  	// 0::/user.slice/user-1000.slice/user@1000.service/app.slice/vte-spawn-891992a2-efbb-4f28-aedb-b24f9e706770.scope
   375  	// Otherwise it's either cgroup v1 or a hybrid hierarchy.
   376  	if bytes.Count(selfCg, []byte("\n")) > 1 {
   377  		t.Skip("cgroup v2 not available")
   378  	}
   379  	cg := bytes.TrimPrefix(selfCg, []byte("0::"))
   380  	if len(cg) == len(selfCg) { // No prefix found.
   381  		t.Skipf("cgroup v2 not available (/proc/self/cgroup contents: %q)", selfCg)
   382  	}
   383  
   384  	// Need clone3 with CLONE_INTO_CGROUP support.
   385  	_, err = syscall.ForkExec("non-existent binary", nil, &syscall.ProcAttr{
   386  		Sys: &syscall.SysProcAttr{
   387  			UseCgroupFD: true,
   388  			CgroupFD:    -1,
   389  		},
   390  	})
   391  	if testenv.SyscallIsNotSupported(err) {
   392  		t.Skipf("clone3 with CLONE_INTO_CGROUP not available: %v", err)
   393  	}
   394  
   395  	// Need an ability to create a sub-cgroup.
   396  	subCgroup, err := os.MkdirTemp(prefix+string(bytes.TrimSpace(cg)), "subcg-")
   397  	if err != nil {
   398  		// ErrPermission or EROFS (#57262) when running in an unprivileged container.
   399  		// ErrNotExist when cgroupfs is not mounted in chroot/schroot.
   400  		if os.IsNotExist(err) || testenv.SyscallIsNotSupported(err) {
   401  			t.Skipf("skipping: %v", err)
   402  		}
   403  		t.Fatal(err)
   404  	}
   405  	t.Cleanup(func() { syscall.Rmdir(subCgroup) })
   406  
   407  	cgroupFD, err := syscall.Open(subCgroup, O_PATH, 0)
   408  	if err != nil {
   409  		t.Fatal(&os.PathError{Op: "open", Path: subCgroup, Err: err})
   410  	}
   411  	t.Cleanup(func() { syscall.Close(cgroupFD) })
   412  
   413  	return cgroupFD, "/" + path.Base(subCgroup)
   414  }
   415  
   416  func TestUseCgroupFD(t *testing.T) {
   417  	testenv.MustHaveExec(t)
   418  	exe, err := os.Executable()
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  
   423  	fd, suffix := prepareCgroupFD(t)
   424  
   425  	cmd := testenv.Command(t, exe, "-test.run=TestUseCgroupFDHelper")
   426  	cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
   427  	cmd.SysProcAttr = &syscall.SysProcAttr{
   428  		UseCgroupFD: true,
   429  		CgroupFD:    fd,
   430  	}
   431  	out, err := cmd.CombinedOutput()
   432  	if err != nil {
   433  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   434  	}
   435  	// NB: this wouldn't work with cgroupns.
   436  	if !bytes.HasSuffix(bytes.TrimSpace(out), []byte(suffix)) {
   437  		t.Fatalf("got: %q, want: a line that ends with %q", out, suffix)
   438  	}
   439  }
   440  
   441  func TestUseCgroupFDHelper(*testing.T) {
   442  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
   443  		return
   444  	}
   445  	defer os.Exit(0)
   446  	// Read and print own cgroup path.
   447  	selfCg, err := os.ReadFile("/proc/self/cgroup")
   448  	if err != nil {
   449  		fmt.Fprintln(os.Stderr, err)
   450  		os.Exit(2)
   451  	}
   452  	fmt.Print(string(selfCg))
   453  }
   454  
   455  func TestCloneTimeNamespace(t *testing.T) {
   456  	testenv.MustHaveExec(t)
   457  
   458  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
   459  		timens, err := os.Readlink("/proc/self/ns/time")
   460  		if err != nil {
   461  			fmt.Fprintln(os.Stderr, err)
   462  			os.Exit(2)
   463  		}
   464  		fmt.Print(string(timens))
   465  		os.Exit(0)
   466  	}
   467  
   468  	exe, err := os.Executable()
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  
   473  	cmd := testenv.Command(t, exe, "-test.run=TestCloneTimeNamespace")
   474  	cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
   475  	cmd.SysProcAttr = &syscall.SysProcAttr{
   476  		Cloneflags: syscall.CLONE_NEWTIME,
   477  	}
   478  	out, err := cmd.CombinedOutput()
   479  	if err != nil {
   480  		if testenv.SyscallIsNotSupported(err) {
   481  			// CLONE_NEWTIME does not appear to be supported.
   482  			t.Skipf("skipping, CLONE_NEWTIME not supported: %v", err)
   483  		}
   484  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   485  	}
   486  
   487  	// Inode numer of the time namespaces should be different.
   488  	// Based on https://man7.org/linux/man-pages/man7/time_namespaces.7.html#EXAMPLES
   489  	timens, err := os.Readlink("/proc/self/ns/time")
   490  	if err != nil {
   491  		t.Fatal(err)
   492  	}
   493  
   494  	parentTimeNS := string(timens)
   495  	childTimeNS := string(out)
   496  	if childTimeNS == parentTimeNS {
   497  		t.Fatalf("expected child time namespace to be different from parent time namespace: %s", parentTimeNS)
   498  	}
   499  }
   500  
   501  type capHeader struct {
   502  	version uint32
   503  	pid     int32
   504  }
   505  
   506  type capData struct {
   507  	effective   uint32
   508  	permitted   uint32
   509  	inheritable uint32
   510  }
   511  
   512  const CAP_SYS_TIME = 25
   513  const CAP_SYSLOG = 34
   514  
   515  type caps struct {
   516  	hdr  capHeader
   517  	data [2]capData
   518  }
   519  
   520  func getCaps() (caps, error) {
   521  	var c caps
   522  
   523  	// Get capability version
   524  	if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 {
   525  		return c, fmt.Errorf("SYS_CAPGET: %v", errno)
   526  	}
   527  
   528  	// Get current capabilities
   529  	if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 {
   530  		return c, fmt.Errorf("SYS_CAPGET: %v", errno)
   531  	}
   532  
   533  	return c, nil
   534  }
   535  
   536  func TestAmbientCaps(t *testing.T) {
   537  	testAmbientCaps(t, false)
   538  }
   539  
   540  func TestAmbientCapsUserns(t *testing.T) {
   541  	testAmbientCaps(t, true)
   542  }
   543  
   544  func testAmbientCaps(t *testing.T, userns bool) {
   545  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
   546  		caps, err := getCaps()
   547  		if err != nil {
   548  			fmt.Fprintln(os.Stderr, err)
   549  			os.Exit(2)
   550  		}
   551  		if caps.data[0].effective&(1<<uint(CAP_SYS_TIME)) == 0 {
   552  			fmt.Fprintln(os.Stderr, "CAP_SYS_TIME unexpectedly not in the effective capability mask")
   553  			os.Exit(2)
   554  		}
   555  		if caps.data[1].effective&(1<<uint(CAP_SYSLOG&31)) == 0 {
   556  			fmt.Fprintln(os.Stderr, "CAP_SYSLOG unexpectedly not in the effective capability mask")
   557  			os.Exit(2)
   558  		}
   559  		os.Exit(0)
   560  	}
   561  
   562  	// skip on android, due to lack of lookup support
   563  	if runtime.GOOS == "android" {
   564  		t.Skip("skipping test on android; see Issue 27327")
   565  	}
   566  
   567  	u, err := user.Lookup("nobody")
   568  	if err != nil {
   569  		t.Fatal(err)
   570  	}
   571  	uid, err := strconv.ParseInt(u.Uid, 0, 32)
   572  	if err != nil {
   573  		t.Fatal(err)
   574  	}
   575  	gid, err := strconv.ParseInt(u.Gid, 0, 32)
   576  	if err != nil {
   577  		t.Fatal(err)
   578  	}
   579  
   580  	// Copy the test binary to a temporary location which is readable by nobody.
   581  	f, err := os.CreateTemp("", "gotest")
   582  	if err != nil {
   583  		t.Fatal(err)
   584  	}
   585  	t.Cleanup(func() {
   586  		f.Close()
   587  		os.Remove(f.Name())
   588  	})
   589  
   590  	testenv.MustHaveExec(t)
   591  	exe, err := os.Executable()
   592  	if err != nil {
   593  		t.Fatal(err)
   594  	}
   595  
   596  	e, err := os.Open(exe)
   597  	if err != nil {
   598  		t.Fatal(err)
   599  	}
   600  	defer e.Close()
   601  	if _, err := io.Copy(f, e); err != nil {
   602  		t.Fatal(err)
   603  	}
   604  	if err := f.Chmod(0755); err != nil {
   605  		t.Fatal(err)
   606  	}
   607  	if err := f.Close(); err != nil {
   608  		t.Fatal(err)
   609  	}
   610  
   611  	cmd := testenv.Command(t, f.Name(), "-test.run="+t.Name())
   612  	cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
   613  	cmd.Stdout = os.Stdout
   614  	cmd.Stderr = os.Stderr
   615  	cmd.SysProcAttr = &syscall.SysProcAttr{
   616  		Credential: &syscall.Credential{
   617  			Uid: uint32(uid),
   618  			Gid: uint32(gid),
   619  		},
   620  		AmbientCaps: []uintptr{CAP_SYS_TIME, CAP_SYSLOG},
   621  	}
   622  	if userns {
   623  		cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER
   624  		const nobody = 65534
   625  		uid := os.Getuid()
   626  		gid := os.Getgid()
   627  		cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{
   628  			ContainerID: int(nobody),
   629  			HostID:      int(uid),
   630  			Size:        int(1),
   631  		}}
   632  		cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{
   633  			ContainerID: int(nobody),
   634  			HostID:      int(gid),
   635  			Size:        int(1),
   636  		}}
   637  
   638  		// Set credentials to run as user and group nobody.
   639  		cmd.SysProcAttr.Credential = &syscall.Credential{
   640  			Uid: nobody,
   641  			Gid: nobody,
   642  		}
   643  	}
   644  	if err := cmd.Run(); err != nil {
   645  		if testenv.SyscallIsNotSupported(err) {
   646  			t.Skipf("skipping: %v: %v", cmd, err)
   647  		}
   648  		t.Fatal(err.Error())
   649  	}
   650  }