diff options
-rw-r--r-- | runsc/container/container_test.go | 21 | ||||
-rw-r--r-- | runsc/container/fs.go | 59 |
2 files changed, 78 insertions, 2 deletions
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index c71bcc46d..aebfb2878 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -1556,6 +1556,27 @@ func TestGoferExits(t *testing.T) { } } +func TestRootNotMount(t *testing.T) { + spec := testutil.NewSpecWithArgs("/bin/true") + + root, err := ioutil.TempDir(testutil.TmpDir(), "root") + if err != nil { + t.Fatalf("failure to create tmp dir: %v", err) + } + spec.Root.Path = root + spec.Root.Readonly = true + spec.Mounts = []specs.Mount{ + {Destination: "/bin", Source: "/bin", Type: "bind", Options: []string{"ro"}}, + {Destination: "/lib", Source: "/lib", Type: "bind", Options: []string{"ro"}}, + {Destination: "/lib64", Source: "/lib64", Type: "bind", Options: []string{"ro"}}, + } + + conf := testutil.TestConfig() + if err := run(spec, conf); err != nil { + t.Fatalf("error running sandbox: %v", err) + } +} + // executeSync synchronously executes a new process. func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) { pid, err := cont.Execute(args) diff --git a/runsc/container/fs.go b/runsc/container/fs.go index a3c5772ba..59edd9488 100644 --- a/runsc/container/fs.go +++ b/runsc/container/fs.go @@ -15,6 +15,7 @@ package container import ( + "bufio" "fmt" "os" "path/filepath" @@ -100,18 +101,72 @@ func setupFS(spec *specs.Spec, conf *boot.Config, bundleDir string) error { } } - // Remount root as readonly after setup is done, if requested. + // If root is read only, check if it needs to be remounted as readonly. if spec.Root.Readonly { + isMountPoint, readonly, err := mountInfo(spec.Root.Path) + if err != nil { + return err + } + if readonly { + return nil + } + if !isMountPoint { + // Readonly root is not a mount point nor read-only. Can't do much other + // than just logging a warning. The gofer will prevent files to be open + // in write mode. + log.Warningf("Mount where root is located is not read-only and cannot be changed: %q", spec.Root.Path) + return nil + } + + // If root is a mount point but not read-only, we can change mount options + // to make it read-only for extra safety. log.Infof("Remounting root as readonly: %q", spec.Root.Path) flags := uintptr(syscall.MS_BIND | syscall.MS_REMOUNT | syscall.MS_RDONLY | syscall.MS_REC) src := spec.Root.Path if err := syscall.Mount(src, src, "bind", flags, ""); err != nil { - return fmt.Errorf("failed to remount root as readonly with source: %q, target: %q, flags: %#x, err: %v", spec.Root.Path, spec.Root.Path, flags, err) + return fmt.Errorf("failed to remount root as read-only with source: %q, target: %q, flags: %#x, err: %v", spec.Root.Path, spec.Root.Path, flags, err) } } return nil } +// mountInfo returns whether the path is a mount point and whether the mount +// that path belongs to is read-only. +func mountInfo(path string) (bool, bool, error) { + // Mounts are listed by their real paths. + realPath, err := filepath.EvalSymlinks(path) + if err != nil { + return false, false, err + } + f, err := os.Open("/proc/mounts") + if err != nil { + return false, false, err + } + scanner := bufio.NewScanner(f) + + var mountPoint string + var readonly bool + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, " ") + if len(parts) < 4 { + return false, false, fmt.Errorf("invalid /proc/mounts line format %q", line) + } + mp := parts[1] + opts := strings.Split(parts[3], ",") + + // Find the closest submount to the path. + if strings.Contains(realPath, mp) && len(mp) > len(mountPoint) { + mountPoint = mp + readonly = specutils.ContainsStr(opts, "ro") + } + } + if err := scanner.Err(); err != nil { + return false, false, err + } + return mountPoint == realPath, readonly, nil +} + // destroyFS unmounts mounts done by runsc under `spec.Root.Path`. This // recovers the container rootfs into the original state. func destroyFS(spec *specs.Spec) error { |