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:
Chris Henzie
2026-03-10 17:14:46 -07:00
parent e15141a1fd
commit f84ddfa4fb
2 changed files with 101 additions and 1 deletions

View File

@@ -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

View File

@@ -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)
})
}
}