From 3987c529f473178c51feb69d5252c7d5c2a8f697 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Mon, 28 Mar 2022 09:10:14 -0400 Subject: Add support for ipc namespace modes "none, private, sharable" Fixes: #13265 Signed-off-by: Daniel J Walsh --- docs/source/markdown/podman-container-inspect.1.md | 2 +- docs/source/markdown/podman-create.1.md | 14 +++-- docs/source/markdown/podman-run.1.md | 4 ++ libpod/container.go | 7 +++ libpod/container_config.go | 4 ++ libpod/container_inspect.go | 27 ++++----- libpod/container_internal.go | 38 ++++++------ libpod/container_internal_linux.go | 6 +- libpod/options.go | 24 ++++++++ libpod/runtime_ctr.go | 4 +- pkg/specgen/generate/container.go | 7 ++- pkg/specgen/generate/namespaces.go | 11 +++- pkg/specgen/generate/security.go | 2 +- pkg/specgenutil/specgen.go | 2 +- test/system/190-run-ipcns.bats | 70 ++++++++++++++++++++++ 15 files changed, 177 insertions(+), 45 deletions(-) create mode 100644 test/system/190-run-ipcns.bats diff --git a/docs/source/markdown/podman-container-inspect.1.md b/docs/source/markdown/podman-container-inspect.1.md index 9945fca7c..4e45bcc40 100644 --- a/docs/source/markdown/podman-container-inspect.1.md +++ b/docs/source/markdown/podman-container-inspect.1.md @@ -219,7 +219,7 @@ $ podman container inspect foobar "DnsSearch": [], "ExtraHosts": [], "GroupAdd": [], - "IpcMode": "private", + "IpcMode": "shareable", "Cgroup": "", "Cgroups": "default", "Links": null, diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index c4d27e321..4f0a3993b 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -504,10 +504,16 @@ To specify multiple static IPv6 addresses per container, set multiple networks u #### **--ipc**=*ipc* -Default is to create a private IPC namespace (POSIX SysV IPC) for the container - `container:`: reuses another container shared memory, semaphores and message queues - `host`: use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure. - `ns:` path to an IPC namespace to join. +Set the IPC namespace mode for a container. The default is to create +a private IPC namespace. + +- "": Use Podman's default, defined in containers.conf. +- **container:**_id_: reuses another container's shared memory, semaphores, and message queues +- **host**: use the host's shared memory, semaphores, and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure. +- **none**: private IPC namespace, with /dev/shm not mounted. +- **ns:**_path_: path to an IPC namespace to join. +- **private**: private IPC namespace. += **shareable**: private IPC namespace with a possibility to share it with other containers. #### **--label**, **-l**=*label* diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index e4ccd0368..d88eb20a4 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -528,9 +528,13 @@ To specify multiple static IPv6 addresses per container, set multiple networks u Set the IPC namespace mode for a container. The default is to create a private IPC namespace. +- "": Use Podman's default, defined in containers.conf. - **container:**_id_: reuses another container shared memory, semaphores and message queues - **host**: use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure. +- **none**: private IPC namespace, with /dev/shm not mounted. - **ns:**_path_: path to an IPC namespace to join. +- **private**: private IPC namespace. += **shareable**: private IPC namespace with a possibility to share it with other containers. #### **--label**, **-l**=*key*=*value* diff --git a/libpod/container.go b/libpod/container.go index 578f16905..bc3cab439 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -291,6 +291,13 @@ func (c *Container) Config() *ContainerConfig { return returnConfig } +// ConfigNoCopy returns the configuration used by the container. +// Note that the returned value is not a copy and must hence +// only be used in a reading fashion. +func (c *Container) ConfigNoCopy() *ContainerConfig { + return c.config +} + // DeviceHostSrc returns the user supplied device to be passed down in the pod func (c *Container) DeviceHostSrc() []spec.LinuxDevice { return c.config.DeviceHostSrc diff --git a/libpod/container_config.go b/libpod/container_config.go index ea644764c..c08cd7604 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -120,6 +120,10 @@ type ContainerRootFSConfig struct { // with the size specified in ShmSize and populate this with the path of // said tmpfs. ShmDir string `json:"ShmDir,omitempty"` + // NoShmShare indicates whether /dev/shm can be shared with other containers + NoShmShare bool `json:"NOShmShare,omitempty"` + // NoShm indicates whether a tmpfs should be created and mounted on /dev/shm + NoShm bool `json:"NoShm,omitempty"` // ShmSize is the size of the container's SHM. Only used if ShmDir was // not set manually at time of creation. ShmSize int64 `json:"shmSize"` diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index c9d0b8a6c..14290ca0d 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -703,32 +703,31 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named } hostConfig.CapAdd = capAdd hostConfig.CapDrop = capDrop - - // IPC Namespace mode - ipcMode := "" - if c.config.IPCNsCtr != "" { - ipcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr) - } else if ctrSpec.Linux != nil { + switch { + case c.config.IPCNsCtr != "": + hostConfig.IpcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr) + case ctrSpec.Linux != nil: // Locate the spec's IPC namespace. // If there is none, it's ipc=host. // If there is one and it has a path, it's "ns:". // If no path, it's default - the empty string. - for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.IPCNamespace { if ns.Path != "" { - ipcMode = fmt.Sprintf("ns:%s", ns.Path) + hostConfig.IpcMode = fmt.Sprintf("ns:%s", ns.Path) } else { - ipcMode = "private" + break } - break } } - if ipcMode == "" { - ipcMode = "host" - } + case c.config.NoShm: + hostConfig.IpcMode = "none" + case c.config.NoShmShare: + hostConfig.IpcMode = "private" + } + if hostConfig.IpcMode == "" { + hostConfig.IpcMode = "shareable" } - hostConfig.IpcMode = ipcMode // Cgroup namespace mode cgroupMode := "" diff --git a/libpod/container_internal.go b/libpod/container_internal.go index f1f467879..c7567a55e 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1507,26 +1507,28 @@ func (c *Container) mountStorage() (_ string, deferredErr error) { return c.state.Mountpoint, nil } - mounted, err := mount.Mounted(c.config.ShmDir) - if err != nil { - return "", errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir) - } - - if !mounted && !MountExists(c.config.Spec.Mounts, "/dev/shm") { - shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) - if err := c.mountSHM(shmOptions); err != nil { - return "", err - } - if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { - return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir) + if !c.config.NoShm { + mounted, err := mount.Mounted(c.config.ShmDir) + if err != nil { + return "", errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir) } - defer func() { - if deferredErr != nil { - if err := c.unmountSHM(c.config.ShmDir); err != nil { - logrus.Errorf("Unmounting SHM for container %s after mount error: %v", c.ID(), err) - } + + if !mounted && !MountExists(c.config.Spec.Mounts, "/dev/shm") { + shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) + if err := c.mountSHM(shmOptions); err != nil { + return "", err } - }() + if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { + return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir) + } + defer func() { + if deferredErr != nil { + if err := c.unmountSHM(c.config.ShmDir); err != nil { + logrus.Errorf("Unmounting SHM for container %s after mount error: %v", c.ID(), err) + } + } + }() + } } // We need to mount the container before volumes - to ensure the copyup diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 11ca169ca..98ba3404f 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1963,8 +1963,10 @@ func (c *Container) makeBindMounts() error { } } - // SHM is always added when we mount the container - c.state.BindMounts["/dev/shm"] = c.config.ShmDir + if c.config.ShmDir != "" { + // If ShmDir has a value SHM is always added when we mount the container + c.state.BindMounts["/dev/shm"] = c.config.ShmDir + } if c.config.Passwd == nil || *c.config.Passwd { newPasswd, newGroup, err := c.generatePasswdAndGroup() diff --git a/libpod/options.go b/libpod/options.go index 2e5454393..34c06cc5d 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -559,6 +559,30 @@ func WithShmDir(dir string) CtrCreateOption { } } +// WithNOShmMount tells libpod whether to mount /dev/shm +func WithNoShm(mount bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NoShm = mount + return nil + } +} + +// WithNoShmShare tells libpod whether to share containers /dev/shm with other containers +func WithNoShmShare(share bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NoShmShare = share + return nil + } +} + // WithSystemd turns on systemd mode in the container func WithSystemd() CtrCreateOption { return func(ctr *Container) error { diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index f92898b1c..7edd49fd1 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -174,6 +174,8 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf return nil, errors.Wrapf(err, "converting containers.conf ShmSize %s to an int", r.config.Containers.ShmSize) } ctr.config.ShmSize = size + ctr.config.NoShm = false + ctr.config.NoShmShare = false ctr.config.StopSignal = 15 ctr.config.StopTimeout = r.config.Engine.StopTimeout @@ -514,7 +516,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai } } - if !MountExists(ctr.config.Spec.Mounts, "/dev/shm") && ctr.config.ShmDir == "" { + if !MountExists(ctr.config.Spec.Mounts, "/dev/shm") && ctr.config.ShmDir == "" && !ctr.config.NoShm { ctr.config.ShmDir = filepath.Join(ctr.bundlePath(), "shm") if err := os.MkdirAll(ctr.config.ShmDir, 0700); err != nil { if !os.IsExist(err) { diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index b38b0e695..f7ea2edfa 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -428,9 +428,12 @@ func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID s case "cgroup": specg.CgroupNS = specgen.Namespace{NSMode: specgen.Default} //default case "ipc": - if conf.ShmDir == "/dev/shm" { + switch conf.ShmDir { + case "/dev/shm": specg.IpcNS = specgen.Namespace{NSMode: specgen.Host} - } else { + case "": + specg.IpcNS = specgen.Namespace{NSMode: specgen.None} + default: specg.IpcNS = specgen.Namespace{NSMode: specgen.Default} //default } case "uts": diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 9ce45aaf0..05c2d1741 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -134,8 +134,17 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. if err != nil { return nil, errors.Wrapf(err, "error looking up container to share ipc namespace with") } + if ipcCtr.ConfigNoCopy().NoShmShare { + return nil, errors.Errorf("joining IPC of container %s is not allowed: non-shareable IPC (hint: use IpcMode:shareable for the donor container)", ipcCtr.ID()) + } toReturn = append(toReturn, libpod.WithIPCNSFrom(ipcCtr)) - toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir())) + if !ipcCtr.ConfigNoCopy().NoShm { + toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir())) + } + case specgen.None: + toReturn = append(toReturn, libpod.WithNoShm(true)) + case specgen.Private: + toReturn = append(toReturn, libpod.WithNoShmShare(true)) } // UTS diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index 988c29832..ec52164ab 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -222,7 +222,7 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, for sysctlKey, sysctlVal := range defaultSysctls { // Ignore mqueue sysctls if --ipc=host if noUseIPC && strings.HasPrefix(sysctlKey, "fs.mqueue.") { - logrus.Infof("Sysctl %s=%s ignored in containers.conf, since IPC Namespace set to host", sysctlKey, sysctlVal) + logrus.Infof("Sysctl %s=%s ignored in containers.conf, since IPC Namespace set to %q", sysctlKey, sysctlVal, s.IpcNS.NSMode) continue } diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 7d4fca846..17c88f609 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -190,7 +190,7 @@ func setNamespaces(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) } } if c.IPC != "" { - s.IpcNS, err = specgen.ParseNamespace(c.IPC) + s.IpcNS, err = specgen.ParseIPCNamespace(c.IPC) if err != nil { return err } diff --git a/test/system/190-run-ipcns.bats b/test/system/190-run-ipcns.bats new file mode 100644 index 000000000..9327d8ec7 --- /dev/null +++ b/test/system/190-run-ipcns.bats @@ -0,0 +1,70 @@ +#!/usr/bin/env bats -*- bats -*- +# shellcheck disable=SC2096 +# +# Tests for podman build +# + +load helpers + +@test "podman --ipc=host" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run --rm --ipc=host $IMAGE readlink /proc/self/ns/ipc + is "$output" "$hostipc" "HostIPC and container IPC should be same" +} + +@test "podman --ipc=none" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run --rm --ipc=none $IMAGE readlink /proc/self/ns/ipc + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman 1 run --rm --ipc=none $IMAGE ls /dev/shm + is "$output" "ls: /dev/shm: No such file or directory" "Should fail with missing /dev/shm" +} + +@test "podman --ipc=private" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run -d --ipc=private --name test $IMAGE sleep 100 + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman 125 run --ipc=container:test --rm $IMAGE readlink /proc/self/ns/ipc + is "$output" ".*is not allowed: non-shareable IPC (hint: use IpcMode:shareable for the donor container)" "Containers should not share private ipc namespace" + run_podman stop -t 0 test + run_podman rm test +} + +@test "podman --ipc=shareable" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run -d --ipc=shareable --name test $IMAGE sleep 100 + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman run --ipc=container:test --rm $IMAGE readlink /proc/self/ns/ipc + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman stop -t 0 test + run_podman rm test +} + +@test "podman --ipc=container@test" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run -d --name test $IMAGE sleep 100 + run_podman exec test readlink /proc/self/ns/ipc + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + testipc=$output + run_podman run --ipc=container:test --rm $IMAGE readlink /proc/self/ns/ipc + is "$output" "$testipc" "Containers should share ipc namespace" + run_podman stop -t 0 test + run_podman rm test +} + +# vim: filetype=sh -- cgit v1.2.3-54-g00ecf