mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Added functionality to create a RestoreEnvironment.
Before a container can be restored, the mounts must be configured. The root and submounts and their key information is compiled into a RestoreEnvironment. Future code will be added to set this created environment before restoring a container. Tests to ensure the correct environment were added. PiperOrigin-RevId: 201544637 Change-Id: Ia894a8b0f80f31104d1c732e113b1d65a4697087
This commit is contained in:
committed by
Shentubot
parent
7d6149063a
commit
f2a687001d
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
+82
-9
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user