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

View as plain text