| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build linux |
| |
| package syscall_test |
| |
| import ( |
| "bytes" |
| "errors" |
| "flag" |
| "fmt" |
| "internal/platform" |
| "internal/syscall/unix" |
| "internal/testenv" |
| "io" |
| "os" |
| "os/exec" |
| "os/user" |
| "path" |
| "path/filepath" |
| "runtime" |
| "strconv" |
| "strings" |
| "syscall" |
| "testing" |
| "time" |
| "unsafe" |
| ) |
| |
| // whoamiNEWUSER returns a command that runs "whoami" with CLONE_NEWUSER, |
| // mapping uid and gid 0 to the actual uid and gid of the test. |
| func whoamiNEWUSER(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd { |
| t.Helper() |
| testenv.MustHaveExecPath(t, "whoami") |
| cmd := testenv.Command(t, "whoami") |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Cloneflags: syscall.CLONE_NEWUSER, |
| UidMappings: []syscall.SysProcIDMap{ |
| {ContainerID: 0, HostID: uid, Size: 1}, |
| }, |
| GidMappings: []syscall.SysProcIDMap{ |
| {ContainerID: 0, HostID: gid, Size: 1}, |
| }, |
| GidMappingsEnableSetgroups: setgroups, |
| } |
| return cmd |
| } |
| |
| func TestCloneNEWUSERAndRemap(t *testing.T) { |
| for _, setgroups := range []bool{false, true} { |
| setgroups := setgroups |
| t.Run(fmt.Sprintf("setgroups=%v", setgroups), func(t *testing.T) { |
| uid := os.Getuid() |
| gid := os.Getgid() |
| |
| cmd := whoamiNEWUSER(t, uid, gid, setgroups) |
| out, err := cmd.CombinedOutput() |
| t.Logf("%v: %v", cmd, err) |
| |
| if uid != 0 && setgroups { |
| t.Logf("as non-root, expected permission error due to unprivileged gid_map") |
| if !os.IsPermission(err) { |
| if err == nil { |
| t.Skipf("unexpected success: probably old kernel without security fix?") |
| } |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported") |
| } |
| t.Fatalf("got non-permission error") // Already logged above. |
| } |
| return |
| } |
| |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| // May be inside a container that disallows CLONE_NEWUSER. |
| t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported") |
| } |
| t.Fatalf("unexpected command failure; output:\n%s", out) |
| } |
| |
| sout := strings.TrimSpace(string(out)) |
| want := "root" |
| if sout != want { |
| t.Fatalf("whoami = %q; want %q", out, want) |
| } |
| }) |
| } |
| } |
| |
| func TestEmptyCredGroupsDisableSetgroups(t *testing.T) { |
| cmd := whoamiNEWUSER(t, os.Getuid(), os.Getgid(), false) |
| cmd.SysProcAttr.Credential = &syscall.Credential{} |
| if err := cmd.Run(); err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: %v: %v", cmd, err) |
| } |
| t.Fatal(err) |
| } |
| } |
| |
| func TestUnshare(t *testing.T) { |
| path := "/proc/net/dev" |
| if _, err := os.Stat(path); err != nil { |
| if os.IsNotExist(err) { |
| t.Skip("kernel doesn't support proc filesystem") |
| } |
| if os.IsPermission(err) { |
| t.Skip("unable to test proc filesystem due to permissions") |
| } |
| t.Fatal(err) |
| } |
| |
| b, err := os.ReadFile(path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| orig := strings.TrimSpace(string(b)) |
| if strings.Contains(orig, "lo:") && strings.Count(orig, ":") == 1 { |
| // This test expects there to be at least 1 more network interface |
| // in addition to the local network interface, so that it can tell |
| // that unshare worked. |
| t.Skip("not enough network interfaces to test unshare with") |
| } |
| |
| cmd := testenv.Command(t, "cat", path) |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Unshareflags: syscall.CLONE_NEWNET, |
| } |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| // CLONE_NEWNET does not appear to be supported. |
| t.Skipf("skipping due to permission error: %v", err) |
| } |
| t.Fatalf("Cmd failed with err %v, output: %s", err, out) |
| } |
| |
| // Check there is only the local network interface. |
| sout := strings.TrimSpace(string(out)) |
| if !strings.Contains(sout, "lo:") { |
| t.Fatalf("Expected lo network interface to exist, got %s", sout) |
| } |
| |
| origLines := strings.Split(orig, "\n") |
| lines := strings.Split(sout, "\n") |
| if len(lines) >= len(origLines) { |
| t.Logf("%s before unshare:\n%s", path, orig) |
| t.Logf("%s after unshare:\n%s", path, sout) |
| t.Fatalf("Got %d lines of output, want < %d", len(lines), len(origLines)) |
| } |
| } |
| |
| func TestGroupCleanup(t *testing.T) { |
| testenv.MustHaveExecPath(t, "id") |
| cmd := testenv.Command(t, "id") |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Credential: &syscall.Credential{ |
| Uid: 0, |
| Gid: 0, |
| }, |
| } |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: %v: %v", cmd, err) |
| } |
| t.Fatalf("Cmd failed with err %v, output: %s", err, out) |
| } |
| strOut := strings.TrimSpace(string(out)) |
| t.Logf("id: %s", strOut) |
| |
| expected := "uid=0(root) gid=0(root)" |
| // Just check prefix because some distros reportedly output a |
| // context parameter; see https://golang.org/issue/16224. |
| // Alpine does not output groups; see https://golang.org/issue/19938. |
| if !strings.HasPrefix(strOut, expected) { |
| t.Errorf("expected prefix: %q", expected) |
| } |
| } |
| |
| func TestGroupCleanupUserNamespace(t *testing.T) { |
| testenv.MustHaveExecPath(t, "id") |
| cmd := testenv.Command(t, "id") |
| uid, gid := os.Getuid(), os.Getgid() |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Cloneflags: syscall.CLONE_NEWUSER, |
| Credential: &syscall.Credential{ |
| Uid: uint32(uid), |
| Gid: uint32(gid), |
| }, |
| UidMappings: []syscall.SysProcIDMap{ |
| {ContainerID: 0, HostID: uid, Size: 1}, |
| }, |
| GidMappings: []syscall.SysProcIDMap{ |
| {ContainerID: 0, HostID: gid, Size: 1}, |
| }, |
| } |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: %v: %v", cmd, err) |
| } |
| t.Fatalf("Cmd failed with err %v, output: %s", err, out) |
| } |
| strOut := strings.TrimSpace(string(out)) |
| t.Logf("id: %s", strOut) |
| |
| // As in TestGroupCleanup, just check prefix. |
| // The actual groups and contexts seem to vary from one distro to the next. |
| expected := "uid=0(root) gid=0(root) groups=0(root)" |
| if !strings.HasPrefix(strOut, expected) { |
| t.Errorf("expected prefix: %q", expected) |
| } |
| } |
| |
| // Test for https://go.dev/issue/19661: unshare fails because systemd |
| // has forced / to be shared |
| func TestUnshareMountNameSpace(t *testing.T) { |
| const mountNotSupported = "mount is not supported: " // Output prefix indicating a test skip. |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| dir := flag.Args()[0] |
| err := syscall.Mount("none", dir, "proc", 0, "") |
| if testenv.SyscallIsNotSupported(err) { |
| fmt.Print(mountNotSupported, err) |
| } else if err != nil { |
| fmt.Fprintf(os.Stderr, "unshare: mount %s: %v\n", dir, err) |
| os.Exit(2) |
| } |
| os.Exit(0) |
| } |
| |
| testenv.MustHaveExec(t) |
| exe, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| d := t.TempDir() |
| t.Cleanup(func() { |
| // If the subprocess fails to unshare the parent directory, force-unmount it |
| // so that the test can clean it up. |
| if _, err := os.Stat(d); err == nil { |
| syscall.Unmount(d, syscall.MNT_FORCE) |
| } |
| }) |
| cmd := testenv.Command(t, exe, "-test.run=^TestUnshareMountNameSpace$", d) |
| cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS} |
| |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: could not start process with CLONE_NEWNS: %v", err) |
| } |
| t.Fatalf("unshare failed: %v\n%s", err, out) |
| } else if len(out) != 0 { |
| if bytes.HasPrefix(out, []byte(mountNotSupported)) { |
| t.Skipf("skipping: helper process reported %s", out) |
| } |
| t.Fatalf("unexpected output from helper process: %s", out) |
| } |
| |
| // How do we tell if the namespace was really unshared? It turns out |
| // to be simple: just try to remove the directory. If it's still mounted |
| // on the rm will fail with EBUSY. |
| if err := os.Remove(d); err != nil { |
| t.Errorf("rmdir failed on %v: %v", d, err) |
| } |
| } |
| |
| // Test for Issue 20103: unshare fails when chroot is used |
| func TestUnshareMountNameSpaceChroot(t *testing.T) { |
| const mountNotSupported = "mount is not supported: " // Output prefix indicating a test skip. |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| dir := flag.Args()[0] |
| err := syscall.Mount("none", dir, "proc", 0, "") |
| if testenv.SyscallIsNotSupported(err) { |
| fmt.Print(mountNotSupported, err) |
| } else if err != nil { |
| fmt.Fprintf(os.Stderr, "unshare: mount %s: %v\n", dir, err) |
| os.Exit(2) |
| } |
| os.Exit(0) |
| } |
| |
| d := t.TempDir() |
| |
| // Since we are doing a chroot, we need the binary there, |
| // and it must be statically linked. |
| testenv.MustHaveGoBuild(t) |
| if platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, false) { |
| t.Skipf("skipping: can't build static binary because %s/%s requires external linking", runtime.GOOS, runtime.GOARCH) |
| } |
| x := filepath.Join(d, "syscall.test") |
| t.Cleanup(func() { |
| // If the subprocess fails to unshare the parent directory, force-unmount it |
| // so that the test can clean it up. |
| if _, err := os.Stat(d); err == nil { |
| syscall.Unmount(d, syscall.MNT_FORCE) |
| } |
| }) |
| |
| cmd := testenv.Command(t, testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall") |
| cmd.Env = append(cmd.Environ(), "CGO_ENABLED=0") |
| if o, err := cmd.CombinedOutput(); err != nil { |
| t.Fatalf("%v: %v\n%s", cmd, err, o) |
| } |
| |
| cmd = testenv.Command(t, "/syscall.test", "-test.run=^TestUnshareMountNameSpaceChroot$", "/") |
| cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS} |
| |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: could not start process with CLONE_NEWNS and Chroot %q: %v", d, err) |
| } |
| t.Fatalf("unshare failed: %v\n%s", err, out) |
| } else if len(out) != 0 { |
| if bytes.HasPrefix(out, []byte(mountNotSupported)) { |
| t.Skipf("skipping: helper process reported %s", out) |
| } |
| t.Fatalf("unexpected output from helper process: %s", out) |
| } |
| |
| // How do we tell if the namespace was really unshared? It turns out |
| // to be simple: just try to remove the executable. If it's still mounted |
| // on, the rm will fail. |
| if err := os.Remove(x); err != nil { |
| t.Errorf("rm failed on %v: %v", x, err) |
| } |
| if err := os.Remove(d); err != nil { |
| t.Errorf("rmdir failed on %v: %v", d, err) |
| } |
| } |
| |
| // Test for Issue 29789: unshare fails when uid/gid mapping is specified |
| func TestUnshareUidGidMapping(t *testing.T) { |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| defer os.Exit(0) |
| if err := syscall.Chroot(os.TempDir()); err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(2) |
| } |
| } |
| |
| if os.Getuid() == 0 { |
| t.Skip("test exercises unprivileged user namespace, fails with privileges") |
| } |
| |
| testenv.MustHaveExec(t) |
| exe, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| cmd := testenv.Command(t, exe, "-test.run=^TestUnshareUidGidMapping$") |
| cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER, |
| GidMappingsEnableSetgroups: false, |
| UidMappings: []syscall.SysProcIDMap{ |
| { |
| ContainerID: 0, |
| HostID: syscall.Getuid(), |
| Size: 1, |
| }, |
| }, |
| GidMappings: []syscall.SysProcIDMap{ |
| { |
| ContainerID: 0, |
| HostID: syscall.Getgid(), |
| Size: 1, |
| }, |
| }, |
| } |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: could not start process with CLONE_NEWNS and CLONE_NEWUSER: %v", err) |
| } |
| t.Fatalf("Cmd failed with err %v, output: %s", err, out) |
| } |
| } |
| |
| func prepareCgroupFD(t *testing.T) (int, string) { |
| t.Helper() |
| |
| const O_PATH = 0x200000 // Same for all architectures, but for some reason not defined in syscall for 386||amd64. |
| |
| // Requires cgroup v2. |
| const prefix = "/sys/fs/cgroup" |
| selfCg, err := os.ReadFile("/proc/self/cgroup") |
| if err != nil { |
| if os.IsNotExist(err) || os.IsPermission(err) { |
| t.Skip(err) |
| } |
| t.Fatal(err) |
| } |
| |
| // Expect a single line like this: |
| // 0::/user.slice/user-1000.slice/user@1000.service/app.slice/vte-spawn-891992a2-efbb-4f28-aedb-b24f9e706770.scope |
| // Otherwise it's either cgroup v1 or a hybrid hierarchy. |
| if bytes.Count(selfCg, []byte("\n")) > 1 { |
| t.Skip("cgroup v2 not available") |
| } |
| cg := bytes.TrimPrefix(selfCg, []byte("0::")) |
| if len(cg) == len(selfCg) { // No prefix found. |
| t.Skipf("cgroup v2 not available (/proc/self/cgroup contents: %q)", selfCg) |
| } |
| |
| // Need an ability to create a sub-cgroup. |
| subCgroup, err := os.MkdirTemp(prefix+string(bytes.TrimSpace(cg)), "subcg-") |
| if err != nil { |
| // ErrPermission or EROFS (#57262) when running in an unprivileged container. |
| // ErrNotExist when cgroupfs is not mounted in chroot/schroot. |
| if os.IsNotExist(err) || testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: %v", err) |
| } |
| t.Fatal(err) |
| } |
| t.Cleanup(func() { syscall.Rmdir(subCgroup) }) |
| |
| cgroupFD, err := syscall.Open(subCgroup, O_PATH, 0) |
| if err != nil { |
| t.Fatal(&os.PathError{Op: "open", Path: subCgroup, Err: err}) |
| } |
| t.Cleanup(func() { syscall.Close(cgroupFD) }) |
| |
| return cgroupFD, "/" + path.Base(subCgroup) |
| } |
| |
| func TestUseCgroupFD(t *testing.T) { |
| testenv.MustHaveExec(t) |
| |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| // Read and print own cgroup path. |
| selfCg, err := os.ReadFile("/proc/self/cgroup") |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(2) |
| } |
| fmt.Print(string(selfCg)) |
| os.Exit(0) |
| } |
| |
| exe, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| fd, suffix := prepareCgroupFD(t) |
| |
| cmd := testenv.Command(t, exe, "-test.run=^TestUseCgroupFD$") |
| cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| UseCgroupFD: true, |
| CgroupFD: fd, |
| } |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) && !errors.Is(err, syscall.EINVAL) { |
| // Can be one of: |
| // - clone3 not supported (old kernel); |
| // - clone3 not allowed (by e.g. seccomp); |
| // - lack of CAP_SYS_ADMIN. |
| t.Skipf("clone3 with CLONE_INTO_CGROUP not available: %v", err) |
| } |
| t.Fatalf("Cmd failed with err %v, output: %s", err, out) |
| } |
| // NB: this wouldn't work with cgroupns. |
| if !bytes.HasSuffix(bytes.TrimSpace(out), []byte(suffix)) { |
| t.Fatalf("got: %q, want: a line that ends with %q", out, suffix) |
| } |
| } |
| |
| func TestCloneTimeNamespace(t *testing.T) { |
| testenv.MustHaveExec(t) |
| |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| timens, err := os.Readlink("/proc/self/ns/time") |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(2) |
| } |
| fmt.Print(string(timens)) |
| os.Exit(0) |
| } |
| |
| exe, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| cmd := testenv.Command(t, exe, "-test.run=^TestCloneTimeNamespace$") |
| cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Cloneflags: syscall.CLONE_NEWTIME, |
| } |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| // CLONE_NEWTIME does not appear to be supported. |
| t.Skipf("skipping, CLONE_NEWTIME not supported: %v", err) |
| } |
| t.Fatalf("Cmd failed with err %v, output: %s", err, out) |
| } |
| |
| // Inode number of the time namespaces should be different. |
| // Based on https://man7.org/linux/man-pages/man7/time_namespaces.7.html#EXAMPLES |
| timens, err := os.Readlink("/proc/self/ns/time") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| parentTimeNS := timens |
| childTimeNS := string(out) |
| if childTimeNS == parentTimeNS { |
| t.Fatalf("expected child time namespace to be different from parent time namespace: %s", parentTimeNS) |
| } |
| } |
| |
| func testPidFD(t *testing.T, userns bool) error { |
| testenv.MustHaveExec(t) |
| |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| // Child: wait for a signal. |
| time.Sleep(time.Hour) |
| } |
| |
| exe, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var pidfd int |
| cmd := testenv.Command(t, exe, "-test.run=^TestPidFD$") |
| cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| PidFD: &pidfd, |
| } |
| if userns { |
| cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER |
| } |
| if err := cmd.Start(); err != nil { |
| return err |
| } |
| defer func() { |
| cmd.Process.Kill() |
| cmd.Wait() |
| }() |
| t.Log("got pidfd:", pidfd) |
| // If pidfd is not supported by the kernel, -1 is returned. |
| if pidfd == -1 { |
| t.Skip("pidfd not supported") |
| } |
| defer syscall.Close(pidfd) |
| |
| // Use pidfd to send a signal to the child. |
| sig := syscall.SIGINT |
| if err := unix.PidFDSendSignal(uintptr(pidfd), sig); err != nil { |
| if err != syscall.EINVAL && testenv.SyscallIsNotSupported(err) { |
| t.Skip("pidfd_send_signal syscall not supported:", err) |
| } |
| t.Fatal("pidfd_send_signal syscall failed:", err) |
| } |
| // Check if the child received our signal. |
| err = cmd.Wait() |
| if cmd.ProcessState == nil || cmd.ProcessState.Sys().(syscall.WaitStatus).Signal() != sig { |
| t.Fatal("unexpected child error:", err) |
| } |
| return nil |
| } |
| |
| func TestPidFD(t *testing.T) { |
| if err := testPidFD(t, false); err != nil { |
| t.Fatal("can't start a process:", err) |
| } |
| } |
| |
| func TestPidFDWithUserNS(t *testing.T) { |
| if err := testPidFD(t, true); err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skip("userns not supported:", err) |
| } |
| t.Fatal("can't start a process:", err) |
| } |
| } |
| |
| func TestPidFDClone3(t *testing.T) { |
| *syscall.ForceClone3 = true |
| defer func() { *syscall.ForceClone3 = false }() |
| |
| if err := testPidFD(t, false); err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skip("clone3 not supported:", err) |
| } |
| t.Fatal("can't start a process:", err) |
| } |
| } |
| |
| type capHeader struct { |
| version uint32 |
| pid int32 |
| } |
| |
| type capData struct { |
| effective uint32 |
| permitted uint32 |
| inheritable uint32 |
| } |
| |
| const CAP_SYS_TIME = 25 |
| const CAP_SYSLOG = 34 |
| |
| type caps struct { |
| hdr capHeader |
| data [2]capData |
| } |
| |
| func getCaps() (caps, error) { |
| var c caps |
| |
| // Get capability version |
| if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 { |
| return c, fmt.Errorf("SYS_CAPGET: %v", errno) |
| } |
| |
| // Get current capabilities |
| if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 { |
| return c, fmt.Errorf("SYS_CAPGET: %v", errno) |
| } |
| |
| return c, nil |
| } |
| |
| func TestAmbientCaps(t *testing.T) { |
| testAmbientCaps(t, false) |
| } |
| |
| func TestAmbientCapsUserns(t *testing.T) { |
| testAmbientCaps(t, true) |
| } |
| |
| func testAmbientCaps(t *testing.T, userns bool) { |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| caps, err := getCaps() |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(2) |
| } |
| if caps.data[0].effective&(1<<uint(CAP_SYS_TIME)) == 0 { |
| fmt.Fprintln(os.Stderr, "CAP_SYS_TIME unexpectedly not in the effective capability mask") |
| os.Exit(2) |
| } |
| if caps.data[1].effective&(1<<uint(CAP_SYSLOG&31)) == 0 { |
| fmt.Fprintln(os.Stderr, "CAP_SYSLOG unexpectedly not in the effective capability mask") |
| os.Exit(2) |
| } |
| os.Exit(0) |
| } |
| |
| // skip on android, due to lack of lookup support |
| if runtime.GOOS == "android" { |
| t.Skip("skipping test on android; see Issue 27327") |
| } |
| |
| u, err := user.Lookup("nobody") |
| if err != nil { |
| t.Fatal(err) |
| } |
| uid, err := strconv.ParseInt(u.Uid, 0, 32) |
| if err != nil { |
| t.Fatal(err) |
| } |
| gid, err := strconv.ParseInt(u.Gid, 0, 32) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Copy the test binary to a temporary location which is readable by nobody. |
| f, err := os.CreateTemp("", "gotest") |
| if err != nil { |
| t.Fatal(err) |
| } |
| t.Cleanup(func() { |
| f.Close() |
| os.Remove(f.Name()) |
| }) |
| |
| testenv.MustHaveExec(t) |
| exe, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| e, err := os.Open(exe) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer e.Close() |
| if _, err := io.Copy(f, e); err != nil { |
| t.Fatal(err) |
| } |
| if err := f.Chmod(0755); err != nil { |
| t.Fatal(err) |
| } |
| if err := f.Close(); err != nil { |
| t.Fatal(err) |
| } |
| |
| cmd := testenv.Command(t, f.Name(), "-test.run=^"+t.Name()+"$") |
| cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") |
| cmd.Stdout = os.Stdout |
| cmd.Stderr = os.Stderr |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Credential: &syscall.Credential{ |
| Uid: uint32(uid), |
| Gid: uint32(gid), |
| }, |
| AmbientCaps: []uintptr{CAP_SYS_TIME, CAP_SYSLOG}, |
| } |
| if userns { |
| cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER |
| const nobody = 65534 |
| uid := os.Getuid() |
| gid := os.Getgid() |
| cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ |
| ContainerID: int(nobody), |
| HostID: uid, |
| Size: int(1), |
| }} |
| cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ |
| ContainerID: int(nobody), |
| HostID: gid, |
| Size: int(1), |
| }} |
| |
| // Set credentials to run as user and group nobody. |
| cmd.SysProcAttr.Credential = &syscall.Credential{ |
| Uid: nobody, |
| Gid: nobody, |
| } |
| } |
| if err := cmd.Run(); err != nil { |
| if testenv.SyscallIsNotSupported(err) { |
| t.Skipf("skipping: %v: %v", cmd, err) |
| } |
| t.Fatal(err.Error()) |
| } |
| } |