summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--runsc/boot/BUILD1
-rw-r--r--runsc/boot/fs.go91
-rw-r--r--runsc/boot/loader_test.go206
3 files changed, 289 insertions, 9 deletions
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD
index 924cc2b90..e96722069 100644
--- a/runsc/boot/BUILD
+++ b/runsc/boot/BUILD
@@ -86,6 +86,7 @@ go_test(
"//pkg/control/server",
"//pkg/log",
"//pkg/sentry/context/contexttest",
+ "//pkg/sentry/fs",
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
],
)
diff --git a/runsc/boot/fs.go b/runsc/boot/fs.go
index 7ebf22de8..7731763de 100644
--- a/runsc/boot/fs.go
+++ b/runsc/boot/fs.go
@@ -220,12 +220,13 @@ func addOverlay(ctx context.Context, conf *Config, lower *fs.Inode, name string,
return fs.NewOverlayRoot(ctx, upper, lower, lowerFlags)
}
-func mountSubmount(ctx context.Context, spec *specs.Spec, conf *Config, mns *fs.MountNamespace, fds *fdDispenser, m specs.Mount) error {
- // Map mount type to filesystem name, and parse out the options that we are
- // capable of dealing with.
- var data []string
+// getMountNameAndOptions retrieves the fsName, data, and useOverlay values
+// used for mounts.
+func getMountNameAndOptions(conf *Config, m specs.Mount, fds *fdDispenser) (string, []string, bool, error) {
var fsName string
+ var data []string
var useOverlay bool
+ var err error
switch m.Type {
case "devpts", "devtmpfs", "proc", "sysfs":
fsName = m.Type
@@ -235,11 +236,8 @@ func mountSubmount(ctx context.Context, spec *specs.Spec, conf *Config, mns *fs.
fsName = m.Type
// tmpfs has some extra supported options that we must pass through.
- var err error
data, err = parseAndFilterOptions(m.Options, "mode", "uid", "gid")
- if err != nil {
- return err
- }
+
case "bind":
switch conf.FileAccess {
case FileAccessProxy:
@@ -250,7 +248,7 @@ func mountSubmount(ctx context.Context, spec *specs.Spec, conf *Config, mns *fs.
fsName = "whitelistfs"
data = []string{"root=" + m.Source, "dont_translate_ownership=true"}
default:
- return fmt.Errorf("invalid file access type: %v", conf.FileAccess)
+ err = fmt.Errorf("invalid file access type: %v", conf.FileAccess)
}
// If configured, add overlay to all writable mounts.
useOverlay = conf.Overlay && !mountFlags(m.Options).ReadOnly
@@ -261,6 +259,20 @@ func mountSubmount(ctx context.Context, spec *specs.Spec, conf *Config, mns *fs.
// them, so this is a warning for now.
// we do not support.
log.Warningf("ignoring unknown filesystem type %q", m.Type)
+ }
+ return fsName, data, useOverlay, err
+}
+
+func mountSubmount(ctx context.Context, spec *specs.Spec, conf *Config, mns *fs.MountNamespace, fds *fdDispenser, m specs.Mount) error {
+ // Map mount type to filesystem name, and parse out the options that we are
+ // capable of dealing with.
+ fsName, data, useOverlay, err := getMountNameAndOptions(conf, m, fds)
+
+ // Return the error or nil that corresponds to the default case in getMountNameAndOptions.
+ if err != nil {
+ return err
+ }
+ if fsName == "" {
return nil
}
@@ -388,6 +400,67 @@ func destinations(mounts []specs.Mount, extra ...string) []string {
return append(ds, extra...)
}
+// mountDevice returns a device string based on the fs type and target
+// of the mount.
+func mountDevice(m specs.Mount) string {
+ if m.Type == "bind" {
+ // Make a device string that includes the target, which is consistent across
+ // S/R and uniquely identifies the connection.
+ return "p9fs-" + m.Destination
+ }
+ // All other fs types use device "none".
+ return "none"
+}
+
+// addRestoreMount adds a mount to the MountSources map used for restoring a
+// checkpointed container.
+func addRestoreMount(conf *Config, renv *fs.RestoreEnvironment, m specs.Mount, fds *fdDispenser) error {
+ fsName, data, _, err := getMountNameAndOptions(conf, m, fds)
+ dataString := strings.Join(data, ",")
+ if err != nil {
+ return err
+ }
+ renv.MountSources[fsName] = append(renv.MountSources[fsName], fs.MountArgs{
+ Dev: mountDevice(m),
+ Flags: mountFlags(m.Options),
+ Data: dataString,
+ })
+ return nil
+}
+
+// createRestoreEnviroment builds a fs.RestoreEnvironment called renv by adding the mounts
+// to the environment.
+func createRestoreEnvironment(spec *specs.Spec, conf *Config, fds *fdDispenser) (*fs.RestoreEnvironment, error) {
+ if conf.FileAccess == FileAccessDirect {
+ return nil, fmt.Errorf("host filesystem with whitelist not supported with S/R")
+ }
+ renv := &fs.RestoreEnvironment{
+ MountSources: make(map[string][]fs.MountArgs),
+ }
+
+ // Add root mount.
+ fd := fds.remove()
+ dataString := strings.Join([]string{"trans=fd", fmt.Sprintf("rfdno=%d", fd), fmt.Sprintf("wfdno=%d", fd), "privateunixsocket=true"}, ",")
+ mf := fs.MountSourceFlags{}
+ if spec.Root.Readonly {
+ mf.ReadOnly = true
+ }
+ const rootFSName = "9p"
+ renv.MountSources[rootFSName] = append(renv.MountSources[rootFSName], fs.MountArgs{
+ Dev: "p9fs-/",
+ Flags: mf,
+ Data: dataString,
+ })
+
+ // Add submounts
+ for _, m := range spec.Mounts {
+ if err := addRestoreMount(conf, renv, m, fds); err != nil {
+ return nil, err
+ }
+ }
+ return renv, nil
+}
+
func mountFlags(opts []string) fs.MountSourceFlags {
mf := fs.MountSourceFlags{}
for _, o := range opts {
diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go
index dab7ad0c5..5ec1084db 100644
--- a/runsc/boot/loader_test.go
+++ b/runsc/boot/loader_test.go
@@ -19,6 +19,7 @@ import (
"io/ioutil"
"math/rand"
"os"
+ "reflect"
"sync"
"testing"
"time"
@@ -27,6 +28,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/control/server"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
)
func init() {
@@ -319,3 +321,207 @@ func TestCreateMountNamespace(t *testing.T) {
}
}
}
+
+// TestRestoreEnvironment tests that the correct mounts are collected from the spec and config
+// in order to build the environment for restoring.
+func TestRestoreEnvironment(t *testing.T) {
+ testCases := []struct {
+ name string
+ spec *specs.Spec
+ conf *Config
+ ioFDs []int
+ errorExpected bool
+ expectedRenv fs.RestoreEnvironment
+ }{
+ {
+ name: "basic spec test",
+ spec: &specs.Spec{
+ Root: &specs.Root{
+ Path: os.TempDir(),
+ Readonly: true,
+ },
+ Mounts: []specs.Mount{
+ {
+ Destination: "/some/very/very/deep/path",
+ Type: "tmpfs",
+ },
+ {
+ Destination: "/proc",
+ Type: "tmpfs",
+ },
+ },
+ },
+ conf: &Config{
+ RootDir: "unused_root_dir",
+ Network: NetworkNone,
+ FileAccess: FileAccessProxy,
+ DisableSeccomp: true,
+ },
+ ioFDs: []int{0},
+ errorExpected: false,
+ expectedRenv: fs.RestoreEnvironment{
+ MountSources: map[string][]fs.MountArgs{
+ "9p": {
+ {
+ Dev: "p9fs-/",
+ Flags: fs.MountSourceFlags{ReadOnly: true},
+ Data: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true",
+ },
+ },
+ "tmpfs": {
+ {
+ Dev: "none",
+ },
+ {
+ Dev: "none",
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "bind type test",
+ spec: &specs.Spec{
+ Root: &specs.Root{
+ Path: os.TempDir(),
+ Readonly: true,
+ },
+ Mounts: []specs.Mount{
+ {
+ Destination: "/dev/fd-foo",
+ Type: "bind",
+ },
+ },
+ },
+ conf: &Config{
+ RootDir: "unused_root_dir",
+ Network: NetworkNone,
+ FileAccess: FileAccessProxy,
+ DisableSeccomp: true,
+ },
+ ioFDs: []int{0, 1},
+ errorExpected: false,
+ expectedRenv: fs.RestoreEnvironment{
+ MountSources: map[string][]fs.MountArgs{
+ "9p": {
+ {
+ Dev: "p9fs-/",
+ Flags: fs.MountSourceFlags{ReadOnly: true},
+ Data: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true",
+ },
+ {
+ Dev: "p9fs-/dev/fd-foo",
+ Data: "trans=fd,rfdno=1,wfdno=1,privateunixsocket=true",
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "options test",
+ spec: &specs.Spec{
+ Root: &specs.Root{
+ Path: os.TempDir(),
+ Readonly: true,
+ },
+ Mounts: []specs.Mount{
+ {
+ Destination: "/dev/fd-foo",
+ Type: "tmpfs",
+ Options: []string{"uid=1022", "noatime"},
+ },
+ },
+ },
+ conf: &Config{
+ RootDir: "unused_root_dir",
+ Network: NetworkNone,
+ FileAccess: FileAccessProxy,
+ DisableSeccomp: true,
+ },
+ ioFDs: []int{0},
+ errorExpected: false,
+ expectedRenv: fs.RestoreEnvironment{
+ MountSources: map[string][]fs.MountArgs{
+ "9p": {
+ {
+ Dev: "p9fs-/",
+ Flags: fs.MountSourceFlags{ReadOnly: true},
+ Data: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true",
+ },
+ },
+ "tmpfs": {
+ {
+ Dev: "none",
+ Flags: fs.MountSourceFlags{NoAtime: true},
+ Data: "uid=1022",
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "whitelist error test",
+ spec: &specs.Spec{
+ Root: &specs.Root{
+ Path: os.TempDir(),
+ Readonly: true,
+ },
+ Mounts: []specs.Mount{
+ {
+ Destination: "/dev/fd-foo",
+ Type: "bind",
+ },
+ },
+ },
+ conf: &Config{
+ RootDir: "unused_root_dir",
+ Network: NetworkNone,
+ FileAccess: FileAccessDirect,
+ DisableSeccomp: true,
+ },
+ ioFDs: []int{0, 1},
+ errorExpected: true,
+ },
+ {
+ name: "bad options test",
+ spec: &specs.Spec{
+ Root: &specs.Root{
+ Path: os.TempDir(),
+ Readonly: true,
+ },
+ Mounts: []specs.Mount{
+ {
+ Destination: "/dev/fd-foo",
+ Type: "tmpfs",
+ Options: []string{"invalid_option=true"},
+ },
+ },
+ },
+ conf: &Config{
+ RootDir: "unused_root_dir",
+ Network: NetworkNone,
+ FileAccess: FileAccessDirect,
+ DisableSeccomp: true,
+ },
+ ioFDs: []int{0},
+ errorExpected: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ fds := &fdDispenser{fds: tc.ioFDs}
+
+ actualRenv, err := createRestoreEnvironment(tc.spec, tc.conf, fds)
+ if !tc.errorExpected && err != nil {
+ t.Fatalf("could not create restore environment for test:%s", tc.name)
+ } else if tc.errorExpected {
+ if err == nil {
+ t.Fatalf("expected an error, but no error occurred.")
+ }
+ } else {
+ if !reflect.DeepEqual(*actualRenv, tc.expectedRenv) {
+ t.Fatalf("restore environments did not match for test:%s", tc.name)
+ }
+ }
+ }
+}