Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proc_reader: handle in_init_tree #3338

Merged
merged 3 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions pkg/observer/observertesthelper/docker/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package docker

import (
"os/exec"
"strings"
"testing"
)

// Create creates a new docker container in the background. The container will
// be killed and removed on test cleanup.
// It returns the containerId on success, or an error if spawning the container failed.
func Create(tb testing.TB, args ...string) (containerId string) {
// note: we are not using `--rm` so we can choose to wait on the container
// with `docker wait`. We remove it manually below in t.Cleanup instead
args = append([]string{"create"}, args...)
id, err := exec.Command("docker", args...).Output()
if err != nil {
tb.Fatalf("failed to spawn docker container %v: %s", args, err)
}

containerId = strings.TrimSpace(string(id))
tb.Cleanup(func() {
err := exec.Command("docker", "rm", "--force", containerId).Run()
if err != nil {
tb.Logf("failed to remove container %s: %s", containerId, err)
}
})

return containerId
}

// Start starts a new docker container with a given ID.
func Start(tb testing.TB, id string) {
err := exec.Command("docker", "start", id).Run()
if err != nil {
tb.Fatalf("failed to start docker container %s: %s", id, err)
}
}

// dockerRun starts a new docker container in the background. The container will
// be killed and removed on test cleanup.
// It returns the containerId on success, or an error if spawning the container failed.
func Run(tb testing.TB, args ...string) (containerId string) {
// note: we are not using `--rm` so we can choose to wait on the container
// with `docker wait`. We remove it manually below in t.Cleanup instead
args = append([]string{"run", "--detach"}, args...)
id, err := exec.Command("docker", args...).Output()
if err != nil {
tb.Fatalf("failed to spawn docker container %v: %s", args, err)
}

containerId = strings.TrimSpace(string(id))
tb.Cleanup(func() {
err := exec.Command("docker", "rm", "--force", containerId).Run()
if err != nil {
tb.Logf("failed to remove container %s: %s", containerId, err)
}
})

return containerId
}

// dockerExec executes a command in a container.
func Exec(tb testing.TB, id string, args ...string) {
args = append([]string{"exec", id}, args...)
err := exec.Command("docker", args...).Run()
if err != nil {
tb.Fatalf("failed to exec in docker container %v: %s", args, err)
}
}
63 changes: 0 additions & 63 deletions pkg/observer/observertesthelper/observer_test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,69 +521,6 @@ func ExecWGCurl(readyWG *sync.WaitGroup, retries uint, args ...string) error {
return err
}

// DockerCreate creates a new docker container in the background. The container will
// be killed and removed on test cleanup.
// It returns the containerId on success, or an error if spawning the container failed.
func DockerCreate(tb testing.TB, args ...string) (containerId string) {
// note: we are not using `--rm` so we can choose to wait on the container
// with `docker wait`. We remove it manually below in t.Cleanup instead
args = append([]string{"create"}, args...)
id, err := exec.Command("docker", args...).Output()
if err != nil {
tb.Fatalf("failed to spawn docker container %v: %s", args, err)
}

containerId = strings.TrimSpace(string(id))
tb.Cleanup(func() {
err := exec.Command("docker", "rm", "--force", containerId).Run()
if err != nil {
tb.Logf("failed to remove container %s: %s", containerId, err)
}
})

return containerId
}

// DockerStart starts a new docker container with a given ID.
func DockerStart(tb testing.TB, id string) {
err := exec.Command("docker", "start", id).Run()
if err != nil {
tb.Fatalf("failed to start docker container %s: %s", id, err)
}
}

// dockerRun starts a new docker container in the background. The container will
// be killed and removed on test cleanup.
// It returns the containerId on success, or an error if spawning the container failed.
func DockerRun(tb testing.TB, args ...string) (containerId string) {
// note: we are not using `--rm` so we can choose to wait on the container
// with `docker wait`. We remove it manually below in t.Cleanup instead
args = append([]string{"run", "--detach"}, args...)
id, err := exec.Command("docker", args...).Output()
if err != nil {
tb.Fatalf("failed to spawn docker container %v: %s", args, err)
}

containerId = strings.TrimSpace(string(id))
tb.Cleanup(func() {
err := exec.Command("docker", "rm", "--force", containerId).Run()
if err != nil {
tb.Logf("failed to remove container %s: %s", containerId, err)
}
})

return containerId
}

// dockerExec executes a command in a container.
func DockerExec(tb testing.TB, id string, args ...string) {
args = append([]string{"exec", id}, args...)
err := exec.Command("docker", args...).Run()
if err != nil {
tb.Fatalf("failed to exec in docker container %v: %s", args, err)
}
}

type fakeK8sWatcher struct {
fakePod, fakeNamespace string
}
Expand Down
9 changes: 5 additions & 4 deletions pkg/sensors/exec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
sm "github.com/cilium/tetragon/pkg/matchers/stringmatcher"
"github.com/cilium/tetragon/pkg/observer"
"github.com/cilium/tetragon/pkg/observer/observertesthelper"
"github.com/cilium/tetragon/pkg/observer/observertesthelper/docker"
"github.com/cilium/tetragon/pkg/option"
proc "github.com/cilium/tetragon/pkg/process"
"github.com/cilium/tetragon/pkg/reader/caps"
Expand Down Expand Up @@ -597,7 +598,7 @@ func TestDocker(t *testing.T) {
observertesthelper.LoopEvents(ctx, t, &doneWG, &readyWG, obs)

readyWG.Wait()
serverDockerID := observertesthelper.DockerRun(t, "--name", "fgs-test-server", "--entrypoint", "nc", "quay.io/cilium/alpine-curl:v1.6.0", "-nvlp", "8081", "-s", "0.0.0.0")
serverDockerID := docker.Run(t, "--name", "fgs-test-server", "--entrypoint", "nc", "quay.io/cilium/alpine-curl:v1.6.0", "-nvlp", "8081", "-s", "0.0.0.0")
time.Sleep(1 * time.Second)

// Tetragon sends 31 bytes + \0 to user-space. Since it might have an arbitrary prefix,
Expand Down Expand Up @@ -637,7 +638,7 @@ func TestInInitTree(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), tus.Conf().CmdWaitTime)
defer cancel()

containerID := observertesthelper.DockerCreate(t, "--name", "in-init-tree-test", "bash", "bash", "-c", "sleep infinity")
containerID := docker.Create(t, "--name", "in-init-tree-test", "bash", "bash", "-c", "sleep infinity")
// Tetragon sends 31 bytes + \0 to user-space. Since it might have an arbitrary prefix,
// match only on the first 24 bytes.
trimmedContainerID := sm.Prefix(containerID[:24])
Expand All @@ -650,9 +651,9 @@ func TestInInitTree(t *testing.T) {
observertesthelper.LoopEvents(ctx, t, &doneWG, &readyWG, obs)

readyWG.Wait()
observertesthelper.DockerStart(t, "in-init-tree-test")
docker.Start(t, "in-init-tree-test")
time.Sleep(1 * time.Second)
observertesthelper.DockerExec(t, "in-init-tree-test", "ls")
docker.Exec(t, "in-init-tree-test", "ls")

// This is the initial cmd, so inInitTree should be true
entrypointChecker := ec.NewProcessChecker().
Expand Down
12 changes: 12 additions & 0 deletions pkg/sensors/exec/procevents/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package procevents

import (
"bytes"
"encoding/hex"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -136,6 +137,17 @@ func procsFindDockerId(cgroups string) (string, int) {
return container, i
}
}
// In some environments, such as the GitHub Ubuntu CI runner, docker cgroups do not contain the docker keyword but do end with a hex ID in their last component. Fall back to a naive approach here to handle that case.
components := strings.Split(s, "/")
if len(components) > 0 {
id := components[len(components)-1]
_, err := hex.DecodeString(id)
if err == nil {
if len(id) >= 31 {
return id[:31], len(strings.Join(components[:len(components)-1], "")) + 1
}
}
}
}
return "", 0
}
Expand Down
62 changes: 37 additions & 25 deletions pkg/sensors/exec/procevents/proc_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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")
Expand Down
39 changes: 39 additions & 0 deletions pkg/sensors/exec/procevents/proc_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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.Create(t, "--name", "procfs-in-init-tree-test", "bash", "bash", "-c", "sleep infinity")

docker.Start(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)
}
}
}
5 changes: 5 additions & 0 deletions pkg/sensors/exec/procevents/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ func TestProcsFindContainerId(t *testing.T) {
assert.Equal(t, i, 80, "ContainerId offset wrong")
assert.Equal(t, d, "0ca2b3cd20e5f55a2bbe8d4aa3f811c", "ContainerId wrong")

p = "11:pids:/actions_job/ec5fd62ba68d0b75a3cbdb7f7f78b526440b7969e22b2b362fb6f429ded42fdc"
d, i = procsFindDockerId(p)
assert.Equal(t, i, 20, "ContainerId offset wrong")
assert.Equal(t, d, "ec5fd62ba68d0b75a3cbdb7f7f78b52", "ContainerId wrong")

p = ""
d, i = procsFindDockerId(p)
assert.Equal(t, d, "", "Expect output '' empty string")
Expand Down
Loading