From 6641852d5121f1013d1affc39db9274b774af1f2 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 19 Jan 2023 10:33:30 +0900 Subject: [PATCH] rootless: support `--ipc=host` Fix issue 44294 Co-authored-by: Sebastiaan van Stijn Signed-off-by: Akihiro Suda (cherry picked from commit b3c5352386c2bb6c46d0f2232bad9e84e53e8d27) Signed-off-by: Sebastiaan van Stijn --- integration/container/ipcmode_linux_test.go | 10 ++-- pkg/rootless/specconv/specconv_linux.go | 57 +++++++++++++++++---- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/integration/container/ipcmode_linux_test.go b/integration/container/ipcmode_linux_test.go index 49141ce58e82b..61d30b3360426 100644 --- a/integration/container/ipcmode_linux_test.go +++ b/integration/container/ipcmode_linux_test.go @@ -115,7 +115,7 @@ func TestIpcModePrivate(t *testing.T) { // also exists on the host. func TestIpcModeShareable(t *testing.T) { skip.If(t, testEnv.IsRemoteDaemon) - skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") + skip.If(t, testEnv.IsRootless, "no support for --ipc=shareable in rootless") testIpcNonePrivateShareable(t, "shareable", true, true) } @@ -191,7 +191,6 @@ func TestAPIIpcModeShareableAndContainer(t *testing.T) { func TestAPIIpcModeHost(t *testing.T) { skip.If(t, testEnv.IsRemoteDaemon) skip.If(t, testEnv.IsUserNamespace) - skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") cfg := containertypes.Config{ Image: "busybox", @@ -263,7 +262,7 @@ func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...strin // TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended. func TestDaemonIpcModeShareable(t *testing.T) { skip.If(t, testEnv.IsRemoteDaemon) - skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") + skip.If(t, testEnv.IsRootless, "no support for --ipc=shareable in rootless") testDaemonIpcPrivateShareable(t, true, "--default-ipc-mode", "shareable") } @@ -277,9 +276,9 @@ func TestDaemonIpcModePrivate(t *testing.T) { // used to check if an IpcMode given in config works as intended func testDaemonIpcFromConfig(t *testing.T, mode string, mustExist bool) { - skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") config := `{"default-ipc-mode": "` + mode + `"}` - file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config)) + // WithMode is needed for rootless + file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config), fs.WithMode(0o644)) defer file.Remove() testDaemonIpcPrivateShareable(t, mustExist, "--config-file", file.Path()) @@ -295,6 +294,7 @@ func TestDaemonIpcModePrivateFromConfig(t *testing.T) { // TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended. func TestDaemonIpcModeShareableFromConfig(t *testing.T) { skip.If(t, testEnv.IsRemoteDaemon) + skip.If(t, testEnv.IsRootless, "no support for --ipc=shareable in rootless") testDaemonIpcFromConfig(t, "shareable", true) } diff --git a/pkg/rootless/specconv/specconv_linux.go b/pkg/rootless/specconv/specconv_linux.go index 16200e22e93fb..b706b5ca6a5f1 100644 --- a/pkg/rootless/specconv/specconv_linux.go +++ b/pkg/rootless/specconv/specconv_linux.go @@ -1,8 +1,10 @@ package specconv // import "github.com/docker/docker/pkg/rootless/specconv" import ( + "fmt" "os" "path" + "path/filepath" "strconv" "strings" @@ -14,6 +16,7 @@ import ( // * Remove non-supported cgroups // * Fix up OOMScoreAdj // * Fix up /proc if --pid=host +// * Fix up /dev/shm and /dev/mqueue if --ipc=host // // v2Controllers should be non-nil only if running with v2 and systemd. func ToRootless(spec *specs.Spec, v2Controllers []string) error { @@ -79,31 +82,48 @@ func toRootless(spec *specs.Spec, v2Controllers []string, currentOOMScoreAdj int } // Fix up /proc if --pid=host - pidHost, err := isPidHost(spec) + pidHost, err := isHostNS(spec, specs.PIDNamespace) if err != nil { return err } - if !pidHost { - return nil + if pidHost { + if err := bindMountHostProcfs(spec); err != nil { + return err + } + } + + // Fix up /dev/shm and /dev/mqueue if --ipc=host + ipcHost, err := isHostNS(spec, specs.IPCNamespace) + if err != nil { + return err } - return bindMountHostProcfs(spec) + if ipcHost { + if err := bindMountHostIPC(spec); err != nil { + return err + } + } + + return nil } -func isPidHost(spec *specs.Spec) (bool, error) { +func isHostNS(spec *specs.Spec, nsType specs.LinuxNamespaceType) (bool, error) { + if strings.Contains(string(nsType), string(os.PathSeparator)) { + return false, fmt.Errorf("unexpected namespace type %q", nsType) + } for _, ns := range spec.Linux.Namespaces { - if ns.Type == specs.PIDNamespace { + if ns.Type == nsType { if ns.Path == "" { return false, nil } - pidNS, err := os.Readlink(ns.Path) + ns, err := os.Readlink(ns.Path) if err != nil { return false, err } - selfPidNS, err := os.Readlink("/proc/self/ns/pid") + selfNS, err := os.Readlink(filepath.Join("/proc/self/ns", string(nsType))) if err != nil { return false, err } - return pidNS == selfPidNS, nil + return ns == selfNS, nil } } return true, nil @@ -136,3 +156,22 @@ func bindMountHostProcfs(spec *specs.Spec) error { return nil } + +// withBindMountHostIPC replaces /dev/shm and /dev/mqueue mount with rbind. +// Required for --ipc=host on rootless. +// +// Based on https://github.com/containerd/nerdctl/blob/v1.1.0/cmd/nerdctl/run.go#L836-L860 +func bindMountHostIPC(s *specs.Spec) error { + for i, m := range s.Mounts { + switch p := path.Clean(m.Destination); p { + case "/dev/shm", "/dev/mqueue": + s.Mounts[i] = specs.Mount{ + Destination: p, + Type: "bind", + Source: p, + Options: []string{"rbind", "nosuid", "noexec", "nodev"}, + } + } + } + return nil +}