Skip to content

Commit

Permalink
fix: select cgroup mountpoint with the smallest inode number
Browse files Browse the repository at this point in the history
When running Tracee from within a container, multiple mount points of the
cgroup filesystem may exist. In such cases, we need to ensure that we select
the mountpoint from the host cgroup namespace. By checking for an inode equal
to 1, we can identify the cgroupfs mountpoint belonging to the host cgroup
namespace. This ensures that Tracee can discover preexisting containers using
the host cgroup filesystem.

Additionally, a warning log is emitted if the cgroup mountpoint does not have
an inode of 1, indicating that it might not be part of the host cgroup
namespace.
  • Loading branch information
yanivagman committed May 28, 2024
1 parent f07388c commit 940917f
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 24 deletions.
10 changes: 10 additions & 0 deletions pkg/cgroup/cgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ func (c *CgroupV1) init() error {
// 2. discover where cgroup is mounted
c.mountpoint = c.mounted.GetMountpoint()

inode := c.mounted.GetMountpointInode()
if inode != 1 {
logger.Warnw("Cgroup mountpoint is not in the host cgroup namespace", "mountpoint", c.mountpoint, "inode", inode)
}

return nil
}

Expand Down Expand Up @@ -265,6 +270,11 @@ func (c *CgroupV2) init() error {
// 2. discover where cgroup is mounted
c.mountpoint = c.mounted.GetMountpoint()

inode := c.mounted.GetMountpointInode()
if inode != 1 {
logger.Warnw("Cgroup mountpoint is not in the host cgroup namespace", "mountpoint", c.mountpoint, "inode", inode)
}

return nil
}

Expand Down
83 changes: 59 additions & 24 deletions pkg/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strings"
"syscall"

"golang.org/x/exp/slices"
"kernel.org/pub/linux/libs/security/libcap/cap"

"github.com/aquasecurity/tracee/pkg/capabilities"
Expand Down Expand Up @@ -38,6 +37,7 @@ type MountHostOnce struct {
target string
fsType string
data string
mpInode int
managed bool
mounted bool
}
Expand All @@ -60,6 +60,14 @@ func NewMountHostOnce(source, fstype, data, where string) (*MountHostOnce, error
return nil, errfmt.WrapError(err)
}
m.managed = true // managed by this object

// Try to get the inode number of the current mountpoint.
var stat syscall.Stat_t
if err := syscall.Stat(m.target, &stat); err != nil {
logger.Warnw("Stat failed", "mountpoint", m.target, "error", err)
} else {
m.mpInode = int(stat.Ino)
}
}

m.mounted = true
Expand Down Expand Up @@ -143,10 +151,14 @@ func (m *MountHostOnce) GetMountpoint() string {
return m.target
}

func (m *MountHostOnce) GetMountpointInode() int {
return m.mpInode
}

// private

func (m *MountHostOnce) isMountedByOS(where string) (bool, error) {
mp, err := SearchMountpointFromHost(m.fsType, m.data)
mp, inode, err := SearchMountpointFromHost(m.fsType, m.data)
if err != nil || mp == "" {
return false, errfmt.WrapError(err)
}
Expand All @@ -155,8 +167,9 @@ func (m *MountHostOnce) isMountedByOS(where string) (bool, error) {
}

m.target = mp // replace given target dir with existing mountpoint
m.mpInode = inode
m.mounted = true
m.managed = false // proforma
m.managed = false

return true, nil
}
Expand Down Expand Up @@ -189,15 +202,30 @@ func IsFileSystemSupported(fsType string) (bool, error) {
return false, nil
}

// SearchMountpointFromHost returns the last mountpoint for a given filesystem type
// containing a searchable string. It confirms the mount originates from the root file
// system.
func SearchMountpointFromHost(fstype string, search string) (string, error) {
// SearchMountpointFromHost scans the /proc/self/mountinfo file to find the oldest
// mountpoint of a specified filesystem type (fstype) that contains a given
// searchable string (search) in its path. This is useful in environments like
// containers where multiple mountpoints may exist, and we need to find the one
// that belongs to the host namespace.
//
// Parameters:
// - fstype: The filesystem type to search for (e.g., "cgroup2", "ext4").
// - search: The substring to search for within the mountpoint path (e.g., "/sys/fs/cgroup").
//
// Returns:
// - string: The path of the oldest matching mountpoint.
// - int: The inode number of the matching mountpoint.
// - error: Any error encountered while reading the /proc/mounts file.
func SearchMountpointFromHost(fstype string, search string) (string, int, error) {
const mountpointIndex = 4
const fsTypeIndex = 8

mp := ""
inode := 0

file, err := os.Open(procMounts)
if err != nil {
return "", errfmt.WrapError(err)
return "", 0, errfmt.WrapError(err)
}
defer func() {
if err := file.Close(); err != nil {
Expand All @@ -208,24 +236,31 @@ func SearchMountpointFromHost(fstype string, search string) (string, error) {
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.Split(scanner.Text(), " ")
mountRoot := line[3]
mountpoint := line[4]
sepIndex := slices.Index(line, "-")
fsTypeIndex := sepIndex + 1
if len(line) <= fsTypeIndex {
continue // Skip lines that do not have enough fields
}

mountpoint := line[mountpointIndex]
currFstype := line[fsTypeIndex]
// Check for the following 3 conditions:
// 1. The fs type is the one we search for
// 2. The mountpoint contains the path we are searching
// 3. The root path in the mounted filesystem is that of the host.
// This means, that the root of the mounted filesystem is /.
// For example, if we are searching for /sys/fs/cgroup, we want to
// be sure that it is not actually .../sys/fs/cgroup, but strictly
// the searched path.
if fstype == currFstype && strings.Contains(mountpoint, search) && mountRoot == "/" {
mp = mountpoint
break

// Check if the current line matches the desired filesystem type and contains the search string.
if fstype == currFstype && strings.Contains(mountpoint, search) {
// Try to get the inode number of the current mountpoint.
var stat syscall.Stat_t
if err := syscall.Stat(mountpoint, &stat); err != nil {
logger.Warnw("Stat failed", "mountpoint", mountpoint, "error", err)
continue // Skip this mountpoint if stat fails
}
currInode := int(stat.Ino)

// Update the result if this is the first match or if the current mountpoint is older or shorter in path length.
if inode == 0 || currInode < inode ||
(currInode == inode && len(mountpoint) < len(mp)) {
mp = mountpoint
inode = currInode
}
}
}

return mp, nil
return mp, inode, nil
}

0 comments on commit 940917f

Please sign in to comment.