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
This commit is contained in:
Fabricio Voznika
2018-10-03 20:44:20 -07:00
committed by Shentubot
parent beac59b37a
commit 3f46f2e501
11 changed files with 222 additions and 24 deletions
+21 -2
View File
@@ -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.
+1 -1
View File
@@ -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)
}
+1
View File
@@ -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 {
+26
View File
@@ -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
```
+5 -9
View File
@@ -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())
}
+6 -11
View File
@@ -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())
}
+27
View File
@@ -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",
],
)
+103
View File
@@ -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())
}
+16
View File
@@ -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
+13
View File
@@ -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) {
+3 -1
View File
@@ -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()