diff options
-rw-r--r-- | runsc/boot/fs.go | 43 | ||||
-rw-r--r-- | runsc/boot/loader.go | 48 | ||||
-rw-r--r-- | runsc/boot/loader_test.go | 2 | ||||
-rw-r--r-- | runsc/container/container_test.go | 106 |
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 |