summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2018-09-30 23:22:13 -0700
committerShentubot <shentubot@google.com>2018-09-30 23:23:03 -0700
commit43e6aff50e23763d12c71b054f100fd91da46736 (patch)
treefac486b04cfa7d74c1c58df9de51fb8041cdbe90
parent9c7eb13079e65100b69b41536a51d2433b05637b (diff)
Don't fail if Root is readonly and is not a mount point
This makes runsc more friendly to run without docker or K8s. PiperOrigin-RevId: 215165586 Change-Id: Id45a9fc24a3c09b1645f60dbaf70e64711a7a4cd
-rw-r--r--runsc/container/container_test.go21
-rw-r--r--runsc/container/fs.go59
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 {