diff --git a/rootfs_linux.go b/rootfs_linux.go index d8c61e97..bbc59d19 100644 --- a/rootfs_linux.go +++ b/rootfs_linux.go @@ -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"}, diff --git a/rootfs_linux_test.go b/rootfs_linux_test.go new file mode 100644 index 00000000..d3e0cf36 --- /dev/null +++ b/rootfs_linux_test.go @@ -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) + } +}