mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
syscall: avoid making assumptions about syscall permissions
We currently check for at least three different permission bits before running tests that require root permissions: we look for UID 0, lack of an LXC container, and lack of a Docker container, and probe a number of distro-specific files in /proc and /sys. The sheer number of these checks suggests that we have probably missed at least one. Per Alan J. Perlis, “If you have a procedure with ten parameters, you probably missed some.” (And, indeed, we definitely have: a Debian patch¹ adds one more environment check!) CL 58170 added some of these container checks, but “decided to go this way instead of just skipping os.IsPermission errors because many of those tests were specifically written to check false positive permission errors.” However, we can't in general distinguish between a false-positive error and a real one caused by a container: if one is making a change to the syscall package, they should run the tests with -v and check for unexpected skips. Notably: - TestUnshare already skips itself if the command fails with an error ending in the string "operation not permitted", which could be caused by a variety of possible bugs. - The Unshare tests added in CL 38471 will fail with a permission error if CLONE_NEWNS is not supported, but it seems to me that if CLONE_NEWNS is supported — sufficient to start the process! — then Unmount must also be supported, and the test can at least check that the two are consistent. - The AmbientCaps tests should fail to start the subprocess with EINVAL or similar (not produce bogus output) if the kernel does not support ambient caps for any reason, which we can then detect. (If the subprocess fails in the way the test is concerned about, it will exit with status 2, not fail to start in the first place.) By executing the system calls and checking for permission errors, this change exposed an existing bug for AmbientCaps (filed as #57208), which was detected by the linux-arm-aws builder. For #57208. Updates #21379. Updates #14693. ¹https://sources.debian.org/patches/golang-1.19/1.19.3-1/0006-skip-userns-test-in-schroot-as-well.patch/ Change-Id: I9b167661fa1bb823168c8b50d8bbbf9643e49f76 Reviewed-on: https://go-review.googlesource.com/c/go/+/456375 Reviewed-by: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Alexander Morozov <lk4d4math@gmail.com> Run-TryBot: Bryan Mills <bcmills@google.com>
This commit is contained in:
parent
c8c646d31b
commit
092671423c
1 changed files with 204 additions and 298 deletions
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
|
@ -26,102 +27,45 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isDocker() bool {
|
// isNotSupported reports whether err may indicate that a system call is
|
||||||
_, err := os.Stat("/.dockerenv")
|
// not supported by the current platform or execution environment.
|
||||||
return err == nil
|
func isNotSupported(err error) bool {
|
||||||
}
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func isLXC() bool {
|
var errno syscall.Errno
|
||||||
return os.Getenv("container") == "lxc"
|
if errors.As(err, &errno) {
|
||||||
}
|
switch errno {
|
||||||
|
case syscall.ENOSYS, syscall.ENOTSUP:
|
||||||
func skipInContainer(t *testing.T) {
|
// Explicitly not supported.
|
||||||
// TODO: the callers of this func are using this func to skip
|
return true
|
||||||
// tests when running as some sort of "fake root" that's uid 0
|
case syscall.EPERM, syscall.EROFS:
|
||||||
// but lacks certain Linux capabilities. Most of the Go builds
|
// User lacks permission: either the call requires root permission and the
|
||||||
// run in privileged containers, though, where root is much
|
// user is not root, or the call is denied by a container security policy.
|
||||||
// closer (if not identical) to the real root. We should test
|
return true
|
||||||
// for what we need exactly (which capabilities are active?),
|
case syscall.EINVAL:
|
||||||
// instead of just assuming "docker == bad". Then we'd get more test
|
// Some containers return EINVAL instead of EPERM if a system call is
|
||||||
// coverage on a bunch of builders too.
|
// denied by security policy.
|
||||||
if isDocker() {
|
return true
|
||||||
t.Skip("skip this test in Docker container")
|
|
||||||
}
|
|
||||||
if isLXC() {
|
|
||||||
t.Skip("skip this test in LXC container")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func skipNoUserNamespaces(t *testing.T) {
|
|
||||||
if _, err := os.Stat("/proc/self/ns/user"); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
t.Skip("kernel doesn't support user namespaces")
|
|
||||||
}
|
|
||||||
if os.IsPermission(err) {
|
|
||||||
t.Skip("unable to test user namespaces due to permissions")
|
|
||||||
}
|
|
||||||
t.Fatalf("Failed to stat /proc/self/ns/user: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func skipUnprivilegedUserClone(t *testing.T) {
|
|
||||||
// Skip the test if the sysctl that prevents unprivileged user
|
|
||||||
// from creating user namespaces is enabled.
|
|
||||||
data, errRead := os.ReadFile("/proc/sys/kernel/unprivileged_userns_clone")
|
|
||||||
if os.IsNotExist(errRead) {
|
|
||||||
// This file is only available in some Debian/Ubuntu kernels.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errRead != nil || len(data) < 1 || data[0] == '0' {
|
|
||||||
t.Skip("kernel prohibits user namespace in unprivileged process")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are in a chroot by checking if the inode of / is
|
|
||||||
// different from 2 (there is no better test available to non-root on
|
|
||||||
// linux).
|
|
||||||
func isChrooted(t *testing.T) bool {
|
|
||||||
root, err := os.Stat("/")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("cannot stat /: %v", err)
|
|
||||||
}
|
|
||||||
return root.Sys().(*syscall.Stat_t).Ino != 2
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkUserNS(t *testing.T) {
|
|
||||||
skipInContainer(t)
|
|
||||||
skipNoUserNamespaces(t)
|
|
||||||
if isChrooted(t) {
|
|
||||||
// create_user_ns in the kernel (see
|
|
||||||
// https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/kernel/user_namespace.c)
|
|
||||||
// forbids the creation of user namespaces when chrooted.
|
|
||||||
t.Skip("cannot create user namespaces when chrooted")
|
|
||||||
}
|
|
||||||
// On some systems, there is a sysctl setting.
|
|
||||||
if os.Getuid() != 0 {
|
|
||||||
skipUnprivilegedUserClone(t)
|
|
||||||
}
|
|
||||||
// On Centos 7 make sure they set the kernel parameter user_namespace=1
|
|
||||||
// See issue 16283 and 20796.
|
|
||||||
if _, err := os.Stat("/sys/module/user_namespace/parameters/enable"); err == nil {
|
|
||||||
buf, _ := os.ReadFile("/sys/module/user_namespace/parameters/enabled")
|
|
||||||
if !strings.HasPrefix(string(buf), "Y") {
|
|
||||||
t.Skip("kernel doesn't support user namespaces")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On Centos 7.5+, user namespaces are disabled if user.max_user_namespaces = 0
|
if errors.Is(err, fs.ErrPermission) {
|
||||||
if _, err := os.Stat("/proc/sys/user/max_user_namespaces"); err == nil {
|
return true
|
||||||
buf, errRead := os.ReadFile("/proc/sys/user/max_user_namespaces")
|
|
||||||
if errRead == nil && buf[0] == '0' {
|
|
||||||
t.Skip("kernel doesn't support user namespaces")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(#41198): Also return true if errors.Is(err, errors.ErrUnsupported).
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func whoamiCmd(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd {
|
// whoamiNEWUSER returns a command that runs "whoami" with CLONE_NEWUSER,
|
||||||
checkUserNS(t)
|
// mapping uid and gid 0 to the actual uid and gid of the test.
|
||||||
cmd := exec.Command("whoami")
|
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{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Cloneflags: syscall.CLONE_NEWUSER,
|
Cloneflags: syscall.CLONE_NEWUSER,
|
||||||
UidMappings: []syscall.SysProcIDMap{
|
UidMappings: []syscall.SysProcIDMap{
|
||||||
|
|
@ -135,70 +79,60 @@ func whoamiCmd(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNEWUSERRemap(t *testing.T, uid, gid int, setgroups bool) {
|
func TestCloneNEWUSERAndRemap(t *testing.T) {
|
||||||
cmd := whoamiCmd(t, uid, gid, setgroups)
|
for _, setgroups := range []bool{false, true} {
|
||||||
out, err := cmd.CombinedOutput()
|
setgroups := setgroups
|
||||||
if err != nil {
|
t.Run(fmt.Sprintf("setgroups=%v", setgroups), func(t *testing.T) {
|
||||||
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
uid := os.Getuid()
|
||||||
}
|
gid := os.Getgid()
|
||||||
sout := strings.TrimSpace(string(out))
|
|
||||||
want := "root"
|
|
||||||
if sout != want {
|
|
||||||
t.Fatalf("whoami = %q; want %q", out, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloneNEWUSERAndRemapRootDisableSetgroups(t *testing.T) {
|
cmd := whoamiNEWUSER(t, uid, gid, setgroups)
|
||||||
if os.Getuid() != 0 {
|
out, err := cmd.CombinedOutput()
|
||||||
t.Skip("skipping root only test")
|
t.Logf("%v: %v", cmd, err)
|
||||||
}
|
|
||||||
testNEWUSERRemap(t, 0, 0, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloneNEWUSERAndRemapRootEnableSetgroups(t *testing.T) {
|
if uid != 0 && setgroups {
|
||||||
if os.Getuid() != 0 {
|
t.Logf("as non-root, expected permission error due to unprivileged gid_map")
|
||||||
t.Skip("skipping root only test")
|
if !os.IsPermission(err) {
|
||||||
}
|
if err == nil {
|
||||||
testNEWUSERRemap(t, 0, 0, true)
|
t.Skipf("unexpected success: probably old kernel without security fix?")
|
||||||
}
|
}
|
||||||
|
if isNotSupported(err) {
|
||||||
|
t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported")
|
||||||
|
}
|
||||||
|
t.Fatalf("got non-permission error") // Already logged above.
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func TestCloneNEWUSERAndRemapNoRootDisableSetgroups(t *testing.T) {
|
if err != nil {
|
||||||
if os.Getuid() == 0 {
|
if isNotSupported(err) {
|
||||||
t.Skip("skipping unprivileged user only test")
|
// May be inside a container that disallows CLONE_NEWUSER.
|
||||||
}
|
t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported")
|
||||||
testNEWUSERRemap(t, os.Getuid(), os.Getgid(), false)
|
}
|
||||||
}
|
t.Fatalf("unexpected command failure; output:\n%s", out)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCloneNEWUSERAndRemapNoRootSetgroupsEnableSetgroups(t *testing.T) {
|
sout := strings.TrimSpace(string(out))
|
||||||
if os.Getuid() == 0 {
|
want := "root"
|
||||||
t.Skip("skipping unprivileged user only test")
|
if sout != want {
|
||||||
}
|
t.Fatalf("whoami = %q; want %q", out, want)
|
||||||
cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), true)
|
}
|
||||||
err := cmd.Run()
|
})
|
||||||
if err == nil {
|
|
||||||
t.Skip("probably old kernel without security fix")
|
|
||||||
}
|
|
||||||
if !os.IsPermission(err) {
|
|
||||||
t.Fatalf("Unprivileged gid_map rewriting with GidMappingsEnableSetgroups must fail")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyCredGroupsDisableSetgroups(t *testing.T) {
|
func TestEmptyCredGroupsDisableSetgroups(t *testing.T) {
|
||||||
cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), false)
|
cmd := whoamiNEWUSER(t, os.Getuid(), os.Getgid(), false)
|
||||||
cmd.SysProcAttr.Credential = &syscall.Credential{}
|
cmd.SysProcAttr.Credential = &syscall.Credential{}
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
|
if isNotSupported(err) {
|
||||||
|
t.Skipf("skipping: %v: %v", cmd, err)
|
||||||
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnshare(t *testing.T) {
|
func TestUnshare(t *testing.T) {
|
||||||
skipInContainer(t)
|
|
||||||
// Make sure we are running as root so we have permissions to use unshare
|
|
||||||
// and create a network namespace.
|
|
||||||
if os.Getuid() != 0 {
|
|
||||||
t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
|
|
||||||
}
|
|
||||||
|
|
||||||
path := "/proc/net/dev"
|
path := "/proc/net/dev"
|
||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
|
@ -209,12 +143,6 @@ func TestUnshare(t *testing.T) {
|
||||||
}
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat("/proc/self/ns/net"); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
t.Skip("kernel doesn't support net namespace")
|
|
||||||
}
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
orig, err := os.ReadFile(path)
|
orig, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -222,17 +150,15 @@ func TestUnshare(t *testing.T) {
|
||||||
}
|
}
|
||||||
origLines := strings.Split(strings.TrimSpace(string(orig)), "\n")
|
origLines := strings.Split(strings.TrimSpace(string(orig)), "\n")
|
||||||
|
|
||||||
cmd := exec.Command("cat", path)
|
cmd := testenv.Command(t, "cat", path)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Unshareflags: syscall.CLONE_NEWNET,
|
Unshareflags: syscall.CLONE_NEWNET,
|
||||||
}
|
}
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "operation not permitted") {
|
if isNotSupported(err) {
|
||||||
// Issue 17206: despite all the checks above,
|
// CLONE_NEWNET does not appear to be supported.
|
||||||
// this still reportedly fails for some users.
|
t.Skipf("skipping due to permission error: %v", err)
|
||||||
// (older kernels?). Just skip.
|
|
||||||
t.Skip("skipping due to permission error")
|
|
||||||
}
|
}
|
||||||
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
|
|
@ -250,10 +176,8 @@ func TestUnshare(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupCleanup(t *testing.T) {
|
func TestGroupCleanup(t *testing.T) {
|
||||||
if os.Getuid() != 0 {
|
testenv.MustHaveExecPath(t, "id")
|
||||||
t.Skip("we need root for credential")
|
cmd := testenv.Command(t, "id")
|
||||||
}
|
|
||||||
cmd := exec.Command("id")
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Credential: &syscall.Credential{
|
Credential: &syscall.Credential{
|
||||||
Uid: 0,
|
Uid: 0,
|
||||||
|
|
@ -262,6 +186,9 @@ func TestGroupCleanup(t *testing.T) {
|
||||||
}
|
}
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isNotSupported(err) {
|
||||||
|
t.Skipf("skipping: %v: %v", cmd, err)
|
||||||
|
}
|
||||||
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
strOut := strings.TrimSpace(string(out))
|
strOut := strings.TrimSpace(string(out))
|
||||||
|
|
@ -277,11 +204,8 @@ func TestGroupCleanup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupCleanupUserNamespace(t *testing.T) {
|
func TestGroupCleanupUserNamespace(t *testing.T) {
|
||||||
if os.Getuid() != 0 {
|
testenv.MustHaveExecPath(t, "id")
|
||||||
t.Skip("we need root for credential")
|
cmd := testenv.Command(t, "id")
|
||||||
}
|
|
||||||
checkUserNS(t)
|
|
||||||
cmd := exec.Command("id")
|
|
||||||
uid, gid := os.Getuid(), os.Getgid()
|
uid, gid := os.Getuid(), os.Getgid()
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Cloneflags: syscall.CLONE_NEWUSER,
|
Cloneflags: syscall.CLONE_NEWUSER,
|
||||||
|
|
@ -298,6 +222,9 @@ func TestGroupCleanupUserNamespace(t *testing.T) {
|
||||||
}
|
}
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isNotSupported(err) {
|
||||||
|
t.Skipf("skipping: %v: %v", cmd, err)
|
||||||
|
}
|
||||||
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
strOut := strings.TrimSpace(string(out))
|
strOut := strings.TrimSpace(string(out))
|
||||||
|
|
@ -311,135 +238,131 @@ func TestGroupCleanupUserNamespace(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestUnshareHelperProcess isn't a real test. It's used as a helper process
|
// Test for https://go.dev/issue/19661: unshare fails because systemd
|
||||||
// for TestUnshareMountNameSpace.
|
// has forced / to be shared
|
||||||
func TestUnshareMountNameSpaceHelper(*testing.T) {
|
|
||||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Exit(0)
|
|
||||||
if err := syscall.Mount("none", flag.Args()[0], "proc", 0, ""); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %v", os.Args, err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for Issue 38471: unshare fails because systemd has forced / to be shared
|
|
||||||
func TestUnshareMountNameSpace(t *testing.T) {
|
func TestUnshareMountNameSpace(t *testing.T) {
|
||||||
skipInContainer(t)
|
testenv.MustHaveExec(t)
|
||||||
// Make sure we are running as root so we have permissions to use unshare
|
|
||||||
// and create a network namespace.
|
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
|
||||||
if os.Getuid() != 0 {
|
dir := flag.Args()[0]
|
||||||
t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
|
err := syscall.Mount("none", dir, "proc", 0, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %#v", dir, err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := os.MkdirTemp("", "unshare")
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("tempdir: %v", err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0], "-test.run=TestUnshareMountNameSpaceHelper", d)
|
d := t.TempDir()
|
||||||
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
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}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS}
|
||||||
|
|
||||||
o, err := cmd.CombinedOutput()
|
o, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), ": permission denied") {
|
if isNotSupported(err) {
|
||||||
t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err)
|
t.Skipf("skipping: could not start process with CLONE_NEWNS: %v", err)
|
||||||
}
|
}
|
||||||
t.Fatalf("unshare failed: %s, %v", o, err)
|
t.Fatalf("unshare failed: %v\n%s", err, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// How do we tell if the namespace was really unshared? It turns 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
|
// to be simple: just try to remove the directory. If it's still mounted
|
||||||
// on the rm will fail with EBUSY. Then we have some cleanup to do:
|
// on the rm will fail with EBUSY.
|
||||||
// we must unmount it, then try to remove it again.
|
|
||||||
|
|
||||||
if err := os.Remove(d); err != nil {
|
if err := os.Remove(d); err != nil {
|
||||||
t.Errorf("rmdir failed on %v: %v", d, err)
|
t.Errorf("rmdir failed on %v: %v", d, err)
|
||||||
if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil {
|
|
||||||
t.Errorf("Can't unmount %v: %v", d, err)
|
|
||||||
}
|
|
||||||
if err := os.Remove(d); err != nil {
|
|
||||||
t.Errorf("rmdir after unmount failed on %v: %v", d, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for Issue 20103: unshare fails when chroot is used
|
// Test for Issue 20103: unshare fails when chroot is used
|
||||||
func TestUnshareMountNameSpaceChroot(t *testing.T) {
|
func TestUnshareMountNameSpaceChroot(t *testing.T) {
|
||||||
skipInContainer(t)
|
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
|
||||||
// Make sure we are running as root so we have permissions to use unshare
|
dir := flag.Args()[0]
|
||||||
// and create a network namespace.
|
err := syscall.Mount("none", dir, "proc", 0, "")
|
||||||
if os.Getuid() != 0 {
|
if err != nil {
|
||||||
t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
|
fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %#v", dir, err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := os.MkdirTemp("", "unshare")
|
d := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("tempdir: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we are doing a chroot, we need the binary there,
|
// Since we are doing a chroot, we need the binary there,
|
||||||
// and it must be statically linked.
|
// and it must be statically linked.
|
||||||
|
testenv.MustHaveGoBuild(t)
|
||||||
x := filepath.Join(d, "syscall.test")
|
x := filepath.Join(d, "syscall.test")
|
||||||
cmd := exec.Command(testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall")
|
t.Cleanup(func() {
|
||||||
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
|
// 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 {
|
if o, err := cmd.CombinedOutput(); err != nil {
|
||||||
t.Fatalf("Build of syscall in chroot failed, output %v, err %v", o, err)
|
t.Fatalf("Build of syscall in chroot failed, output %v, err %v", o, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command("/syscall.test", "-test.run=TestUnshareMountNameSpaceHelper", "/")
|
cmd = testenv.Command(t, "/syscall.test", "-test.run=TestUnshareMountNameSpaceChroot", "/")
|
||||||
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS}
|
||||||
|
|
||||||
o, err := cmd.CombinedOutput()
|
o, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), ": permission denied") {
|
if isNotSupported(err) {
|
||||||
t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err)
|
t.Skipf("skipping: could not start process with CLONE_NEWNS and Chroot %q: %v", d, err)
|
||||||
}
|
}
|
||||||
t.Fatalf("unshare failed: %s, %v", o, err)
|
t.Fatalf("unshare failed: %v\n%s", err, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// How do we tell if the namespace was really unshared? It turns 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
|
// to be simple: just try to remove the executable. If it's still mounted
|
||||||
// on, the rm will fail. Then we have some cleanup to do:
|
// on, the rm will fail.
|
||||||
// we must force unmount it, then try to remove it again.
|
|
||||||
|
|
||||||
if err := os.Remove(x); err != nil {
|
if err := os.Remove(x); err != nil {
|
||||||
t.Errorf("rm failed on %v: %v", x, err)
|
t.Errorf("rm failed on %v: %v", x, err)
|
||||||
if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil {
|
|
||||||
t.Fatalf("Can't unmount %v: %v", d, err)
|
|
||||||
}
|
|
||||||
if err := os.Remove(x); err != nil {
|
|
||||||
t.Fatalf("rm failed on %v: %v", x, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Remove(d); err != nil {
|
if err := os.Remove(d); err != nil {
|
||||||
t.Errorf("rmdir failed on %v: %v", d, err)
|
t.Errorf("rmdir failed on %v: %v", d, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnshareUidGidMappingHelper(*testing.T) {
|
|
||||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Exit(0)
|
|
||||||
if err := syscall.Chroot(os.TempDir()); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for Issue 29789: unshare fails when uid/gid mapping is specified
|
// Test for Issue 29789: unshare fails when uid/gid mapping is specified
|
||||||
func TestUnshareUidGidMapping(t *testing.T) {
|
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 {
|
if os.Getuid() == 0 {
|
||||||
t.Skip("test exercises unprivileged user namespace, fails with privileges")
|
t.Skip("test exercises unprivileged user namespace, fails with privileges")
|
||||||
}
|
}
|
||||||
checkUserNS(t)
|
|
||||||
cmd := exec.Command(os.Args[0], "-test.run=TestUnshareUidGidMappingHelper")
|
testenv.MustHaveExec(t)
|
||||||
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
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{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
|
Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
|
||||||
GidMappingsEnableSetgroups: false,
|
GidMappingsEnableSetgroups: false,
|
||||||
|
|
@ -460,6 +383,9 @@ func TestUnshareUidGidMapping(t *testing.T) {
|
||||||
}
|
}
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isNotSupported(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)
|
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -497,8 +423,7 @@ func prepareCgroupFD(t *testing.T) (int, string) {
|
||||||
CgroupFD: -1,
|
CgroupFD: -1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// // EPERM can be returned if clone3 is not enabled by seccomp.
|
if isNotSupported(err) {
|
||||||
if err == syscall.ENOSYS || err == syscall.EPERM {
|
|
||||||
t.Skipf("clone3 with CLONE_INTO_CGROUP not available: %v", err)
|
t.Skipf("clone3 with CLONE_INTO_CGROUP not available: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -507,8 +432,8 @@ func prepareCgroupFD(t *testing.T) (int, string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// ErrPermission or EROFS (#57262) when running in an unprivileged container.
|
// ErrPermission or EROFS (#57262) when running in an unprivileged container.
|
||||||
// ErrNotExist when cgroupfs is not mounted in chroot/schroot.
|
// ErrNotExist when cgroupfs is not mounted in chroot/schroot.
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) || errors.Is(err, syscall.EROFS) {
|
if os.IsNotExist(err) || isNotSupported(err) {
|
||||||
t.Skip(err)
|
t.Skipf("skipping: %v", err)
|
||||||
}
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -524,10 +449,16 @@ func prepareCgroupFD(t *testing.T) (int, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUseCgroupFD(t *testing.T) {
|
func TestUseCgroupFD(t *testing.T) {
|
||||||
|
testenv.MustHaveExec(t)
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
fd, suffix := prepareCgroupFD(t)
|
fd, suffix := prepareCgroupFD(t)
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0], "-test.run=TestUseCgroupFDHelper")
|
cmd := testenv.Command(t, exe, "-test.run=TestUseCgroupFDHelper")
|
||||||
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
UseCgroupFD: true,
|
UseCgroupFD: true,
|
||||||
CgroupFD: fd,
|
CgroupFD: fd,
|
||||||
|
|
@ -591,68 +522,31 @@ func getCaps() (caps, error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustSupportAmbientCaps(t *testing.T) {
|
|
||||||
var uname syscall.Utsname
|
|
||||||
if err := syscall.Uname(&uname); err != nil {
|
|
||||||
t.Fatalf("Uname: %v", err)
|
|
||||||
}
|
|
||||||
var buf [65]byte
|
|
||||||
for i, b := range uname.Release {
|
|
||||||
buf[i] = byte(b)
|
|
||||||
}
|
|
||||||
ver := string(buf[:])
|
|
||||||
ver, _, _ = strings.Cut(ver, "\x00")
|
|
||||||
if strings.HasPrefix(ver, "2.") ||
|
|
||||||
strings.HasPrefix(ver, "3.") ||
|
|
||||||
strings.HasPrefix(ver, "4.1.") ||
|
|
||||||
strings.HasPrefix(ver, "4.2.") {
|
|
||||||
t.Skipf("kernel version %q predates required 4.3; skipping test", ver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAmbientCapsHelper isn't a real test. It's used as a helper process for
|
|
||||||
// TestAmbientCaps.
|
|
||||||
func TestAmbientCapsHelper(*testing.T) {
|
|
||||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Exit(0)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAmbientCaps(t *testing.T) {
|
func TestAmbientCaps(t *testing.T) {
|
||||||
// Make sure we are running as root so we have permissions to use unshare
|
|
||||||
// and create a network namespace.
|
|
||||||
if os.Getuid() != 0 {
|
|
||||||
t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
|
|
||||||
}
|
|
||||||
|
|
||||||
testAmbientCaps(t, false)
|
testAmbientCaps(t, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAmbientCapsUserns(t *testing.T) {
|
func TestAmbientCapsUserns(t *testing.T) {
|
||||||
checkUserNS(t)
|
|
||||||
testAmbientCaps(t, true)
|
testAmbientCaps(t, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAmbientCaps(t *testing.T, userns bool) {
|
func testAmbientCaps(t *testing.T, userns bool) {
|
||||||
skipInContainer(t)
|
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
|
||||||
mustSupportAmbientCaps(t)
|
caps, err := getCaps()
|
||||||
|
if err != nil {
|
||||||
skipUnprivilegedUserClone(t)
|
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
|
// skip on android, due to lack of lookup support
|
||||||
if runtime.GOOS == "android" {
|
if runtime.GOOS == "android" {
|
||||||
|
|
@ -677,9 +571,18 @@ func testAmbientCaps(t *testing.T, userns bool) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.Remove(f.Name())
|
t.Cleanup(func() {
|
||||||
defer f.Close()
|
f.Close()
|
||||||
e, err := os.Open(os.Args[0])
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -694,8 +597,8 @@ func testAmbientCaps(t *testing.T, userns bool) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(f.Name(), "-test.run=TestAmbientCapsHelper")
|
cmd := testenv.Command(t, f.Name(), "-test.run="+t.Name())
|
||||||
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
|
@ -728,6 +631,9 @@ func testAmbientCaps(t *testing.T, userns bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
|
if isNotSupported(err) {
|
||||||
|
t.Skipf("skipping: %v: %v", cmd, err)
|
||||||
|
}
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue