diff --git a/bpf/process/bpf_exit.c b/bpf/process/bpf_exit.c index 2d7698efb6f..90282467f38 100644 --- a/bpf/process/bpf_exit.c +++ b/bpf/process/bpf_exit.c @@ -7,11 +7,57 @@ char _license[] __attribute__((section("license"), used)) = "GPL"; -__attribute__((section("tracepoint/sys_exit"), used)) int -event_exit(struct sched_execve_args *ctx) +/* + * Hooking on do_task_dead kernel function, which is the last one the + * task would execute after exiting. It's stable since v4.19, so it's + * safe to hook for us. + * + * To find out if we are the last thread of execution in the task we + * use current->signal->live counter (thanks Djalal! ;-) ) + * + * It's initialized for thread leader: + * + * clone { + * copy_process + * copy_signal + * atomic_set(&sig->live, 1); + * } + * + * Incremented for each new thread: + * + * clone { + * copy_process + * atomic_inc(¤t->signal->live); + * ... + * wake_up_new_task + * } + * + * Decremented for each exiting thread: + * + * do_exit { + * atomic_dec_and_test(&tsk->signal->live); + * ... + * do_task_dead + * __schedule + * BUG + * } + * + * If task->signal->live == 0 we are the last thread of execution and we + * won't race with another clone, because there's no other thread to call + * it (current thread is in do_exit). + */ +__attribute__((section("kprobe/do_task_dead"), used)) int +event_exit(struct pt_regs *ctx) { + struct task_struct *task = (struct task_struct *)get_current_task(); __u64 pid_tgid = get_current_pid_tgid(); + struct signal_struct *signal; + atomic_t live; - event_exit_send(ctx, pid_tgid); + probe_read(&signal, sizeof(signal), _(&task->signal)); + probe_read(&live, sizeof(live), _(&signal->live)); + + if (live.counter == 0) + event_exit_send(ctx, pid_tgid >> 32); return 0; } diff --git a/bpf/process/bpf_exit.h b/bpf/process/bpf_exit.h index 420ac8e86a7..ed8c9f8f880 100644 --- a/bpf/process/bpf_exit.h +++ b/bpf/process/bpf_exit.h @@ -17,21 +17,9 @@ struct { __type(value, struct msg_exit); } exit_heap_map SEC(".maps"); -static inline __attribute__((always_inline)) void event_exit_send(struct sched_execve_args *ctx, __u64 current) +static inline __attribute__((always_inline)) void event_exit_send(void *ctx, __u32 tgid) { struct execve_map_value *enter; - __u32 pid, tgid; - - pid = current & 0xFFFFffff; - tgid = current >> 32; - - /* We are only tracking group leaders so if tgid is not - * the same as the pid then this is an untracked child - * and we can skip the lookup/insert/delete cycle that - * would otherwise occur. - */ - if (pid != tgid) - return; /* It is safe to do a map_lookup_event() here because * we must have captured the execve case in order for an diff --git a/pkg/sensors/base/base.go b/pkg/sensors/base/base.go index 59c060397f1..9089d385e4d 100644 --- a/pkg/sensors/base/base.go +++ b/pkg/sensors/base/base.go @@ -22,10 +22,10 @@ var ( Exit = program.Builder( "bpf_exit.o", - "sched/sched_process_exit", - "tracepoint/sys_exit", + "do_task_dead", + "kprobe/do_task_dead", "event_exit", - "tracepoint", + "kprobe", ) Fork = program.Builder( diff --git a/pkg/sensors/exec/exit_test.go b/pkg/sensors/exec/exit_test.go index 708660532da..059b39d80be 100644 --- a/pkg/sensors/exec/exit_test.go +++ b/pkg/sensors/exec/exit_test.go @@ -54,8 +54,6 @@ func TestExit(t *testing.T) { } func TestExitLeader(t *testing.T) { - t.Skip("due to github.com/cilium/tetragon/pull/987") - var doneWG, readyWG sync.WaitGroup defer doneWG.Wait() @@ -132,8 +130,6 @@ func TestExitLeader(t *testing.T) { // // In our test we check that the parent of the /bin/echo command is the exit-tester program. func TestExitZombie(t *testing.T) { - t.Skip("due to github.com/cilium/tetragon/pull/987") - var doneWG, readyWG sync.WaitGroup defer doneWG.Wait() diff --git a/pkg/testutils/sensors/load.go b/pkg/testutils/sensors/load.go index d51c01e7653..ea58aca9894 100644 --- a/pkg/testutils/sensors/load.go +++ b/pkg/testutils/sensors/load.go @@ -115,7 +115,7 @@ func mergeSensorMaps(t *testing.T, maps1, maps2 []SensorMap, progs1, progs2 []Se func mergeInBaseSensorMaps(t *testing.T, sensorMaps []SensorMap, sensorProgs []SensorProg) ([]SensorMap, []SensorProg) { var baseProgs = []SensorProg{ 0: SensorProg{Name: "event_execve", Type: ebpf.TracePoint}, - 1: SensorProg{Name: "event_exit", Type: ebpf.TracePoint}, + 1: SensorProg{Name: "event_exit", Type: ebpf.Kprobe}, 2: SensorProg{Name: "event_wake_up_new_task", Type: ebpf.Kprobe}, 3: SensorProg{Name: "execve_send", Type: ebpf.TracePoint}, }