From 618963eccb544d9413ab6cf2fd6c36c52b63a764 Mon Sep 17 00:00:00 2001 From: William Findlay Date: Fri, 24 Jan 2025 11:38:23 -0500 Subject: [PATCH] proc_reader: handle in_init_tree Recently we introduced the in_init_tree flag into execve map values to indicate whether a process is a member of the initial process tree for a container. This worked well for containers started after Tetragon, but broke for cases where the container was started before Tetragon, since our procfs walk did not account for the in_init_tree flag. Fix this behaviour by introducing logic in the procfs walk to account for this. Signed-off-by: William Findlay --- pkg/sensors/exec/procevents/proc_reader.go | 62 +++++++++++-------- .../exec/procevents/proc_reader_test.go | 39 ++++++++++++ 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/pkg/sensors/exec/procevents/proc_reader.go b/pkg/sensors/exec/procevents/proc_reader.go index 6e3f0d3aba9..7917338cc84 100644 --- a/pkg/sensors/exec/procevents/proc_reader.go +++ b/pkg/sensors/exec/procevents/proc_reader.go @@ -320,6 +320,41 @@ func updateExecveMapStats(procs int64) { } } +func procToKeyValue(p procs, inInitTree map[uint32]struct{}) (*execvemap.ExecveKey, *execvemap.ExecveValue) { + k := &execvemap.ExecveKey{Pid: p.pid} + v := &execvemap.ExecveValue{} + + v.Parent.Pid = p.ppid + v.Parent.Ktime = p.pktime + v.Process.Pid = p.pid + v.Process.Ktime = p.ktime + v.Flags = 0 + v.Nspid = p.nspid + v.Capabilities.Permitted = p.permitted + v.Capabilities.Effective = p.effective + v.Capabilities.Inheritable = p.inheritable + v.Namespaces.UtsInum = p.uts_ns + v.Namespaces.IpcInum = p.ipc_ns + v.Namespaces.MntInum = p.mnt_ns + v.Namespaces.PidInum = p.pid_ns + v.Namespaces.PidChildInum = p.pid_for_children_ns + v.Namespaces.NetInum = p.net_ns + v.Namespaces.TimeInum = p.time_ns + v.Namespaces.TimeChildInum = p.time_for_children_ns + v.Namespaces.CgroupInum = p.cgroup_ns + v.Namespaces.UserInum = p.user_ns + pathLength := copy(v.Binary.Path[:], p.exe) + v.Binary.PathLength = int32(pathLength) + + _, parentInInitTree := inInitTree[p.ppid] + if v.Nspid == 1 || parentInInitTree { + v.Flags |= api.EventInInitTree + inInitTree[p.pid] = struct{}{} + } + + return k, v +} + func writeExecveMap(procs []procs) { mapDir := bpf.MapPrefixPath() @@ -335,32 +370,9 @@ func writeExecveMap(procs []procs) { panic(err) } } + inInitTree := make(map[uint32]struct{}) for _, p := range procs { - k := &execvemap.ExecveKey{Pid: p.pid} - v := &execvemap.ExecveValue{} - - v.Parent.Pid = p.ppid - v.Parent.Ktime = p.pktime - v.Process.Pid = p.pid - v.Process.Ktime = p.ktime - v.Flags = 0 - v.Nspid = p.nspid - v.Capabilities.Permitted = p.permitted - v.Capabilities.Effective = p.effective - v.Capabilities.Inheritable = p.inheritable - v.Namespaces.UtsInum = p.uts_ns - v.Namespaces.IpcInum = p.ipc_ns - v.Namespaces.MntInum = p.mnt_ns - v.Namespaces.PidInum = p.pid_ns - v.Namespaces.PidChildInum = p.pid_for_children_ns - v.Namespaces.NetInum = p.net_ns - v.Namespaces.TimeInum = p.time_ns - v.Namespaces.TimeChildInum = p.time_for_children_ns - v.Namespaces.CgroupInum = p.cgroup_ns - v.Namespaces.UserInum = p.user_ns - pathLength := copy(v.Binary.Path[:], p.exe) - v.Binary.PathLength = int32(pathLength) - + k, v := procToKeyValue(p, inInitTree) err := m.Put(k, v) if err != nil { logger.GetLogger().WithField("value", v).WithError(err).Warn("failed to put value in execve_map") diff --git a/pkg/sensors/exec/procevents/proc_reader_test.go b/pkg/sensors/exec/procevents/proc_reader_test.go index c0e514e15b3..a512ca9a5bf 100644 --- a/pkg/sensors/exec/procevents/proc_reader_test.go +++ b/pkg/sensors/exec/procevents/proc_reader_test.go @@ -4,8 +4,15 @@ package procevents import ( + "os/exec" + "strconv" + "strings" "testing" + "time" + "github.com/cilium/tetragon/pkg/api" + "github.com/cilium/tetragon/pkg/observer/observertesthelper/docker" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -20,3 +27,35 @@ func TestListRunningProcs(t *testing.T) { require.Equal(t, p.pid, p.tid) } } + +func TestInInitTreeProcfs(t *testing.T) { + if err := exec.Command("docker", "version").Run(); err != nil { + t.Skipf("docker not available. skipping test: %s", err) + } + + containerID := docker.DockerCreate(t, "--name", "procfs-in-init-tree-test", "bash", "bash", "-c", "sleep infinity") + + docker.DockerStart(t, "procfs-in-init-tree-test") + time.Sleep(1 * time.Second) + + rootPidOutput, err := exec.Command("docker", "inspect", "-f", "{{.State.Pid}}", containerID).Output() + require.NoError(t, err, "root pid should fetch") + rootPid, err := strconv.Atoi(strings.TrimSpace(string(rootPidOutput))) + require.NoError(t, err, "root pid should parse") + + procs, err := listRunningProcs("/proc") + require.NoError(t, err) + require.NotNil(t, procs) + require.NotEqual(t, 0, len(procs)) + + inInitTree := make(map[uint32]struct{}) + for _, p := range procs { + require.NotZero(t, p.pid) + require.Equal(t, p.pid, p.tid) + _, v := procToKeyValue(p, inInitTree) + if v.Process.Pid == uint32(rootPid) || v.Parent.Pid == uint32(rootPid) { + isInInitTree := v.Flags&api.EventInInitTree == api.EventInInitTree + assert.True(t, isInInitTree) + } + } +}