From d26ea78e42ebf18219b88e01c6252f30aa764aa2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 21:41:31 +0000 Subject: [PATCH 01/15] Move apparmor into security sub dir Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 0e85c0e4be..ce51db37f9 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" From 60a90970bc4add3547064004f08c19ab5027141b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 23:03:52 +0000 Subject: [PATCH 02/15] Add restrictions to proc in libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/create.go | 3 + daemon/execdriver/native/driver.go | 7 ++ pkg/libcontainer/nsinit/init.go | 2 +- pkg/libcontainer/nsinit/mount.go | 67 +++++++++++-------- .../security/restrict/restrict.go | 46 +++++++++++++ 5 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 pkg/libcontainer/security/restrict/restrict.go diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index ef17ce7042..1edbd17ad3 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -25,6 +25,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container container.Cgroups.Name = c.ID // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + container.Context["restriction_path"] = d.restrictionPath if err := d.createNetwork(container, c); err != nil { return nil, err @@ -81,6 +82,8 @@ func (d *driver) setPrivileged(container *libcontainer.Container) error { c.Enabled = true } container.Cgroups.DeviceAccess = true + delete(container.Context, "restriction_path") + if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "unconfined" } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index ab82cdcc65..31a2eb0dae 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -62,6 +62,7 @@ type driver struct { root string initPath string activeContainers map[string]*exec.Cmd + restrictionPath string } func NewDriver(root, initPath string) (*driver, error) { @@ -72,8 +73,14 @@ func NewDriver(root, initPath string) (*driver, error) { if err := apparmor.InstallDefaultProfile(filepath.Join(root, "../..", BackupApparmorProfilePath)); err != nil { return nil, err } + restrictionPath := filepath.Join(root, "empty") + if err := os.MkdirAll(restrictionPath, 0700); err != nil { + return nil, err + } + return &driver{ root: root, + restrictionPath: restrictionPath, initPath: initPath, activeContainers: make(map[string]*exec.Cmd), }, nil diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index ce51db37f9..fb3a895a78 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -61,7 +61,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil { + if err := setupNewMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index dd6b1c8a43..e4869a0ecb 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" "os" @@ -21,9 +22,9 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD // // There is no need to unmount the new mounts because as soon as the mount namespace // is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error { +func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { flag := syscall.MS_PRIVATE - if noPivotRoot { + if container.NoPivotRoot { flag = syscall.MS_SLAVE } if err := system.Mount("", "/", "", uintptr(flag|syscall.MS_REC), ""); err != nil { @@ -32,44 +33,28 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, mountLabel); err != nil { + if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { return fmt.Errorf("mount system %s", err) } - - for _, m := range bindMounts { - var ( - flags = syscall.MS_BIND | syscall.MS_REC - dest = filepath.Join(rootfs, m.Destination) - ) - if !m.Writable { - flags = flags | syscall.MS_RDONLY - } - if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { - return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) - } - if !m.Writable { - if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { - return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) - } - } - if m.Private { - if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { - return fmt.Errorf("mounting %s private %s", dest, err) - } + if err := setupBindmounts(rootfs, container.Mounts); err != nil { + return fmt.Errorf("bind mounts %s", err) + } + if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { + if err := restrict.Restrict(rootfs, restrictionPath); err != nil { + return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { return fmt.Errorf("copy dev nodes %s", err) } - if err := setupPtmx(rootfs, console, mountLabel); err != nil { + if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } - if noPivotRoot { + if container.NoPivotRoot { if err := rootMsMove(rootfs); err != nil { return err } @@ -79,7 +64,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons } } - if readonly { + if container.ReadonlyFs { if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } @@ -263,3 +248,29 @@ func remountSys() error { } return nil } + +func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { + for _, m := range bindMounts { + var ( + flags = syscall.MS_BIND | syscall.MS_REC + dest = filepath.Join(rootfs, m.Destination) + ) + if !m.Writable { + flags = flags | syscall.MS_RDONLY + } + if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { + return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) + } + if !m.Writable { + if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { + return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) + } + } + if m.Private { + if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { + return fmt.Errorf("mounting %s private %s", dest, err) + } + } + } + return nil +} diff --git a/pkg/libcontainer/security/restrict/restrict.go b/pkg/libcontainer/security/restrict/restrict.go new file mode 100644 index 0000000000..d5c1dbbe26 --- /dev/null +++ b/pkg/libcontainer/security/restrict/restrict.go @@ -0,0 +1,46 @@ +package restrict + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "path/filepath" + "syscall" +) + +const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY + +var restrictions = map[string]string{ + // dirs + "/proc/sys": "", + "/proc/irq": "", + "/proc/acpi": "", + + // files + "/proc/sysrq-trigger": "/dev/null", + "/proc/kcore": "/dev/null", +} + +// Restrict locks down access to many areas of proc +// by using the asumption that the user does not have mount caps to +// revert the changes made here +func Restrict(rootfs, empty string) error { + for dest, source := range restrictions { + dest = filepath.Join(rootfs, dest) + + // we don't have a "/dev/null" for dirs so have the requester pass a dir + // for us to bind mount + switch source { + case "": + source = empty + default: + source = filepath.Join(rootfs, source) + } + if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + } + return nil +} From 0779a8c3287fbf7ff1938df10897b551b839cbee Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 23:27:27 +0000 Subject: [PATCH 03/15] Add lxc support for restricting proc Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/lxc/driver.go | 34 +++++++++++++++++---------- daemon/execdriver/lxc/lxc_template.go | 9 ++++++- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 1ebb73e807..1232d608a3 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -59,9 +59,10 @@ func init() { } type driver struct { - root string // root path for the driver to use - apparmor bool - sharedRoot bool + root string // root path for the driver to use + apparmor bool + sharedRoot bool + restrictionPath string } func NewDriver(root string, apparmor bool) (*driver, error) { @@ -69,10 +70,15 @@ func NewDriver(root string, apparmor bool) (*driver, error) { if err := linkLxcStart(root); err != nil { return nil, err } + restrictionPath := filepath.Join(root, "empty") + if err := os.MkdirAll(restrictionPath, 0700); err != nil { + return nil, err + } return &driver{ - apparmor: apparmor, - root: root, - sharedRoot: rootIsShared(), + apparmor: apparmor, + root: root, + sharedRoot: rootIsShared(), + restrictionPath: restrictionPath, }, nil } @@ -403,14 +409,16 @@ func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { if err := LxcTemplateCompiled.Execute(fo, struct { *execdriver.Command - AppArmor bool - ProcessLabel string - MountLabel string + AppArmor bool + ProcessLabel string + MountLabel string + RestrictionSource string }{ - Command: c, - AppArmor: d.apparmor, - ProcessLabel: process, - MountLabel: mount, + Command: c, + AppArmor: d.apparmor, + ProcessLabel: process, + MountLabel: mount, + RestrictionSource: d.restrictionPath, }); err != nil { return "", err } diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index f4cb3d19eb..25c227ef15 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -109,8 +109,15 @@ lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabS {{if .AppArmor}} lxc.aa_profile = unconfined {{else}} -#lxc.aa_profile = unconfined +# not unconfined {{end}} +{{else}} +# restrict access to proc +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/sys none bind,ro 0 0 +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/irq none bind,ro 0 0 +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/acpi none bind,ro 0 0 +lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/sysrq-trigger none bind,ro 0 0 +lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/kcore none bind,ro 0 0 {{end}} # limits From 81e5026a6afb282589704fd5f6bcac9ed50108ea Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 11:45:39 +0000 Subject: [PATCH 04/15] No not mount sysfs by default for non privilged containers Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/lxc/lxc_template.go | 2 + daemon/execdriver/native/create.go | 11 +- integration-cli/docker_cli_run_test.go | 275 +------------------------ pkg/libcontainer/container.go | 27 ++- pkg/libcontainer/nsinit/mount.go | 42 ++-- 5 files changed, 67 insertions(+), 290 deletions(-) diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 25c227ef15..bc94e7a19d 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -88,7 +88,9 @@ lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noex # WARNING: sysfs is a known attack vector and should probably be disabled # if your userspace allows it. eg. see http://bit.ly/T9CkqJ +{{if .Privileged}} lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 +{{end}} {{if .Tty}} lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0 diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 1edbd17ad3..e26ff8d2b8 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -82,6 +82,9 @@ func (d *driver) setPrivileged(container *libcontainer.Container) error { c.Enabled = true } container.Cgroups.DeviceAccess = true + + // add sysfs as a mount for privileged containers + container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "sysfs"}) delete(container.Context, "restriction_path") if apparmor.IsEnabled() { @@ -101,7 +104,13 @@ func (d *driver) setupCgroups(container *libcontainer.Container, c *execdriver.C func (d *driver) setupMounts(container *libcontainer.Container, c *execdriver.Command) error { for _, m := range c.Mounts { - container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) + container.Mounts = append(container.Mounts, libcontainer.Mount{ + Type: "bind", + Source: m.Source, + Destination: m.Destination, + Writable: m.Writable, + Private: m.Private, + }) } return nil } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index d356f5f4de..40781294ae 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -389,279 +389,24 @@ func TestMultipleVolumesFrom(t *testing.T) { logDone("run - multiple volumes from") } -// this tests verifies the ID format for the container -func TestVerifyContainerID(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") - out, exit, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - if exit != 0 { - t.Fatalf("expected exit code 0 received %d", exit) - } - match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) - if err != nil { - t.Fatal(err) - } - if !match { - t.Fatalf("Invalid container ID: %s", out) +func TestSysNotAvaliableInNonPrivilegedContainers(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "ls", "/sys/kernel") + if code, err := runCommand(cmd); err == nil || code == 0 { + t.Fatal("sys should not be available in a non privileged container") } deleteAllContainers() - logDone("run - verify container ID") + logDone("run - sys not avaliable in non privileged container") } -// Test that creating a container with a volume doesn't crash. Regression test for #995. -func TestCreateVolume(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) +func TestSysAvaliableInPrivilegedContainers(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "ls", "/sys/kernel") + if code, err := runCommand(cmd); err != nil || code != 0 { + t.Fatalf("sys should be available in privileged container") } deleteAllContainers() - logDone("run - create docker mangaed volume") -} - -func TestExitCode(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") - - exit, err := runCommand(cmd) - if err == nil { - t.Fatal("should not have a non nil error") - } - if exit != 72 { - t.Fatalf("expected exit code 72 received %d", exit) - } - - deleteAllContainers() - - logDone("run - correct exit code") -} - -func TestUserDefaultsToRoot(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) - } - deleteAllContainers() - - logDone("run - default user") -} - -func TestUserByName(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) - } - deleteAllContainers() - - logDone("run - user by name") -} - -func TestUserByID(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { - t.Fatalf("expected daemon user got %s", out) - } - deleteAllContainers() - - logDone("run - user by id") -} - -func TestUserNotFound(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") - - _, err := runCommand(cmd) - if err == nil { - t.Fatal("unknown user should cause container to fail") - } - deleteAllContainers() - - logDone("run - user not found") -} - -func TestRunTwoConcurrentContainers(t *testing.T) { - group := sync.WaitGroup{} - group.Add(2) - - for i := 0; i < 2; i++ { - go func() { - defer group.Done() - cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - }() - } - - group.Wait() - - deleteAllContainers() - - logDone("run - two concurrent containers") -} - -func TestEnvironment(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "busybox", "env") - cmd.Env = append(os.Environ(), - "TRUE=false", - "TRICKY=tri\ncky\n", - ) - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - - actualEnv := strings.Split(out, "\n") - if actualEnv[len(actualEnv)-1] == "" { - actualEnv = actualEnv[:len(actualEnv)-1] - } - sort.Strings(actualEnv) - - goodEnv := []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "HOME=/", - "HOSTNAME=testing", - "FALSE=true", - "TRUE=false", - "TRICKY=tri", - "cky", - "", - } - sort.Strings(goodEnv) - if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", ")) - } - for i := range goodEnv { - if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) - } - } - - deleteAllContainers() - - logDone("run - verify environment") -} - -func TestContainerNetwork(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - - deleteAllContainers() - - logDone("run - test container network via ping") -} - -// Issue #4681 -func TestLoopbackWhenNetworkDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "127.0.0.1") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - - deleteAllContainers() - - logDone("run - test container loopback when networking disabled") -} - -func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ip", "a", "show", "up") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - - interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(out, -1) - if len(interfaces) != 1 { - t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) - } - if !strings.HasSuffix(interfaces[0], ": lo") { - t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) - } - - deleteAllContainers() - - logDone("run - test loopback only exists when networking disabled") -} - -func TestPrivilegedCanMknod(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test privileged can mknod") -} - -func TestUnPrivilegedCanMknod(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test un-privileged can mknod") -} - -func TestPrivilegedCanMount(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test privileged can mount") -} - -func TestUnPrivilegedCannotMount(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") - - out, _, err := runCommandWithOutput(cmd) - if err == nil { - t.Fatal(err, out) - } - - if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test un-privileged cannot mount") + logDone("run - sys avaliable in privileged container") } diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index c7cac35428..1e032c0642 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -23,7 +23,7 @@ type Container struct { Networks []*Network `json:"networks,omitempty"` // nil for host's network stack Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts []Mount `json:"mounts,omitempty"` + Mounts Mounts `json:"mounts,omitempty"` } // Network defines configuration for a container's networking stack @@ -38,11 +38,22 @@ type Network struct { Mtu int `json:"mtu,omitempty"` } -// Bind mounts from the host system to the container -// -type Mount struct { - Source string `json:"source"` // Source path, in the host namespace - Destination string `json:"destination"` // Destination path, in the container - Writable bool `json:"writable"` - Private bool `json:"private"` +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + +type Mount struct { + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` } diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index e4869a0ecb..ee480328c0 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -17,6 +17,14 @@ import ( // default mount point flags const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV +type mount struct { + source string + path string + device string + flags int + data string +} + // setupNewMountNamespace is used to initialize a new mount namespace for an new // container in the rootfs that is specified. // @@ -33,7 +41,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { + if err := mountSystem(rootfs, container); err != nil { return fmt.Errorf("mount system %s", err) } if err := setupBindmounts(rootfs, container.Mounts); err != nil { @@ -183,19 +191,8 @@ func setupConsole(rootfs, console string, mountLabel string) error { // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace -func mountSystem(rootfs string, mountLabel string) error { - for _, m := range []struct { - source string - path string - device string - flags int - data string - }{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, - {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } { +func mountSystem(rootfs string, container *libcontainer.Container) error { + for _, m := range newSystemMounts(rootfs, container.Context["mount_label"], container.Mounts) { if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { return fmt.Errorf("mkdirall %s %s", m.path, err) } @@ -249,8 +246,8 @@ func remountSys() error { return nil } -func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { - for _, m := range bindMounts { +func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { + for _, m := range bindMounts.OfType("bind") { var ( flags = syscall.MS_BIND | syscall.MS_REC dest = filepath.Join(rootfs, m.Destination) @@ -274,3 +271,16 @@ func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { } return nil } + +func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, + } + + if len(mounts.OfType("sysfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) + } + return systemMounts +} From 5ba1242bdc309352c2b0b9a1ef9e07fe835e4857 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:30:09 +0000 Subject: [PATCH 05/15] Mount over dev and only copy allowed nodes in Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/create.go | 2 ++ pkg/libcontainer/nsinit/mount.go | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index e26ff8d2b8..76d65bd15c 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -34,6 +34,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container if err := d.setPrivileged(container); err != nil { return nil, err } + } else { + container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"}) } if err := d.setupCgroups(container, c); err != nil { return nil, err diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index ee480328c0..81229ef2d3 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -47,14 +47,14 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } + if err := copyDevNodes(rootfs); err != nil { + return fmt.Errorf("copy dev nodes %s", err) + } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { if err := restrict.Restrict(rootfs, restrictionPath); err != nil { return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { - return fmt.Errorf("copy dev nodes %s", err) - } if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } @@ -273,12 +273,20 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { } func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - systemMounts := []mount{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + devMounts := []mount{ {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, } + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + } + + if len(mounts.OfType("devtmpfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) + } + systemMounts = append(systemMounts, devMounts...) + if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) } From de3d51b0a824e31d7e245aed958d53f436456699 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:42:53 +0000 Subject: [PATCH 06/15] Move console into its own package Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/console/console.go | 46 +++++++++++++++++++++++++++++ pkg/libcontainer/nsinit/mount.go | 41 +++---------------------- 2 files changed, 50 insertions(+), 37 deletions(-) create mode 100644 pkg/libcontainer/console/console.go diff --git a/pkg/libcontainer/console/console.go b/pkg/libcontainer/console/console.go new file mode 100644 index 0000000000..deee544184 --- /dev/null +++ b/pkg/libcontainer/console/console.go @@ -0,0 +1,46 @@ +// +build linux + +package console + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/label" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Setup initializes the proper /dev/console inside the rootfs path +func Setup(rootfs, consolePath, mountLabel string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + stat, err := os.Stat(consolePath) + if err != nil { + return fmt.Errorf("stat console %s %s", consolePath, err) + } + var ( + st = stat.Sys().(*syscall.Stat_t) + dest = filepath.Join(rootfs, "dev/console") + ) + if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove %s %s", dest, err) + } + if err := os.Chmod(consolePath, 0600); err != nil { + return err + } + if err := os.Chown(consolePath, 0, 0); err != nil { + return err + } + if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { + return fmt.Errorf("mknod %s %s", dest, err) + } + if err := label.SetFileLabel(consolePath, mountLabel); err != nil { + return fmt.Errorf("set file label %s %s", dest, err) + } + if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("bind %s to %s %s", consolePath, dest, err) + } + return nil +} diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 81229ef2d3..c85058a9f6 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" @@ -155,40 +156,6 @@ func copyDevNode(rootfs, node string) error { return nil } -// setupConsole ensures that the container has a proper /dev/console setup -func setupConsole(rootfs, console string, mountLabel string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - stat, err := os.Stat(console) - if err != nil { - return fmt.Errorf("stat console %s %s", console, err) - } - var ( - st = stat.Sys().(*syscall.Stat_t) - dest = filepath.Join(rootfs, "dev/console") - ) - if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s %s", dest, err) - } - if err := os.Chmod(console, 0600); err != nil { - return err - } - if err := os.Chown(console, 0, 0); err != nil { - return err - } - if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { - return fmt.Errorf("mknod %s %s", dest, err) - } - if err := label.SetFileLabel(console, mountLabel); err != nil { - return fmt.Errorf("SetFileLabel Failed %s %s", dest, err) - } - if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { - return fmt.Errorf("bind %s to %s %s", console, dest, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -205,7 +172,7 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { // setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and // finishes setting up /dev/console -func setupPtmx(rootfs, console string, mountLabel string) error { +func setupPtmx(rootfs, consolePath, mountLabel string) error { ptmx := filepath.Join(rootfs, "dev/ptmx") if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { return err @@ -213,8 +180,8 @@ func setupPtmx(rootfs, console string, mountLabel string) error { if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } - if console != "" { - if err := setupConsole(rootfs, console, mountLabel); err != nil { + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { return err } } From 05b611574f85c7ff7d479e04e01ac2b57b233591 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:06:56 +0000 Subject: [PATCH 07/15] Refactor mounts into pkg to make changes easier Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- .../{nsinit/mount.go => mount/init.go} | 153 +++--------------- pkg/libcontainer/mount/msmoveroot.go | 19 +++ pkg/libcontainer/mount/nodes/nodes.go | 49 ++++++ pkg/libcontainer/mount/pivotroot.go | 31 ++++ pkg/libcontainer/mount/ptmx.go | 26 +++ pkg/libcontainer/mount/readonly.go | 12 ++ pkg/libcontainer/mount/remount.go | 31 ++++ pkg/libcontainer/nsinit/execin.go | 5 +- pkg/libcontainer/nsinit/init.go | 3 +- 9 files changed, 191 insertions(+), 138 deletions(-) rename pkg/libcontainer/{nsinit/mount.go => mount/init.go} (51%) create mode 100644 pkg/libcontainer/mount/msmoveroot.go create mode 100644 pkg/libcontainer/mount/nodes/nodes.go create mode 100644 pkg/libcontainer/mount/pivotroot.go create mode 100644 pkg/libcontainer/mount/ptmx.go create mode 100644 pkg/libcontainer/mount/readonly.go create mode 100644 pkg/libcontainer/mount/remount.go diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/mount/init.go similarity index 51% rename from pkg/libcontainer/nsinit/mount.go rename to pkg/libcontainer/mount/init.go index c85058a9f6..2a5e47a4a4 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/mount/init.go @@ -1,15 +1,14 @@ // +build linux -package nsinit +package mount import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/console" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" - "io/ioutil" "os" "path/filepath" "syscall" @@ -26,13 +25,13 @@ type mount struct { data string } -// setupNewMountNamespace is used to initialize a new mount namespace for an new -// container in the rootfs that is specified. -// -// There is no need to unmount the new mounts because as soon as the mount namespace -// is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { - flag := syscall.MS_PRIVATE +// InitializeMountNamespace setups up the devices, mount points, and filesystems for use inside a +// new mount namepsace +func InitializeMountNamespace(rootfs, console string, container *libcontainer.Container) error { + var ( + err error + flag = syscall.MS_PRIVATE + ) if container.NoPivotRoot { flag = syscall.MS_SLAVE } @@ -48,7 +47,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := copyDevNodes(rootfs); err != nil { + if err := nodes.CopyN(rootfs, nodes.DefaultNodes); err != nil { return fmt.Errorf("copy dev nodes %s", err) } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { @@ -56,7 +55,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return fmt.Errorf("restrict %s", err) } } - if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { + if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { @@ -64,18 +63,17 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont } if container.NoPivotRoot { - if err := rootMsMove(rootfs); err != nil { - return err - } + err = MsMoveRoot(rootfs) } else { - if err := rootPivot(rootfs); err != nil { - return err - } + err = PivotRoot(rootfs) + } + if err != nil { + return err } if container.ReadonlyFs { - if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { - return fmt.Errorf("mounting %s as readonly %s", rootfs, err) + if err := SetReadonly(); err != nil { + return fmt.Errorf("set readonly %s", err) } } @@ -84,78 +82,6 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return nil } -// use a pivot root to setup the rootfs -func rootPivot(rootfs string) error { - pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") - if err != nil { - return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) - } - if err := system.Pivotroot(rootfs, pivotDir); err != nil { - return fmt.Errorf("pivot_root %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - // path to pivot dir now changed, update - pivotDir = filepath.Join("/", filepath.Base(pivotDir)) - if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { - return fmt.Errorf("unmount pivot_root dir %s", err) - } - if err := os.Remove(pivotDir); err != nil { - return fmt.Errorf("remove pivot_root dir %s", err) - } - return nil -} - -// use MS_MOVE and chroot to setup the rootfs -func rootMsMove(rootfs string) error { - if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { - return fmt.Errorf("mount move %s into / %s", rootfs, err) - } - if err := system.Chroot("."); err != nil { - return fmt.Errorf("chroot . %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - return nil -} - -// copyDevNodes mknods the hosts devices so the new container has access to them -func copyDevNodes(rootfs string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - for _, node := range []string{ - "null", - "zero", - "full", - "random", - "urandom", - "tty", - } { - if err := copyDevNode(rootfs, node); err != nil { - return err - } - } - return nil -} - -func copyDevNode(rootfs, node string) error { - stat, err := os.Stat(filepath.Join("/dev", node)) - if err != nil { - return err - } - var ( - dest = filepath.Join(rootfs, "dev", node) - st = stat.Sys().(*syscall.Stat_t) - ) - if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { - return fmt.Errorf("copy %s %s", node, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -170,49 +96,6 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { return nil } -// setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and -// finishes setting up /dev/console -func setupPtmx(rootfs, consolePath, mountLabel string) error { - ptmx := filepath.Join(rootfs, "dev/ptmx") - if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { - return err - } - if err := os.Symlink("pts/ptmx", ptmx); err != nil { - return fmt.Errorf("symlink dev ptmx %s", err) - } - if consolePath != "" { - if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { - return err - } - } - return nil -} - -// remountProc is used to detach and remount the proc filesystem -// commonly needed with running a new process inside an existing container -func remountProc() error { - if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { - return err - } - if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { - return err - } - return nil -} - -func remountSys() error { - if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { - if err != syscall.EINVAL { - return err - } - } else { - if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { - return err - } - } - return nil -} - func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { for _, m := range bindMounts.OfType("bind") { var ( diff --git a/pkg/libcontainer/mount/msmoveroot.go b/pkg/libcontainer/mount/msmoveroot.go new file mode 100644 index 0000000000..b336c86495 --- /dev/null +++ b/pkg/libcontainer/mount/msmoveroot.go @@ -0,0 +1,19 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func MsMoveRoot(rootfs string) error { + if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { + return fmt.Errorf("mount move %s into / %s", rootfs, err) + } + if err := system.Chroot("."); err != nil { + return fmt.Errorf("chroot . %s", err) + } + return system.Chdir("/") +} diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go new file mode 100644 index 0000000000..5022f85b0b --- /dev/null +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -0,0 +1,49 @@ +// +build linux + +package nodes + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Default list of device nodes to copy +var DefaultNodes = []string{ + "null", + "zero", + "full", + "random", + "urandom", + "tty", +} + +// CopyN copies the device node from the host into the rootfs +func CopyN(rootfs string, nodesToCopy []string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + for _, node := range nodesToCopy { + if err := Copy(rootfs, node); err != nil { + return err + } + } + return nil +} + +func Copy(rootfs, node string) error { + stat, err := os.Stat(filepath.Join("/dev", node)) + if err != nil { + return err + } + var ( + dest = filepath.Join(rootfs, "dev", node) + st = stat.Sys().(*syscall.Stat_t) + ) + if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { + return fmt.Errorf("copy %s %s", node, err) + } + return nil +} diff --git a/pkg/libcontainer/mount/pivotroot.go b/pkg/libcontainer/mount/pivotroot.go new file mode 100644 index 0000000000..447f5904b2 --- /dev/null +++ b/pkg/libcontainer/mount/pivotroot.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "io/ioutil" + "os" + "path/filepath" + "syscall" +) + +func PivotRoot(rootfs string) error { + pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") + if err != nil { + return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) + } + if err := system.Pivotroot(rootfs, pivotDir); err != nil { + return fmt.Errorf("pivot_root %s", err) + } + if err := system.Chdir("/"); err != nil { + return fmt.Errorf("chdir / %s", err) + } + // path to pivot dir now changed, update + pivotDir = filepath.Join("/", filepath.Base(pivotDir)) + if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { + return fmt.Errorf("unmount pivot_root dir %s", err) + } + return os.Remove(pivotDir) +} diff --git a/pkg/libcontainer/mount/ptmx.go b/pkg/libcontainer/mount/ptmx.go new file mode 100644 index 0000000000..f6ca534637 --- /dev/null +++ b/pkg/libcontainer/mount/ptmx.go @@ -0,0 +1,26 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer/console" + "os" + "path/filepath" +) + +func SetupPtmx(rootfs, consolePath, mountLabel string) error { + ptmx := filepath.Join(rootfs, "dev/ptmx") + if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Symlink("pts/ptmx", ptmx); err != nil { + return fmt.Errorf("symlink dev ptmx %s", err) + } + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { + return err + } + } + return nil +} diff --git a/pkg/libcontainer/mount/readonly.go b/pkg/libcontainer/mount/readonly.go new file mode 100644 index 0000000000..0658358ad6 --- /dev/null +++ b/pkg/libcontainer/mount/readonly.go @@ -0,0 +1,12 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func SetReadonly() error { + return system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, "") +} diff --git a/pkg/libcontainer/mount/remount.go b/pkg/libcontainer/mount/remount.go new file mode 100644 index 0000000000..3e00509ae0 --- /dev/null +++ b/pkg/libcontainer/mount/remount.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func RemountProc() error { + if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { + return err + } + if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { + return err + } + return nil +} + +func RemountSys() error { + if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + return err + } + } else { + if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { + return err + } + } + return nil +} diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index 9017af06e9..b79881015f 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/system" "os" "path/filepath" @@ -63,10 +64,10 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s if err := system.Unshare(syscall.CLONE_NEWNS); err != nil { return -1, err } - if err := remountProc(); err != nil { + if err := mount.RemountProc(); err != nil { return -1, fmt.Errorf("remount proc %s", err) } - if err := remountSys(); err != nil { + if err := mount.RemountSys(); err != nil { return -1, fmt.Errorf("remount sys %s", err) } goto dropAndExec diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index fb3a895a78..6e6b0e5a8e 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" @@ -61,7 +62,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { From a949d39f195e7b87288b10b0ef31843e6a3d8eb0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:15:28 +0000 Subject: [PATCH 08/15] Move rest of console functions to pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/console/console.go | 14 ++++++++++++ pkg/libcontainer/nsinit/init.go | 34 +++++++---------------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/pkg/libcontainer/console/console.go b/pkg/libcontainer/console/console.go index deee544184..05cd08a92e 100644 --- a/pkg/libcontainer/console/console.go +++ b/pkg/libcontainer/console/console.go @@ -44,3 +44,17 @@ func Setup(rootfs, consolePath, mountLabel string) error { } return nil } + +func OpenAndDup(consolePath string) error { + slave, err := system.OpenTerminal(consolePath, syscall.O_RDWR) + if err != nil { + return fmt.Errorf("open terminal %s", err) + } + if err := system.Dup2(slave.Fd(), 0); err != nil { + return err + } + if err := system.Dup2(slave.Fd(), 1); err != nil { + return err + } + return system.Dup2(slave.Fd(), 2) +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 6e6b0e5a8e..9aac9a40eb 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" @@ -22,7 +23,7 @@ import ( // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // and other options required for the new container. -func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error { +func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error { rootfs, err := utils.ResolveRootfs(uncleanRootfs) if err != nil { return err @@ -38,20 +39,16 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol ns.logger.Println("received context from parent") syncPipe.Close() - if console != "" { - ns.logger.Printf("setting up %s as console\n", console) - slave, err := system.OpenTerminal(console, syscall.O_RDWR) - if err != nil { - return fmt.Errorf("open terminal %s", err) - } - if err := dupSlave(slave); err != nil { - return fmt.Errorf("dup2 slave %s", err) + if consolePath != "" { + ns.logger.Printf("setting up %s as console\n", consolePath) + if err := console.OpenAndDup(consolePath); err != nil { + return err } } if _, err := system.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } - if console != "" { + if consolePath != "" { if err := system.Setctty(); err != nil { return fmt.Errorf("setctty %s", err) } @@ -62,7 +59,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, consolePath, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { @@ -116,21 +113,6 @@ func setupUser(container *libcontainer.Container) error { return nil } -// dupSlave dup2 the pty slave's fd into stdout and stdin and ensures that -// the slave's fd is 0, or stdin -func dupSlave(slave *os.File) error { - if err := system.Dup2(slave.Fd(), 0); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 1); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 2); err != nil { - return err - } - return nil -} - // setupVethNetwork uses the Network config if it is not nil to initialize // the new veth interface inside the container for use by changing the name to eth0 // setting the MTU and IP address along with the default gateway From 156987c118f6f4067794e09e90aabeee0002d05c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:26:23 +0000 Subject: [PATCH 09/15] Move mounts into types.go Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 20 -------------------- pkg/libcontainer/mount/init.go | 11 +++++------ pkg/libcontainer/types.go | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 1e032c0642..ddcc6cab70 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -37,23 +37,3 @@ type Network struct { Gateway string `json:"gateway,omitempty"` Mtu int `json:"mtu,omitempty"` } - -type Mounts []Mount - -func (s Mounts) OfType(t string) Mounts { - out := Mounts{} - for _, m := range s { - if m.Type == t { - out = append(out, m) - } - } - return out -} - -type Mount struct { - Type string `json:"type,omitempty"` - Source string `json:"source,omitempty"` // Source path, in the host namespace - Destination string `json:"destination,omitempty"` // Destination path, in the container - Writable bool `json:"writable,omitempty"` - Private bool `json:"private,omitempty"` -} diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 2a5e47a4a4..06b2c82f56 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -122,12 +122,9 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { return nil } +// TODO: this is crappy right now and should be cleaned up with a better way of handling system and +// standard bind mounts allowing them to be more dymanic func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - devMounts := []mount{ - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } - systemMounts := []mount{ {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, } @@ -135,7 +132,9 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo if len(mounts.OfType("devtmpfs")) == 1 { systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) } - systemMounts = append(systemMounts, devMounts...) + systemMounts = append(systemMounts, + mount{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + mount{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}) if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index d4818c3ffe..ade3c32f1d 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -11,6 +11,26 @@ var ( ErrUnsupported = errors.New("Unsupported method") ) +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + +type Mount struct { + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` +} + // namespaceList is used to convert the libcontainer types // into the names of the files located in /proc//ns/* for // each namespace From 7a0b3610664c2269fd5932f294adae72e6e54020 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:28:56 +0000 Subject: [PATCH 10/15] Move capabilities into security pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 2 +- pkg/libcontainer/{ => security}/capabilities/capabilities.go | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pkg/libcontainer/{ => security}/capabilities/capabilities.go (100%) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 9aac9a40eb..22fdfbeeb7 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -11,11 +11,11 @@ import ( "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" + "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" diff --git a/pkg/libcontainer/capabilities/capabilities.go b/pkg/libcontainer/security/capabilities/capabilities.go similarity index 100% rename from pkg/libcontainer/capabilities/capabilities.go rename to pkg/libcontainer/security/capabilities/capabilities.go From 2d6c3674349c09318e8d1fb3ce43dbabc15c97da Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:31:45 +0000 Subject: [PATCH 11/15] Increment native driver version with these changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 31a2eb0dae..8b374d9938 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -23,7 +23,7 @@ import ( const ( DriverName = "native" - Version = "0.1" + Version = "0.2" BackupApparmorProfilePath = "apparmor/docker.back" // relative to docker root ) From 2d31aeb911fc94baa88f975110c5ccd45d041acb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:44:11 +0000 Subject: [PATCH 12/15] Update container.json and readme Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/README.md | 193 ++++++++++++++++++++++---------- pkg/libcontainer/container.json | 192 +++++++++++++++++++++++-------- 2 files changed, 277 insertions(+), 108 deletions(-) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index d6d0fbae44..31031b26cd 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -16,76 +16,149 @@ process are specified in this file. The configuration is used for each process Sample `container.json` file: ```json { + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], "networks" : [ { - "gateway" : "172.17.42.1", + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", "context" : { "bridge" : "docker0", "prefix" : "veth" }, - "address" : "172.17.0.2/16", - "type" : "veth", - "mtu" : 1500 - } - ], - "cgroups" : { - "parent" : "docker", - "name" : "11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620" - }, - "tty" : true, - "environment" : [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "HOSTNAME=11bb30683fb0", - "TERM=xterm" - ], - "capabilities_mask" : [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "context" : { - "apparmor_profile" : "docker-default" - }, - "mounts" : [ - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/resolv.conf", - "writable" : false, - "destination" : "/etc/resolv.conf", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hostname", - "writable" : false, - "destination" : "/etc/hostname", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hosts", - "writable" : false, - "destination" : "/etc/hosts", - "private" : true + "gateway" : "172.17.42.1" } ], "namespaces" : [ - "NEWNS", - "NEWUTS", - "NEWIPC", - "NEWPID", - "NEWNET" + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } ] } ``` diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index f045315a41..f15a49ab05 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -1,50 +1,146 @@ { - "hostname": "koye", - "tty": true, - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm-256color" - ], - "namespaces": [ - "NEWIPC", - "NEWNS", - "NEWPID", - "NEWUTS", - "NEWNET" - ], - "capabilities_mask": [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "networks": [{ - "type": "veth", - "context": { - "bridge": "docker0", - "prefix": "dock" - }, - "address": "172.17.0.100/16", - "gateway": "172.17.42.1", - "mtu": 1500 - } - ], - "cgroups": { - "name": "docker-koye", - "parent": "docker", - "memory": 5248000 - } + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], + "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], + "networks" : [ + { + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", + "context" : { + "bridge" : "docker0", + "prefix" : "veth" + }, + "gateway" : "172.17.42.1" + } + ], + "namespaces" : [ + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } + ] } From 90678b31331de54598c7a6665c3e7a78bfe6ed63 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 18 Apr 2014 14:35:16 -0700 Subject: [PATCH 13/15] Update create with apparmor import Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 277 +++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 40781294ae..5973f2fe1b 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -389,6 +389,283 @@ func TestMultipleVolumesFrom(t *testing.T) { logDone("run - multiple volumes from") } +// this tests verifies the ID format for the container +func TestVerifyContainerID(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, exit, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + if exit != 0 { + t.Fatalf("expected exit code 0 received %d", exit) + } + match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) + if err != nil { + t.Fatal(err) + } + if !match { + t.Fatalf("Invalid container ID: %s", out) + } + + deleteAllContainers() + + logDone("run - verify container ID") +} + +// Test that creating a container with a volume doesn't crash. Regression test for #995. +func TestCreateVolume(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - create docker mangaed volume") +} + +func TestExitCode(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") + + exit, err := runCommand(cmd) + if err == nil { + t.Fatal("should not have a non nil error") + } + if exit != 72 { + t.Fatalf("expected exit code 72 received %d", exit) + } + + deleteAllContainers() + + logDone("run - correct exit code") +} + +func TestUserDefaultsToRoot(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("expected root user got %s", out) + } + deleteAllContainers() + + logDone("run - default user") +} + +func TestUserByName(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("expected root user got %s", out) + } + deleteAllContainers() + + logDone("run - user by name") +} + +func TestUserByID(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { + t.Fatalf("expected daemon user got %s", out) + } + deleteAllContainers() + + logDone("run - user by id") +} + +func TestUserNotFound(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") + + _, err := runCommand(cmd) + if err == nil { + t.Fatal("unknown user should cause container to fail") + } + deleteAllContainers() + + logDone("run - user not found") +} + +func TestRunTwoConcurrentContainers(t *testing.T) { + group := sync.WaitGroup{} + group.Add(2) + + for i := 0; i < 2; i++ { + go func() { + defer group.Done() + cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + }() + } + + group.Wait() + + deleteAllContainers() + + logDone("run - two concurrent containers") +} + +func TestEnvironment(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "busybox", "env") + cmd.Env = append(os.Environ(), + "TRUE=false", + "TRICKY=tri\ncky\n", + ) + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + actualEnv := strings.Split(out, "\n") + if actualEnv[len(actualEnv)-1] == "" { + actualEnv = actualEnv[:len(actualEnv)-1] + } + sort.Strings(actualEnv) + + goodEnv := []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOME=/", + "HOSTNAME=testing", + "FALSE=true", + "TRUE=false", + "TRICKY=tri", + "cky", + "", + } + sort.Strings(goodEnv) + if len(goodEnv) != len(actualEnv) { + t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", ")) + } + for i := range goodEnv { + if actualEnv[i] != goodEnv[i] { + t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + } + } + + deleteAllContainers() + + logDone("run - verify environment") +} + +func TestContainerNetwork(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - test container network via ping") +} + +// Issue #4681 +func TestLoopbackWhenNetworkDisabled(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "127.0.0.1") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - test container loopback when networking disabled") +} + +func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ip", "a", "show", "up") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(out, -1) + if len(interfaces) != 1 { + t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) + } + if !strings.HasSuffix(interfaces[0], ": lo") { + t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) + } + + deleteAllContainers() + + logDone("run - test loopback only exists when networking disabled") +} + +func TestPrivilegedCanMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test privileged can mknod") +} + +func TestUnPrivilegedCanMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test un-privileged can mknod") +} + +func TestPrivilegedCanMount(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test privileged can mount") +} + +func TestUnPrivilegedCannotMount(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") + + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual == "ok" { + t.Fatalf("expected output not ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test un-privileged cannot mount") +} + func TestSysNotAvaliableInNonPrivilegedContainers(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "busybox", "ls", "/sys/kernel") if code, err := runCommand(cmd); err == nil || code == 0 { From fa5cabf9fe9e257d64638043ca2fd08a7bf96cb3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 21 Apr 2014 12:07:07 -0700 Subject: [PATCH 14/15] Update init for new apparmor import path Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 22fdfbeeb7..67095fdba1 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -14,7 +14,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" - "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" From d5c9f61ecc1c8167322a8cc3b41f29a35c80b9b8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Apr 2014 18:12:07 -0700 Subject: [PATCH 15/15] Ignore isnot exists errors for proc paths Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/security/restrict/restrict.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/libcontainer/security/restrict/restrict.go b/pkg/libcontainer/security/restrict/restrict.go index d5c1dbbe26..291d6ca5dc 100644 --- a/pkg/libcontainer/security/restrict/restrict.go +++ b/pkg/libcontainer/security/restrict/restrict.go @@ -2,9 +2,11 @@ package restrict import ( "fmt" - "github.com/dotcloud/docker/pkg/system" + "os" "path/filepath" "syscall" + + "github.com/dotcloud/docker/pkg/system" ) const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY @@ -36,6 +38,9 @@ func Restrict(rootfs, empty string) error { source = filepath.Join(rootfs, source) } if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + if os.IsNotExist(err) { + continue + } return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) } if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil {