Skip to content

Commit

Permalink
Include namespace inode numbers on process events (#213)
Browse files Browse the repository at this point in the history
This diff adds the inode number of each namespace the process belongs to, to
process events. Two namespaces were excluded: pid_for_children and
time_for_children, see namespaces(7).

The pid namespace is a bit special as it's not in nsproxy, the user facing pid
level is the last/deepest.

There was an inclusion of mnt ns, that is not used by beats or endpoint, I kept
it but made it fetch from the new "path".

While here, add support for changing the compiler in gen_initramfs.sh by setting
CC. While Fedora includes a ${arch}-linux-gnu-gcc, it doesn't include matching
headers, so I can't really compile the tests locally.
  • Loading branch information
haesbaert authored Nov 20, 2024
1 parent 05ce927 commit c16e7ef
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 18 deletions.
13 changes: 13 additions & 0 deletions GPL/Events/EbpfEventProto.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@ struct ebpf_file_info {
uint64_t ctime;
} __attribute__((packed));

struct ebpf_namespace_info {
uint32_t uts_inonum;
uint32_t ipc_inonum;
uint32_t mnt_inonum;
uint32_t net_inonum;
uint32_t cgroup_inonum;
uint32_t time_inonum;
uint32_t pid_inonum;
} __attribute__((packed));

// Full events follow
struct ebpf_file_delete_event {
struct ebpf_event_header hdr;
Expand Down Expand Up @@ -223,6 +233,7 @@ struct ebpf_process_fork_event {
struct ebpf_cred_info creds;
struct ebpf_tty_dev ctty;
char comm[TASK_COMM_LEN];
struct ebpf_namespace_info ns;

// Variable length fields: pids_ss_cgroup_path
struct ebpf_varlen_fields_start vl_fields;
Expand All @@ -238,6 +249,7 @@ struct ebpf_process_exec_event {
struct ebpf_cred_info creds;
struct ebpf_tty_dev ctty;
char comm[TASK_COMM_LEN];
struct ebpf_namespace_info ns;
uint32_t inode_nlink;
uint32_t flags;

Expand All @@ -251,6 +263,7 @@ struct ebpf_process_exit_event {
struct ebpf_cred_info creds;
struct ebpf_tty_dev ctty;
char comm[TASK_COMM_LEN];
struct ebpf_namespace_info ns;
int32_t exit_code;

// Variable length fields: pids_ss_cgroup_path
Expand Down
25 changes: 14 additions & 11 deletions GPL/Events/File/Probe.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ DECL_FUNC_ARG_EXISTS(vfs_rename, rd);
DECL_FUNC_ARG(do_truncate, filp);
DECL_FUNC_RET(do_truncate);

static int mntns(const struct task_struct *task)
{
return BPF_CORE_READ(task, nsproxy, mnt_ns, ns.inum);
}

static int do_unlinkat__enter()
{
struct ebpf_events_state state = {};
Expand Down Expand Up @@ -129,9 +124,11 @@ static int vfs_unlink__exit(int ret)
ebpf_cred_info__fill(&event->creds, task);

struct path p;
p.dentry = &state->unlink.de;
p.mnt = state->unlink.mnt;
event->mntns = mntns(task);
p.dentry = &state->unlink.de;
p.mnt = state->unlink.mnt;
struct ebpf_namespace_info ns;
ebpf_ns__fill(&ns, task);
event->mntns = ns.mnt_inonum;
bpf_get_current_comm(event->comm, TASK_COMM_LEN);
ebpf_file_info__fill(&event->finfo, p.dentry);

Expand Down Expand Up @@ -236,7 +233,9 @@ static void prepare_and_send_file_event(struct file *f,
struct path p = BPF_CORE_READ(f, f_path);
ebpf_pid_info__fill(&event->pids, task);
ebpf_cred_info__fill(&event->creds, task);
event->mntns = mntns(task);
struct ebpf_namespace_info ns;
ebpf_ns__fill(&ns, task);
event->mntns = ns.mnt_inonum;
bpf_get_current_comm(event->comm, TASK_COMM_LEN);
ebpf_file_info__fill(&event->finfo, p.dentry);

Expand Down Expand Up @@ -489,7 +488,9 @@ static int vfs_rename__exit(int ret)
event->hdr.ts_boot = bpf_ktime_get_boot_ns_helper();
ebpf_pid_info__fill(&event->pids, task);
ebpf_cred_info__fill(&event->creds, task);
event->mntns = mntns(task);
struct ebpf_namespace_info ns;
ebpf_ns__fill(&ns, task);
event->mntns = ns.mnt_inonum;
bpf_get_current_comm(event->comm, TASK_COMM_LEN);
ebpf_file_info__fill(&event->finfo, de);

Expand Down Expand Up @@ -559,7 +560,9 @@ static void file_modify_event__emit(enum ebpf_file_change_type typ, struct path
event->change_type = typ;
ebpf_pid_info__fill(&event->pids, task);
ebpf_cred_info__fill(&event->creds, task);
event->mntns = mntns(task);
struct ebpf_namespace_info ns;
ebpf_ns__fill(&ns, task);
event->mntns = ns.mnt_inonum;
bpf_get_current_comm(event->comm, TASK_COMM_LEN);
struct dentry *d = BPF_CORE_READ(path, dentry);
ebpf_file_info__fill(&event->finfo, d);
Expand Down
21 changes: 21 additions & 0 deletions GPL/Events/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,27 @@ static void ebpf_comm__fill(char *comm, size_t len, const struct task_struct *ta
read_kernel_str_or_empty_str(comm, len, BPF_CORE_READ(task, comm));
}

static void ebpf_ns__fill(struct ebpf_namespace_info *nsi, const struct task_struct *task)
{
struct pid *pid;
int pid_level;

nsi->uts_inonum = BPF_CORE_READ(task, nsproxy, uts_ns, ns.inum);
nsi->ipc_inonum = BPF_CORE_READ(task, nsproxy, ipc_ns, ns.inum);
nsi->mnt_inonum = BPF_CORE_READ(task, nsproxy, mnt_ns, ns.inum);
nsi->net_inonum = BPF_CORE_READ(task, nsproxy, net_ns, ns.inum);
nsi->cgroup_inonum = BPF_CORE_READ(task, nsproxy, cgroup_ns, ns.inum);
nsi->time_inonum = BPF_CORE_READ(task, nsproxy, time_ns, ns.inum);

pid = BPF_CORE_READ(task, thread_pid);
if (pid == NULL) {
nsi->pid_inonum = 0;
return;
}
pid_level = BPF_CORE_READ(pid, level);
nsi->pid_inonum = BPF_CORE_READ(pid, numbers[pid_level].ns, ns.inum);
}

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
Expand Down
3 changes: 3 additions & 0 deletions GPL/Events/Process/Probe.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ int BPF_PROG(sched_process_fork, const struct task_struct *parent, const struct
ebpf_cred_info__fill(&event->creds, parent);
ebpf_ctty__fill(&event->ctty, child);
ebpf_comm__fill(event->comm, sizeof(event->comm), child);
ebpf_ns__fill(&event->ns, child);

// Variable length fields
ebpf_vl_fields__init(&event->vl_fields);
Expand Down Expand Up @@ -111,6 +112,7 @@ int BPF_PROG(sched_process_exec,
ebpf_cred_info__fill(&event->creds, task);
ebpf_ctty__fill(&event->ctty, task);
ebpf_comm__fill(event->comm, sizeof(event->comm), task);
ebpf_ns__fill(&event->ns, task);

// set setuid and setgid flags
struct file *f = BPF_CORE_READ(binprm, file);
Expand Down Expand Up @@ -211,6 +213,7 @@ static int taskstats_exit__enter(const struct task_struct *task, int group_dead)
ebpf_cred_info__fill(&event->creds, task);
ebpf_ctty__fill(&event->ctty, task);
ebpf_comm__fill(event->comm, sizeof(event->comm), task);
ebpf_ns__fill(&event->ns, task);

// Variable length fields
ebpf_vl_fields__init(&event->vl_fields);
Expand Down
23 changes: 23 additions & 0 deletions non-GPL/Events/EventsTrace/EventsTrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,26 @@ static void out_file_info(const char *name, struct ebpf_file_info *finfo)
out_object_end();
}

static void out_ns_info(const char *name, struct ebpf_namespace_info *ns)
{
printf("\"%s\":", name);
out_object_start();
out_uint("uts", ns->uts_inonum);
out_comma();
out_uint("ipc", ns->ipc_inonum);
out_comma();
out_uint("mnt", ns->mnt_inonum);
out_comma();
out_uint("net", ns->net_inonum);
out_comma();
out_uint("cgroup", ns->cgroup_inonum);
out_comma();
out_uint("time", ns->time_inonum);
out_comma();
out_uint("pid", ns->pid_inonum);
out_object_end();
}

static void out_null_delimited_string_array(const char *name, char *buf, size_t buf_size)
{
// buf is an array (argv, env etc.) with multiple values delimited by a '\0'
Expand Down Expand Up @@ -768,6 +788,9 @@ static void out_process_fork(struct ebpf_process_fork_event *evt)
out_comma();

out_string("comm", evt->comm);
out_comma();

out_ns_info("ns", &evt->ns);

struct ebpf_varlen_field *field;
FOR_EACH_VARLEN_FIELD(evt->vl_fields, field)
Expand Down
2 changes: 1 addition & 1 deletion testing/scripts/gen_initramfs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ build_testbins() {
for c_src in *.c; do
local bin_path=bin/$arch/$(basename $c_src .c)

${arch}-linux-gnu-gcc -g -static $c_src -o $bin_path \
${CC-${arch}-linux-gnu-gcc} -g -static $c_src -o $bin_path \
|| exit_error "compilation of $c_src for $arch failed (see above)"
done

Expand Down
9 changes: 5 additions & 4 deletions testing/testrunner/ebpf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ func ForkExit(t *testing.T, et *Runner) {
require.Equal(t, forkEvent.ChildPids.Sid, forkEvent.ParentPids.Sid)
require.Equal(t, forkEvent.ChildPids.Pgid, forkEvent.ParentPids.Pgid)
require.NotEqual(t, forkEvent.ChildPids.Tgid, forkEvent.ParentPids.Tgid)

// Check if all namespace values match /proc/self/ns/*
ns, err := FetchNsFromProc()
require.NoError(t, err)
require.Equal(t, forkEvent.Ns, ns)
}

func ForkExec(t *testing.T, et *Runner) {
Expand Down Expand Up @@ -154,7 +159,6 @@ func ForkExec(t *testing.T, et *Runner) {
require.Equal(t, execEvent.Env[0], "TEST_ENV_KEY1=TEST_ENV_VAL1")
require.Equal(t, execEvent.Env[1], "TEST_ENV_KEY2=TEST_ENV_VAL2")
require.Equal(t, execEvent.Cwd, "/")

}

func FileCreate(t *testing.T, et *Runner) {
Expand Down Expand Up @@ -185,7 +189,6 @@ func FileCreate(t *testing.T, et *Runner) {
}

func FileDelete(t *testing.T, et *Runner) {

var binOutput struct {
PidInfo TestPidInfo `json:"pid_info"`
FileNameOrig string `json:"filename_orig"`
Expand Down Expand Up @@ -426,7 +429,6 @@ func Tcpv4ConnectionAttempt(t *testing.T, et *Runner) {
require.Equal(t, ev.Net.DestPort, binOutput.ServerPort)
require.Equal(t, ev.Net.NetNs, binOutput.NetNs)
require.Equal(t, ev.Comm, "tcpv4_connect")

}

func Tcpv4ConnectionAccept(t *testing.T, et *Runner) {
Expand Down Expand Up @@ -665,5 +667,4 @@ func TestEbpf(t *testing.T) {
run.Stop()
})
}

}
64 changes: 62 additions & 2 deletions testing/testrunner/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -90,11 +91,22 @@ type FileInfo struct {
Ctime uint64 `json:"ctime"`
}

type NsInfo struct {
Uts uint32 `json:"uts"`
Ipc uint32 `json:"ipc"`
Mnt uint32 `json:"mnt"`
Net uint32 `json:"net"`
Cgroup uint32 `json:"cgroup"`
Time uint32 `json:"time"`
Pid uint32 `json:"pid"`
}

type ProcessForkEvent struct {
ParentPids PidInfo `json:"parent_pids"`
ChildPids PidInfo `json:"child_pids"`
Creds CredInfo `json:"creds"`
Ctty TtyInfo `json:"ctty"`
Ns NsInfo `json:"ns"`
}

type ProcessExecEvent struct {
Expand Down Expand Up @@ -195,8 +207,10 @@ var testBinaryPath = "/"
var eventsTracePath = "/EventsTrace"

// Path to the TC filter test binary and probe. This one is weird and lives outside the rest of the test binaries
var tcTestPath = "/BPFTcFilterTests"
var tcObjPath = "/TcFilter.bpf.o"
var (
tcTestPath = "/BPFTcFilterTests"
tcObjPath = "/TcFilter.bpf.o"
)

// init will run at startup and figure out if we're running in the bluebox test env or not,
// and set paths for the binaries as needed
Expand Down Expand Up @@ -327,3 +341,49 @@ func PrintDebugOutputOnFail() {

fmt.Println("BPF test failed, see errors and stacktrace above")
}

func FetchNsFromProc() (NsInfo, error) {
var ns NsInfo

fetch := func(name string, dst *uint32) error {
s, err := os.Readlink("/proc/self/ns/" + name)
if err != nil {
return err
}
start := strings.IndexByte(s, '[')
if start == -1 {
return fmt.Errorf("`[` not found for ns %s", name)
}
start++
end := strings.IndexByte(s, ']')
if end == -1 {
return fmt.Errorf("`]` not found for ns %s", name)
}
v, err := strconv.Atoi(s[start:end])
if err != nil {
return err
}
*dst = uint32(v)
return nil
}

calls := []struct {
name string
dst *uint32
}{
{"uts", &ns.Uts},
{"ipc", &ns.Ipc},
{"mnt", &ns.Mnt},
{"net", &ns.Net},
{"cgroup", &ns.Cgroup},
{"time", &ns.Time},
{"pid", &ns.Pid},
}
for _, call := range calls {
if err := fetch(call.name, call.dst); err != nil {
return ns, err
}
}

return ns, nil
}

0 comments on commit c16e7ef

Please sign in to comment.