github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/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  func isDocker() bool {
    29  	_, err := os.Stat("/.dockerenv")
    30  	return err == nil
    31  }
    32  
    33  func isLXC() bool {
    34  	return os.Getenv("container") == "lxc"
    35  }
    36  
    37  func skipInContainer(t *testing.T) {
    38  	// TODO: the callers of this func are using this func to skip
    39  	// tests when running as some sort of "fake root" that's uid 0
    40  	// but lacks certain Linux capabilities. Most of the Go builds
    41  	// run in privileged containers, though, where root is much
    42  	// closer (if not identical) to the real root. We should test
    43  	// for what we need exactly (which capabilities are active?),
    44  	// instead of just assuming "docker == bad". Then we'd get more test
    45  	// coverage on a bunch of builders too.
    46  	if isDocker() {
    47  		t.Skip("skip this test in Docker container")
    48  	}
    49  	if isLXC() {
    50  		t.Skip("skip this test in LXC container")
    51  	}
    52  }
    53  
    54  func skipNoUserNamespaces(t *testing.T) {
    55  	if _, err := os.Stat("/proc/self/ns/user"); err != nil {
    56  		if os.IsNotExist(err) {
    57  			t.Skip("kernel doesn't support user namespaces")
    58  		}
    59  		if os.IsPermission(err) {
    60  			t.Skip("unable to test user namespaces due to permissions")
    61  		}
    62  		t.Fatalf("Failed to stat /proc/self/ns/user: %v", err)
    63  	}
    64  }
    65  
    66  func skipUnprivilegedUserClone(t *testing.T) {
    67  	// Skip the test if the sysctl that prevents unprivileged user
    68  	// from creating user namespaces is enabled.
    69  	data, errRead := os.ReadFile("/proc/sys/kernel/unprivileged_userns_clone")
    70  	if os.IsNotExist(errRead) {
    71  		// This file is only available in some Debian/Ubuntu kernels.
    72  		return
    73  	}
    74  	if errRead != nil || len(data) < 1 || data[0] == '0' {
    75  		t.Skip("kernel prohibits user namespace in unprivileged process")
    76  	}
    77  }
    78  
    79  // Check if we are in a chroot by checking if the inode of / is
    80  // different from 2 (there is no better test available to non-root on
    81  // linux).
    82  func isChrooted(t *testing.T) bool {
    83  	root, err := os.Stat("/")
    84  	if err != nil {
    85  		t.Fatalf("cannot stat /: %v", err)
    86  	}
    87  	return root.Sys().(*syscall.Stat_t).Ino != 2
    88  }
    89  
    90  func checkUserNS(t *testing.T) {
    91  	skipInContainer(t)
    92  	skipNoUserNamespaces(t)
    93  	if isChrooted(t) {
    94  		// create_user_ns in the kernel (see
    95  		// https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/kernel/user_namespace.c)
    96  		// forbids the creation of user namespaces when chrooted.
    97  		t.Skip("cannot create user namespaces when chrooted")
    98  	}
    99  	// On some systems, there is a sysctl setting.
   100  	if os.Getuid() != 0 {
   101  		skipUnprivilegedUserClone(t)
   102  	}
   103  	// On Centos 7 make sure they set the kernel parameter user_namespace=1
   104  	// See issue 16283 and 20796.
   105  	if _, err := os.Stat("/sys/module/user_namespace/parameters/enable"); err == nil {
   106  		buf, _ := os.ReadFile("/sys/module/user_namespace/parameters/enabled")
   107  		if !strings.HasPrefix(string(buf), "Y") {
   108  			t.Skip("kernel doesn't support user namespaces")
   109  		}
   110  	}
   111  
   112  	// On Centos 7.5+, user namespaces are disabled if user.max_user_namespaces = 0
   113  	if _, err := os.Stat("/proc/sys/user/max_user_namespaces"); err == nil {
   114  		buf, errRead := os.ReadFile("/proc/sys/user/max_user_namespaces")
   115  		if errRead == nil && buf[0] == '0' {
   116  			t.Skip("kernel doesn't support user namespaces")
   117  		}
   118  	}
   119  }
   120  
   121  func whoamiCmd(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd {
   122  	checkUserNS(t)
   123  	cmd := exec.Command("whoami")
   124  	cmd.SysProcAttr = &syscall.SysProcAttr{
   125  		Cloneflags: syscall.CLONE_NEWUSER,
   126  		UidMappings: []syscall.SysProcIDMap{
   127  			{ContainerID: 0, HostID: uid, Size: 1},
   128  		},
   129  		GidMappings: []syscall.SysProcIDMap{
   130  			{ContainerID: 0, HostID: gid, Size: 1},
   131  		},
   132  		GidMappingsEnableSetgroups: setgroups,
   133  	}
   134  	return cmd
   135  }
   136  
   137  func testNEWUSERRemap(t *testing.T, uid, gid int, setgroups bool) {
   138  	cmd := whoamiCmd(t, uid, gid, setgroups)
   139  	out, err := cmd.CombinedOutput()
   140  	if err != nil {
   141  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   142  	}
   143  	sout := strings.TrimSpace(string(out))
   144  	want := "root"
   145  	if sout != want {
   146  		t.Fatalf("whoami = %q; want %q", out, want)
   147  	}
   148  }
   149  
   150  func TestCloneNEWUSERAndRemapRootDisableSetgroups(t *testing.T) {
   151  	if os.Getuid() != 0 {
   152  		t.Skip("skipping root only test")
   153  	}
   154  	testNEWUSERRemap(t, 0, 0, false)
   155  }
   156  
   157  func TestCloneNEWUSERAndRemapRootEnableSetgroups(t *testing.T) {
   158  	if os.Getuid() != 0 {
   159  		t.Skip("skipping root only test")
   160  	}
   161  	testNEWUSERRemap(t, 0, 0, true)
   162  }
   163  
   164  func TestCloneNEWUSERAndRemapNoRootDisableSetgroups(t *testing.T) {
   165  	if os.Getuid() == 0 {
   166  		t.Skip("skipping unprivileged user only test")
   167  	}
   168  	testNEWUSERRemap(t, os.Getuid(), os.Getgid(), false)
   169  }
   170  
   171  func TestCloneNEWUSERAndRemapNoRootSetgroupsEnableSetgroups(t *testing.T) {
   172  	if os.Getuid() == 0 {
   173  		t.Skip("skipping unprivileged user only test")
   174  	}
   175  	cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), true)
   176  	err := cmd.Run()
   177  	if err == nil {
   178  		t.Skip("probably old kernel without security fix")
   179  	}
   180  	if !os.IsPermission(err) {
   181  		t.Fatalf("Unprivileged gid_map rewriting with GidMappingsEnableSetgroups must fail")
   182  	}
   183  }
   184  
   185  func TestEmptyCredGroupsDisableSetgroups(t *testing.T) {
   186  	cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), false)
   187  	cmd.SysProcAttr.Credential = &syscall.Credential{}
   188  	if err := cmd.Run(); err != nil {
   189  		t.Fatal(err)
   190  	}
   191  }
   192  
   193  func TestUnshare(t *testing.T) {
   194  	skipInContainer(t)
   195  	// Make sure we are running as root so we have permissions to use unshare
   196  	// and create a network namespace.
   197  	if os.Getuid() != 0 {
   198  		t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
   199  	}
   200  
   201  	path := "/proc/net/dev"
   202  	if _, err := os.Stat(path); err != nil {
   203  		if os.IsNotExist(err) {
   204  			t.Skip("kernel doesn't support proc filesystem")
   205  		}
   206  		if os.IsPermission(err) {
   207  			t.Skip("unable to test proc filesystem due to permissions")
   208  		}
   209  		t.Fatal(err)
   210  	}
   211  	if _, err := os.Stat("/proc/self/ns/net"); err != nil {
   212  		if os.IsNotExist(err) {
   213  			t.Skip("kernel doesn't support net namespace")
   214  		}
   215  		t.Fatal(err)
   216  	}
   217  
   218  	orig, err := os.ReadFile(path)
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	origLines := strings.Split(strings.TrimSpace(string(orig)), "\n")
   223  
   224  	cmd := exec.Command("cat", path)
   225  	cmd.SysProcAttr = &syscall.SysProcAttr{
   226  		Unshareflags: syscall.CLONE_NEWNET,
   227  	}
   228  	out, err := cmd.CombinedOutput()
   229  	if err != nil {
   230  		if strings.Contains(err.Error(), "operation not permitted") {
   231  			// Issue 17206: despite all the checks above,
   232  			// this still reportedly fails for some users.
   233  			// (older kernels?). Just skip.
   234  			t.Skip("skipping due to permission error")
   235  		}
   236  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   237  	}
   238  
   239  	// Check there is only the local network interface
   240  	sout := strings.TrimSpace(string(out))
   241  	if !strings.Contains(sout, "lo:") {
   242  		t.Fatalf("Expected lo network interface to exist, got %s", sout)
   243  	}
   244  
   245  	lines := strings.Split(sout, "\n")
   246  	if len(lines) >= len(origLines) {
   247  		t.Fatalf("Got %d lines of output, want <%d", len(lines), len(origLines))
   248  	}
   249  }
   250  
   251  func TestGroupCleanup(t *testing.T) {
   252  	if os.Getuid() != 0 {
   253  		t.Skip("we need root for credential")
   254  	}
   255  	cmd := exec.Command("id")
   256  	cmd.SysProcAttr = &syscall.SysProcAttr{
   257  		Credential: &syscall.Credential{
   258  			Uid: 0,
   259  			Gid: 0,
   260  		},
   261  	}
   262  	out, err := cmd.CombinedOutput()
   263  	if err != nil {
   264  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   265  	}
   266  	strOut := strings.TrimSpace(string(out))
   267  	t.Logf("id: %s", strOut)
   268  
   269  	expected := "uid=0(root) gid=0(root)"
   270  	// Just check prefix because some distros reportedly output a
   271  	// context parameter; see https://golang.org/issue/16224.
   272  	// Alpine does not output groups; see https://golang.org/issue/19938.
   273  	if !strings.HasPrefix(strOut, expected) {
   274  		t.Errorf("expected prefix: %q", expected)
   275  	}
   276  }
   277  
   278  func TestGroupCleanupUserNamespace(t *testing.T) {
   279  	if os.Getuid() != 0 {
   280  		t.Skip("we need root for credential")
   281  	}
   282  	checkUserNS(t)
   283  	cmd := exec.Command("id")
   284  	uid, gid := os.Getuid(), os.Getgid()
   285  	cmd.SysProcAttr = &syscall.SysProcAttr{
   286  		Cloneflags: syscall.CLONE_NEWUSER,
   287  		Credential: &syscall.Credential{
   288  			Uid: uint32(uid),
   289  			Gid: uint32(gid),
   290  		},
   291  		UidMappings: []syscall.SysProcIDMap{
   292  			{ContainerID: 0, HostID: uid, Size: 1},
   293  		},
   294  		GidMappings: []syscall.SysProcIDMap{
   295  			{ContainerID: 0, HostID: gid, Size: 1},
   296  		},
   297  	}
   298  	out, err := cmd.CombinedOutput()
   299  	if err != nil {
   300  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   301  	}
   302  	strOut := strings.TrimSpace(string(out))
   303  	t.Logf("id: %s", strOut)
   304  
   305  	// As in TestGroupCleanup, just check prefix.
   306  	// The actual groups and contexts seem to vary from one distro to the next.
   307  	expected := "uid=0(root) gid=0(root) groups=0(root)"
   308  	if !strings.HasPrefix(strOut, expected) {
   309  		t.Errorf("expected prefix: %q", expected)
   310  	}
   311  }
   312  
   313  // TestUnshareHelperProcess isn't a real test. It's used as a helper process
   314  // for TestUnshareMountNameSpace.
   315  func TestUnshareMountNameSpaceHelper(*testing.T) {
   316  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
   317  		return
   318  	}
   319  	defer os.Exit(0)
   320  	if err := syscall.Mount("none", flag.Args()[0], "proc", 0, ""); err != nil {
   321  		fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %v", os.Args, err)
   322  		os.Exit(2)
   323  	}
   324  }
   325  
   326  // Test for Issue 38471: unshare fails because systemd has forced / to be shared
   327  func TestUnshareMountNameSpace(t *testing.T) {
   328  	skipInContainer(t)
   329  	// Make sure we are running as root so we have permissions to use unshare
   330  	// and create a network namespace.
   331  	if os.Getuid() != 0 {
   332  		t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
   333  	}
   334  
   335  	d, err := os.MkdirTemp("", "unshare")
   336  	if err != nil {
   337  		t.Fatalf("tempdir: %v", err)
   338  	}
   339  
   340  	cmd := exec.Command(os.Args[0], "-test.run=TestUnshareMountNameSpaceHelper", d)
   341  	cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
   342  	cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS}
   343  
   344  	o, err := cmd.CombinedOutput()
   345  	if err != nil {
   346  		if strings.Contains(err.Error(), ": permission denied") {
   347  			t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err)
   348  		}
   349  		t.Fatalf("unshare failed: %s, %v", o, err)
   350  	}
   351  
   352  	// How do we tell if the namespace was really unshared? It turns out
   353  	// to be simple: just try to remove the directory. If it's still mounted
   354  	// on the rm will fail with EBUSY. Then we have some cleanup to do:
   355  	// we must unmount it, then try to remove it again.
   356  
   357  	if err := os.Remove(d); err != nil {
   358  		t.Errorf("rmdir failed on %v: %v", d, err)
   359  		if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil {
   360  			t.Errorf("Can't unmount %v: %v", d, err)
   361  		}
   362  		if err := os.Remove(d); err != nil {
   363  			t.Errorf("rmdir after unmount failed on %v: %v", d, err)
   364  		}
   365  	}
   366  }
   367  
   368  // Test for Issue 20103: unshare fails when chroot is used
   369  func TestUnshareMountNameSpaceChroot(t *testing.T) {
   370  	skipInContainer(t)
   371  	// Make sure we are running as root so we have permissions to use unshare
   372  	// and create a network namespace.
   373  	if os.Getuid() != 0 {
   374  		t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
   375  	}
   376  
   377  	d, err := os.MkdirTemp("", "unshare")
   378  	if err != nil {
   379  		t.Fatalf("tempdir: %v", err)
   380  	}
   381  
   382  	// Since we are doing a chroot, we need the binary there,
   383  	// and it must be statically linked.
   384  	x := filepath.Join(d, "syscall.test")
   385  	cmd := exec.Command(testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall")
   386  	cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
   387  	if o, err := cmd.CombinedOutput(); err != nil {
   388  		t.Fatalf("Build of syscall in chroot failed, output %v, err %v", o, err)
   389  	}
   390  
   391  	cmd = exec.Command("/syscall.test", "-test.run=TestUnshareMountNameSpaceHelper", "/")
   392  	cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
   393  	cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS}
   394  
   395  	o, err := cmd.CombinedOutput()
   396  	if err != nil {
   397  		if strings.Contains(err.Error(), ": permission denied") {
   398  			t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err)
   399  		}
   400  		t.Fatalf("unshare failed: %s, %v", o, err)
   401  	}
   402  
   403  	// How do we tell if the namespace was really unshared? It turns out
   404  	// to be simple: just try to remove the executable. If it's still mounted
   405  	// on, the rm will fail. Then we have some cleanup to do:
   406  	// we must force unmount it, then try to remove it again.
   407  
   408  	if err := os.Remove(x); err != nil {
   409  		t.Errorf("rm failed on %v: %v", x, err)
   410  		if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil {
   411  			t.Fatalf("Can't unmount %v: %v", d, err)
   412  		}
   413  		if err := os.Remove(x); err != nil {
   414  			t.Fatalf("rm failed on %v: %v", x, err)
   415  		}
   416  	}
   417  
   418  	if err := os.Remove(d); err != nil {
   419  		t.Errorf("rmdir failed on %v: %v", d, err)
   420  	}
   421  }
   422  
   423  func TestUnshareUidGidMappingHelper(*testing.T) {
   424  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
   425  		return
   426  	}
   427  	defer os.Exit(0)
   428  	if err := syscall.Chroot(os.TempDir()); err != nil {
   429  		fmt.Fprintln(os.Stderr, err)
   430  		os.Exit(2)
   431  	}
   432  }
   433  
   434  // Test for Issue 29789: unshare fails when uid/gid mapping is specified
   435  func TestUnshareUidGidMapping(t *testing.T) {
   436  	if os.Getuid() == 0 {
   437  		t.Skip("test exercises unprivileged user namespace, fails with privileges")
   438  	}
   439  	checkUserNS(t)
   440  	cmd := exec.Command(os.Args[0], "-test.run=TestUnshareUidGidMappingHelper")
   441  	cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
   442  	cmd.SysProcAttr = &syscall.SysProcAttr{
   443  		Unshareflags:               syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
   444  		GidMappingsEnableSetgroups: false,
   445  		UidMappings: []syscall.SysProcIDMap{
   446  			{
   447  				ContainerID: 0,
   448  				HostID:      syscall.Getuid(),
   449  				Size:        1,
   450  			},
   451  		},
   452  		GidMappings: []syscall.SysProcIDMap{
   453  			{
   454  				ContainerID: 0,
   455  				HostID:      syscall.Getgid(),
   456  				Size:        1,
   457  			},
   458  		},
   459  	}
   460  	out, err := cmd.CombinedOutput()
   461  	if err != nil {
   462  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   463  	}
   464  }
   465  
   466  func prepareCgroupFD(t *testing.T) (int, string) {
   467  	t.Helper()
   468  
   469  	const O_PATH = 0x200000 // Same for all architectures, but for some reason not defined in syscall for 386||amd64.
   470  
   471  	// Requires cgroup v2.
   472  	const prefix = "/sys/fs/cgroup"
   473  	selfCg, err := os.ReadFile("/proc/self/cgroup")
   474  	if err != nil {
   475  		if os.IsNotExist(err) || os.IsPermission(err) {
   476  			t.Skip(err)
   477  		}
   478  		t.Fatal(err)
   479  	}
   480  
   481  	// Expect a single line like this:
   482  	// 0::/user.slice/user-1000.slice/user@1000.service/app.slice/vte-spawn-891992a2-efbb-4f28-aedb-b24f9e706770.scope
   483  	// Otherwise it's either cgroup v1 or a hybrid hierarchy.
   484  	if bytes.Count(selfCg, []byte("\n")) > 1 {
   485  		t.Skip("cgroup v2 not available")
   486  	}
   487  	cg := bytes.TrimPrefix(selfCg, []byte("0::"))
   488  	if len(cg) == len(selfCg) { // No prefix found.
   489  		t.Skipf("cgroup v2 not available (/proc/self/cgroup contents: %q)", selfCg)
   490  	}
   491  
   492  	// Need clone3 with CLONE_INTO_CGROUP support.
   493  	_, err = syscall.ForkExec("non-existent binary", nil, &syscall.ProcAttr{
   494  		Sys: &syscall.SysProcAttr{
   495  			UseCgroupFD: true,
   496  			CgroupFD:    -1,
   497  		},
   498  	})
   499  	// // EPERM can be returned if clone3 is not enabled by seccomp.
   500  	if err == syscall.ENOSYS || err == syscall.EPERM {
   501  		t.Skipf("clone3 with CLONE_INTO_CGROUP not available: %v", err)
   502  	}
   503  
   504  	// Need an ability to create a sub-cgroup.
   505  	subCgroup, err := os.MkdirTemp(prefix+string(bytes.TrimSpace(cg)), "subcg-")
   506  	if err != nil {
   507  		if os.IsPermission(err) {
   508  			t.Skip(err)
   509  		}
   510  		t.Fatal(err)
   511  	}
   512  	t.Cleanup(func() { syscall.Rmdir(subCgroup) })
   513  
   514  	cgroupFD, err := syscall.Open(subCgroup, O_PATH, 0)
   515  	if err != nil {
   516  		t.Fatal(&os.PathError{Op: "open", Path: subCgroup, Err: err})
   517  	}
   518  	t.Cleanup(func() { syscall.Close(cgroupFD) })
   519  
   520  	return cgroupFD, "/" + path.Base(subCgroup)
   521  }
   522  
   523  func TestUseCgroupFD(t *testing.T) {
   524  	fd, suffix := prepareCgroupFD(t)
   525  
   526  	cmd := exec.Command(os.Args[0], "-test.run=TestUseCgroupFDHelper")
   527  	cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
   528  	cmd.SysProcAttr = &syscall.SysProcAttr{
   529  		UseCgroupFD: true,
   530  		CgroupFD:    fd,
   531  	}
   532  	out, err := cmd.CombinedOutput()
   533  	if err != nil {
   534  		t.Fatalf("Cmd failed with err %v, output: %s", err, out)
   535  	}
   536  	// NB: this wouldn't work with cgroupns.
   537  	if !bytes.HasSuffix(bytes.TrimSpace(out), []byte(suffix)) {
   538  		t.Fatalf("got: %q, want: a line that ends with %q", out, suffix)
   539  	}
   540  }
   541  
   542  func TestUseCgroupFDHelper(*testing.T) {
   543  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
   544  		return
   545  	}
   546  	defer os.Exit(0)
   547  	// Read and print own cgroup path.
   548  	selfCg, err := os.ReadFile("/proc/self/cgroup")
   549  	if err != nil {
   550  		fmt.Fprintln(os.Stderr, err)
   551  		os.Exit(2)
   552  	}
   553  	fmt.Print(string(selfCg))
   554  }
   555  
   556  type capHeader struct {
   557  	version uint32
   558  	pid     int32
   559  }
   560  
   561  type capData struct {
   562  	effective   uint32
   563  	permitted   uint32
   564  	inheritable uint32
   565  }
   566  
   567  const CAP_SYS_TIME = 25
   568  const CAP_SYSLOG = 34
   569  
   570  type caps struct {
   571  	hdr  capHeader
   572  	data [2]capData
   573  }
   574  
   575  func getCaps() (caps, error) {
   576  	var c caps
   577  
   578  	// Get capability version
   579  	if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 {
   580  		return c, fmt.Errorf("SYS_CAPGET: %v", errno)
   581  	}
   582  
   583  	// Get current capabilities
   584  	if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 {
   585  		return c, fmt.Errorf("SYS_CAPGET: %v", errno)
   586  	}
   587  
   588  	return c, nil
   589  }
   590  
   591  func mustSupportAmbientCaps(t *testing.T) {
   592  	var uname syscall.Utsname
   593  	if err := syscall.Uname(&uname); err != nil {
   594  		t.Fatalf("Uname: %v", err)
   595  	}
   596  	var buf [65]byte
   597  	for i, b := range uname.Release {
   598  		buf[i] = byte(b)
   599  	}
   600  	ver := string(buf[:])
   601  	ver, _, _ = strings.Cut(ver, "\x00")
   602  	if strings.HasPrefix(ver, "2.") ||
   603  		strings.HasPrefix(ver, "3.") ||
   604  		strings.HasPrefix(ver, "4.1.") ||
   605  		strings.HasPrefix(ver, "4.2.") {
   606  		t.Skipf("kernel version %q predates required 4.3; skipping test", ver)
   607  	}
   608  }
   609  
   610  // TestAmbientCapsHelper isn't a real test. It's used as a helper process for
   611  // TestAmbientCaps.
   612  func TestAmbientCapsHelper(*testing.T) {
   613  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
   614  		return
   615  	}
   616  	defer os.Exit(0)
   617  
   618  	caps, err := getCaps()
   619  	if err != nil {
   620  		fmt.Fprintln(os.Stderr, err)
   621  		os.Exit(2)
   622  	}
   623  	if caps.data[0].effective&(1<<uint(CAP_SYS_TIME)) == 0 {
   624  		fmt.Fprintln(os.Stderr, "CAP_SYS_TIME unexpectedly not in the effective capability mask")
   625  		os.Exit(2)
   626  	}
   627  	if caps.data[1].effective&(1<<uint(CAP_SYSLOG&31)) == 0 {
   628  		fmt.Fprintln(os.Stderr, "CAP_SYSLOG unexpectedly not in the effective capability mask")
   629  		os.Exit(2)
   630  	}
   631  }
   632  
   633  func TestAmbientCaps(t *testing.T) {
   634  	// Make sure we are running as root so we have permissions to use unshare
   635  	// and create a network namespace.
   636  	if os.Getuid() != 0 {
   637  		t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
   638  	}
   639  
   640  	testAmbientCaps(t, false)
   641  }
   642  
   643  func TestAmbientCapsUserns(t *testing.T) {
   644  	checkUserNS(t)
   645  	testAmbientCaps(t, true)
   646  }
   647  
   648  func testAmbientCaps(t *testing.T, userns bool) {
   649  	skipInContainer(t)
   650  	mustSupportAmbientCaps(t)
   651  
   652  	skipUnprivilegedUserClone(t)
   653  
   654  	// skip on android, due to lack of lookup support
   655  	if runtime.GOOS == "android" {
   656  		t.Skip("skipping test on android; see Issue 27327")
   657  	}
   658  
   659  	u, err := user.Lookup("nobody")
   660  	if err != nil {
   661  		t.Fatal(err)
   662  	}
   663  	uid, err := strconv.ParseInt(u.Uid, 0, 32)
   664  	if err != nil {
   665  		t.Fatal(err)
   666  	}
   667  	gid, err := strconv.ParseInt(u.Gid, 0, 32)
   668  	if err != nil {
   669  		t.Fatal(err)
   670  	}
   671  
   672  	// Copy the test binary to a temporary location which is readable by nobody.
   673  	f, err := os.CreateTemp("", "gotest")
   674  	if err != nil {
   675  		t.Fatal(err)
   676  	}
   677  	defer os.Remove(f.Name())
   678  	defer f.Close()
   679  	e, err := os.Open(os.Args[0])
   680  	if err != nil {
   681  		t.Fatal(err)
   682  	}
   683  	defer e.Close()
   684  	if _, err := io.Copy(f, e); err != nil {
   685  		t.Fatal(err)
   686  	}
   687  	if err := f.Chmod(0755); err != nil {
   688  		t.Fatal(err)
   689  	}
   690  	if err := f.Close(); err != nil {
   691  		t.Fatal(err)
   692  	}
   693  
   694  	cmd := exec.Command(f.Name(), "-test.run=TestAmbientCapsHelper")
   695  	cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
   696  	cmd.Stdout = os.Stdout
   697  	cmd.Stderr = os.Stderr
   698  	cmd.SysProcAttr = &syscall.SysProcAttr{
   699  		Credential: &syscall.Credential{
   700  			Uid: uint32(uid),
   701  			Gid: uint32(gid),
   702  		},
   703  		AmbientCaps: []uintptr{CAP_SYS_TIME, CAP_SYSLOG},
   704  	}
   705  	if userns {
   706  		cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER
   707  		const nobody = 65534
   708  		uid := os.Getuid()
   709  		gid := os.Getgid()
   710  		cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{
   711  			ContainerID: int(nobody),
   712  			HostID:      int(uid),
   713  			Size:        int(1),
   714  		}}
   715  		cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{
   716  			ContainerID: int(nobody),
   717  			HostID:      int(gid),
   718  			Size:        int(1),
   719  		}}
   720  
   721  		// Set credentials to run as user and group nobody.
   722  		cmd.SysProcAttr.Credential = &syscall.Credential{
   723  			Uid: nobody,
   724  			Gid: nobody,
   725  		}
   726  	}
   727  	if err := cmd.Run(); err != nil {
   728  		t.Fatal(err.Error())
   729  	}
   730  }