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

View as plain text