gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/kernel/cgroup.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kernel
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"sort"
    21  
    22  	"gvisor.dev/gvisor/pkg/atomicbitops"
    23  	"gvisor.dev/gvisor/pkg/context"
    24  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    25  	"gvisor.dev/gvisor/pkg/fspath"
    26  	"gvisor.dev/gvisor/pkg/log"
    27  	"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
    28  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    29  )
    30  
    31  // InvalidCgroupHierarchyID indicates an uninitialized hierarchy ID.
    32  const InvalidCgroupHierarchyID uint32 = 0
    33  
    34  // InvalidCgroupID indicates an uninitialized cgroup ID.
    35  const InvalidCgroupID uint32 = 0
    36  
    37  // CgroupControllerType is the name of a cgroup controller.
    38  type CgroupControllerType string
    39  
    40  // Available cgroup controllers.
    41  const (
    42  	CgroupControllerCPU     = CgroupControllerType("cpu")
    43  	CgroupControllerCPUAcct = CgroupControllerType("cpuacct")
    44  	CgroupControllerCPUSet  = CgroupControllerType("cpuset")
    45  	CgroupControllerDevices = CgroupControllerType("devices")
    46  	CgroupControllerJob     = CgroupControllerType("job")
    47  	CgroupControllerMemory  = CgroupControllerType("memory")
    48  	CgroupControllerPIDs    = CgroupControllerType("pids")
    49  )
    50  
    51  // CgroupCtrls is the list of cgroup controllers.
    52  var CgroupCtrls = []CgroupControllerType{"cpu", "cpuacct", "cpuset", "devices", "job", "memory", "pids"}
    53  
    54  // ParseCgroupController parses a string as a CgroupControllerType.
    55  func ParseCgroupController(val string) (CgroupControllerType, error) {
    56  	switch val {
    57  	case "cpu":
    58  		return CgroupControllerCPU, nil
    59  	case "cpuacct":
    60  		return CgroupControllerCPUAcct, nil
    61  	case "cpuset":
    62  		return CgroupControllerCPUSet, nil
    63  	case "devices":
    64  		return CgroupControllerDevices, nil
    65  	case "job":
    66  		return CgroupControllerJob, nil
    67  	case "memory":
    68  		return CgroupControllerMemory, nil
    69  	case "pids":
    70  		return CgroupControllerPIDs, nil
    71  	default:
    72  		return "", fmt.Errorf("no such cgroup controller")
    73  	}
    74  }
    75  
    76  // CgroupResourceType represents a resource type tracked by a particular
    77  // controller.
    78  type CgroupResourceType int
    79  
    80  // Resources for the cpuacct controller.
    81  const (
    82  	// CgroupResourcePID represents a charge for pids.current.
    83  	CgroupResourcePID CgroupResourceType = iota
    84  )
    85  
    86  // CgroupController is the common interface to cgroup controllers available to
    87  // the entire sentry. The controllers themselves are defined by cgroupfs.
    88  //
    89  // Callers of this interface are often unable access synchronization needed to
    90  // ensure returned values remain valid. Some of values returned from this
    91  // interface are thus snapshots in time, and may become stale. This is ok for
    92  // many callers like procfs.
    93  type CgroupController interface {
    94  	// Returns the type of this cgroup controller (ex "memory", "cpu"). Returned
    95  	// value is valid for the lifetime of the controller.
    96  	Type() CgroupControllerType
    97  
    98  	// Hierarchy returns the ID of the hierarchy this cgroup controller is
    99  	// attached to. Returned value is valid for the lifetime of the controller.
   100  	HierarchyID() uint32
   101  
   102  	// EffectiveRootCgroup returns the effective root cgroup for this
   103  	// controller. This is either the actual root of the underlying cgroupfs
   104  	// filesystem, or the override root configured at sandbox startup. Returned
   105  	// value is valid for the lifetime of the controller.
   106  	EffectiveRootCgroup() Cgroup
   107  
   108  	// NumCgroups returns the number of cgroups managed by this controller.
   109  	// Returned value is a snapshot in time.
   110  	NumCgroups() uint64
   111  
   112  	// Enabled returns whether this controller is enabled. Returned value is a
   113  	// snapshot in time.
   114  	Enabled() bool
   115  }
   116  
   117  // Cgroup represents a named pointer to a cgroup in cgroupfs. When a task enters
   118  // a cgroup, it holds a reference on the underlying dentry pointing to the
   119  // cgroup.
   120  //
   121  // +stateify savable
   122  type Cgroup struct {
   123  	*kernfs.Dentry
   124  	CgroupImpl
   125  }
   126  
   127  // decRef drops a reference on the cgroup. This must happen outside a Task.mu
   128  // critical section.
   129  func (c *Cgroup) decRef() {
   130  	c.Dentry.DecRef(context.Background())
   131  }
   132  
   133  // Path returns the absolute path of c, relative to its hierarchy root.
   134  func (c *Cgroup) Path() string {
   135  	return c.FSLocalPath()
   136  }
   137  
   138  // Walk returns the cgroup at p, starting from c.
   139  func (c *Cgroup) Walk(ctx context.Context, vfsObj *vfs.VirtualFilesystem, p fspath.Path) (Cgroup, error) {
   140  	d, err := c.Dentry.WalkDentryTree(ctx, vfsObj, p)
   141  	if err != nil {
   142  		return Cgroup{}, err
   143  	}
   144  	return Cgroup{
   145  		Dentry:     d,
   146  		CgroupImpl: d.Inode().(CgroupImpl),
   147  	}, nil
   148  }
   149  
   150  // CgroupMigrationContext represents an in-flight cgroup migration for
   151  // a single task.
   152  type CgroupMigrationContext struct {
   153  	src Cgroup
   154  	dst Cgroup
   155  	t   *Task
   156  }
   157  
   158  // Abort cancels a migration.
   159  func (ctx *CgroupMigrationContext) Abort() {
   160  	ctx.dst.AbortMigrate(ctx.t, &ctx.src)
   161  }
   162  
   163  // Commit completes a migration.
   164  func (ctx *CgroupMigrationContext) Commit() {
   165  	ctx.dst.CommitMigrate(ctx.t, &ctx.src)
   166  
   167  	ctx.t.mu.Lock()
   168  	delete(ctx.t.cgroups, ctx.src)
   169  	ctx.src.DecRef(ctx.t)
   170  	ctx.dst.IncRef()
   171  	ctx.t.cgroups[ctx.dst] = struct{}{}
   172  	ctx.t.mu.Unlock()
   173  }
   174  
   175  // CgroupImpl is the common interface to cgroups.
   176  type CgroupImpl interface {
   177  	// Controllers lists the controller associated with this cgroup.
   178  	Controllers() []CgroupController
   179  
   180  	// HierarchyID returns the id of the hierarchy that contains this cgroup.
   181  	HierarchyID() uint32
   182  
   183  	// Name returns the name for this cgroup, if any. If no name was provided
   184  	// when the hierarchy was created, returns "".
   185  	Name() string
   186  
   187  	// Enter moves t into this cgroup.
   188  	Enter(t *Task)
   189  
   190  	// Leave moves t out of this cgroup.
   191  	Leave(t *Task)
   192  
   193  	// PrepareMigrate initiates a migration of t from src to this cgroup. See
   194  	// cgroupfs.controller.PrepareMigrate.
   195  	PrepareMigrate(t *Task, src *Cgroup) error
   196  
   197  	// CommitMigrate completes an in-flight migration. See
   198  	// cgroupfs.controller.CommitMigrate.
   199  	CommitMigrate(t *Task, src *Cgroup)
   200  
   201  	// AbortMigrate cancels an in-flight migration. See
   202  	// cgroupfs.controller.AbortMigrate.
   203  	AbortMigrate(t *Task, src *Cgroup)
   204  
   205  	// Charge charges a controller in this cgroup for a particular resource. key
   206  	// must match a valid resource for the specified controller type.
   207  	//
   208  	// The implementer should silently succeed if no matching controllers are
   209  	// found.
   210  	//
   211  	// The underlying implementation will panic if passed an incompatible
   212  	// resource type for a given controller.
   213  	//
   214  	// See cgroupfs.controller.Charge.
   215  	Charge(t *Task, d *kernfs.Dentry, ctl CgroupControllerType, res CgroupResourceType, value int64) error
   216  
   217  	// ReadControlFromBackground allows a background context to read a cgroup's
   218  	// control values.
   219  	ReadControl(ctx context.Context, name string) (string, error)
   220  
   221  	// WriteControl allows a background context to write a cgroup's control
   222  	// values.
   223  	WriteControl(ctx context.Context, name string, val string) error
   224  
   225  	// ID returns the id of this cgroup.
   226  	ID() uint32
   227  }
   228  
   229  // hierarchy represents a cgroupfs filesystem instance, with a unique set of
   230  // controllers attached to it. Multiple cgroupfs mounts may reference the same
   231  // hierarchy.
   232  //
   233  // +stateify savable
   234  type hierarchy struct {
   235  	id   uint32
   236  	name string
   237  	// These are a subset of the controllers in CgroupRegistry.controllers,
   238  	// grouped here by hierarchy for convenient lookup.
   239  	controllers map[CgroupControllerType]CgroupController
   240  	// fs is not owned by hierarchy. The FS is responsible for unregistering the
   241  	// hierarchy on destruction, which removes this association.
   242  	fs *vfs.Filesystem
   243  }
   244  
   245  func (h *hierarchy) match(ctypes []CgroupControllerType) bool {
   246  	if len(ctypes) != len(h.controllers) {
   247  		return false
   248  	}
   249  	for _, ty := range ctypes {
   250  		if _, ok := h.controllers[ty]; !ok {
   251  			return false
   252  		}
   253  	}
   254  	return true
   255  }
   256  
   257  // cgroupFS is the public interface to cgroupfs. This lets the kernel package
   258  // refer to cgroupfs.filesystem methods without directly depending on the
   259  // cgroupfs package, which would lead to a circular dependency.
   260  type cgroupFS interface {
   261  	// Returns the vfs.Filesystem for the cgroupfs.
   262  	VFSFilesystem() *vfs.Filesystem
   263  
   264  	// InitializeHierarchyID sets the hierarchy ID for this filesystem during
   265  	// filesystem creation. May only be called before the filesystem is visible
   266  	// to the vfs layer.
   267  	InitializeHierarchyID(hid uint32)
   268  
   269  	// RootCgroup returns the root cgroup of this instance. This returns the
   270  	// actual root, and ignores any overrides setting an effective root.
   271  	RootCgroup() Cgroup
   272  }
   273  
   274  // CgroupRegistry tracks the active set of cgroup controllers on the system.
   275  //
   276  // +stateify savable
   277  type CgroupRegistry struct {
   278  	// lastHierarchyID is the id of the last allocated cgroup hierarchy. Valid
   279  	// ids are from 1 to math.MaxUint32.
   280  	//
   281  	lastHierarchyID atomicbitops.Uint32
   282  
   283  	// lastCgroupID is the id of the last allocated cgroup. Valid ids are
   284  	// from 1 to math.MaxUint32.
   285  	//
   286  	lastCgroupID atomicbitops.Uint32
   287  
   288  	mu cgroupMutex `state:"nosave"`
   289  
   290  	// controllers is the set of currently known cgroup controllers on the
   291  	// system.
   292  	//
   293  	// +checklocks:mu
   294  	controllers map[CgroupControllerType]CgroupController
   295  
   296  	// hierarchies is the active set of cgroup hierarchies. This contains all
   297  	// hierarchies on the system.
   298  	//
   299  	// +checklocks:mu
   300  	hierarchies map[uint32]hierarchy
   301  
   302  	// hierarchiesByName is a map of named hierarchies. Only named hierarchies
   303  	// are tracked on this map.
   304  	//
   305  	// +checklocks:mu
   306  	hierarchiesByName map[string]hierarchy
   307  
   308  	// cgroups is the active set of cgroups. This contains all the cgroups
   309  	// on the system.
   310  	//
   311  	// +checklocks:mu
   312  	cgroups map[uint32]CgroupImpl
   313  }
   314  
   315  func newCgroupRegistry() *CgroupRegistry {
   316  	return &CgroupRegistry{
   317  		controllers:       make(map[CgroupControllerType]CgroupController),
   318  		hierarchies:       make(map[uint32]hierarchy),
   319  		hierarchiesByName: make(map[string]hierarchy),
   320  		cgroups:           make(map[uint32]CgroupImpl),
   321  	}
   322  }
   323  
   324  // nextHierarchyID returns a newly allocated, unique hierarchy ID.
   325  func (r *CgroupRegistry) nextHierarchyID() (uint32, error) {
   326  	if hid := r.lastHierarchyID.Add(1); hid != 0 {
   327  		return hid, nil
   328  	}
   329  	return InvalidCgroupHierarchyID, fmt.Errorf("cgroup hierarchy ID overflow")
   330  }
   331  
   332  // FindHierarchy returns a cgroup filesystem containing exactly the set of
   333  // controllers named in ctypes, and optionally the name specified in name if it
   334  // isn't empty. If no such FS is found, FindHierarchy return nil. FindHierarchy
   335  // takes a reference on the returned FS, which is transferred to the caller.
   336  func (r *CgroupRegistry) FindHierarchy(name string, ctypes []CgroupControllerType) (*vfs.Filesystem, error) {
   337  	r.mu.Lock()
   338  	defer r.mu.Unlock()
   339  
   340  	// If we have a hierarchy name, lookup by name.
   341  	if name != "" {
   342  		h, ok := r.hierarchiesByName[name]
   343  		if !ok {
   344  			// Name not found.
   345  			return nil, nil
   346  		}
   347  
   348  		if h.match(ctypes) {
   349  			if !h.fs.TryIncRef() {
   350  				// May be racing with filesystem destruction, see below.
   351  				r.unregisterLocked(h.id)
   352  				return nil, nil
   353  			}
   354  			return h.fs, nil
   355  		}
   356  
   357  		// Name matched, but controllers didn't. Fail per linux
   358  		// kernel/cgroup.c:cgroup_mount().
   359  		log.Debugf("cgroupfs: Registry lookup for name=%s controllers=%v failed; named matched but controllers didn't (have controllers=%v)", name, ctypes, h.controllers)
   360  		return nil, linuxerr.EBUSY
   361  	}
   362  
   363  	for _, h := range r.hierarchies {
   364  		if h.match(ctypes) {
   365  			if !h.fs.TryIncRef() {
   366  				// Racing with filesystem destruction, namely h.fs.Release.
   367  				// Since we hold r.mu, we know the hierarchy hasn't been
   368  				// unregistered yet, but its associated filesystem is tearing
   369  				// down.
   370  				//
   371  				// If we simply indicate the hierarchy wasn't found without
   372  				// cleaning up the registry, the caller can race with the
   373  				// unregister and find itself temporarily unable to create a new
   374  				// hierarchy with a subset of the relevant controllers.
   375  				//
   376  				// To keep the result of FindHierarchy consistent with the
   377  				// uniqueness of controllers enforced by Register, drop the
   378  				// dying hierarchy now. The eventual unregister by the FS
   379  				// teardown will become a no-op.
   380  				r.unregisterLocked(h.id)
   381  				return nil, nil
   382  			}
   383  			return h.fs, nil
   384  		}
   385  	}
   386  
   387  	return nil, nil
   388  }
   389  
   390  // FindCgroup locates a cgroup with the given parameters.
   391  //
   392  // A cgroup is considered a match even if it contains other controllers on the
   393  // same hierarchy.
   394  func (r *CgroupRegistry) FindCgroup(ctx context.Context, ctype CgroupControllerType, path string) (Cgroup, error) {
   395  	p := fspath.Parse(path)
   396  	if !p.Absolute {
   397  		return Cgroup{}, fmt.Errorf("path must be absolute")
   398  	}
   399  	k := KernelFromContext(ctx)
   400  	vfsfs, err := r.FindHierarchy("", []CgroupControllerType{ctype})
   401  	if err != nil {
   402  		return Cgroup{}, err
   403  	}
   404  	if vfsfs == nil {
   405  		return Cgroup{}, fmt.Errorf("controller not active")
   406  	}
   407  	defer vfsfs.DecRef(ctx)
   408  
   409  	rootCG := vfsfs.Impl().(cgroupFS).RootCgroup()
   410  
   411  	if !p.HasComponents() {
   412  		// Explicit root '/'.
   413  		return rootCG, nil
   414  	}
   415  
   416  	return rootCG.Walk(ctx, k.VFS(), p)
   417  }
   418  
   419  // Register registers the provided set of controllers with the registry as a new
   420  // hierarchy. If any controller is already registered, the function returns an
   421  // error without modifying the registry. Register sets the hierarchy ID for the
   422  // filesystem on success.
   423  func (r *CgroupRegistry) Register(name string, cs []CgroupController, fs cgroupFS) error {
   424  	r.mu.Lock()
   425  	defer r.mu.Unlock()
   426  
   427  	if name == "" && len(cs) == 0 {
   428  		return fmt.Errorf("can't register hierarchy with both no controllers and no name")
   429  	}
   430  
   431  	for _, c := range cs {
   432  		if _, ok := r.controllers[c.Type()]; ok {
   433  			return fmt.Errorf("controllers may only be mounted on a single hierarchy")
   434  		}
   435  	}
   436  
   437  	if _, ok := r.hierarchiesByName[name]; name != "" && ok {
   438  		return fmt.Errorf("hierarchy named %q already exists", name)
   439  	}
   440  
   441  	hid, err := r.nextHierarchyID()
   442  	if err != nil {
   443  		return err
   444  	}
   445  
   446  	// Must not fail below here, once we publish the hierarchy ID.
   447  
   448  	fs.InitializeHierarchyID(hid)
   449  
   450  	h := hierarchy{
   451  		id:          hid,
   452  		name:        name,
   453  		controllers: make(map[CgroupControllerType]CgroupController),
   454  		fs:          fs.VFSFilesystem(),
   455  	}
   456  	for _, c := range cs {
   457  		n := c.Type()
   458  		r.controllers[n] = c
   459  		h.controllers[n] = c
   460  	}
   461  	r.hierarchies[hid] = h
   462  	if name != "" {
   463  		r.hierarchiesByName[name] = h
   464  	}
   465  	return nil
   466  }
   467  
   468  // Unregister removes a previously registered hierarchy from the registry. If no
   469  // such hierarchy is registered, Unregister is a no-op.
   470  func (r *CgroupRegistry) Unregister(hid uint32) {
   471  	r.mu.Lock()
   472  	r.unregisterLocked(hid)
   473  	r.mu.Unlock()
   474  }
   475  
   476  // Precondition: Caller must hold r.mu.
   477  // +checklocks:r.mu
   478  func (r *CgroupRegistry) unregisterLocked(hid uint32) {
   479  	if h, ok := r.hierarchies[hid]; ok {
   480  		for name := range h.controllers {
   481  			delete(r.controllers, name)
   482  		}
   483  		delete(r.hierarchies, hid)
   484  	}
   485  }
   486  
   487  // computeInitialGroups takes a reference on each of the returned cgroups. The
   488  // caller takes ownership of this returned reference.
   489  func (r *CgroupRegistry) computeInitialGroups(inherit map[Cgroup]struct{}) map[Cgroup]struct{} {
   490  	r.mu.Lock()
   491  	defer r.mu.Unlock()
   492  
   493  	ctlSet := make(map[CgroupControllerType]CgroupController)
   494  	cgset := make(map[Cgroup]struct{})
   495  
   496  	// Remember controllers from the inherited cgroups set...
   497  	for cg := range inherit {
   498  		cg.IncRef() // Ref transferred to caller.
   499  		for _, ctl := range cg.Controllers() {
   500  			ctlSet[ctl.Type()] = ctl
   501  			cgset[cg] = struct{}{}
   502  		}
   503  	}
   504  
   505  	// ... and add the root cgroups of all the missing controllers.
   506  	for name, ctl := range r.controllers {
   507  		if _, ok := ctlSet[name]; !ok {
   508  			cg := ctl.EffectiveRootCgroup()
   509  			// Multiple controllers may share the same hierarchy, so may have
   510  			// the same root cgroup. Grab a single ref per hierarchy root.
   511  			if _, ok := cgset[cg]; ok {
   512  				continue
   513  			}
   514  			cg.IncRef() // Ref transferred to caller.
   515  			cgset[cg] = struct{}{}
   516  		}
   517  	}
   518  	return cgset
   519  }
   520  
   521  // GenerateProcCgroups writes the contents of /proc/cgroups to buf.
   522  func (r *CgroupRegistry) GenerateProcCgroups(buf *bytes.Buffer) {
   523  	r.mu.Lock()
   524  	entries := make([]string, 0, len(r.controllers))
   525  	for _, c := range r.controllers {
   526  		en := 0
   527  		if c.Enabled() {
   528  			en = 1
   529  		}
   530  		entries = append(entries, fmt.Sprintf("%s\t%d\t%d\t%d\n", c.Type(), c.HierarchyID(), c.NumCgroups(), en))
   531  	}
   532  	r.mu.Unlock()
   533  
   534  	sort.Strings(entries)
   535  	fmt.Fprint(buf, "#subsys_name\thierarchy\tnum_cgroups\tenabled\n")
   536  	for _, e := range entries {
   537  		fmt.Fprint(buf, e)
   538  	}
   539  }
   540  
   541  // NextCgroupID returns a newly allocated, unique cgroup ID.
   542  func (r *CgroupRegistry) NextCgroupID() (uint32, error) {
   543  	if cid := r.lastCgroupID.Add(1); cid != 0 {
   544  		return cid, nil
   545  	}
   546  	return InvalidCgroupID, fmt.Errorf("cgroup ID overflow")
   547  }
   548  
   549  // AddCgroup adds the ID and cgroup in the map.
   550  func (r *CgroupRegistry) AddCgroup(cg CgroupImpl) {
   551  	r.mu.Lock()
   552  	r.cgroups[cg.ID()] = cg
   553  	r.mu.Unlock()
   554  }
   555  
   556  // GetCgroup returns the cgroup associated with the cgroup ID.
   557  func (r *CgroupRegistry) GetCgroup(cid uint32) (CgroupImpl, error) {
   558  	r.mu.Lock()
   559  	defer r.mu.Unlock()
   560  	cg, ok := r.cgroups[cid]
   561  	if !ok {
   562  		return nil, fmt.Errorf("cgroup with ID %d does not exist", cid)
   563  	}
   564  	return cg, nil
   565  }