Skip to content

Commit

Permalink
Find process in mount NS if none are found in the cache
Browse files Browse the repository at this point in the history
The PIDs in mount NS cache is updated from `sched_process_exec` events, but the PIDs from the events cannot be used for procfs access if Tracee is not running in the root PID namespace (for example on WSL).
To work around this limitation, if no PID in the cache exists for the requested mount NS, we search through procfs to find any PID in the requested mount NS and add it to the cache.
  • Loading branch information
oshaked1 committed Oct 13, 2024
1 parent e79c970 commit df0c42a
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 24 deletions.
70 changes: 46 additions & 24 deletions pkg/containers/path_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/aquasecurity/tracee/pkg/bucketscache"
"github.com/aquasecurity/tracee/pkg/errfmt"
"github.com/aquasecurity/tracee/pkg/logger"
"github.com/aquasecurity/tracee/pkg/utils/proc"
)

// ContainerPathResolver generates an accessible absolute path from the root
Expand Down Expand Up @@ -45,36 +46,57 @@ func (cPathRes *ContainerPathResolver) GetHostAbsPath(mountNSAbsolutePath string
pids := cPathRes.mountNSPIDsCache.GetBucket(uint32(mountNS))

for _, pid := range pids {
// cap.SYS_PTRACE is needed here. Instead of raising privileges, since
// this is called too frequently, if the needed event is being traced,
// the needed capabilities are added to the Base ring and are always set
// as effective.
//
// (Note: To change this behavior we need a privileged process/server)

procRootPath := fmt.Sprintf("/proc/%d/root", int(pid))

// fs.FS interface requires relative paths, so the '/' prefix should be trimmed.
entries, err := fs.ReadDir(cPathRes.fs, strings.TrimPrefix(procRootPath, "/"))
procRoot, err := cPathRes.getProcessFSRoot(uint(pid))
if err != nil {
// This process is either not alive or we don't have permissions to access.
// Try next pid in mount ns to find accessible path to mount ns files.
logger.Debugw(
"Finding mount NS path",
"Unreachable proc root path", procRootPath,
"error", err.Error(),
)
logger.Debugw("Could not access process FS", "pid", pid, "error", err)
continue
}
if len(entries) == 0 {
return "", errfmt.Errorf("empty directory")
}
if err == nil {
return fmt.Sprintf("%s%s", procRootPath, mountNSAbsolutePath), nil
}

return fmt.Sprintf("%s%s", procRoot, mountNSAbsolutePath), nil
}

// No PIDs registered in this namespace, or couldn't access FS root of any of the PIDs found.
// Try finding one in procfs.
pid, err := proc.GetAnyProcessInNS("mnt", mountNS)
if err != nil {
// Couldn't find a process in this namespace using procfs
return "", ErrContainerFSUnreachable
}

procRoot, err := cPathRes.getProcessFSRoot(pid)
if err != nil {
logger.Debugw("Could not access process FS", "pid", pid, "error", err)
return "", ErrContainerFSUnreachable
}

// Register this process in the mount namespace
cPathRes.mountNSPIDsCache.AddBucketItem(uint32(mountNS), uint32(pid))

return fmt.Sprintf("%s%s", procRoot, mountNSAbsolutePath), nil
}

func (cPathRes *ContainerPathResolver) getProcessFSRoot(pid uint) (string, error) {
// cap.SYS_PTRACE is needed here. Instead of raising privileges, since
// this is called too frequently, if the needed event is being traced,
// the needed capabilities are added to the Base ring and are always set
// as effective.
//
// (Note: To change this behavior we need a privileged process/server)

procRootPath := fmt.Sprintf("/proc/%d/root", pid)

// fs.FS interface requires relative paths, so the '/' prefix should be trimmed.
entries, err := fs.ReadDir(cPathRes.fs, strings.TrimPrefix(procRootPath, "/"))
if err != nil {
// This process is either not alive or we don't have permissions to access.
return "", errfmt.Errorf("failed accessing process FS root %s: %v", procRootPath, err)
}
if len(entries) == 0 {
return "", errfmt.Errorf("process FS root (%s) is empty", procRootPath)
}

return "", ErrContainerFSUnreachable
return procRootPath, nil
}

var (
Expand Down
1 change: 1 addition & 0 deletions pkg/ebpf/processor_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func processKernelReadFile(event *trace.Event) error {
// processSchedProcessExec processes a sched_process_exec event by capturing the executed file.
func (t *Tracee) processSchedProcessExec(event *trace.Event) error {
// cache this pid by it's mnt ns
// TODO: don't do this if Tracee is not in the root PID NS?
if event.ProcessID == 1 {
t.pidsInMntns.ForceAddBucketItem(uint32(event.MountNS), uint32(event.HostProcessID))
} else {
Expand Down
28 changes: 28 additions & 0 deletions pkg/utils/proc/ns.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,31 @@ func extractNSFromLink(link string) (int, error) {
}
return ns, nil
}

// GetAnyProcessInNS returns the PID of any process in the given namespace type and number.
// It returns the first process it finds when iterating over /proc that satisfies the request.
// To do so, it requires access to the /proc file system, and CAP_SYS_PTRACE capability.
func GetAnyProcessInNS(nsName string, nsNum int) (uint, error) {
entries, err := os.ReadDir("/proc")
if err != nil {
return 0, errfmt.Errorf("could not read proc dir: %v", err)
}

for _, entry := range entries {
pid, err := strconv.ParseUint(entry.Name(), 10, 32)
if err != nil {
// Not a PID directory
continue
}
ns, err := GetProcNS(uint(pid), nsName)
if err != nil {
logger.Infow("Failed fetching process namespace", "pid", pid, "namespace", nsName, "error", err)
continue
}
if ns == nsNum {
return uint(pid), nil
}
}

return 0, errfmt.Errorf("could not find any process in %s namesapce %d", nsName, nsNum)
}

0 comments on commit df0c42a

Please sign in to comment.