summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2018-10-03 20:43:18 -0700
committerShentubot <shentubot@google.com>2018-10-03 20:44:20 -0700
commit3f46f2e5017106d1569f759b8d19aee6e9827c58 (patch)
tree8dfdc15d3af75e808944099ca2b0b85ea6ee12de
parentbeac59b37a8b0ea834904870e5c236d2627947a2 (diff)
Fix sandbox chroot
Sandbox was setting chroot, but was not chaging the working dir. Added test to ensure this doesn't happen in the future. PiperOrigin-RevId: 215676270 Change-Id: I14352d3de64a4dcb90e50948119dc8328c9c15e1
-rwxr-xr-xkokoro/run_tests.sh23
-rw-r--r--runsc/sandbox/chroot.go2
-rw-r--r--runsc/sandbox/sandbox.go1
-rw-r--r--runsc/test/README.md26
-rw-r--r--runsc/test/image/image_test.go14
-rw-r--r--runsc/test/integration/integration_test.go17
-rw-r--r--runsc/test/root/BUILD27
-rw-r--r--runsc/test/root/chroot_test.go103
-rw-r--r--runsc/test/root/root.go16
-rw-r--r--runsc/test/testutil/docker.go13
-rw-r--r--runsc/test/testutil/testutil.go4
11 files changed, 222 insertions, 24 deletions
diff --git a/kokoro/run_tests.sh b/kokoro/run_tests.sh
index 665d63390..3f8841cee 100755
--- a/kokoro/run_tests.sh
+++ b/kokoro/run_tests.sh
@@ -35,6 +35,11 @@ bazel build //...
runtime=runsc_test_$((RANDOM))
sudo -n ./runsc/test/install.sh --runtime ${runtime}
+# Best effort to uninstall the runtime
+uninstallRuntime() {
+ sudo -n ./runsc/test/install.sh -u --runtime ${runtime}
+}
+
# Run the tests and upload results.
#
# We turn off "-e" flag because we must move the log files even if the test
@@ -43,6 +48,7 @@ set +e
bazel test --test_output=errors //...
exit_code=${?}
+# Execute local tests that require docker.
if [[ ${exit_code} -eq 0 ]]; then
# These names are used to exclude tests not supported in certain
# configuration, e.g. save/restore not supported with hostnet.
@@ -59,8 +65,21 @@ if [[ ${exit_code} -eq 0 ]]; then
done
fi
-# Best effort to uninstall
-sudo -n ./runsc/test/install.sh -u --runtime ${runtime}
+# Execute local tests that require superuser.
+if [[ ${exit_code} -eq 0 ]]; then
+ bazel build //runsc/test/root:root_test
+ root_test=$(find -L ./bazel-bin/ -executable -type f -name root_test | grep __main__)
+ if [[ ! -f "${root_test}" ]]; then
+ uninstallRuntime
+ echo "root_test executable not found"
+ exit 1
+ fi
+ sudo -n -E RUNSC_RUNTIME=${runtime} ${root_test}
+ exit_code=${?}
+fi
+
+uninstallRuntime
+
set -e
# Find and rename all test xml and log files so that Sponge can pick them up.
diff --git a/runsc/sandbox/chroot.go b/runsc/sandbox/chroot.go
index 30a4bae35..35b19a0b1 100644
--- a/runsc/sandbox/chroot.go
+++ b/runsc/sandbox/chroot.go
@@ -55,7 +55,7 @@ func setUpChroot() (string, error) {
log.Infof("Setting up sandbox chroot in %q", chroot)
// Mount /proc.
- if err := mountInChroot(chroot, "proc", "/proc", "proc", 0); err != nil {
+ if err := mountInChroot(chroot, "proc", "/proc", "proc", syscall.MS_NOSUID|syscall.MS_NODEV|syscall.MS_NOEXEC); err != nil {
return "", fmt.Errorf("error mounting proc in chroot: %v", err)
}
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index 1ed1ab61d..847417a15 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -475,6 +475,7 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund
}
s.Chroot = chroot // Remember path so it can cleaned up.
cmd.SysProcAttr.Chroot = chroot
+ cmd.Dir = "/"
cmd.Args[0] = "/runsc"
cmd.Path = "/runsc"
} else {
diff --git a/runsc/test/README.md b/runsc/test/README.md
new file mode 100644
index 000000000..5929cbeb6
--- /dev/null
+++ b/runsc/test/README.md
@@ -0,0 +1,26 @@
+# Tests
+
+The tests defined under this path are verifying functionality beyond what unit
+tests can cover, e.g. integration and end to end tests. Due to their nature,
+they may need extra setup in the test machine and extra configuration to run.
+
+- **integration:** defines integration tests that uses `docker run` to test
+ functionality.
+- **image:** basic end to end test for popular images.
+- **root:** tests that require to be run as root.
+- **testutil:** utilities library to support the tests.
+
+The following setup steps are required in order to run these tests:
+
+
+ `./runsc/test/install.sh [--runtime <name>]`
+
+The tests expect the runtime name to be provided in the `RUNSC_RUNTIME`
+environment variable (default: `runsc-test`). To run the tests execute:
+
+
+```
+bazel test --test_env=RUNSC_RUNTIME=runsc-test \
+ //runsc/test/image:image_test \
+ //runsc/test/integration:integration_test
+```
diff --git a/runsc/test/image/image_test.go b/runsc/test/image/image_test.go
index 71d992115..341bdc1d5 100644
--- a/runsc/test/image/image_test.go
+++ b/runsc/test/image/image_test.go
@@ -12,17 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package image provides end-to-end image tests for runsc. These tests require
-// docker and runsc to be installed on the machine. To set it up, run:
-//
-// ./runsc/test/install.sh [--runtime <name>]
-//
-// The tests expect the runtime name to be provided in the RUNSC_RUNTIME
-// environment variable (default: runsc-test).
-//
+// Package image provides end-to-end image tests for runsc.
+
// Each test calls docker commands to start up a container, and tests that it is
// behaving properly, like connecting to a port or looking at the output. The
// container is killed and deleted at the end.
+//
+// Setup instruction in runsc/test/README.md.
package image
import (
@@ -307,7 +303,7 @@ func TestRuby(t *testing.T) {
}
}
-func MainTest(m *testing.M) {
+func TestMain(m *testing.M) {
testutil.EnsureSupportedDockerVersion()
os.Exit(m.Run())
}
diff --git a/runsc/test/integration/integration_test.go b/runsc/test/integration/integration_test.go
index 457b5fbf5..5f24aeed5 100644
--- a/runsc/test/integration/integration_test.go
+++ b/runsc/test/integration/integration_test.go
@@ -12,18 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package image provides end-to-end integration tests for runsc. These tests require
-// docker and runsc to be installed on the machine. To set it up, run:
-//
-// ./runsc/test/install.sh [--runtime <name>]
-//
-// The tests expect the runtime name to be provided in the RUNSC_RUNTIME
-// environment variable (default: runsc-test).
+// Package integration provides end-to-end integration tests for runsc.
//
// Each test calls docker commands to start up a container, and tests that it is
-// behaving properly, with various runsc commands. The container is killed and deleted
-// at the end.
-
+// behaving properly, with various runsc commands. The container is killed and
+// deleted at the end.
+//
+// Setup instruction in runsc/test/README.md.
package integration
import (
@@ -184,7 +179,7 @@ func TestConnectToSelf(t *testing.T) {
}
}
-func MainTest(m *testing.M) {
+func TestMain(m *testing.M) {
testutil.EnsureSupportedDockerVersion()
os.Exit(m.Run())
}
diff --git a/runsc/test/root/BUILD b/runsc/test/root/BUILD
new file mode 100644
index 000000000..dbc0f1d26
--- /dev/null
+++ b/runsc/test/root/BUILD
@@ -0,0 +1,27 @@
+package(licenses = ["notice"]) # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "root",
+ srcs = ["root.go"],
+ importpath = "gvisor.googlesource.com/gvisor/runsc/test/root",
+)
+
+go_test(
+ name = "root_test",
+ size = "small",
+ srcs = ["chroot_test.go"],
+ embed = [":root"],
+ tags = [
+ # Requires docker and runsc to be configured before the test runs.
+ # Also test only runs as root.
+ "manual",
+ "local",
+ ],
+ deps = [
+ "//runsc/specutils",
+ "//runsc/test/testutil",
+ "@com_github_syndtr_gocapability//capability:go_default_library",
+ ],
+)
diff --git a/runsc/test/root/chroot_test.go b/runsc/test/root/chroot_test.go
new file mode 100644
index 000000000..5c59e7451
--- /dev/null
+++ b/runsc/test/root/chroot_test.go
@@ -0,0 +1,103 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package root is used for tests that requires sysadmin privileges run. First,
+// follow the setup instruction in runsc/test/README.md. To run these test:
+//
+// bazel build //runsc/test/root:root_test
+// root_test=$(find -L ./bazel-bin/ -executable -type f -name root_test | grep __main__)
+// sudo RUNSC_RUNTIME=runsc-test ${root_test}
+package root
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/syndtr/gocapability/capability"
+ "gvisor.googlesource.com/gvisor/runsc/specutils"
+ "gvisor.googlesource.com/gvisor/runsc/test/testutil"
+)
+
+// TestChroot verifies that the sandbox is chroot'd and that mounts are cleaned
+// up after the sandbox is destroyed.
+func TestChroot(t *testing.T) {
+ d := testutil.MakeDocker("chroot-test")
+ if err := d.Run("alpine", "sleep", "10000"); err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ }
+ defer d.CleanUp()
+
+ pid, err := d.SandboxPid()
+ if err != nil {
+ t.Fatalf("Docker.SandboxPid(): %v", err)
+ }
+
+ // Check that sandbox is chroot'ed.
+ chroot, err := filepath.EvalSymlinks(filepath.Join("/proc", strconv.Itoa(pid), "root"))
+ if err != nil {
+ t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err)
+ }
+ if want := "/tmp/runsc-sandbox-chroot-"; !strings.HasPrefix(chroot, want) {
+ t.Errorf("sandbox is not chroot'd, it should be inside: %q, got: %q", want, chroot)
+ }
+
+ path, err := filepath.EvalSymlinks(filepath.Join("/proc", strconv.Itoa(pid), "cwd"))
+ if err != nil {
+ t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err)
+ }
+ if chroot != path {
+ t.Errorf("sandbox current dir is wrong, want: %q, got: %q", chroot, path)
+ }
+
+ fi, err := ioutil.ReadDir(chroot)
+ if err != nil {
+ t.Fatalf("error listing %q: %v", chroot, err)
+ }
+ if want, got := 2, len(fi); want != got {
+ t.Fatalf("chroot dir got %d entries, want %d", want, got)
+ }
+
+ // chroot dir is prepared by runsc and should contains only the executable
+ // and /proc.
+ files := []string{fi[0].Name(), fi[1].Name()}
+ sort.Strings(files)
+ if want := []string{"proc", "runsc"}; !reflect.DeepEqual(files, want) {
+ t.Errorf("chroot got children %v, want %v", files, want)
+ }
+
+ d.CleanUp()
+
+ // Check that chroot directory was cleaned up.
+ if _, err := os.Stat(chroot); err == nil || !os.IsNotExist(err) {
+ t.Errorf("chroot directory %q was not deleted: %v", chroot, err)
+ }
+}
+
+func TestMain(m *testing.M) {
+ testutil.EnsureSupportedDockerVersion()
+
+ if !specutils.HasCapabilities(capability.CAP_SYS_ADMIN, capability.CAP_DAC_OVERRIDE) {
+ fmt.Println("Test requires sysadmin privileges to run. Try again with sudo.")
+ os.Exit(1)
+ }
+
+ os.Exit(m.Run())
+}
diff --git a/runsc/test/root/root.go b/runsc/test/root/root.go
new file mode 100644
index 000000000..790f62c29
--- /dev/null
+++ b/runsc/test/root/root.go
@@ -0,0 +1,16 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package root is empty. See chroot_test.go for description.
+package root
diff --git a/runsc/test/testutil/docker.go b/runsc/test/testutil/docker.go
index 55ca353b8..cf61f2c10 100644
--- a/runsc/test/testutil/docker.go
+++ b/runsc/test/testutil/docker.go
@@ -267,6 +267,19 @@ func (d *Docker) FindPort(sandboxPort int) (int, error) {
return port, nil
}
+// SandboxPid returns the PID to the sandbox process.
+func (d *Docker) SandboxPid() (int, error) {
+ out, err := do("inspect", "-f={{.State.Pid}}", d.Name)
+ if err != nil {
+ return -1, fmt.Errorf("error retrieving pid: %v", err)
+ }
+ pid, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n"))
+ if err != nil {
+ return -1, fmt.Errorf("error parsing pid %q: %v", out, err)
+ }
+ return pid, nil
+}
+
// WaitForOutput calls 'docker logs' to retrieve containers output and searches
// for the given pattern.
func (d *Docker) WaitForOutput(pattern string, timeout time.Duration) (string, error) {
diff --git a/runsc/test/testutil/testutil.go b/runsc/test/testutil/testutil.go
index cdc7f78c3..b4664995c 100644
--- a/runsc/test/testutil/testutil.go
+++ b/runsc/test/testutil/testutil.go
@@ -238,7 +238,7 @@ func WaitForHTTP(port int, timeout time.Duration) error {
}
// RunAsRoot ensures the test runs with CAP_SYS_ADMIN and CAP_SYS_CHROOT. If
-// need it will create a new user namespace and reexecute the test as root
+// needed it will create a new user namespace and re-execute the test as root
// inside of the namespace. This functionr returns when it's running as root. If
// it needs to create another process, it will exit from there and not return.
func RunAsRoot() {
@@ -246,6 +246,8 @@ func RunAsRoot() {
return
}
+ fmt.Println("*** Re-running test as root in new user namespace ***")
+
// Current process doesn't have CAP_SYS_ADMIN, create user namespace and run
// as root inside that namespace to get it.
runtime.LockOSThread()