summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--runsc/boot/fs.go43
-rw-r--r--runsc/boot/loader.go48
-rw-r--r--runsc/boot/loader_test.go2
-rw-r--r--runsc/container/container_test.go106
4 files changed, 125 insertions, 74 deletions
diff --git a/runsc/boot/fs.go b/runsc/boot/fs.go
index 86cbe1169..e5b7663d0 100644
--- a/runsc/boot/fs.go
+++ b/runsc/boot/fs.go
@@ -51,21 +51,30 @@ func (f *fdDispenser) empty() bool {
return len(f.fds) == 0
}
-// createMountNamespace creates a mount manager containing the root filesystem
-// and all mounts.
-func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, ioFDs []int) (*fs.MountNamespace, error) {
+// createMountNamespace creates a mount namespace containing the root filesystem
+// and all mounts. 'rootCtx' is used to walk directories to find mount points.
+func createMountNamespace(userCtx context.Context, rootCtx context.Context, spec *specs.Spec, conf *Config, ioFDs []int) (*fs.MountNamespace, error) {
fds := &fdDispenser{fds: ioFDs}
-
- // Create the MountNamespace from the root.
- rootInode, err := createRootMount(ctx, spec, conf, fds)
+ rootInode, err := createRootMount(rootCtx, spec, conf, fds)
if err != nil {
- return nil, fmt.Errorf("failed to create root overlay: %v", err)
+ return nil, fmt.Errorf("failed to create root mount: %v", err)
}
- mns, err := fs.NewMountNamespace(ctx, rootInode)
+ mns, err := fs.NewMountNamespace(userCtx, rootInode)
if err != nil {
- return nil, fmt.Errorf("failed to construct MountNamespace: %v", err)
+ return nil, fmt.Errorf("failed to create root mount namespace: %v", err)
+ }
+ if err := configureMounts(rootCtx, spec, conf, mns, fds); err != nil {
+ return nil, fmt.Errorf("failed to configure mounts: %v", err)
+ }
+ if !fds.empty() {
+ return nil, fmt.Errorf("not all mount points were consumed, remaining: %v", fds)
}
+ return mns, nil
+}
+// configureMounts iterates over Spec.Mounts and mounts them in the specified
+// mount namespace.
+func configureMounts(ctx context.Context, spec *specs.Spec, conf *Config, mns *fs.MountNamespace, fds *fdDispenser) error {
// Keep track of whether proc, sys, and tmp were mounted.
var procMounted, sysMounted, tmpMounted bool
@@ -88,7 +97,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
}
if err := mountSubmount(ctx, spec, conf, mns, fds, m); err != nil {
- return nil, err
+ return err
}
}
@@ -97,7 +106,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
Type: "devtmpfs",
Destination: "/dev",
}); err != nil {
- return nil, err
+ return err
}
// Mount proc and sys even if the user did not ask for it, as the spec
@@ -107,7 +116,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
Type: "proc",
Destination: "/proc",
}); err != nil {
- return nil, err
+ return err
}
}
if !sysMounted {
@@ -115,7 +124,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
Type: "sysfs",
Destination: "/sys",
}); err != nil {
- return nil, err
+ return err
}
}
@@ -127,15 +136,11 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
Type: "tmpfs",
Destination: "/tmp",
}); err != nil {
- return nil, err
+ return err
}
}
- if !fds.empty() {
- return nil, fmt.Errorf("not all mount points were consumed, remaining: %v", fds)
- }
-
- return mns, nil
+ return nil
}
// createRootMount creates the root filesystem.
diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go
index 566f2eb46..76edbb905 100644
--- a/runsc/boot/loader.go
+++ b/runsc/boot/loader.go
@@ -137,9 +137,6 @@ func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console
extraKGIDs,
caps,
auth.NewRootUserNamespace())
- if err != nil {
- return nil, fmt.Errorf("error creating credentials: %v", err)
- }
// Create user namespace.
// TODO: Not clear what domain name should be here. It is
@@ -159,22 +156,6 @@ func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console
return nil, fmt.Errorf("error getting executable path: %v", err)
}
- // Create the process arguments.
- procArgs := kernel.CreateProcessArgs{
- Filename: exec,
- Argv: spec.Process.Args,
- Envv: spec.Process.Env,
- WorkingDirectory: spec.Process.Cwd,
- Credentials: creds,
- // Creating the FDMap requires that we have kernel.Kernel.fdMapUids, so
- // it must wait until we have a Kernel.
- Umask: uint(syscall.Umask(0)),
- Limits: ls,
- MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
- UTSNamespace: utsns,
- IPCNamespace: ipcns,
- }
-
// Create an empty network stack because the network namespace may be empty at
// this point. Netns is configured before Run() is called. Netstack is
// configured using a control uRPC message. Host network is configured inside
@@ -219,14 +200,39 @@ func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console
return nil, fmt.Errorf("error creating control server: %v", err)
}
+ // Create the process arguments.
+ procArgs := kernel.CreateProcessArgs{
+ Filename: exec,
+ Argv: spec.Process.Args,
+ Envv: spec.Process.Env,
+ WorkingDirectory: spec.Process.Cwd,
+ Credentials: creds,
+ // Creating the FDMap requires that we have kernel.Kernel.fdMapUids, so
+ // it must wait until we have a Kernel.
+ Umask: uint(syscall.Umask(0)),
+ Limits: ls,
+ MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
+ UTSNamespace: utsns,
+ IPCNamespace: ipcns,
+ }
ctx := procArgs.NewContext(k)
+ // Use root user to configure mounts. The current user might not have
+ // permission to do so.
+ rootProcArgs := kernel.CreateProcessArgs{
+ WorkingDirectory: "/",
+ Credentials: auth.NewRootCredentials(creds.UserNamespace),
+ Umask: uint(syscall.Umask(0022)),
+ MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
+ }
+ rootCtx := rootProcArgs.NewContext(k)
+
// Create the virtual filesystem.
- mm, err := createMountNamespace(ctx, spec, conf, ioFDs)
+ mns, err := createMountNamespace(ctx, rootCtx, spec, conf, ioFDs)
if err != nil {
return nil, fmt.Errorf("error creating mounts: %v", err)
}
- k.SetRootMountNamespace(mm)
+ k.SetRootMountNamespace(mns)
// Create the FD map, which will set stdin, stdout, and stderr. If console
// is true, then ioctl calls will be passed through to the host fd.
diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go
index d2e5fe74e..5bc6f1646 100644
--- a/runsc/boot/loader_test.go
+++ b/runsc/boot/loader_test.go
@@ -239,7 +239,7 @@ func TestCreateMountNamespace(t *testing.T) {
for _, tc := range testCases {
ctx := contexttest.Context(t)
- mm, err := createMountNamespace(ctx, &tc.spec, conf, nil)
+ mm, err := createMountNamespace(ctx, ctx, &tc.spec, conf, nil)
if err != nil {
t.Fatalf("createMountNamespace test case %q failed: %v", tc.name, err)
}
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index e1674d631..24e9de3ce 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -20,6 +20,7 @@ import (
"io"
"io/ioutil"
"os"
+ "path"
"path/filepath"
"reflect"
"strings"
@@ -132,6 +133,34 @@ func waitForProcessList(s *container.Container, expected []*control.Process) err
return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(expected))
}
+// procListsEqual is used to check whether 2 Process lists are equal for all
+// implemented fields.
+func procListsEqual(got, want []*control.Process) bool {
+ if len(got) != len(want) {
+ return false
+ }
+ for i := range got {
+ pd1 := got[i]
+ pd2 := want[i]
+ // Zero out unimplemented and timing dependant fields.
+ pd1.Time, pd2.Time = "", ""
+ pd1.STime, pd2.STime = "", ""
+ pd1.C, pd2.C = 0, 0
+ if *pd1 != *pd2 {
+ return false
+ }
+ }
+ return true
+}
+
+func procListToString(pl []*control.Process) string {
+ strs := make([]string, 0, len(pl))
+ for _, p := range pl {
+ strs = append(strs, fmt.Sprintf("%+v", p))
+ }
+ return fmt.Sprintf("[%s]", strings.Join(strs, ","))
+}
+
// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle.
// It verifies after each step that the container can be loaded from disk, and
// has the correct status.
@@ -434,17 +463,6 @@ func TestCapabilities(t *testing.T) {
Type: "bind",
})
- // Capability below is needed to mount TempDir above in case the user doesn't
- // have access to all parents that lead to TempDir.
- caps := []string{"CAP_DAC_OVERRIDE"}
- spec.Process.Capabilities = &specs.LinuxCapabilities{
- Bounding: caps,
- Effective: caps,
- Inheritable: caps,
- Permitted: caps,
- Ambient: caps,
- }
-
rootDir, bundleDir, conf, err := setupContainer(spec)
if err != nil {
t.Fatalf("error setting up container: %v", err)
@@ -621,32 +639,54 @@ func TestSpecUnsupported(t *testing.T) {
}
}
-// procListsEqual is used to check whether 2 Process lists are equal for all
-// implemented fields.
-func procListsEqual(got, want []*control.Process) bool {
- if len(got) != len(want) {
- return false
+// TestRunNonRoot checks that sandbox can be configured when running as
+// non-priviledged user.
+func TestRunNonRoot(t *testing.T) {
+ spec := newSpecWithArgs("/bin/true")
+ spec.Process.User.UID = 343
+ spec.Process.User.GID = 2401
+
+ // User that container runs as can't list '$TMP/blocked' and would fail to
+ // mount it.
+ dir := path.Join(os.TempDir(), "blocked")
+ if err := os.Mkdir(dir, 0700); err != nil {
+ t.Fatalf("os.MkDir(%q) failed: %v", dir, err)
}
- for i := range got {
- pd1 := got[i]
- pd2 := want[i]
- // Zero out unimplemented and timing dependant fields.
- pd1.Time, pd2.Time = "", ""
- pd1.STime, pd2.STime = "", ""
- pd1.C, pd2.C = 0, 0
- if *pd1 != *pd2 {
- return false
- }
+ dir = path.Join(dir, "test")
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatalf("os.MkDir(%q) failed: %v", dir, err)
}
- return true
-}
-func procListToString(pl []*control.Process) string {
- strs := make([]string, 0, len(pl))
- for _, p := range pl {
- strs = append(strs, fmt.Sprintf("%+v", p))
+ // We generate files in the host temporary directory.
+ spec.Mounts = append(spec.Mounts, specs.Mount{
+ Destination: dir,
+ Source: dir,
+ Type: "bind",
+ })
+
+ rootDir, bundleDir, conf, err := setupContainer(spec)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+ defer os.RemoveAll(bundleDir)
+
+ // Create, start and wait for the container.
+ s, err := container.Create(uniqueContainerID(), spec, conf, bundleDir, "", "")
+ if err != nil {
+ t.Fatalf("error creating container: %v", err)
+ }
+ defer s.Destroy()
+ if err := s.Start(conf); err != nil {
+ t.Fatalf("error starting container: %v", err)
+ }
+ ws, err := s.Wait()
+ if err != nil {
+ t.Errorf("error waiting on container: %v", err)
+ }
+ if !ws.Exited() || ws.ExitStatus() != 0 {
+ t.Errorf("container failed, waitStatus: %v", ws)
}
- return fmt.Sprintf("[%s]", strings.Join(strs, ","))
}
// TestMain acts like runsc if it is called with the "boot" argument, otherwise