Prechádzať zdrojové kódy

Merge pull request #13073 from jfrazelle/add_1.6.1_commits

Add 1.6.1 commits
David Calavera 10 rokov pred
rodič
commit
23193efd93

+ 12 - 0
CHANGELOG.md

@@ -1,5 +1,17 @@
 # Changelog
 
+## 1.6.1 (2015-05-07)
+
+####  Security
+- Fix read/write /proc paths (CVE-2015-3630)
+- Prohibit VOLUME /proc and VOLUME / (CVE-2015-3631)
+- Fix opening of file-descriptor 1 (CVE-2015-3627)
+- Fix symlink traversal on container respawn allowing local privilege escalation (CVE-2015-3629)
+- Prohibit mount of /sys
+
+#### Runtime
+- Update Apparmor policy to not allow mounts
+
 ## 1.6.0 (2015-04-07)
 
 #### Builder

+ 1 - 8
daemon/execdriver/native/create.go

@@ -6,12 +6,10 @@ import (
 	"errors"
 	"fmt"
 	"net"
-	"path/filepath"
 	"strings"
 	"syscall"
 
 	"github.com/docker/docker/daemon/execdriver"
-	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/libcontainer/apparmor"
 	"github.com/docker/libcontainer/configs"
 	"github.com/docker/libcontainer/devices"
@@ -231,10 +229,6 @@ func (d *driver) setupMounts(container *configs.Config, c *execdriver.Command) e
 	container.Mounts = defaultMounts
 
 	for _, m := range c.Mounts {
-		dest, err := symlink.FollowSymlinkInScope(filepath.Join(c.Rootfs, m.Destination), c.Rootfs)
-		if err != nil {
-			return err
-		}
 		flags := syscall.MS_BIND | syscall.MS_REC
 		if !m.Writable {
 			flags |= syscall.MS_RDONLY
@@ -242,10 +236,9 @@ func (d *driver) setupMounts(container *configs.Config, c *execdriver.Command) e
 		if m.Slave {
 			flags |= syscall.MS_SLAVE
 		}
-
 		container.Mounts = append(container.Mounts, &configs.Mount{
 			Source:      m.Source,
-			Destination: dest,
+			Destination: m.Destination,
 			Device:      "bind",
 			Flags:       flags,
 		})

+ 8 - 1
daemon/execdriver/native/template/default_template.go

@@ -82,9 +82,16 @@ func New() *configs.Config {
 		},
 		MaskPaths: []string{
 			"/proc/kcore",
+			"/proc/latency_stats",
+			"/proc/timer_stats",
 		},
 		ReadonlyPaths: []string{
-			"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
+			"/proc/asound",
+			"/proc/bus",
+			"/proc/fs",
+			"/proc/irq",
+			"/proc/sys",
+			"/proc/sysrq-trigger",
 		},
 	}
 

+ 1 - 1
hack/vendor.sh

@@ -67,7 +67,7 @@ mv tmp-digest src/github.com/docker/distribution/digest
 mkdir -p src/github.com/docker/distribution/registry
 mv tmp-api src/github.com/docker/distribution/registry/api
 
-clone git github.com/docker/libcontainer 6607689b1d06743003a45a722d9fe0bef36b274e
+clone git github.com/docker/libcontainer 1b471834b45063b61e0aedefbb1739a8f34b414e
 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
 rm -rf src/github.com/docker/libcontainer/vendor
 eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"

+ 62 - 0
integration-cli/docker_cli_run_test.go

@@ -3056,3 +3056,65 @@ func (s *DockerSuite) TestRunPidHostWithChildIsKillable(c *check.C) {
 		c.Fatal("Kill container timed out")
 	}
 }
+
+func (s *DockerSuite) TestRunWithTooSmallMemoryLimit(c *check.C) {
+	defer deleteAllContainers()
+	// this memory limit is 1 byte less than the min, which is 4MB
+	// https://github.com/docker/docker/blob/v1.5.0/daemon/create.go#L22
+	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-m", "4194303", "busybox"))
+	if err == nil || !strings.Contains(out, "Minimum memory limit allowed is 4MB") {
+		c.Fatalf("expected run to fail when using too low a memory limit: %q", out)
+	}
+}
+
+func (s *DockerSuite) TestRunWriteToProcAsound(c *check.C) {
+	defer deleteAllContainers()
+	code, err := runCommand(exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "echo 111 >> /proc/asound/version"))
+	if err == nil || code == 0 {
+		c.Fatal("standard container should not be able to write to /proc/asound")
+	}
+}
+
+func (s *DockerSuite) TestRunReadProcTimer(c *check.C) {
+	defer deleteAllContainers()
+	out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/timer_stats"))
+	if err != nil || code != 0 {
+		c.Fatal(err)
+	}
+	if strings.Trim(out, "\n ") != "" {
+		c.Fatalf("expected to receive no output from /proc/timer_stats but received %q", out)
+	}
+}
+
+func (s *DockerSuite) TestRunReadProcLatency(c *check.C) {
+	// some kernels don't have this configured so skip the test if this file is not found
+	// on the host running the tests.
+	if _, err := os.Stat("/proc/latency_stats"); err != nil {
+		c.Skip("kernel doesnt have latency_stats configured")
+		return
+	}
+	defer deleteAllContainers()
+	out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/latency_stats"))
+	if err != nil || code != 0 {
+		c.Fatal(err)
+	}
+	if strings.Trim(out, "\n ") != "" {
+		c.Fatalf("expected to receive no output from /proc/latency_stats but received %q", out)
+	}
+}
+
+func (s *DockerSuite) TestMountIntoProc(c *check.C) {
+	defer deleteAllContainers()
+	code, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/proc//sys", "busybox", "true"))
+	if err == nil || code == 0 {
+		c.Fatal("container should not be able to mount into /proc")
+	}
+}
+
+func (s *DockerSuite) TestMountIntoSys(c *check.C) {
+	defer deleteAllContainers()
+	code, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/sys/", "busybox", "true"))
+	if err == nil || code == 0 {
+		c.Fatal("container should not be able to mount into /sys")
+	}
+}

+ 1 - 14
vendor/src/github.com/docker/libcontainer/SPEC.md

@@ -217,17 +217,6 @@ profile <profile_name> flags=(attach_disconnected,mediate_deleted) {
   file,
   umount,
 
-  mount fstype=tmpfs,
-  mount fstype=mqueue,
-  mount fstype=fuse.*,
-  mount fstype=binfmt_misc -> /proc/sys/fs/binfmt_misc/,
-  mount fstype=efivarfs -> /sys/firmware/efi/efivars/,
-  mount fstype=fusectl -> /sys/fs/fuse/connections/,
-  mount fstype=securityfs -> /sys/kernel/security/,
-  mount fstype=debugfs -> /sys/kernel/debug/,
-  mount fstype=proc -> /proc/,
-  mount fstype=sysfs -> /sys/,
-
   deny @{PROC}/sys/fs/** wklx,
   deny @{PROC}/sysrq-trigger rwklx,
   deny @{PROC}/mem rwklx,
@@ -235,9 +224,7 @@ profile <profile_name> flags=(attach_disconnected,mediate_deleted) {
   deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
   deny @{PROC}/sys/kernel/*/** wklx,
 
-  deny mount options=(ro, remount) -> /,
-  deny mount fstype=debugfs -> /var/lib/ureadahead/debugfs/,
-  deny mount fstype=devpts,
+  deny mount,
 
   deny /sys/[^f]*/** wklx,
   deny /sys/f[^s]*/** wklx,

+ 1 - 14
vendor/src/github.com/docker/libcontainer/apparmor/gen.go

@@ -27,17 +27,6 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
   file,
   umount,
 
-  mount fstype=tmpfs,
-  mount fstype=mqueue,
-  mount fstype=fuse.*,
-  mount fstype=binfmt_misc -> /proc/sys/fs/binfmt_misc/,
-  mount fstype=efivarfs -> /sys/firmware/efi/efivars/,
-  mount fstype=fusectl -> /sys/fs/fuse/connections/,
-  mount fstype=securityfs -> /sys/kernel/security/,
-  mount fstype=debugfs -> /sys/kernel/debug/,
-  mount fstype=proc -> /proc/,
-  mount fstype=sysfs -> /sys/,
-
   deny @{PROC}/sys/fs/** wklx,
   deny @{PROC}/sysrq-trigger rwklx,
   deny @{PROC}/mem rwklx,
@@ -45,9 +34,7 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
   deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
   deny @{PROC}/sys/kernel/*/** wklx,
 
-  deny mount options=(ro, remount) -> /,
-  deny mount fstype=debugfs -> /var/lib/ureadahead/debugfs/,
-  deny mount fstype=devpts,
+  deny mount,
 
   deny /sys/[^f]*/** wklx,
   deny /sys/f[^s]*/** wklx,

+ 42 - 8
vendor/src/github.com/docker/libcontainer/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"
@@ -48,11 +49,6 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
 	if err := setupPtmx(config, console); err != nil {
 		return newSystemError(err)
 	}
-	// stdin, stdout and stderr could be pointing to /dev/null from parent namespace.
-	// re-open them inside this namespace.
-	if err := reOpenDevNull(config.Rootfs); err != nil {
-		return newSystemError(err)
-	}
 	if err := setupDevSymlinks(config.Rootfs); err != nil {
 		return newSystemError(err)
 	}
@@ -67,6 +63,9 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
 	if err != nil {
 		return newSystemError(err)
 	}
+	if err := reOpenDevNull(config.Rootfs); err != nil {
+		return newSystemError(err)
+	}
 	if config.Readonlyfs {
 		if err := setReadonly(); err != nil {
 			return newSystemError(err)
@@ -139,6 +138,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 +206,29 @@ 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 {
+	if filepath.Clean(rootfs) == filepath.Clean(dest) {
+		return fmt.Errorf("mounting into / is prohibited")
+	}
+	invalidDestinations := []string{
+		"/proc",
+		"/sys",
+	}
+	for _, invalid := range invalidDestinations {
+		path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest)
+		if err != nil {
+			return err
+		}
+		if path == "." || !strings.HasPrefix(path, "..") {
+			return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid)
+		}
+	}
+	return nil
+}
+
 func setupDevSymlinks(rootfs string) error {
 	var links = [][2]string{
 		{"/proc/self/fd", "/dev/fd"},
@@ -221,11 +253,13 @@ func setupDevSymlinks(rootfs string) error {
 	return nil
 }
 
-// If stdin, stdout or stderr are pointing to '/dev/null' in the global mount namespace,
-// this method will make them point to '/dev/null' in this namespace.
+// If stdin, stdout, and/or stderr are pointing to `/dev/null` in the parent's rootfs
+// this method will make them point to `/dev/null` in this container's rootfs.  This
+// needs to be called after we chroot/pivot into the container's rootfs so that any
+// symlinks are resolved locally.
 func reOpenDevNull(rootfs string) error {
 	var stat, devNullStat syscall.Stat_t
-	file, err := os.Open(filepath.Join(rootfs, "/dev/null"))
+	file, err := os.Open("/dev/null")
 	if err != nil {
 		return fmt.Errorf("Failed to open /dev/null - %s", err)
 	}

+ 37 - 0
vendor/src/github.com/docker/libcontainer/rootfs_linux_test.go

@@ -0,0 +1,37 @@
+// +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)
+	}
+}
+
+func TestCheckMountRoot(t *testing.T) {
+	dest := "/rootfs"
+	err := checkMountDestination("/rootfs", dest)
+	if err == nil {
+		t.Fatal(err)
+	}
+}