1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright Authors of Tetragon
21
"go.uber.org/multierr"
22
"golang.org/x/sys/unix"
24
"github.com/cilium/tetragon/pkg/defaults"
25
"github.com/cilium/tetragon/pkg/logger"
26
"github.com/cilium/tetragon/pkg/option"
27
"github.com/sirupsen/logrus"
31
// Generic unset value that means undefined or not set
32
CGROUP_UNSET_VALUE = 0
34
// Max cgroup subsystems count that is used from BPF side
35
// to define a max index for the default controllers on tasks.
36
// For further documentation check BPF part.
37
CGROUP_SUBSYS_COUNT = 15
39
// The default hierarchy for cgroupv2
40
CGROUP_DEFAULT_HIERARCHY = 0
43
type CgroupModeCode int
47
* https://systemd.io/CGROUP_DELEGATION/
48
* But this should work also for non-systemd environments: where
49
* only legacy or unified are available by default.
51
CGROUP_UNDEF CgroupModeCode = iota
52
CGROUP_LEGACY CgroupModeCode = 1
53
CGROUP_HYBRID CgroupModeCode = 2
54
CGROUP_UNIFIED CgroupModeCode = 3
57
type DeploymentCode int
59
type deploymentEnv struct {
66
DEPLOY_UNKNOWN DeploymentCode = iota
67
DEPLOY_K8S DeploymentCode = 1 // K8s deployment
68
DEPLOY_CONTAINER DeploymentCode = 2 // Container docker, podman, etc
69
DEPLOY_SD_SERVICE DeploymentCode = 10 // Systemd service
70
DEPLOY_SD_USER DeploymentCode = 11 // Systemd user session
73
type CgroupController struct {
74
Id uint32 // Hierarchy unique ID
75
Idx uint32 // Cgroup SubSys index
76
Name string // Controller name
77
Active bool // Will be set to true if controller is set and active
81
// Path where default cgroupfs is mounted
82
defaultCgroupRoot = "/sys/fs/cgroup"
84
/* Cgroup controllers that we are interested in
85
* are usually the ones that are setup by systemd
86
* or other init programs.
88
CgroupControllers = []CgroupController{
89
{Name: "memory"}, // Memory first
90
{Name: "pids"}, // pids second
91
{Name: "cpuset"}, // fallback
94
cgroupv2Hierarchy = "0::"
96
/* Ordered from nested to top cgroup parents
97
* For k8s we check also config k8s flags.
99
deployments = []deploymentEnv{
100
{id: DEPLOY_K8S, str: "kube"},
101
{id: DEPLOY_CONTAINER, str: "docker"},
102
{id: DEPLOY_CONTAINER, str: "podman"},
103
{id: DEPLOY_CONTAINER, str: "libpod"},
104
{id: DEPLOY_SD_SERVICE, str: "system.slice"},
105
{id: DEPLOY_SD_USER, str: "user.slice"},
108
detectDeploymentOnce sync.Once
109
deploymentMode DeploymentCode
111
detectCgrpModeOnce sync.Once
112
cgroupMode CgroupModeCode
114
detectCgroupFSOnce sync.Once
118
// Cgroup Migration Path
119
findMigPath sync.Once
120
cgrpMigrationPath string
122
// Cgroup Tracking Hierarchy
124
cgrpSubsystemIdx uint32
127
func (code CgroupModeCode) String() string {
129
CGROUP_UNDEF: "undefined",
130
CGROUP_LEGACY: "Legacy mode (Cgroupv1)",
131
CGROUP_HYBRID: "Hybrid mode (Cgroupv1 and Cgroupv2)",
132
CGROUP_UNIFIED: "Unified mode (Cgroupv2)",
136
func (op DeploymentCode) String() string {
138
DEPLOY_UNKNOWN: "unknown",
139
DEPLOY_K8S: "Kubernetes",
140
DEPLOY_CONTAINER: "Container",
141
DEPLOY_SD_SERVICE: "systemd service",
142
DEPLOY_SD_USER: "systemd user session",
146
// DetectCgroupFSMagic() runs by default DetectCgroupMode()
147
// CgroupFsMagicStr() Returns "Cgroupv2" or "Cgroupv1" based on passed magic.
148
func CgroupFsMagicStr(magic uint64) string {
149
if magic == unix.CGROUP2_SUPER_MAGIC {
151
} else if magic == unix.CGROUP_SUPER_MAGIC {
158
func GetCgroupFSMagic() uint64 {
162
func GetCgroupFSPath() string {
166
type FileHandle struct {
170
func GetCgroupIdFromPath(cgroupPath string) (uint64, error) {
173
handle, _, err := unix.NameToHandleAt(unix.AT_FDCWD, cgroupPath, 0)
178
err = binary.Read(bytes.NewBuffer(handle.Bytes()), binary.LittleEndian, &fh)
180
return 0, fmt.Errorf("decoding NameToHandleAt data failed: %v", err)
186
func parseCgroupSubSysIds(filePath string) error {
187
var allcontrollers []string
189
file, err := os.Open(filePath)
196
fscanner := bufio.NewScanner(file)
199
fscanner.Scan() // ignore first entry
200
for fscanner.Scan() {
201
line := fscanner.Text()
202
fields := strings.Fields(line)
204
allcontrollers = append(allcontrollers, fields[0])
206
// No need to read enabled field as it can be enabled on
207
// root without having a proper cgroup name to reflect that
208
// or the controller is not active on the unified cgroupv2.
209
for i, controller := range CgroupControllers {
210
if fields[0] == controller.Name {
211
/* We care only for the controllers that we want */
212
if idx >= CGROUP_SUBSYS_COUNT {
213
/* Maybe some cgroups are not upstream? */
214
return fmt.Errorf("Cgroup default subsystem '%s' is indexed at idx=%d higher than CGROUP_SUBSYS_COUNT=%d",
215
fields[0], idx, CGROUP_SUBSYS_COUNT)
218
id, err := strconv.ParseUint(fields[1], 10, 32)
220
CgroupControllers[i].Id = uint32(id)
221
CgroupControllers[i].Idx = uint32(idx)
222
CgroupControllers[i].Active = true
225
logger.GetLogger().WithFields(logrus.Fields{
226
"cgroup.fs": cgroupFSPath,
227
"cgroup.controller.name": controller.Name,
228
}).WithError(err).Warnf("parsing controller line from '%s' failed", filePath)
235
logger.GetLogger().WithFields(logrus.Fields{
236
"cgroup.fs": cgroupFSPath,
237
"cgroup.controllers": fmt.Sprintf("[%s]", strings.Join(allcontrollers, " ")),
238
}).Debugf("Cgroup available controllers")
240
// Could not find 'memory', 'pids' nor 'cpuset' controllers, are they compiled in?
242
err = fmt.Errorf("detect cgroup controllers IDs from '%s' failed", filePath)
243
logger.GetLogger().WithFields(logrus.Fields{
244
"cgroup.fs": cgroupFSPath,
245
}).WithError(err).Warnf("Cgroup controllers 'memory', 'pids' and 'cpuset' are missing")
249
for _, controller := range CgroupControllers {
250
// Print again everything that is available or not
251
if controller.Active {
252
logger.GetLogger().WithFields(logrus.Fields{
253
"cgroup.fs": cgroupFSPath,
254
"cgroup.controller.name": controller.Name,
255
"cgroup.controller.hierarchyID": controller.Id,
256
"cgroup.controller.index": controller.Idx,
257
}).Infof("Supported cgroup controller '%s' is active on the system", controller.Name)
260
err = fmt.Errorf("controller '%s' is not active", controller.Name)
261
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Supported cgroup controller '%s' is not active", controller.Name)
268
// DiscoverSubSysIds() Discover Cgroup SubSys IDs and indexes.
269
// of the corresponding controllers that we are interested
270
// in. We need this dynamic behavior since these controllers are
272
func DiscoverSubSysIds() error {
273
return parseCgroupSubSysIds(filepath.Join(option.Config.ProcFS, "cgroups"))
276
func setDeploymentMode(cgroupPath string) error {
277
if deploymentMode != DEPLOY_UNKNOWN {
281
if option.Config.EnableK8s {
282
deploymentMode = DEPLOY_K8S
286
if cgroupPath == "" {
287
// Probably namespaced
288
deploymentMode = DEPLOY_CONTAINER
292
// Last go through the deployments
293
for _, d := range deployments {
294
if strings.Contains(cgroupPath, d.str) {
295
deploymentMode = d.id
300
return fmt.Errorf("detect deployment mode failed, no match on Cgroup path '%s'", cgroupPath)
303
func GetDeploymentMode() DeploymentCode {
304
return deploymentMode
307
func GetCgroupMode() CgroupModeCode {
311
func setCgrpHierarchyID(controller *CgroupController) {
312
cgrpHierarchy = controller.Id
315
func setCgrp2HierarchyID() {
316
cgrpHierarchy = CGROUP_DEFAULT_HIERARCHY
319
func setCgrpSubsystemIdx(controller *CgroupController) {
320
cgrpSubsystemIdx = controller.Idx
323
// GetCgrpHierarchyID() returns the ID of the Cgroup hierarchy
324
// that is used to track processes. This is used for Cgroupv1 as for
325
// Cgroupv2 we run in the default hierarchy.
326
func GetCgrpHierarchyID() uint32 {
330
// GetCgrpSubsystemIdx() returns the Index of the subsys
331
// or hierarchy to be used to track processes.
332
func GetCgrpSubsystemIdx() uint32 {
333
return cgrpSubsystemIdx
336
// GetCgrpControllerName() returns the name of the controller that is
337
// being used as fallback from the css to get cgroup information and
339
func GetCgrpControllerName() string {
340
for _, controller := range CgroupControllers {
341
if controller.Active && controller.Idx == cgrpSubsystemIdx {
342
return controller.Name
348
// Validates cgroupPaths obtained from /proc/self/cgroup based on Cgroupv1
349
// and returns it on success
350
func getValidCgroupv1Path(cgroupPaths []string) (string, error) {
351
for _, controller := range CgroupControllers {
352
// First lets go again over list of active controllers
353
if !controller.Active {
354
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).Debugf("Cgroup controller '%s' is not active", controller.Name)
358
for _, s := range cgroupPaths {
359
if strings.Contains(s, fmt.Sprintf(":%s:", controller.Name)) {
360
idx := strings.Index(s, "/")
362
cgroupPath := filepath.Join(cgroupFSPath, controller.Name, path)
363
finalpath := filepath.Join(cgroupPath, "cgroup.procs")
364
_, err := os.Stat(finalpath)
366
// Probably namespaced... run the deployment mode detection
367
err = setDeploymentMode(path)
369
mode := GetDeploymentMode()
370
if mode == DEPLOY_K8S || mode == DEPLOY_CONTAINER {
371
// Cgroups are namespaced let's try again
372
cgroupPath = filepath.Join(cgroupFSPath, controller.Name)
373
finalpath = filepath.Join(cgroupPath, "cgroup.procs")
374
_, err = os.Stat(finalpath)
380
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Failed to validate Cgroupv1 path '%s'", finalpath)
384
// Run the deployment mode detection last again, fine to rerun.
385
err = setDeploymentMode(path)
387
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warn("Failed to detect deployment mode from Cgroupv1 path")
391
logger.GetLogger().WithFields(logrus.Fields{
392
"cgroup.fs": cgroupFSPath,
393
"cgroup.controller.name": controller.Name,
394
"cgroup.controller.hierarchyID": controller.Id,
395
"cgroup.controller.index": controller.Idx,
396
}).Infof("Cgroupv1 controller '%s' will be used", controller.Name)
398
setCgrpHierarchyID(&controller)
399
setCgrpSubsystemIdx(&controller)
400
logger.GetLogger().WithFields(logrus.Fields{
401
"cgroup.fs": cgroupFSPath,
402
"cgroup.path": cgroupPath,
403
}).Info("Cgroupv1 hierarchy validated successfully")
404
return finalpath, nil
409
// Cgroupv1 hierarchy is not properly setup we can not support such systems,
410
// reason should have been logged in above messages.
411
return "", fmt.Errorf("could not validate Cgroupv1 hierarchies")
414
// Lookup Cgroupv2 active controllers and returns one that we support
415
func getCgroupv2Controller(cgroupPath string) (*CgroupController, error) {
416
file := filepath.Join(cgroupPath, "cgroup.controllers")
417
data, err := os.ReadFile(file)
419
return nil, fmt.Errorf("failed to read %s: %v", file, err)
422
activeControllers := strings.TrimRight(string(data), "\n")
423
if len(activeControllers) == 0 {
424
return nil, fmt.Errorf("no active controllers from '%s'", file)
427
logger.GetLogger().WithFields(logrus.Fields{
428
"cgroup.fs": cgroupFSPath,
429
"cgroup.controllers": strings.Fields(activeControllers),
430
}).Info("Cgroupv2 supported controllers detected successfully")
432
for i, controller := range CgroupControllers {
433
if controller.Active && strings.Contains(activeControllers, controller.Name) {
434
logger.GetLogger().WithFields(logrus.Fields{
435
"cgroup.fs": cgroupFSPath,
436
"cgroup.controller.name": controller.Name,
437
"cgroup.controller.hierarchyID": controller.Id,
438
"cgroup.controller.index": controller.Idx,
439
}).Infof("Cgroupv2 controller '%s' will be used as a fallback for the default hierarchy", controller.Name)
440
return &CgroupControllers[i], nil
444
// Cgroupv2 hierarchy does not have the appropriate controllers.
445
// Maybe init system or any other component failed to prepare cgroups properly.
446
return nil, fmt.Errorf("Cgroupv2 no appropriate active controller")
449
// Validates cgroupPaths obtained from /proc/self/cgroup based on Cgroupv2
450
func getValidCgroupv2Path(cgroupPaths []string) (string, error) {
451
for _, s := range cgroupPaths {
452
if strings.Contains(s, cgroupv2Hierarchy) {
453
idx := strings.Index(s, "/")
455
cgroupPath := filepath.Join(cgroupFSPath, path)
456
finalpath := filepath.Join(cgroupPath, "cgroup.procs")
457
_, err := os.Stat(finalpath)
459
// Namespaced ? let's force the check
460
err = setDeploymentMode(path)
462
mode := GetDeploymentMode()
463
if mode == DEPLOY_K8S || mode == DEPLOY_CONTAINER {
464
// Cgroups are namespaced let's try again
465
cgroupPath = cgroupFSPath
466
finalpath = filepath.Join(cgroupPath, "cgroup.procs")
467
_, err = os.Stat(finalpath)
473
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Failed to validate Cgroupv2 path '%s'", finalpath)
477
// This should not be necessary but there are broken setups out there
478
// without cgroupv2 default bpf helpers
479
controller, err := getCgroupv2Controller(cgroupPath)
481
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Failed to detect current Cgroupv2 active controller")
485
// Run the deployment mode detection last again, fine to rerun.
486
err = setDeploymentMode(path)
488
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warn("Failed to detect deployment mode from Cgroupv2 path")
492
setCgrp2HierarchyID()
493
setCgrpSubsystemIdx(controller)
494
logger.GetLogger().WithFields(logrus.Fields{
495
"cgroup.fs": cgroupFSPath,
496
"cgroup.path": cgroupPath,
497
}).Info("Cgroupv2 hierarchy validated successfully")
498
return finalpath, nil
502
// Cgroupv2 hierarchy is not properly setup we can not support such systems,
503
// reason should have been logged in above messages.
504
return "", fmt.Errorf("could not validate Cgroupv2 hierarchy")
507
func getPidCgroupPaths(pid uint32) ([]string, error) {
508
file := filepath.Join(option.Config.ProcFS, fmt.Sprint(pid), "cgroup")
510
cgroups, err := os.ReadFile(file)
512
return nil, fmt.Errorf("failed to read %s: %v", file, err)
515
if len(cgroups) == 0 {
516
return nil, fmt.Errorf("no entry from %s", file)
519
return strings.Split(strings.TrimSpace(string(cgroups)), "\n"), nil
522
func findMigrationPath(pid uint32) (string, error) {
523
if cgrpMigrationPath != "" {
524
return cgrpMigrationPath, nil
527
cgroupPaths, err := getPidCgroupPaths(pid)
529
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Unable to get Cgroup paths for pid=%d", pid)
533
mode, err := DetectCgroupMode()
538
/* Run the validate and get cgroup migration path once
539
* as it triggers lot of checks.
541
findMigPath.Do(func() {
544
case CGROUP_LEGACY, CGROUP_HYBRID:
545
cgrpMigrationPath, err = getValidCgroupv1Path(cgroupPaths)
547
cgrpMigrationPath, err = getValidCgroupv2Path(cgroupPaths)
549
err = fmt.Errorf("could not detect Cgroup Mode")
553
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Unable to find Cgroup migration path for pid=%d", pid)
557
if cgrpMigrationPath == "" {
558
return "", fmt.Errorf("could not detect Cgroup migration path for pid=%d", pid)
561
return cgrpMigrationPath, nil
564
func detectCgroupMode(cgroupfs string) (CgroupModeCode, error) {
565
var st syscall.Statfs_t
567
if err := syscall.Statfs(cgroupfs, &st); err != nil {
568
return CGROUP_UNDEF, err
571
if st.Type == unix.CGROUP2_SUPER_MAGIC {
572
return CGROUP_UNIFIED, nil
573
} else if st.Type == unix.TMPFS_MAGIC {
574
err := syscall.Statfs(filepath.Join(cgroupfs, "unified"), &st)
575
if err == nil && st.Type == unix.CGROUP2_SUPER_MAGIC {
576
return CGROUP_HYBRID, nil
578
return CGROUP_LEGACY, nil
581
return CGROUP_UNDEF, fmt.Errorf("wrong type '%d' for cgroupfs '%s'", st.Type, cgroupfs)
584
// DetectCgroupMode() Returns the current Cgroup mode that is applied to the system
585
// This applies to systemd and non-systemd machines, possible values:
586
// - CGROUP_UNDEF: undefined
587
// - CGROUP_LEGACY: Cgroupv1 legacy controllers
588
// - CGROUP_HYBRID: Cgroupv1 and Cgroupv2 set up by systemd
589
// - CGROUP_UNIFIED: Pure Cgroupv2 hierarchy
591
// Reference: https://systemd.io/CGROUP_DELEGATION/
592
func DetectCgroupMode() (CgroupModeCode, error) {
593
detectCgrpModeOnce.Do(func() {
595
cgroupFSPath = defaultCgroupRoot
596
cgroupMode, err = detectCgroupMode(cgroupFSPath)
598
logger.GetLogger().WithError(err).WithField("cgroup.fs", cgroupFSPath).Debug("Could not detect Cgroup Mode")
599
cgroupMode, err = detectCgroupMode(defaults.Cgroup2Dir)
601
logger.GetLogger().WithError(err).WithField("cgroup.fs", defaults.Cgroup2Dir).Debug("Could not detect Cgroup Mode")
603
cgroupFSPath = defaults.Cgroup2Dir
606
if cgroupMode != CGROUP_UNDEF {
607
logger.GetLogger().WithFields(logrus.Fields{
608
"cgroup.fs": cgroupFSPath,
609
"cgroup.mode": cgroupMode.String(),
610
}).Infof("Cgroup mode detection succeeded")
614
if cgroupMode == CGROUP_UNDEF {
615
return CGROUP_UNDEF, fmt.Errorf("could not detect Cgroup Mode")
618
return cgroupMode, nil
621
func detectDeploymentMode() (DeploymentCode, error) {
622
mode := GetDeploymentMode()
623
if mode != DEPLOY_UNKNOWN {
627
// Let's call findMigrationPath in case to parse own cgroup
628
// paths and detect the deployment mode.
630
_, err := findMigrationPath(uint32(pid))
632
return DEPLOY_UNKNOWN, err
635
return GetDeploymentMode(), nil
638
func DetectDeploymentMode() (uint32, error) {
639
detectDeploymentOnce.Do(func() {
640
mode, err := detectDeploymentMode()
642
logger.GetLogger().WithFields(logrus.Fields{
643
"cgroup.fs": cgroupFSPath,
644
}).WithError(err).Warn("Detection of deployment mode failed")
648
logger.GetLogger().WithFields(logrus.Fields{
649
"cgroup.fs": cgroupFSPath,
650
"deployment.mode": DeploymentCode(mode).String(),
651
}).Info("Deployment mode detection succeeded")
654
mode := GetDeploymentMode()
655
if mode == DEPLOY_UNKNOWN {
656
return uint32(mode), fmt.Errorf("detect deployment mode failed, could not parse process cgroup paths")
659
return uint32(mode), nil
662
// DetectCgroupFSMagic() runs by default DetectCgroupMode()
663
// Return the Cgroupfs v1 or v2 that will be used by bpf programs
664
func DetectCgroupFSMagic() (uint64, error) {
665
// Run get cgroup mode again in case
666
mode, err := DetectCgroupMode()
668
return CGROUP_UNSET_VALUE, err
671
// Run this once and log output
672
detectCgroupFSOnce.Do(func() {
674
case CGROUP_LEGACY, CGROUP_HYBRID:
675
/* In both legacy or Hybrid modes we switch to Cgroupv1 from bpf side. */
676
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).Debug("Cgroup BPF helpers will run in raw Cgroup mode")
677
cgroupFSMagic = unix.CGROUP_SUPER_MAGIC
679
logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).Debug("Cgroup BPF helpers will run in Cgroupv2 mode or fallback to raw Cgroup on errors")
680
cgroupFSMagic = unix.CGROUP2_SUPER_MAGIC
684
if cgroupFSMagic == CGROUP_UNSET_VALUE {
685
return CGROUP_UNSET_VALUE, fmt.Errorf("could not detect Cgroup filesystem Magic")
688
return cgroupFSMagic, nil
691
// CgroupNameFromCstr() Returns a Golang string from the passed C language format string.
692
func CgroupNameFromCStr(cstr []byte) string {
693
i := bytes.IndexByte(cstr, 0)
697
return string(cstr[:i])
700
func tryHostCgroup(path string) error {
701
var st, pst unix.Stat_t
702
if err := unix.Lstat(path, &st); err != nil {
703
return fmt.Errorf("cannot determine cgroup root: error acessing path '%s': %w", path, err)
706
parent := filepath.Dir(path)
707
if err := unix.Lstat(parent, &pst); err != nil {
708
return fmt.Errorf("cannot determine cgroup root: error acessing parent path '%s': %w", parent, err)
711
if st.Dev == pst.Dev {
712
return fmt.Errorf("cannot determine cgroup root: '%s' does not appear to be a mount point", path)
715
fst := unix.Statfs_t{}
716
if err := unix.Statfs(path, &fst); err != nil {
717
return fmt.Errorf("cannot determine cgroup root: failed to get info for '%s'", path)
721
case unix.CGROUP2_SUPER_MAGIC, unix.CGROUP_SUPER_MAGIC:
724
return fmt.Errorf("cannot determine cgroup root: path '%s' is not a cgroup fs", path)
728
// HostCgroupRoot tries to retrieve the host cgroup root
730
// For cgroupv1, we return the directory of the contoller currently used.
732
// NB(kkourt): for now we are checking /sys/fs/cgroup under host /proc's init.
733
// For systems where the cgroup is mounted in a non-standard location, we could
734
// also check host's /proc/mounts.
735
func HostCgroupRoot() (string, error) {
736
components := []string{
737
option.Config.ProcFS, "1", "root",
738
"sys", "fs", "cgroup",
739
GetCgrpControllerName(),
742
path1 := filepath.Join(components...)
743
err1 := tryHostCgroup(path1)
748
path2 := filepath.Join(components[:len(components)-1]...)
749
err2 := tryHostCgroup(path2)
754
err := multierr.Append(
755
fmt.Errorf("failed to set path %s as cgroup root %w", path1, err1),
756
fmt.Errorf("failed to set path %s as cgroup root %w", path2, err2),
758
return "", fmt.Errorf("failed to set cgroup root: %w", err)