summaryrefslogtreecommitdiffhomepage
path: root/runsc
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2018-05-24 14:27:05 -0700
committerShentubot <shentubot@google.com>2018-05-24 14:27:57 -0700
commite48f7078761b00552ac74068c184ee4fb90fe9aa (patch)
tree8cb4ff41119d6fa8c28ed11d33f350bae4321ad1 /runsc
parent7996ae7ccf284718fc98f5ba34c94b044b858ec2 (diff)
Configure sandbox as superuser
Container user might not have enough priviledge to walk directories and mount filesystems. Instead, create superuser to perform these steps of the configuration. PiperOrigin-RevId: 197953667 Change-Id: I643650ab654e665408e2af1b8e2f2aa12d58d4fb
Diffstat (limited to 'runsc')
-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