diff --git a/internal/cri/opts/spec_linux_opts.go b/internal/cri/opts/spec_linux_opts.go index 348e699496..1818352736 100644 --- a/internal/cri/opts/spec_linux_opts.go +++ b/internal/cri/opts/spec_linux_opts.go @@ -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 diff --git a/internal/cri/opts/spec_linux_test.go b/internal/cri/opts/spec_linux_test.go index 1c9942f80c..2d729d1bb8 100644 --- a/internal/cri/opts/spec_linux_test.go +++ b/internal/cri/opts/spec_linux_test.go @@ -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) + }) + } +}