diff options
author | Fabricio Voznika <fvoznika@google.com> | 2018-10-18 12:41:07 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-10-18 12:42:24 -0700 |
commit | f3ffa4db525ea1a1d36307ea9593ed7b5e014ca7 (patch) | |
tree | e490c99350392544e21abb8953fe3c656a676221 /runsc/container | |
parent | 2a697791d1a473c76973f135f3af9240a32ad668 (diff) |
Resolve mount paths while setting up root fs mount
It's hard to resolve symlinks inside the sandbox because rootfs and mounts
may be read-only, forcing us to create mount points inside lower layer of an
overlay, **before** the volumes are mounted.
Since the destination must already be resolved outside the sandbox when creating
mounts, take this opportunity to rewrite the spec with paths resolved.
"runsc boot" will use the "resolved" spec to load mounts. In addition, symlink
traversals were disabled while mounting containers inside the sandbox.
It haven't been able to write a good test for it. So I'm relying on manual tests
for now.
PiperOrigin-RevId: 217749904
Change-Id: I7ac434d5befd230db1488446cda03300cc0751a9
Diffstat (limited to 'runsc/container')
-rw-r--r-- | runsc/container/container.go | 26 | ||||
-rw-r--r-- | runsc/container/container_test.go | 2 | ||||
-rw-r--r-- | runsc/container/fs.go | 30 |
3 files changed, 44 insertions, 14 deletions
diff --git a/runsc/container/container.go b/runsc/container/container.go index 0ec4d03c1..f76bad1aa 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -271,6 +271,19 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo // init container in the sandbox. if specutils.ShouldCreateSandbox(spec) { log.Debugf("Creating new sandbox for container %q", id) + + // Setup rootfs and mounts. It returns a new mount list with destination + // paths resolved. Since the spec for the root container is read from disk, + // Write the new spec to a new file that will be used by the sandbox. + cleanMounts, err := setupFS(spec, conf, bundleDir) + if err != nil { + return nil, fmt.Errorf("setup mounts: %v", err) + } + spec.Mounts = cleanMounts + if err := specutils.WriteCleanSpec(bundleDir, spec); err != nil { + return nil, fmt.Errorf("writing clean spec: %v", err) + } + ioFiles, err := c.createGoferProcess(spec, conf, bundleDir) if err != nil { return nil, err @@ -351,6 +364,15 @@ func (c *Container) Start(conf *boot.Config) error { return err } } else { + // Setup rootfs and mounts. It returns a new mount list with destination + // paths resolved. Replace the original spec with new mount list and start + // container. + cleanMounts, err := setupFS(c.Spec, conf, c.BundleDir) + if err != nil { + return fmt.Errorf("setup mounts: %v", err) + } + c.Spec.Mounts = cleanMounts + // Create the gofer process. ioFiles, err := c.createGoferProcess(c.Spec, conf, c.BundleDir) if err != nil { @@ -691,10 +713,6 @@ func (c *Container) waitForStopped() error { } func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundleDir string) ([]*os.File, error) { - if err := setupFS(spec, conf, bundleDir); err != nil { - return nil, fmt.Errorf("failed to setup mounts: %v", err) - } - // Start with the general config flags. args := conf.ToFlags() args = append(args, "gofer", "--bundle", bundleDir) diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index e2bb7d8ec..662591b3b 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -458,7 +458,7 @@ func TestAppExitStatus(t *testing.T) { defer os.RemoveAll(rootDir2) defer os.RemoveAll(bundleDir2) - ws, err = Run(testutil.UniqueContainerID(), succSpec, conf, bundleDir2, "", "", "") + ws, err = Run(testutil.UniqueContainerID(), errSpec, conf, bundleDir2, "", "", "") if err != nil { t.Fatalf("error running container: %v", err) } diff --git a/runsc/container/fs.go b/runsc/container/fs.go index 59edd9488..2ed42fd93 100644 --- a/runsc/container/fs.go +++ b/runsc/container/fs.go @@ -73,9 +73,13 @@ var optionsMap = map[string]mapping{ // This allows the gofer serving the containers to be chroot under this // directory to create an extra layer to security in case the gofer gets // compromised. -func setupFS(spec *specs.Spec, conf *boot.Config, bundleDir string) error { +// Returns list of mounts equivalent to 'spec.Mounts' with all destination paths +// cleaned and with symlinks resolved. +func setupFS(spec *specs.Spec, conf *boot.Config, bundleDir string) ([]specs.Mount, error) { + rv := make([]specs.Mount, 0, len(spec.Mounts)) for _, m := range spec.Mounts { if m.Type != "bind" || !specutils.IsSupportedDevMount(m) { + rv = append(rv, m) continue } @@ -83,39 +87,47 @@ func setupFS(spec *specs.Spec, conf *boot.Config, bundleDir string) error { // container. dst, err := resolveSymlinks(spec.Root.Path, m.Destination) if err != nil { - return fmt.Errorf("failed to resolve symlinks: %v", err) + return nil, fmt.Errorf("failed to resolve symlinks: %v", err) } flags := optionsToFlags(m.Options) flags |= syscall.MS_BIND log.Infof("Mounting src: %q, dst: %q, flags: %#x", m.Source, dst, flags) if err := specutils.Mount(m.Source, dst, m.Type, flags); err != nil { - return fmt.Errorf("failed to mount %v: %v", m, err) + return nil, fmt.Errorf("failed to mount %v: %v", m, err) } // Make the mount a slave, so that for recursive bind mount, umount won't // propagate to the source. flags = syscall.MS_SLAVE | syscall.MS_REC if err := syscall.Mount("", dst, "", uintptr(flags), ""); err != nil { - return fmt.Errorf("failed to rslave mount dst: %q, flags: %#x, err: %v", dst, flags, err) + return nil, fmt.Errorf("failed to rslave mount dst: %q, flags: %#x, err: %v", dst, flags, err) } + + cpy := m + relDst, err := filepath.Rel(spec.Root.Path, dst) + if err != nil { + panic(fmt.Sprintf("%q could not be made relative to %q: %v", dst, spec.Root.Path, err)) + } + cpy.Destination = filepath.Join("/", relDst) + rv = append(rv, cpy) } // 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 + return nil, err } if readonly { - return nil + return rv, 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 + return rv, nil } // If root is a mount point but not read-only, we can change mount options @@ -124,10 +136,10 @@ func setupFS(spec *specs.Spec, conf *boot.Config, bundleDir string) error { 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 read-only with source: %q, target: %q, flags: %#x, err: %v", spec.Root.Path, spec.Root.Path, flags, err) + return nil, 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 + return rv, nil } // mountInfo returns whether the path is a mount point and whether the mount |