diff options
-rw-r--r-- | runsc/boot/BUILD | 1 | ||||
-rw-r--r-- | runsc/boot/fs.go | 91 | ||||
-rw-r--r-- | runsc/boot/loader_test.go | 206 |
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) + } + } + } +} |