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

View as plain text