Skip to content

Commit

Permalink
Add support for ipc namespace modes "none, private, sharable"
Browse files Browse the repository at this point in the history
Fixes: containers#13265

Signed-off-by: Daniel J Walsh <[email protected]>
  • Loading branch information
rhatdan committed Apr 12, 2022
1 parent 87d129e commit 3987c52
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 45 deletions.
2 changes: 1 addition & 1 deletion docs/source/markdown/podman-container-inspect.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ $ podman container inspect foobar
"DnsSearch": [],
"ExtraHosts": [],
"GroupAdd": [],
"IpcMode": "private",
"IpcMode": "shareable",
"Cgroup": "",
"Cgroups": "default",
"Links": null,
Expand Down
14 changes: 10 additions & 4 deletions docs/source/markdown/podman-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<name|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.
`ns:<path>` 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*

Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-run.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*

Expand Down
7 changes: 7 additions & 0 deletions libpod/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions libpod/container_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
27 changes: 13 additions & 14 deletions libpod/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 := ""
Expand Down
38 changes: 20 additions & 18 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
24 changes: 24 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 5 additions & 2 deletions pkg/specgen/generate/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
11 changes: 10 additions & 1 deletion pkg/specgen/generate/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/specgen/generate/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/specgenutil/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
70 changes: 70 additions & 0 deletions test/system/190-run-ipcns.bats
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 3987c52

Please sign in to comment.