package daemon // import "github.com/docker/docker/daemon" import ( "context" "os" "path/filepath" "testing" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/daemon/config" "github.com/docker/docker/daemon/network" "github.com/docker/docker/libnetwork" nwconfig "github.com/docker/docker/libnetwork/config" "github.com/google/go-cmp/cmp/cmpopts" "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" ) func setupFakeDaemon(t *testing.T, c *container.Container) *Daemon { t.Helper() root := t.TempDir() rootfs := filepath.Join(root, "rootfs") err := os.MkdirAll(rootfs, 0o755) assert.NilError(t, err) netController, err := libnetwork.New(nwconfig.OptionDataDir(t.TempDir())) assert.NilError(t, err) d := &Daemon{ // some empty structs to avoid getting a panic // caused by a null pointer dereference linkIndex: newLinkIndex(), netController: netController, imageService: &fakeImageService{}, } c.Root = root c.BaseFS = rootfs if c.Config == nil { c.Config = new(containertypes.Config) } if c.HostConfig == nil { c.HostConfig = new(containertypes.HostConfig) } if c.NetworkSettings == nil { c.NetworkSettings = &network.Settings{Networks: make(map[string]*network.EndpointSettings)} } // HORRIBLE HACK: clean up shm mounts leaked by some tests. Otherwise the // offending tests would fail due to the mounts blocking the temporary // directory from being cleaned up. t.Cleanup(func() { if c.ShmPath != "" { var err error for err == nil { // Some tests over-mount over the same path multiple times. err = unix.Unmount(c.ShmPath, unix.MNT_DETACH) } } }) return d } type fakeImageService struct { ImageService } func (i *fakeImageService) StorageDriver() string { return "overlay" } // TestTmpfsDevShmNoDupMount checks that a user-specified /dev/shm tmpfs // mount (as in "docker run --tmpfs /dev/shm:rw,size=NNN") does not result // in "Duplicate mount point" error from the engine. // https://github.com/moby/moby/issues/35455 func TestTmpfsDevShmNoDupMount(t *testing.T) { skip.If(t, os.Getuid() != 0, "skipping test that requires root") c := &container.Container{ ShmPath: "foobar", // non-empty, for c.IpcMounts() to work HostConfig: &containertypes.HostConfig{ IpcMode: containertypes.IPCModeShareable, // default mode // --tmpfs /dev/shm:rw,exec,size=NNN Tmpfs: map[string]string{ "/dev/shm": "rw,exec,size=1g", }, }, } d := setupFakeDaemon(t, c) _, err := d.createSpec(context.TODO(), &configStore{}, c, nil) assert.Check(t, err) } // TestIpcPrivateVsReadonly checks that in case of IpcMode: private // and ReadonlyRootfs: true (as in "docker run --ipc private --read-only") // the resulting /dev/shm mount is NOT made read-only. // https://github.com/moby/moby/issues/36503 func TestIpcPrivateVsReadonly(t *testing.T) { skip.If(t, os.Getuid() != 0, "skipping test that requires root") c := &container.Container{ HostConfig: &containertypes.HostConfig{ IpcMode: containertypes.IPCModePrivate, ReadonlyRootfs: true, }, } d := setupFakeDaemon(t, c) s, err := d.createSpec(context.TODO(), &configStore{}, c, nil) assert.Check(t, err) // Find the /dev/shm mount in ms, check it does not have ro for _, m := range s.Mounts { if m.Destination != "/dev/shm" { continue } assert.Check(t, is.Equal(false, inSlice(m.Options, "ro"))) } } // TestSysctlOverride ensures that any implicit sysctls (such as // Config.Domainname) are overridden by an explicit sysctl in the HostConfig. func TestSysctlOverride(t *testing.T) { skip.If(t, os.Getuid() != 0, "skipping test that requires root") c := &container.Container{ Config: &containertypes.Config{ Hostname: "foobar", Domainname: "baz.cyphar.com", }, HostConfig: &containertypes.HostConfig{ NetworkMode: "bridge", Sysctls: map[string]string{}, }, } d := setupFakeDaemon(t, c) // Ensure that the implicit sysctl is set correctly. s, err := d.createSpec(context.TODO(), &configStore{}, c, nil) assert.NilError(t, err) assert.Equal(t, s.Hostname, "foobar") assert.Equal(t, s.Linux.Sysctl["kernel.domainname"], c.Config.Domainname) if sysctlExists("net.ipv4.ip_unprivileged_port_start") { assert.Equal(t, s.Linux.Sysctl["net.ipv4.ip_unprivileged_port_start"], "0") } if sysctlExists("net.ipv4.ping_group_range") { assert.Equal(t, s.Linux.Sysctl["net.ipv4.ping_group_range"], "0 2147483647") } // Set an explicit sysctl. c.HostConfig.Sysctls["kernel.domainname"] = "foobar.net" assert.Assert(t, c.HostConfig.Sysctls["kernel.domainname"] != c.Config.Domainname) c.HostConfig.Sysctls["net.ipv4.ip_unprivileged_port_start"] = "1024" s, err = d.createSpec(context.TODO(), &configStore{}, c, nil) assert.NilError(t, err) assert.Equal(t, s.Hostname, "foobar") assert.Equal(t, s.Linux.Sysctl["kernel.domainname"], c.HostConfig.Sysctls["kernel.domainname"]) assert.Equal(t, s.Linux.Sysctl["net.ipv4.ip_unprivileged_port_start"], c.HostConfig.Sysctls["net.ipv4.ip_unprivileged_port_start"]) // Ensure the ping_group_range is not set on a daemon with user-namespaces enabled s, err = d.createSpec(context.TODO(), &configStore{Config: config.Config{RemappedRoot: "dummy:dummy"}}, c, nil) assert.NilError(t, err) _, ok := s.Linux.Sysctl["net.ipv4.ping_group_range"] assert.Assert(t, !ok) // Ensure the ping_group_range is set on a container in "host" userns mode // on a daemon with user-namespaces enabled c.HostConfig.UsernsMode = "host" s, err = d.createSpec(context.TODO(), &configStore{Config: config.Config{RemappedRoot: "dummy:dummy"}}, c, nil) assert.NilError(t, err) assert.Equal(t, s.Linux.Sysctl["net.ipv4.ping_group_range"], "0 2147483647") } // TestSysctlOverrideHost ensures that any implicit network sysctls are not set // with host networking func TestSysctlOverrideHost(t *testing.T) { skip.If(t, os.Getuid() != 0, "skipping test that requires root") c := &container.Container{ Config: &containertypes.Config{}, HostConfig: &containertypes.HostConfig{ NetworkMode: "host", Sysctls: map[string]string{}, }, } d := setupFakeDaemon(t, c) // Ensure that the implicit sysctl is not set s, err := d.createSpec(context.TODO(), &configStore{}, c, nil) assert.NilError(t, err) assert.Equal(t, s.Linux.Sysctl["net.ipv4.ip_unprivileged_port_start"], "") assert.Equal(t, s.Linux.Sysctl["net.ipv4.ping_group_range"], "") // Set an explicit sysctl. c.HostConfig.Sysctls["net.ipv4.ip_unprivileged_port_start"] = "1024" s, err = d.createSpec(context.TODO(), &configStore{}, c, nil) assert.NilError(t, err) assert.Equal(t, s.Linux.Sysctl["net.ipv4.ip_unprivileged_port_start"], c.HostConfig.Sysctls["net.ipv4.ip_unprivileged_port_start"]) } func TestGetSourceMount(t *testing.T) { // must be able to find source mount for / mnt, _, err := getSourceMount("/") assert.NilError(t, err) assert.Equal(t, mnt, "/") // must be able to find source mount for current directory cwd, err := os.Getwd() assert.NilError(t, err) _, _, err = getSourceMount(cwd) assert.NilError(t, err) } func TestDefaultResources(t *testing.T) { skip.If(t, os.Getuid() != 0, "skipping test that requires root") // TODO: is this actually true? I'm guilty of following the cargo cult here. c := &container.Container{ HostConfig: &containertypes.HostConfig{ IpcMode: containertypes.IPCModeNone, }, } d := setupFakeDaemon(t, c) s, err := d.createSpec(context.Background(), &configStore{}, c, nil) assert.NilError(t, err) checkResourcesAreUnset(t, s.Linux.Resources) } func checkResourcesAreUnset(t *testing.T, r *specs.LinuxResources) { t.Helper() if r != nil { if r.Memory != nil { assert.Check(t, is.DeepEqual(r.Memory, &specs.LinuxMemory{})) } if r.CPU != nil { assert.Check(t, is.DeepEqual(r.CPU, &specs.LinuxCPU{})) } assert.Check(t, is.Nil(r.Pids)) if r.BlockIO != nil { assert.Check(t, is.DeepEqual(r.BlockIO, &specs.LinuxBlockIO{}, cmpopts.EquateEmpty())) } if r.Network != nil { assert.Check(t, is.DeepEqual(r.Network, &specs.LinuxNetwork{}, cmpopts.EquateEmpty())) } } }