diff --git a/cmd/runtimetest/main.go b/cmd/runtimetest/main.go index a45078af8..6125ee4f6 100644 --- a/cmd/runtimetest/main.go +++ b/cmd/runtimetest/main.go @@ -24,6 +24,8 @@ import ( "github.com/opencontainers/runtime-tools/cmd/runtimetest/mount" rfc2119 "github.com/opencontainers/runtime-tools/error" "github.com/opencontainers/runtime-tools/specerror" + + "golang.org/x/sys/unix" ) // PrGetNoNewPrivs isn't exposed in Golang so we define it ourselves copying the value from @@ -346,6 +348,71 @@ func validateRootFS(spec *rspec.Spec) error { return nil } +func validateRootfsPropagation(spec *rspec.Spec) error { + if spec.Linux == nil || spec.Linux.RootfsPropagation == "" { + return nil + } + + targetDir, err := ioutil.TempDir("/", "target") + if err != nil { + return err + } + defer os.RemoveAll(targetDir) + + switch spec.Linux.RootfsPropagation { + case "shared", "slave", "private": + mountDir, err := ioutil.TempDir("/", "mount") + if err != nil { + return err + } + defer os.RemoveAll(mountDir) + + testDir, err := ioutil.TempDir("/", "test") + if err != nil { + return err + } + defer os.RemoveAll(testDir) + + tmpfile, err := ioutil.TempFile(testDir, "example") + if err != nil { + return err + } + defer os.Remove(tmpfile.Name()) + + if err := unix.Mount("/", targetDir, "", unix.MS_BIND|unix.MS_REC, ""); err != nil { + return err + } + defer unix.Unmount(targetDir, unix.MNT_DETACH) + if err := unix.Mount(testDir, mountDir, "", unix.MS_BIND|unix.MS_REC, ""); err != nil { + return err + } + defer unix.Unmount(mountDir, unix.MNT_DETACH) + if _, err := os.Stat(filepath.Join(targetDir, filepath.Join(mountDir, filepath.Base(tmpfile.Name())))); os.IsNotExist(err) { + if spec.Linux.RootfsPropagation == "shared" { + return fmt.Errorf("rootfs should be %s, but not", spec.Linux.RootfsPropagation) + } + return nil + } + if spec.Linux.RootfsPropagation == "shared" { + return nil + } + return fmt.Errorf("rootfs should be %s, but not", spec.Linux.RootfsPropagation) + case "unbindable": + if err := unix.Mount("/", targetDir, "", unix.MS_BIND|unix.MS_REC, ""); err != nil { + if err == syscall.EINVAL { + return nil + } + return err + } + defer unix.Unmount(targetDir, unix.MNT_DETACH) + return fmt.Errorf("rootfs expected to be unbindable, but not") + default: + logrus.Warnf("unrecognized linux.rootfsPropagation %s", spec.Linux.RootfsPropagation) + } + + return nil +} + func validateDefaultFS(spec *rspec.Spec) error { mountInfos, err := mount.GetMounts() if err != nil { @@ -779,6 +846,10 @@ func run(context *cli.Context) error { test: validateROPaths, description: "read only paths", }, + { + test: validateRootfsPropagation, + description: "rootfs propagation", + }, { test: validateSysctls, description: "sysctls", diff --git a/validation/validation_test.go b/validation/validation_test.go index e232acb48..3003da47e 100644 --- a/validation/validation_test.go +++ b/validation/validation_test.go @@ -116,6 +116,30 @@ func TestValidateHostname(t *testing.T) { assert.Nil(t, runtimeInsideValidate(g)) } +func TestValidateRootfsPropagationPrivate(t *testing.T) { + t.Skip("has not been implemented yet") +} + +func TestValidateRootfsPropagationSlave(t *testing.T) { + t.Skip("has not been implemented yet") +} + +func TestValidateRootfsPropagationShared(t *testing.T) { + g := getDefaultGenerator() + g.SetupPrivileged(true) + g.SetLinuxRootPropagation("shared") + + assert.Nil(t, runtimeInsideValidate(g)) +} + +func TestValidateRootfsPropagationUnbindable(t *testing.T) { + g := getDefaultGenerator() + g.SetupPrivileged(true) + g.SetLinuxRootPropagation("unbindable") + + assert.Nil(t, runtimeInsideValidate(g)) +} + // Test whether mounts are correctly mounted func TestValidateMounts(t *testing.T) { // TODO mounts generation options have not been implemented