Eval mount destination after each mount

User specified mounts much be evaluated after each mount because
symlinks in nested mounts can invalidate the next mount.

Also check that any bind mounts are not inside /proc or /sys to ensure
that we are able to mask over certian paths inside.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2015-04-21 13:46:27 -07:00
parent 08cf3beaf0
commit 3c25c9b9cf
2 changed files with 72 additions and 0 deletions

View File

@ -13,6 +13,7 @@ import (
"syscall"
"time"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/label"
@ -139,6 +140,16 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
// unable to bind anything to it.
return err
}
// ensure that the destination of the bind mount is resolved of symlinks at mount time because
// any previous mounts can invalidate the next mount's destination.
// this can happen when a user specifies mounts within other mounts to cause breakouts or other
// evil stuff to try to escape the container's rootfs.
if dest, err = symlink.FollowSymlinkInScope(filepath.Join(rootfs, m.Destination), rootfs); err != nil {
return err
}
if err := checkMountDestination(rootfs, dest); err != nil {
return err
}
if err := createIfNotExists(dest, stat.IsDir()); err != nil {
return err
}
@ -197,6 +208,38 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
return nil
}
// checkMountDestination checks to ensure that the mount destination is not over the
// top of /proc or /sys.
// dest is required to be an abs path and have any symlinks resolved before calling this function.
func checkMountDestination(rootfs, dest string) error {
invalidDestinations := []string{
"/proc",
"/sys",
}
for _, invalid := range invalidDestinations {
if dirIsChild(filepath.Join(rootfs, invalid), dest) {
return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid)
}
}
return nil
}
// dirIsChild compare the parts of the dir to check if it is located
// inside root. comparing the individual parts ensures that false positives
// are not found.
func dirIsChild(root, dir string) bool {
var (
rootParts = strings.Split(filepath.Clean(root), string(filepath.Separator))
dirParts = strings.Split(filepath.Clean(dir), string(filepath.Separator))
)
for i, p := range rootParts {
if p != dirParts[i] {
return false
}
}
return true
}
func setupDevSymlinks(rootfs string) error {
var links = [][2]string{
{"/proc/self/fd", "/dev/fd"},

29
rootfs_linux_test.go Normal file
View File

@ -0,0 +1,29 @@
// +build linux
package libcontainer
import "testing"
func TestCheckMountDestOnProc(t *testing.T) {
dest := "/rootfs/proc/"
err := checkMountDestination("/rootfs", dest)
if err == nil {
t.Fatal("destination inside proc should return an error")
}
}
func TestCheckMountDestInSys(t *testing.T) {
dest := "/rootfs//sys/fs/cgroup"
err := checkMountDestination("/rootfs", dest)
if err == nil {
t.Fatal("destination inside proc should return an error")
}
}
func TestCheckMountDestFalsePositive(t *testing.T) {
dest := "/rootfs/sysfiles/fs/cgroup"
err := checkMountDestination("/rootfs", dest)
if err != nil {
t.Fatal(err)
}
}