Files
Shailend ChandandgVisor bot c8a8d4a815 cgroup2: Support the nsdelegate mount option
This mount option turns cgroup namespaces into delegation boundaries,
which results in the following two behaviors:

First, resource control. A cgroup's resource-control files carve out
the parent's resources, so they belong to the delegater, not to the
subtree's occupants. Accordingly, tasks in a non-init cgroup namespace
may no longer write to interface files of their own namespace root.
The delegatable files (cgroup.procs, cgroup.subtree_control) are exempt.
Subtree tasks cannot change what share of resources the subtree receives,
but remain free to organize processes and distribute that share within it.

Second, containment. Migration (including via CLONE_INTO_CGROUP) now
requires that both the source and destination cgroups be descendants of
the cgroupns root of the task performing the migration, failing with
ENOENT otherwise (actually it is the cgroupns of the task that opened the
fd for the migration file: this detail forces me to snapshot the cgroupns
against the fd, increasing the size of this patch).

PiperOrigin-RevId: 954892289
2026-07-27 16:30:46 -07:00

160 lines
4.4 KiB
Go

// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cgroup2fs
import (
"math"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/usage"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// Aliased for brevity.
const (
firstController = kernel.Cgroup2Ctrl(0)
numControllers = kernel.Cgroup2NumControllers
)
var ctrlNames = map[string]kernel.Cgroup2Ctrl{
"cpu": kernel.Cgroup2CPU,
"memory": kernel.Cgroup2Memory,
"pids": kernel.Cgroup2PIDs,
"cpuset": kernel.Cgroup2CPUSet,
}
var ctrlTypeStr = map[kernel.Cgroup2Ctrl]string{
kernel.Cgroup2CPU: "cpu",
kernel.Cgroup2Memory: "memory",
kernel.Cgroup2PIDs: "pids",
kernel.Cgroup2CPUSet: "cpuset",
}
// ctrlSet is an array mapping ctrlTypes to their matching active controller instances.
type ctrlSet [kernel.Cgroup2NumControllers]controller
func (cs *ctrlSet) fork() *ctrlSet {
clone := *cs
return &clone
}
// interfaceFile defines a cgroup interface file to be instantiated.
type interfaceFile struct {
name string
source vfs.DynamicBytesSource
perm linux.FileMode
isEvent bool
onEventCreated func(inode *eventFile)
showAtRoot bool
ctrl controller
// nsDelegatable marks files that remain writable on a cgroup namespace
// root from inside the namespace when the "nsdelegate" mount option is
// set.
nsDelegatable bool
}
type attachCtx struct {
tasks map[*kernel.Task]struct{}
oldNodes map[*kernel.Task]*cgroup
}
// controller is an interface for a cgroup subsystem controller.
type controller interface {
// Task lifecycle operations.
// Invoked during CreateProcess, clone(), exit() etc.
canEnter(ctx context.Context, t *kernel.Task) bool
cancelEnter(ctx context.Context, t *kernel.Task)
enter(ctx context.Context, t *kernel.Task)
exit(ctx context.Context, t *kernel.Task)
// Task migration operations.
// Invoked by cgroup v2 admin operations.
canAttach(ctx context.Context, actx *attachCtx) bool
cancelAttach(ctx context.Context, actx *attachCtx)
attach(ctx context.Context, actx *attachCtx)
// interfaceFiles returns definitions for tailored interface files specific to this controller.
interfaceFiles() []interfaceFile
// interfaceFileNames returns the names of the interface files tied to this controller.
interfaceFileNames() []string
// detach marks this controller instance as detached/disabled.
detach()
// isActive returns true if the hosting cgroup hasn't detached this controller yet.
isActive() bool
}
// newController instantiates a new controller of the given type for the specified cgroup.
// +checklocksread:c.fs.treeMu
func (c *cgroup) newController(cType kernel.Cgroup2Ctrl) controller {
var parent controller
if c.parent != nil {
parent = c.parent.ctrls[cType] // +checklocksforce: c.fs.treeMu is locked
if parent == nil {
panic("unified cgroups inconsistency: parent controller is nil")
}
}
switch cType {
case kernel.Cgroup2CPU:
var cpuParent *cpu
if parent != nil {
cpuParent = parent.(*cpu)
}
cc := &cpu{
c: c,
parent: cpuParent,
baselineCharges: make(map[*kernel.Task]usage.CPUStats),
}
cc.weight.Store(100)
cc.maxUSec.Store(math.MaxInt64)
cc.periodUSec.Store(100000)
return cc
case kernel.Cgroup2Memory:
var memParent *memory
if parent != nil {
memParent = parent.(*memory)
}
memCtrl := &memory{c: c, parent: memParent, id: c.fs.nextMemoryID()}
memCtrl.maxBytes.Store(math.MaxInt64)
memCtrl.highBytes.Store(math.MaxInt64)
return memCtrl
case kernel.Cgroup2PIDs:
var pidsParent *pids
if parent != nil {
pidsParent = parent.(*pids)
}
return &pids{
c: c,
parent: pidsParent,
max: pidLimitUnlimited,
}
case kernel.Cgroup2CPUSet:
var cpusetParent *cpuset
if parent != nil {
cpusetParent = parent.(*cpuset)
}
return &cpuset{c: c, parent: cpusetParent}
default:
return nil
}
}