mirror of
https://github.com/containerd/containerd.git
synced 2026-08-09 01:21:15 +00:00
Preserve host cgroup mount options for privileged containers
Privileged containers don't have a cgroup namespace and share the host's cgroup namespace. Mounting cgroup2 inside these containers can inadvertently alter the host's cgroup2 VFS superblock mount options because they are shared. To prevent this, update WithMounts to read the host's /sys/fs/cgroup mount options and explicitly propagate nsdelegate and memory_recursiveprot into the container's mount spec. This avoids stripping them on the host when they are not in the hardcoded default set. Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -70,11 +71,35 @@ func withMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*ru
|
||||
if cgroupWritable {
|
||||
mode = "rw"
|
||||
}
|
||||
|
||||
cgroupOptions := []string{"nosuid", "noexec", "nodev", "relatime", mode}
|
||||
|
||||
hasCgroupNS := false
|
||||
if s.Linux != nil {
|
||||
hasCgroupNS = slices.ContainsFunc(s.Linux.Namespaces, func(ns runtimespec.LinuxNamespace) bool {
|
||||
return ns.Type == runtimespec.CgroupNamespace
|
||||
})
|
||||
}
|
||||
|
||||
// If a container shares the host's cgroup namespace, mounting cgroup2
|
||||
// inside the container applies the new mount options to the single shared
|
||||
// cgroup2 VFS superblock. Therefore, explicitly copy these options from
|
||||
// the host's /sys/fs/cgroup to avoid being stripped.
|
||||
if !hasCgroupNS {
|
||||
if mountInfo, err := osi.LookupMount("/sys/fs/cgroup"); err == nil {
|
||||
for opt := range strings.SplitSeq(mountInfo.VFSOptions, ",") {
|
||||
if opt == "nsdelegate" || opt == "memory_recursiveprot" {
|
||||
cgroupOptions = append(cgroupOptions, opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.Mounts = append(s.Mounts, runtimespec.Mount{
|
||||
Source: "cgroup",
|
||||
Destination: "/sys/fs/cgroup",
|
||||
Type: "cgroup",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "relatime", mode},
|
||||
Options: cgroupOptions,
|
||||
})
|
||||
|
||||
// Copy all mounts from default mounts, except for
|
||||
|
||||
@@ -17,10 +17,15 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/containerd/v2/core/mount"
|
||||
ostesting "github.com/containerd/containerd/v2/pkg/os/testing"
|
||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
)
|
||||
|
||||
func TestMergeGids(t *testing.T) {
|
||||
@@ -45,3 +50,73 @@ func TestRestrictOOMScoreAdj(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, got, current+1)
|
||||
}
|
||||
|
||||
func TestWithMountsCgroupNamespaceOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hasCgroupNS bool
|
||||
hostMountOpts string
|
||||
expectedOpts []string
|
||||
}{
|
||||
{
|
||||
name: "has cgroupns, should use default options",
|
||||
hasCgroupNS: true,
|
||||
hostMountOpts: "rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot",
|
||||
expectedOpts: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
|
||||
},
|
||||
{
|
||||
name: "no cgroupns, with host options present",
|
||||
hasCgroupNS: false,
|
||||
hostMountOpts: "rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot",
|
||||
expectedOpts: []string{"nosuid", "noexec", "nodev", "relatime", "ro", "nsdelegate", "memory_recursiveprot"},
|
||||
},
|
||||
{
|
||||
name: "no cgroupns, with host missing nsdelegate",
|
||||
hasCgroupNS: false,
|
||||
hostMountOpts: "rw,nosuid,nodev,noexec,relatime,memory_recursiveprot",
|
||||
expectedOpts: []string{"nosuid", "noexec", "nodev", "relatime", "ro", "memory_recursiveprot"},
|
||||
},
|
||||
{
|
||||
name: "no cgroupns, with host missing all extra options",
|
||||
hasCgroupNS: false,
|
||||
hostMountOpts: "rw,nosuid,nodev,noexec,relatime",
|
||||
expectedOpts: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fakeOS := ostesting.NewFakeOS()
|
||||
fakeOS.LookupMountFn = func(path string) (mount.Info, error) {
|
||||
if path == "/sys/fs/cgroup" {
|
||||
return mount.Info{VFSOptions: tt.hostMountOpts}, nil
|
||||
}
|
||||
return mount.Info{}, nil
|
||||
}
|
||||
|
||||
config := &runtime.ContainerConfig{
|
||||
Linux: &runtime.LinuxContainerConfig{},
|
||||
}
|
||||
|
||||
spec := &runtimespec.Spec{}
|
||||
if tt.hasCgroupNS {
|
||||
spec.Linux = &runtimespec.Linux{Namespaces: []runtimespec.LinuxNamespace{{Type: runtimespec.CgroupNamespace}}}
|
||||
}
|
||||
|
||||
opt := withMounts(fakeOS, config, nil, "", nil, false)
|
||||
err := opt(context.Background(), nil, nil, spec)
|
||||
require.NoError(t, err)
|
||||
|
||||
var cgroupMount *runtimespec.Mount
|
||||
for _, m := range spec.Mounts {
|
||||
if m.Destination == "/sys/fs/cgroup" {
|
||||
cgroupMount = &m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
require.NotNil(t, cgroupMount)
|
||||
assert.ElementsMatch(t, tt.expectedOpts, cgroupMount.Options)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user