aboutsummaryrefslogtreecommitdiff
path: root/src/syscall/exec_linux_test.go
diff options
context:
space:
mode:
authorRonald G. Minnich <rminnich@gmail.com>2017-03-22 14:40:55 -0700
committerIan Lance Taylor <iant@golang.org>2017-03-23 17:53:18 +0000
commitd8ed449d8eae5b39ffe227ef7f56785e978dd5e2 (patch)
treed58d458c481787b97a63a62ee676442beb9f7667 /src/syscall/exec_linux_test.go
parent9ecfd177cfe9783919175780fe8f29a0e4a99f4e (diff)
downloadgo-d8ed449d8eae5b39ffe227ef7f56785e978dd5e2.tar.gz
go-d8ed449d8eae5b39ffe227ef7f56785e978dd5e2.zip
os/exec: handle Unshareflags with CLONE_NEWNS
In some newer Linux distros, systemd forces all mount namespaces to be shared, starting at /. This disables the CLONE_NEWNS flag in unshare(2) and clone(2). While this problem is most commonly seen on systems with systemd, it can happen anywhere, due to how Linux namespaces now work. Hence, to create a private mount namespace, it is not sufficient to just set CLONE_NEWS; you have to call mount(2) to change the behavior of namespaces, i.e. mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) This is tested and working and we can now correctly start child process with private namespaces on Linux distros that use systemd. The new test works correctly on Ubuntu 16.04.2 LTS. It fails if I comment out the new Mount, and succeeds otherwise. In each case it correctly cleans up after itself. Fixes #19661 Change-Id: I52240b59628e3772b529d9bbef7166606b0c157d Reviewed-on: https://go-review.googlesource.com/38471 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/syscall/exec_linux_test.go')
-rw-r--r--src/syscall/exec_linux_test.go62
1 files changed, 62 insertions, 0 deletions
diff --git a/src/syscall/exec_linux_test.go b/src/syscall/exec_linux_test.go
index 7a4b571760..ed44ddf7f3 100644
--- a/src/syscall/exec_linux_test.go
+++ b/src/syscall/exec_linux_test.go
@@ -7,6 +7,8 @@
package syscall_test
import (
+ "flag"
+ "fmt"
"io/ioutil"
"os"
"os/exec"
@@ -253,3 +255,63 @@ func TestGroupCleanupUserNamespace(t *testing.T) {
}
t.Errorf("id command output: %q, expected one of %q", strOut, expected)
}
+
+// TestUnshareHelperProcess isn't a real test. It's used as a helper process
+// for TestUnshareMountNameSpace.
+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) {
+ // 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")
+ }
+
+ // When running under the Go continuous build, skip tests for
+ // now when under Kubernetes. (where things are root but not quite)
+ // Both of these are our own environment variables.
+ // See Issue 12815.
+ if os.Getenv("GO_BUILDER_NAME") != "" && os.Getenv("IN_KUBERNETES") == "1" {
+ t.Skip("skipping test on Kubernetes-based builders; see Issue 12815")
+ }
+
+ d, err := ioutil.TempDir("", "unshare")
+ if err != nil {
+ t.Fatalf("tempdir: %v", err)
+ }
+
+ cmd := exec.Command(os.Args[0], "-test.run=TestUnshareMountNameSpaceHelper", d)
+ cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
+ cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS}
+
+ o, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("unshare failed: %v, %v", o, err)
+ }
+
+ // 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. Then we have some cleanup to do:
+ // we must unmount it, then try to remove it again.
+
+ if err := os.Remove(d); err != nil {
+ 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)
+ }
+ }
+}