diff --git a/internal/ebpf/c/clone.bpf.c b/internal/ebpf/c/clone.bpf.c index 3c452ff34..4e881d261 100644 --- a/internal/ebpf/c/clone.bpf.c +++ b/internal/ebpf/c/clone.bpf.c @@ -1,19 +1,20 @@ //go:build ignore #include "common/events.h" -#include "bpf_core_read.h" #include "bpf_tracing.h" char LICENSE[] SEC("license") = "Dual MIT/GPL"; static __always_inline int handle_clone_enter(u64 flags, u32 syscall_id) { - u64 key = bpf_get_current_pid_tgid(); - struct scratch_value val = {}; + struct scratch_value *val; - val.arg0 = flags; - val.arg1 = syscall_id; - bpf_map_update_elem(&scratch, &key, &val, BPF_ANY); + val = borrow_scratch(); + if (!val) + return 0; + val->flags = flags; + val->arg1 = syscall_id; + store_scratch(val); return 0; } @@ -22,9 +23,6 @@ static __always_inline int handle_clone_exit(long ret, u32 syscall_id) u64 key = bpf_get_current_pid_tgid(); struct scratch_value *val; struct syscall_event *e; - struct task_struct *task; - struct task_struct *parent; - const struct cred *cred; /* Successful clones are emitted from sched_process_fork with child identity. * Leave scratch in place so the fork handler can recover clone flags. @@ -32,29 +30,25 @@ static __always_inline int handle_clone_exit(long ret, u32 syscall_id) if (ret >= 0) return 0; + if (!type_enabled(EVT_TYPE_CLONE)) { + bpf_map_delete_elem(&scratch, &key); + return 0; + } + e = reserve_event(); - if (!e) + if (!e) { + bpf_map_delete_elem(&scratch, &key); return 0; + } e->type = EVT_TYPE_CLONE; e->syscall_id = syscall_id; e->retval = ret; - fill_current_ids(e); - - task = (struct task_struct *)bpf_get_current_task(); - e->start_boottime = BPF_CORE_READ(task, start_boottime); - parent = BPF_CORE_READ(task, real_parent); - if (parent) - e->ppid = BPF_CORE_READ(parent, tgid); - cred = BPF_CORE_READ(task, real_cred); - if (cred) { - e->uid = BPF_CORE_READ(cred, euid.val); - e->gid = BPF_CORE_READ(cred, egid.val); - } + fill_current_task(e); val = bpf_map_lookup_elem(&scratch, &key); if (val) { - e->flags = val->arg0; + copy_scratch(e, val); if (!e->syscall_id) e->syscall_id = (u32)val->arg1; } @@ -126,6 +120,8 @@ int BPF_PROG(handle_sched_process_fork, struct task_struct *parent, struct task_ if (!child || !parent) return 0; + if (!type_enabled(EVT_TYPE_CLONE)) + return 0; e = reserve_event(); if (!e) @@ -154,7 +150,7 @@ int BPF_PROG(handle_sched_process_fork, struct task_struct *parent, struct task_ key = ((u64)parent->tgid << 32) | (u32)parent->pid; val = bpf_map_lookup_elem(&scratch, &key); if (val) { - e->flags = val->arg0; + copy_scratch(e, val); e->syscall_id = (u32)val->arg1; bpf_map_delete_elem(&scratch, &key); } diff --git a/internal/ebpf/c/common/events.h b/internal/ebpf/c/common/events.h index 4328072f6..185eea59c 100644 --- a/internal/ebpf/c/common/events.h +++ b/internal/ebpf/c/common/events.h @@ -4,12 +4,25 @@ #include "vmlinux.h" #include "bpf_helpers.h" +#include "bpf_core_read.h" #ifndef BPF_ANY #define BPF_ANY 0 #endif #define EVT_FILENAME_LEN 256 +#define EVT_TYPE_MAX 32 + +/* Set when a userspace copy was longer than the destination and was cut off. */ +#define TRUNC_FILENAME (1u << 0) /* filename[]: exec path, open/unlink/rename source */ +#define TRUNC_AUX (1u << 1) /* aux[]: rename destination or sockaddr bytes */ + +/* UAPI openat2 argument; not a CO-RE kernel type. */ +struct open_how { + u64 flags; + u64 mode; + u64 resolve; +}; struct trace_event_raw_sys_enter { unsigned short common_type; @@ -37,6 +50,17 @@ struct trace_event_raw_sys_exit { #define EVT_TYPE_EXECVE 1 #define EVT_TYPE_EXIT 2 #define EVT_TYPE_CLONE 3 +#define EVT_TYPE_OPENAT 4 +#define EVT_TYPE_UNLINK 5 +#define EVT_TYPE_RENAME 6 +#define EVT_TYPE_CONNECT 7 +#define EVT_TYPE_ACCEPT 8 +#define EVT_TYPE_MMAP 9 +#define EVT_TYPE_PROCESS_VM_READ 10 +#define EVT_TYPE_PROCESS_VM_WRITE 11 +#define EVT_TYPE_KILL 12 +#define EVT_TYPE_PTRACE 13 +#define EVT_TYPE_PRCTL 14 struct syscall_event { u32 type; @@ -48,18 +72,32 @@ struct syscall_event { u32 gid; u32 syscall_id; s64 retval; - /* Interpreted per event type; clone stores the raw clone flags. */ + /* Interpreted per event type: clone flags, open flags, mmap flags, accept flags. */ u64 flags; + /* Generic argument slots, interpreted per event type. */ + u64 arg0; + u64 arg1; + u64 arg2; + u64 arg3; u64 start_boottime; u64 timestamp_ns; + u32 truncated; + u32 pad; /* keeps comm[] 8-byte aligned; no semantic value */ u8 comm[TASK_COMM_LEN]; - u8 filename[EVT_FILENAME_LEN]; + u8 filename[EVT_FILENAME_LEN]; /* exec path, open/unlink/rename source */ + u8 aux[EVT_FILENAME_LEN]; /* rename destination or sockaddr bytes */ }; struct scratch_value { u64 arg0; u64 arg1; + u64 arg2; + u64 arg3; + u64 flags; + u32 truncated; + u32 pad; u8 filename[EVT_FILENAME_LEN]; + u8 aux[EVT_FILENAME_LEN]; }; struct { @@ -74,13 +112,32 @@ struct { __type(value, u64); } drop_count SEC(".maps"); +/* Correlates sys_enter state with the matching sys_exit. The task can + * migrate CPUs between entry and exit, so this must be a global hash, not + * a per-CPU one. LRU eviction reclaims entries whose exit never fired + * (e.g. the task was killed mid-syscall). + */ struct { - __uint(type, BPF_MAP_TYPE_PERCPU_HASH); - __uint(max_entries, 8192); + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 2048); __type(key, u64); __type(value, struct scratch_value); } scratch SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __uint(max_entries, 1); + __type(key, u32); + __type(value, struct scratch_value); +} scratch_heap SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, EVT_TYPE_MAX); + __type(key, u32); + __type(value, u8); +} enabled SEC(".maps"); + static __always_inline void account_drop(void) { u32 key = 0; @@ -92,6 +149,16 @@ static __always_inline void account_drop(void) __sync_fetch_and_add(count, 1); } +static __always_inline int type_enabled(u32 type) +{ + u8 *on; + + if (type >= EVT_TYPE_MAX) + return 0; + on = bpf_map_lookup_elem(&enabled, &type); + return on && *on; +} + static __always_inline struct syscall_event *reserve_event(void) { struct syscall_event *e; @@ -118,3 +185,153 @@ static __always_inline void fill_current_ids(struct syscall_event *e) e->gid = uidgid >> 32; bpf_get_current_comm(&e->comm, sizeof(e->comm)); } + +static __always_inline void fill_current_task(struct syscall_event *e) +{ + struct task_struct *task; + struct task_struct *parent; + const struct cred *cred; + + fill_current_ids(e); + task = (struct task_struct *)bpf_get_current_task(); + e->start_boottime = BPF_CORE_READ(task, start_boottime); + parent = BPF_CORE_READ(task, real_parent); + if (parent) + e->ppid = BPF_CORE_READ(parent, tgid); + cred = BPF_CORE_READ(task, real_cred); + if (cred) { + e->uid = BPF_CORE_READ(cred, euid.val); + e->gid = BPF_CORE_READ(cred, egid.val); + } +} + +static __always_inline u32 read_user_str(void *dst, u32 size, const char *src) +{ + long n; + + if (!src) + return 0; + n = bpf_probe_read_user_str(dst, size, src); + if (n == size) + return 1; + return 0; +} + +static __always_inline void copy_scratch(struct syscall_event *e, struct scratch_value *val) +{ + if (!val) + return; + e->arg0 = val->arg0; + e->arg1 = val->arg1; + e->arg2 = val->arg2; + e->arg3 = val->arg3; + if (!e->flags) + e->flags = val->flags; + e->truncated = val->truncated; + __builtin_memcpy(&e->filename, val->filename, sizeof(e->filename)); + __builtin_memcpy(&e->aux, val->aux, sizeof(e->aux)); +} + +static __always_inline struct scratch_value *borrow_scratch(void) +{ + u32 zero = 0; + struct scratch_value *val; + + val = bpf_map_lookup_elem(&scratch_heap, &zero); + if (!val) + return NULL; + __builtin_memset(val, 0, sizeof(*val)); + return val; +} + +static __always_inline int store_scratch(struct scratch_value *val) +{ + u64 key = bpf_get_current_pid_tgid(); + + return bpf_map_update_elem(&scratch, &key, val, BPF_ANY); +} + +static __always_inline int submit_from_scratch(u32 type, long ret, u32 syscall_id) +{ + u64 key = bpf_get_current_pid_tgid(); + struct scratch_value *val; + struct syscall_event *e; + + if (!type_enabled(type)) { + bpf_map_delete_elem(&scratch, &key); + return 0; + } + + e = reserve_event(); + if (!e) { + bpf_map_delete_elem(&scratch, &key); + return 0; + } + + e->type = type; + e->syscall_id = syscall_id; + e->retval = ret; + fill_current_task(e); + val = bpf_map_lookup_elem(&scratch, &key); + if (val) + copy_scratch(e, val); + bpf_map_delete_elem(&scratch, &key); + bpf_ringbuf_submit(e, 0); + return 0; +} + +/* x86-64 syscall ABI. arg3 is r10, not rcx. */ +static __always_inline u64 syscall_nr(struct pt_regs *regs) +{ + return BPF_CORE_READ(regs, orig_ax); +} + +static __always_inline u64 syscall_arg0(struct pt_regs *regs) +{ + return BPF_CORE_READ(regs, di); +} + +static __always_inline u64 syscall_arg1(struct pt_regs *regs) +{ + return BPF_CORE_READ(regs, si); +} + +static __always_inline u64 syscall_arg2(struct pt_regs *regs) +{ + return BPF_CORE_READ(regs, dx); +} + +static __always_inline u64 syscall_arg3(struct pt_regs *regs) +{ + return BPF_CORE_READ(regs, r10); +} + +static __always_inline u64 syscall_arg4(struct pt_regs *regs) +{ + return BPF_CORE_READ(regs, r8); +} + +static __always_inline int submit_args(u32 type, long ret, u32 syscall_id, + u64 arg0, u64 arg1, u64 arg2, u64 arg3, u64 flags) +{ + struct syscall_event *e; + + if (!type_enabled(type)) + return 0; + + e = reserve_event(); + if (!e) + return 0; + + e->type = type; + e->syscall_id = syscall_id; + e->retval = ret; + e->arg0 = arg0; + e->arg1 = arg1; + e->arg2 = arg2; + e->arg3 = arg3; + e->flags = flags; + fill_current_task(e); + bpf_ringbuf_submit(e, 0); + return 0; +} diff --git a/internal/ebpf/c/ctl.bpf.c b/internal/ebpf/c/ctl.bpf.c new file mode 100644 index 000000000..02cbb3ffe --- /dev/null +++ b/internal/ebpf/c/ctl.bpf.c @@ -0,0 +1,48 @@ +//go:build ignore + +#include "common/events.h" +#include "bpf_tracing.h" + +char LICENSE[] SEC("license") = "Dual MIT/GPL"; + +/* x86-64 syscall numbers. Register-only syscalls are captured at + * tp_btf/sys_exit, which supplies pt_regs (args + retval) so there is + * no enter probe or scratch slot. The per-syscall sys_exit tracepoint + * only carries id and ret. + */ +#define NR_PTRACE 101 +#define NR_KILL 62 +#define NR_TKILL 200 +#define NR_TGKILL 234 +#define NR_PRCTL 157 + +SEC("tp_btf/sys_exit") +int BPF_PROG(handle_sys_exit, struct pt_regs *regs, long ret) +{ + u64 id; + + if (!regs) + return 0; + id = syscall_nr(regs); + switch (id) { + case NR_KILL: + case NR_TKILL: + return submit_args(EVT_TYPE_KILL, ret, (u32)id, + syscall_arg0(regs), syscall_arg1(regs), 0, 0, 0); + case NR_TGKILL: + /* pid, tid, sig: emit the thread-group id and the signal. */ + return submit_args(EVT_TYPE_KILL, ret, (u32)id, + syscall_arg0(regs), syscall_arg2(regs), 0, 0, 0); + case NR_PTRACE: + return submit_args(EVT_TYPE_PTRACE, ret, (u32)id, + syscall_arg0(regs), syscall_arg1(regs), + syscall_arg2(regs), syscall_arg3(regs), 0); + case NR_PRCTL: + return submit_args(EVT_TYPE_PRCTL, ret, (u32)id, + syscall_arg0(regs), syscall_arg1(regs), + syscall_arg2(regs), syscall_arg3(regs), + syscall_arg4(regs)); + default: + return 0; + } +} diff --git a/internal/ebpf/c/execve.bpf.c b/internal/ebpf/c/execve.bpf.c index 32211a588..afffb5740 100644 --- a/internal/ebpf/c/execve.bpf.c +++ b/internal/ebpf/c/execve.bpf.c @@ -1,20 +1,20 @@ //go:build ignore #include "common/events.h" -#include "bpf_core_read.h" #include "bpf_tracing.h" char LICENSE[] SEC("license") = "Dual MIT/GPL"; static __always_inline int handle_exec_enter(const char *filename, u32 syscall_id) { - u64 key = bpf_get_current_pid_tgid(); - struct scratch_value val = {}; + struct scratch_value *val; - val.arg1 = syscall_id; - if (filename) - bpf_probe_read_user_str(&val.filename, sizeof(val.filename), filename); - bpf_map_update_elem(&scratch, &key, &val, BPF_ANY); + val = borrow_scratch(); + if (!val) + return 0; + val->arg1 = syscall_id; + val->truncated |= read_user_str(val->filename, sizeof(val->filename), filename); + store_scratch(val); return 0; } @@ -23,35 +23,28 @@ static __always_inline int handle_exec_exit(long ret, u32 syscall_id) u64 key = bpf_get_current_pid_tgid(); struct scratch_value *val; struct syscall_event *e; - struct task_struct *task; - struct task_struct *parent; - const struct cred *cred; + + if (!type_enabled(EVT_TYPE_EXECVE)) { + bpf_map_delete_elem(&scratch, &key); + return 0; + } e = reserve_event(); - if (!e) + if (!e) { + bpf_map_delete_elem(&scratch, &key); return 0; + } e->type = EVT_TYPE_EXECVE; e->syscall_id = syscall_id; e->retval = ret; - fill_current_ids(e); - - task = (struct task_struct *)bpf_get_current_task(); - e->start_boottime = BPF_CORE_READ(task, start_boottime); - parent = BPF_CORE_READ(task, real_parent); - if (parent) - e->ppid = BPF_CORE_READ(parent, tgid); - cred = BPF_CORE_READ(task, real_cred); - if (cred) { - e->uid = BPF_CORE_READ(cred, euid.val); - e->gid = BPF_CORE_READ(cred, egid.val); - } + fill_current_task(e); val = bpf_map_lookup_elem(&scratch, &key); if (val) { if (!e->syscall_id) e->syscall_id = (u32)val->arg1; - __builtin_memcpy(&e->filename, val->filename, sizeof(e->filename)); + copy_scratch(e, val); } bpf_map_delete_elem(&scratch, &key); diff --git a/internal/ebpf/c/exit.bpf.c b/internal/ebpf/c/exit.bpf.c index f5569f792..ead1dadfd 100644 --- a/internal/ebpf/c/exit.bpf.c +++ b/internal/ebpf/c/exit.bpf.c @@ -26,6 +26,8 @@ int BPF_PROG(handle_sched_process_exit, struct task_struct *task) */ if (task->pid != task->tgid) return 0; + if (!type_enabled(EVT_TYPE_EXIT)) + return 0; e = reserve_event(); if (!e) diff --git a/internal/ebpf/c/file.bpf.c b/internal/ebpf/c/file.bpf.c new file mode 100644 index 000000000..5e5353780 --- /dev/null +++ b/internal/ebpf/c/file.bpf.c @@ -0,0 +1,158 @@ +//go:build ignore + +#include "common/events.h" +#include "bpf_tracing.h" + +char LICENSE[] SEC("license") = "Dual MIT/GPL"; + +static __always_inline int handle_openat_enter(s64 dirfd, const char *pathname, u64 flags, u64 mode) +{ + struct scratch_value *val; + + if (!type_enabled(EVT_TYPE_OPENAT)) + return 0; + val = borrow_scratch(); + if (!val) + return 0; + val->arg0 = (u64)dirfd; + val->arg1 = mode; + val->flags = flags; + val->truncated |= read_user_str(val->filename, sizeof(val->filename), pathname); + store_scratch(val); + return 0; +} + +SEC("tp/syscalls/sys_enter_open") +int handle_sys_enter_open(struct trace_event_raw_sys_enter *ctx) +{ + return handle_openat_enter(-100 /* AT_FDCWD */, (const char *)ctx->args[0], ctx->args[1], ctx->args[2]); +} + +SEC("tp/syscalls/sys_exit_open") +int handle_sys_exit_open(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_OPENAT, ctx->ret, (u32)ctx->id); +} + +SEC("tp/syscalls/sys_enter_openat") +int handle_sys_enter_openat(struct trace_event_raw_sys_enter *ctx) +{ + return handle_openat_enter((s64)ctx->args[0], (const char *)ctx->args[1], ctx->args[2], ctx->args[3]); +} + +SEC("tp/syscalls/sys_exit_openat") +int handle_sys_exit_openat(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_OPENAT, ctx->ret, (u32)ctx->id); +} + +SEC("tp/syscalls/sys_enter_openat2") +int handle_sys_enter_openat2(struct trace_event_raw_sys_enter *ctx) +{ + struct open_how how = {}; + + if (ctx->args[2]) + bpf_probe_read_user(&how, sizeof(how), (const void *)ctx->args[2]); + return handle_openat_enter((s64)ctx->args[0], (const char *)ctx->args[1], how.flags, how.mode); +} + +SEC("tp/syscalls/sys_exit_openat2") +int handle_sys_exit_openat2(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_OPENAT, ctx->ret, (u32)ctx->id); +} + +static __always_inline int handle_unlink_enter(s64 dirfd, const char *pathname, u64 flags) +{ + struct scratch_value *val; + + if (!type_enabled(EVT_TYPE_UNLINK)) + return 0; + val = borrow_scratch(); + if (!val) + return 0; + val->arg0 = (u64)dirfd; + val->flags = flags; + val->truncated |= read_user_str(val->filename, sizeof(val->filename), pathname); + store_scratch(val); + return 0; +} + +SEC("tp/syscalls/sys_enter_unlink") +int handle_sys_enter_unlink(struct trace_event_raw_sys_enter *ctx) +{ + return handle_unlink_enter(-100 /* AT_FDCWD */, (const char *)ctx->args[0], 0); +} + +SEC("tp/syscalls/sys_exit_unlink") +int handle_sys_exit_unlink(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_UNLINK, ctx->ret, (u32)ctx->id); +} + +SEC("tp/syscalls/sys_enter_unlinkat") +int handle_sys_enter_unlinkat(struct trace_event_raw_sys_enter *ctx) +{ + return handle_unlink_enter((s64)ctx->args[0], (const char *)ctx->args[1], ctx->args[2]); +} + +SEC("tp/syscalls/sys_exit_unlinkat") +int handle_sys_exit_unlinkat(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_UNLINK, ctx->ret, (u32)ctx->id); +} + +static __always_inline int handle_rename_enter(s64 olddirfd, const char *oldpath, s64 newdirfd, const char *newpath, u64 flags) +{ + struct scratch_value *val; + + if (!type_enabled(EVT_TYPE_RENAME)) + return 0; + val = borrow_scratch(); + if (!val) + return 0; + val->arg0 = (u64)olddirfd; + val->arg1 = (u64)newdirfd; + val->flags = flags; + val->truncated |= read_user_str(val->filename, sizeof(val->filename), oldpath); + if (read_user_str(val->aux, sizeof(val->aux), newpath)) + val->truncated |= TRUNC_AUX; + store_scratch(val); + return 0; +} + +SEC("tp/syscalls/sys_enter_rename") +int handle_sys_enter_rename(struct trace_event_raw_sys_enter *ctx) +{ + return handle_rename_enter(-100 /* AT_FDCWD */, (const char *)ctx->args[0], -100, (const char *)ctx->args[1], 0); +} + +SEC("tp/syscalls/sys_exit_rename") +int handle_sys_exit_rename(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_RENAME, ctx->ret, (u32)ctx->id); +} + +SEC("tp/syscalls/sys_enter_renameat") +int handle_sys_enter_renameat(struct trace_event_raw_sys_enter *ctx) +{ + return handle_rename_enter((s64)ctx->args[0], (const char *)ctx->args[1], (s64)ctx->args[2], (const char *)ctx->args[3], 0); +} + +SEC("tp/syscalls/sys_exit_renameat") +int handle_sys_exit_renameat(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_RENAME, ctx->ret, (u32)ctx->id); +} + +SEC("tp/syscalls/sys_enter_renameat2") +int handle_sys_enter_renameat2(struct trace_event_raw_sys_enter *ctx) +{ + return handle_rename_enter((s64)ctx->args[0], (const char *)ctx->args[1], (s64)ctx->args[2], (const char *)ctx->args[3], ctx->args[4]); +} + +SEC("tp/syscalls/sys_exit_renameat2") +int handle_sys_exit_renameat2(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_RENAME, ctx->ret, (u32)ctx->id); +} diff --git a/internal/ebpf/c/mem.bpf.c b/internal/ebpf/c/mem.bpf.c new file mode 100644 index 000000000..de77d9c6e --- /dev/null +++ b/internal/ebpf/c/mem.bpf.c @@ -0,0 +1,81 @@ +//go:build ignore + +#include "common/events.h" +#include "bpf_tracing.h" + +char LICENSE[] SEC("license") = "Dual MIT/GPL"; + +struct iovec { + u64 iov_base; + u64 iov_len; +}; + +SEC("tp/syscalls/sys_enter_mmap") +int handle_sys_enter_mmap(struct trace_event_raw_sys_enter *ctx) +{ + struct scratch_value *val; + + if (!type_enabled(EVT_TYPE_MMAP)) + return 0; + val = borrow_scratch(); + if (!val) + return 0; + val->arg0 = ctx->args[5]; /* offset */ + val->arg1 = ctx->args[1]; /* length */ + val->arg2 = ctx->args[2]; /* prot */ + val->arg3 = ctx->args[4]; /* fd */ + val->flags = ctx->args[3]; + store_scratch(val); + return 0; +} + +SEC("tp/syscalls/sys_exit_mmap") +int handle_sys_exit_mmap(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_MMAP, ctx->ret, (u32)ctx->id); +} + +static __always_inline int handle_process_vm_enter(u32 type, u64 pid, const void *rvec, u64 flags) +{ + struct scratch_value *val; + struct iovec iov = {}; + + if (!type_enabled(type)) + return 0; + val = borrow_scratch(); + if (!val) + return 0; + val->arg0 = pid; + val->flags = flags; + if (rvec) { + bpf_probe_read_user(&iov, sizeof(iov), rvec); + val->arg2 = iov.iov_base; + val->arg3 = iov.iov_len; + } + store_scratch(val); + return 0; +} + +SEC("tp/syscalls/sys_enter_process_vm_readv") +int handle_sys_enter_process_vm_readv(struct trace_event_raw_sys_enter *ctx) +{ + return handle_process_vm_enter(EVT_TYPE_PROCESS_VM_READ, ctx->args[0], (const void *)ctx->args[3], ctx->args[5]); +} + +SEC("tp/syscalls/sys_exit_process_vm_readv") +int handle_sys_exit_process_vm_readv(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_PROCESS_VM_READ, ctx->ret, (u32)ctx->id); +} + +SEC("tp/syscalls/sys_enter_process_vm_writev") +int handle_sys_enter_process_vm_writev(struct trace_event_raw_sys_enter *ctx) +{ + return handle_process_vm_enter(EVT_TYPE_PROCESS_VM_WRITE, ctx->args[0], (const void *)ctx->args[3], ctx->args[5]); +} + +SEC("tp/syscalls/sys_exit_process_vm_writev") +int handle_sys_exit_process_vm_writev(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_PROCESS_VM_WRITE, ctx->ret, (u32)ctx->id); +} diff --git a/internal/ebpf/c/net.bpf.c b/internal/ebpf/c/net.bpf.c new file mode 100644 index 000000000..9df0001eb --- /dev/null +++ b/internal/ebpf/c/net.bpf.c @@ -0,0 +1,103 @@ +//go:build ignore + +#include "common/events.h" +#include "bpf_tracing.h" + +char LICENSE[] SEC("license") = "Dual MIT/GPL"; + +/* Copy exactly addrlen bytes: a fixed-size read past the caller's sockaddr + * can cross into an unmapped page and fail wholesale, losing the address. + */ +static __always_inline void read_sockaddr(struct scratch_value *val, const void *addr, u64 addrlen) +{ + u64 len = addrlen; + + if (len > sizeof(val->aux)) { + len = sizeof(val->aux); + val->truncated |= TRUNC_AUX; + } + if (addr && len) + bpf_probe_read_user(val->aux, len, addr); +} + +SEC("tp/syscalls/sys_enter_connect") +int handle_sys_enter_connect(struct trace_event_raw_sys_enter *ctx) +{ + struct scratch_value *val; + + if (!type_enabled(EVT_TYPE_CONNECT)) + return 0; + val = borrow_scratch(); + if (!val) + return 0; + val->arg0 = ctx->args[0]; + val->arg1 = ctx->args[2]; + read_sockaddr(val, (const void *)ctx->args[1], ctx->args[2]); + store_scratch(val); + return 0; +} + +SEC("tp/syscalls/sys_exit_connect") +int handle_sys_exit_connect(struct trace_event_raw_sys_exit *ctx) +{ + return submit_from_scratch(EVT_TYPE_CONNECT, ctx->ret, (u32)ctx->id); +} + +static __always_inline int handle_accept_enter(u64 fd, const void *addr, const void *addrlen, u64 flags) +{ + struct scratch_value *val; + + if (!type_enabled(EVT_TYPE_ACCEPT)) + return 0; + val = borrow_scratch(); + if (!val) + return 0; + val->arg0 = fd; + val->arg1 = (u64)addrlen; + val->arg2 = (u64)addr; + val->flags = flags; + store_scratch(val); + return 0; +} + +/* addrlen is value-result: the kernel writes the peer address and its actual + * size only once accept returns, so the sockaddr is copied at exit. + */ +static __always_inline int handle_accept_exit(long ret, u32 syscall_id) +{ + u64 key = bpf_get_current_pid_tgid(); + struct scratch_value *val; + u32 addrlen = 0; + + val = bpf_map_lookup_elem(&scratch, &key); + if (val && val->arg2 && ret >= 0) { + if (val->arg1) + bpf_probe_read_user(&addrlen, sizeof(addrlen), (const void *)val->arg1); + read_sockaddr(val, (const void *)val->arg2, addrlen); + } + return submit_from_scratch(EVT_TYPE_ACCEPT, ret, syscall_id); +} + +SEC("tp/syscalls/sys_enter_accept") +int handle_sys_enter_accept(struct trace_event_raw_sys_enter *ctx) +{ + return handle_accept_enter(ctx->args[0], (const void *)ctx->args[1], (const void *)ctx->args[2], 0); +} + +SEC("tp/syscalls/sys_exit_accept") +int handle_sys_exit_accept(struct trace_event_raw_sys_exit *ctx) +{ + return handle_accept_exit(ctx->ret, (u32)ctx->id); +} + +SEC("tp/syscalls/sys_enter_accept4") +int handle_sys_enter_accept4(struct trace_event_raw_sys_enter *ctx) +{ + return handle_accept_enter(ctx->args[0], (const void *)ctx->args[1], (const void *)ctx->args[2], ctx->args[3]); +} + +SEC("tp/syscalls/sys_exit_accept4") +int handle_sys_exit_accept4(struct trace_event_raw_sys_exit *ctx) +{ + return handle_accept_exit(ctx->ret, (u32)ctx->id); +} diff --git a/internal/ebpf/clone_bpfeb.go b/internal/ebpf/clone_bpfeb.go index 0c1de89c0..bca4d7573 100644 --- a/internal/ebpf/clone_bpfeb.go +++ b/internal/ebpf/clone_bpfeb.go @@ -14,10 +14,16 @@ import ( ) type cloneScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadClone returns the embedded CollectionSpec for clone. @@ -77,9 +83,11 @@ type cloneProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type cloneMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // cloneVariableSpecs contains global variables before they are loaded into the kernel. @@ -108,16 +116,20 @@ func (o *cloneObjects) Close() error { // // It can be passed to loadCloneObjects or ebpf.CollectionSpec.LoadAndAssign. type cloneMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *cloneMaps) Close() error { return _CloneClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/clone_bpfeb.o b/internal/ebpf/clone_bpfeb.o index 40ba3e286..091806274 100644 Binary files a/internal/ebpf/clone_bpfeb.o and b/internal/ebpf/clone_bpfeb.o differ diff --git a/internal/ebpf/clone_bpfel.go b/internal/ebpf/clone_bpfel.go index 36266195f..64364fe61 100644 --- a/internal/ebpf/clone_bpfel.go +++ b/internal/ebpf/clone_bpfel.go @@ -14,10 +14,16 @@ import ( ) type cloneScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadClone returns the embedded CollectionSpec for clone. @@ -77,9 +83,11 @@ type cloneProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type cloneMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // cloneVariableSpecs contains global variables before they are loaded into the kernel. @@ -108,16 +116,20 @@ func (o *cloneObjects) Close() error { // // It can be passed to loadCloneObjects or ebpf.CollectionSpec.LoadAndAssign. type cloneMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *cloneMaps) Close() error { return _CloneClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/clone_bpfel.o b/internal/ebpf/clone_bpfel.o index 9985e02c1..c84804e21 100644 Binary files a/internal/ebpf/clone_bpfel.o and b/internal/ebpf/clone_bpfel.o differ diff --git a/internal/ebpf/consumer.go b/internal/ebpf/consumer.go index ca1d022cf..ea5ea037b 100644 --- a/internal/ebpf/consumer.go +++ b/internal/ebpf/consumer.go @@ -27,6 +27,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/event/params" "github.com/rabbitstack/fibratus/pkg/ps" pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" + "github.com/rabbitstack/fibratus/pkg/util/va" ) var ( @@ -62,6 +63,19 @@ func applyProcessState(psnap ps.Snapshotter, evt *event.Event) { evt.PS = existing _ = psnap.AddThread(evt) } + case evt.Type == event.Mmap && succeeded(evt): + ok, existing := psnap.Find(evt.PID) + if ok && existing != nil { + evt.PS = existing + if retainMmap(evt) { + existing.AddMmap(pstypes.Mmap{ + BaseAddress: va.Address(evt.GetParamAsUint64(params.MemBaseAddress)), + Size: evt.GetParamAsUint64(params.MemRegionSize), + Protection: evt.GetParamAsUint32(params.MemProtect), + Type: mmapKind(evt.GetParamAsUint64(params.MmapFlags)), + }) + } + } case evt.Type == event.Exit: ok, existing := psnap.Find(evt.PID) if ok { @@ -169,6 +183,30 @@ func succeeded(evt *event.Event) bool { return ret >= 0 } +const mapAnonymous = 0x20 + +func mmapKind(flags uint64) string { + if flags&mapAnonymous != 0 { + return "anonymous" + } + return "file" +} + +// retainMmap reports whether the mapping should be stored on process state. +// Anonymous mappings are frequent (heap, stacks, JIT) and we do not yet +// hook munmap, so keeping them would grow without bound. File-backed +// mappings are the useful set (shared objects, mapped files). +func retainMmap(evt *event.Event) bool { + if mmapKind(evt.GetParamAsUint64(params.MmapFlags)) != "file" { + return false + } + fd, err := evt.Params.GetInt64(params.FD) + if err != nil { + return false + } + return fd >= 0 +} + func enrichEvent(evt *event.Event) { if evt == nil { return diff --git a/internal/ebpf/ctl_bpfeb.go b/internal/ebpf/ctl_bpfeb.go new file mode 100644 index 000000000..c41298b34 --- /dev/null +++ b/internal/ebpf/ctl_bpfeb.go @@ -0,0 +1,159 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (mips || mips64 || ppc64 || s390x) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type ctlScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadCtl returns the embedded CollectionSpec for ctl. +func loadCtl() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_CtlBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load ctl: %w", err) + } + + return spec, err +} + +// loadCtlObjects loads ctl and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *ctlObjects +// *ctlPrograms +// *ctlMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadCtlObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadCtl() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// ctlSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlSpecs struct { + ctlProgramSpecs + ctlMapSpecs + ctlVariableSpecs +} + +// ctlProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlProgramSpecs struct { + HandleSysExit *ebpf.ProgramSpec `ebpf:"handle_sys_exit"` +} + +// ctlMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// ctlVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlVariableSpecs struct { +} + +// ctlObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlObjects struct { + ctlPrograms + ctlMaps + ctlVariables +} + +func (o *ctlObjects) Close() error { + return _CtlClose( + &o.ctlPrograms, + &o.ctlMaps, + ) +} + +// ctlMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *ctlMaps) Close() error { + return _CtlClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// ctlVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlVariables struct { +} + +// ctlPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlPrograms struct { + HandleSysExit *ebpf.Program `ebpf:"handle_sys_exit"` +} + +func (p *ctlPrograms) Close() error { + return _CtlClose( + p.HandleSysExit, + ) +} + +func _CtlClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed ctl_bpfeb.o +var _CtlBytes []byte diff --git a/internal/ebpf/ctl_bpfeb.o b/internal/ebpf/ctl_bpfeb.o new file mode 100644 index 000000000..effa49e87 Binary files /dev/null and b/internal/ebpf/ctl_bpfeb.o differ diff --git a/internal/ebpf/ctl_bpfel.go b/internal/ebpf/ctl_bpfel.go new file mode 100644 index 000000000..d28873580 --- /dev/null +++ b/internal/ebpf/ctl_bpfel.go @@ -0,0 +1,159 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 || wasm) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type ctlScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadCtl returns the embedded CollectionSpec for ctl. +func loadCtl() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_CtlBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load ctl: %w", err) + } + + return spec, err +} + +// loadCtlObjects loads ctl and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *ctlObjects +// *ctlPrograms +// *ctlMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadCtlObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadCtl() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// ctlSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlSpecs struct { + ctlProgramSpecs + ctlMapSpecs + ctlVariableSpecs +} + +// ctlProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlProgramSpecs struct { + HandleSysExit *ebpf.ProgramSpec `ebpf:"handle_sys_exit"` +} + +// ctlMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// ctlVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ctlVariableSpecs struct { +} + +// ctlObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlObjects struct { + ctlPrograms + ctlMaps + ctlVariables +} + +func (o *ctlObjects) Close() error { + return _CtlClose( + &o.ctlPrograms, + &o.ctlMaps, + ) +} + +// ctlMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *ctlMaps) Close() error { + return _CtlClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// ctlVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlVariables struct { +} + +// ctlPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadCtlObjects or ebpf.CollectionSpec.LoadAndAssign. +type ctlPrograms struct { + HandleSysExit *ebpf.Program `ebpf:"handle_sys_exit"` +} + +func (p *ctlPrograms) Close() error { + return _CtlClose( + p.HandleSysExit, + ) +} + +func _CtlClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed ctl_bpfel.o +var _CtlBytes []byte diff --git a/internal/ebpf/ctl_bpfel.o b/internal/ebpf/ctl_bpfel.o new file mode 100644 index 000000000..21888bdd0 Binary files /dev/null and b/internal/ebpf/ctl_bpfel.o differ diff --git a/internal/ebpf/doc.go b/internal/ebpf/doc.go index 8d281edf7..d283bd6b7 100644 --- a/internal/ebpf/doc.go +++ b/internal/ebpf/doc.go @@ -16,8 +16,9 @@ * limitations under the License. */ -// Package ebpf hosts the Linux eBPF instrumentation backend. The process -// event source loads CO-RE programs, captures execve/exit/clone, and -// reconciles live events against an iter/task baseline. The spike -// subdirectory remains as a feasibility prototype. +// Package ebpf hosts the Linux eBPF instrumentation backend. The event +// source loads CO-RE programs, captures process, file, network, memory, +// and process-control syscalls, and reconciles live events against an +// iter/task baseline. The spike subdirectory remains as a feasibility +// prototype. package ebpf diff --git a/internal/ebpf/events.go b/internal/ebpf/events.go index 3a54d2ba5..ec78e5add 100644 --- a/internal/ebpf/events.go +++ b/internal/ebpf/events.go @@ -42,21 +42,27 @@ const ( // rawEvent mirrors struct syscall_event in c/common/events.h. type rawEvent struct { - Type uint32 - PID uint32 - TID uint32 - TGID uint32 - PPID uint32 - UID uint32 - GID uint32 - SyscallID uint32 - Retval int64 - // Flags is interpreted per event type; clone stores the raw clone flags. + Type uint32 + PID uint32 + TID uint32 + TGID uint32 + PPID uint32 + UID uint32 + GID uint32 + SyscallID uint32 + Retval int64 Flags uint64 + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 StartBootTime uint64 TimestampNs uint64 + Truncated uint32 + Pad uint32 Comm [commLen]byte Filename [filenameLen]byte + Aux [filenameLen]byte } func decodeRawEvent(raw []byte) (rawEvent, error) { @@ -69,18 +75,14 @@ func decodeRawEvent(raw []byte) (rawEvent, error) { func (r rawEvent) comm() string { return cString(r.Comm[:]) } func (r rawEvent) filename() string { return cString(r.Filename[:]) } +func (r rawEvent) aux() string { return cString(r.Aux[:]) } func (r rawEvent) eventType() event.Type { - switch r.Type { - case uint32(event.Execve): - return event.Execve - case uint32(event.Exit): - return event.Exit - case uint32(event.Clone): - return event.Clone - default: - return event.UnknownType + typ := event.Type(r.Type) + if typ.Exists() { + return typ } + return event.UnknownType } func (r rawEvent) processID() uint64 { @@ -130,6 +132,55 @@ func (r rawEvent) toEvent() *event.Event { evt.Params.Append(params.ExitStatus, params.Int64, r.Retval) case event.Clone: evt.Params.Append(params.CloneFlags, params.Uint64, r.Flags) + case event.Openat: + evt.Params.Append(params.DirFD, params.Int64, int64(r.Arg0)) + evt.Params.Append(params.FilePath, params.Path, r.filename()) + evt.Params.Append(params.FileFlags, params.Uint64, r.Flags) + evt.Params.Append(params.FileMode, params.Uint64, r.Arg1) + if r.Retval >= 0 { + evt.Params.Append(params.FD, params.Int64, r.Retval) + } + case event.Unlink: + evt.Params.Append(params.DirFD, params.Int64, int64(r.Arg0)) + evt.Params.Append(params.FilePath, params.Path, r.filename()) + evt.Params.Append(params.FileFlags, params.Uint64, r.Flags) + case event.Rename: + evt.Params.Append(params.DirFD, params.Int64, int64(r.Arg0)) + evt.Params.Append(params.NewDirFD, params.Int64, int64(r.Arg1)) + evt.Params.Append(params.FilePath, params.Path, r.filename()) + evt.Params.Append(params.FileNewPath, params.Path, r.aux()) + evt.Params.Append(params.FileFlags, params.Uint64, r.Flags) + case event.Connect, event.Accept: + appendSockParams(evt, r) + case event.Mmap: + addr := uint64(0) + if r.Retval >= 0 { + addr = uint64(r.Retval) + } + evt.Params.Append(params.MemBaseAddress, params.Address, addr) + evt.Params.Append(params.MemRegionSize, params.Uint64, r.Arg1) + evt.Params.Append(params.MemProtect, params.Uint32, uint32(r.Arg2)) + evt.Params.Append(params.MmapFlags, params.Uint64, r.Flags) + evt.Params.Append(params.FD, params.Int64, int64(r.Arg3)) + evt.Params.Append(params.MmapOffset, params.Uint64, r.Arg0) + case event.ProcessVMRead, event.ProcessVMWrite: + evt.Params.Append(params.TargetProcessID, params.PID, uint64(uint32(r.Arg0))) + evt.Params.Append(params.MemBaseAddress, params.Address, r.Arg2) + evt.Params.Append(params.MemRegionSize, params.Uint64, r.Arg3) + evt.Params.Append(params.MmapFlags, params.Uint64, r.Flags) + case event.Kill: + evt.Params.Append(params.TargetProcessID, params.PID, uint64(uint32(r.Arg0))) + evt.Params.Append(params.Signal, params.Int32, int32(r.Arg1)) + case event.Ptrace: + evt.Params.Append(params.PtraceRequest, params.Int64, int64(r.Arg0)) + evt.Params.Append(params.TargetProcessID, params.PID, uint64(uint32(r.Arg1))) + evt.Params.Append(params.PtraceAddr, params.Address, r.Arg2) + evt.Params.Append(params.PtraceData, params.Uint64, r.Arg3) + case event.Prctl: + evt.Params.Append(params.PrctlOption, params.Int64, int64(r.Arg0)) + } + if r.Truncated != 0 { + evt.Params.Append(params.Truncated, params.Uint32, r.Truncated) } return evt } diff --git a/internal/ebpf/execve_bpfeb.go b/internal/ebpf/execve_bpfeb.go index cbad50c9e..c0d9eb2ac 100644 --- a/internal/ebpf/execve_bpfeb.go +++ b/internal/ebpf/execve_bpfeb.go @@ -14,10 +14,16 @@ import ( ) type execveScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadExecve returns the embedded CollectionSpec for execve. @@ -72,9 +78,11 @@ type execveProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type execveMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // execveVariableSpecs contains global variables before they are loaded into the kernel. @@ -103,16 +111,20 @@ func (o *execveObjects) Close() error { // // It can be passed to loadExecveObjects or ebpf.CollectionSpec.LoadAndAssign. type execveMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *execveMaps) Close() error { return _ExecveClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/execve_bpfeb.o b/internal/ebpf/execve_bpfeb.o index 6e17e3b90..87913ab3c 100644 Binary files a/internal/ebpf/execve_bpfeb.o and b/internal/ebpf/execve_bpfeb.o differ diff --git a/internal/ebpf/execve_bpfel.go b/internal/ebpf/execve_bpfel.go index 7b1d8a45e..5f4f6e613 100644 --- a/internal/ebpf/execve_bpfel.go +++ b/internal/ebpf/execve_bpfel.go @@ -14,10 +14,16 @@ import ( ) type execveScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadExecve returns the embedded CollectionSpec for execve. @@ -72,9 +78,11 @@ type execveProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type execveMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // execveVariableSpecs contains global variables before they are loaded into the kernel. @@ -103,16 +111,20 @@ func (o *execveObjects) Close() error { // // It can be passed to loadExecveObjects or ebpf.CollectionSpec.LoadAndAssign. type execveMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *execveMaps) Close() error { return _ExecveClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/execve_bpfel.o b/internal/ebpf/execve_bpfel.o index cc808bde2..d1daca305 100644 Binary files a/internal/ebpf/execve_bpfel.o and b/internal/ebpf/execve_bpfel.o differ diff --git a/internal/ebpf/exit_bpfeb.go b/internal/ebpf/exit_bpfeb.go index 41ac2799d..cb7458b1f 100644 --- a/internal/ebpf/exit_bpfeb.go +++ b/internal/ebpf/exit_bpfeb.go @@ -14,10 +14,16 @@ import ( ) type exitScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadExit returns the embedded CollectionSpec for exit. @@ -69,9 +75,11 @@ type exitProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type exitMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // exitVariableSpecs contains global variables before they are loaded into the kernel. @@ -100,16 +108,20 @@ func (o *exitObjects) Close() error { // // It can be passed to loadExitObjects or ebpf.CollectionSpec.LoadAndAssign. type exitMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *exitMaps) Close() error { return _ExitClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/exit_bpfeb.o b/internal/ebpf/exit_bpfeb.o index 64f217fa0..0a2fc972b 100644 Binary files a/internal/ebpf/exit_bpfeb.o and b/internal/ebpf/exit_bpfeb.o differ diff --git a/internal/ebpf/exit_bpfel.go b/internal/ebpf/exit_bpfel.go index 0ddb7d384..af3511b69 100644 --- a/internal/ebpf/exit_bpfel.go +++ b/internal/ebpf/exit_bpfel.go @@ -14,10 +14,16 @@ import ( ) type exitScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadExit returns the embedded CollectionSpec for exit. @@ -69,9 +75,11 @@ type exitProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type exitMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // exitVariableSpecs contains global variables before they are loaded into the kernel. @@ -100,16 +108,20 @@ func (o *exitObjects) Close() error { // // It can be passed to loadExitObjects or ebpf.CollectionSpec.LoadAndAssign. type exitMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *exitMaps) Close() error { return _ExitClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/exit_bpfel.o b/internal/ebpf/exit_bpfel.o index 853c4d11a..8ca470c6d 100644 Binary files a/internal/ebpf/exit_bpfel.o and b/internal/ebpf/exit_bpfel.o differ diff --git a/internal/ebpf/file_bpfeb.go b/internal/ebpf/file_bpfeb.go new file mode 100644 index 000000000..edf459214 --- /dev/null +++ b/internal/ebpf/file_bpfeb.go @@ -0,0 +1,204 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (mips || mips64 || ppc64 || s390x) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type fileScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadFile returns the embedded CollectionSpec for file. +func loadFile() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_FileBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load file: %w", err) + } + + return spec, err +} + +// loadFileObjects loads file and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *fileObjects +// *filePrograms +// *fileMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadFileObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadFile() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// fileSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileSpecs struct { + fileProgramSpecs + fileMapSpecs + fileVariableSpecs +} + +// fileProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileProgramSpecs struct { + HandleSysEnterOpen *ebpf.ProgramSpec `ebpf:"handle_sys_enter_open"` + HandleSysEnterOpenat *ebpf.ProgramSpec `ebpf:"handle_sys_enter_openat"` + HandleSysEnterOpenat2 *ebpf.ProgramSpec `ebpf:"handle_sys_enter_openat2"` + HandleSysEnterRename *ebpf.ProgramSpec `ebpf:"handle_sys_enter_rename"` + HandleSysEnterRenameat *ebpf.ProgramSpec `ebpf:"handle_sys_enter_renameat"` + HandleSysEnterRenameat2 *ebpf.ProgramSpec `ebpf:"handle_sys_enter_renameat2"` + HandleSysEnterUnlink *ebpf.ProgramSpec `ebpf:"handle_sys_enter_unlink"` + HandleSysEnterUnlinkat *ebpf.ProgramSpec `ebpf:"handle_sys_enter_unlinkat"` + HandleSysExitOpen *ebpf.ProgramSpec `ebpf:"handle_sys_exit_open"` + HandleSysExitOpenat *ebpf.ProgramSpec `ebpf:"handle_sys_exit_openat"` + HandleSysExitOpenat2 *ebpf.ProgramSpec `ebpf:"handle_sys_exit_openat2"` + HandleSysExitRename *ebpf.ProgramSpec `ebpf:"handle_sys_exit_rename"` + HandleSysExitRenameat *ebpf.ProgramSpec `ebpf:"handle_sys_exit_renameat"` + HandleSysExitRenameat2 *ebpf.ProgramSpec `ebpf:"handle_sys_exit_renameat2"` + HandleSysExitUnlink *ebpf.ProgramSpec `ebpf:"handle_sys_exit_unlink"` + HandleSysExitUnlinkat *ebpf.ProgramSpec `ebpf:"handle_sys_exit_unlinkat"` +} + +// fileMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// fileVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileVariableSpecs struct { +} + +// fileObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type fileObjects struct { + filePrograms + fileMaps + fileVariables +} + +func (o *fileObjects) Close() error { + return _FileClose( + &o.filePrograms, + &o.fileMaps, + ) +} + +// fileMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type fileMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *fileMaps) Close() error { + return _FileClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// fileVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type fileVariables struct { +} + +// filePrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type filePrograms struct { + HandleSysEnterOpen *ebpf.Program `ebpf:"handle_sys_enter_open"` + HandleSysEnterOpenat *ebpf.Program `ebpf:"handle_sys_enter_openat"` + HandleSysEnterOpenat2 *ebpf.Program `ebpf:"handle_sys_enter_openat2"` + HandleSysEnterRename *ebpf.Program `ebpf:"handle_sys_enter_rename"` + HandleSysEnterRenameat *ebpf.Program `ebpf:"handle_sys_enter_renameat"` + HandleSysEnterRenameat2 *ebpf.Program `ebpf:"handle_sys_enter_renameat2"` + HandleSysEnterUnlink *ebpf.Program `ebpf:"handle_sys_enter_unlink"` + HandleSysEnterUnlinkat *ebpf.Program `ebpf:"handle_sys_enter_unlinkat"` + HandleSysExitOpen *ebpf.Program `ebpf:"handle_sys_exit_open"` + HandleSysExitOpenat *ebpf.Program `ebpf:"handle_sys_exit_openat"` + HandleSysExitOpenat2 *ebpf.Program `ebpf:"handle_sys_exit_openat2"` + HandleSysExitRename *ebpf.Program `ebpf:"handle_sys_exit_rename"` + HandleSysExitRenameat *ebpf.Program `ebpf:"handle_sys_exit_renameat"` + HandleSysExitRenameat2 *ebpf.Program `ebpf:"handle_sys_exit_renameat2"` + HandleSysExitUnlink *ebpf.Program `ebpf:"handle_sys_exit_unlink"` + HandleSysExitUnlinkat *ebpf.Program `ebpf:"handle_sys_exit_unlinkat"` +} + +func (p *filePrograms) Close() error { + return _FileClose( + p.HandleSysEnterOpen, + p.HandleSysEnterOpenat, + p.HandleSysEnterOpenat2, + p.HandleSysEnterRename, + p.HandleSysEnterRenameat, + p.HandleSysEnterRenameat2, + p.HandleSysEnterUnlink, + p.HandleSysEnterUnlinkat, + p.HandleSysExitOpen, + p.HandleSysExitOpenat, + p.HandleSysExitOpenat2, + p.HandleSysExitRename, + p.HandleSysExitRenameat, + p.HandleSysExitRenameat2, + p.HandleSysExitUnlink, + p.HandleSysExitUnlinkat, + ) +} + +func _FileClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed file_bpfeb.o +var _FileBytes []byte diff --git a/internal/ebpf/file_bpfeb.o b/internal/ebpf/file_bpfeb.o new file mode 100644 index 000000000..1c46e1106 Binary files /dev/null and b/internal/ebpf/file_bpfeb.o differ diff --git a/internal/ebpf/file_bpfel.go b/internal/ebpf/file_bpfel.go new file mode 100644 index 000000000..1b89ee110 --- /dev/null +++ b/internal/ebpf/file_bpfel.go @@ -0,0 +1,204 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 || wasm) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type fileScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadFile returns the embedded CollectionSpec for file. +func loadFile() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_FileBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load file: %w", err) + } + + return spec, err +} + +// loadFileObjects loads file and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *fileObjects +// *filePrograms +// *fileMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadFileObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadFile() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// fileSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileSpecs struct { + fileProgramSpecs + fileMapSpecs + fileVariableSpecs +} + +// fileProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileProgramSpecs struct { + HandleSysEnterOpen *ebpf.ProgramSpec `ebpf:"handle_sys_enter_open"` + HandleSysEnterOpenat *ebpf.ProgramSpec `ebpf:"handle_sys_enter_openat"` + HandleSysEnterOpenat2 *ebpf.ProgramSpec `ebpf:"handle_sys_enter_openat2"` + HandleSysEnterRename *ebpf.ProgramSpec `ebpf:"handle_sys_enter_rename"` + HandleSysEnterRenameat *ebpf.ProgramSpec `ebpf:"handle_sys_enter_renameat"` + HandleSysEnterRenameat2 *ebpf.ProgramSpec `ebpf:"handle_sys_enter_renameat2"` + HandleSysEnterUnlink *ebpf.ProgramSpec `ebpf:"handle_sys_enter_unlink"` + HandleSysEnterUnlinkat *ebpf.ProgramSpec `ebpf:"handle_sys_enter_unlinkat"` + HandleSysExitOpen *ebpf.ProgramSpec `ebpf:"handle_sys_exit_open"` + HandleSysExitOpenat *ebpf.ProgramSpec `ebpf:"handle_sys_exit_openat"` + HandleSysExitOpenat2 *ebpf.ProgramSpec `ebpf:"handle_sys_exit_openat2"` + HandleSysExitRename *ebpf.ProgramSpec `ebpf:"handle_sys_exit_rename"` + HandleSysExitRenameat *ebpf.ProgramSpec `ebpf:"handle_sys_exit_renameat"` + HandleSysExitRenameat2 *ebpf.ProgramSpec `ebpf:"handle_sys_exit_renameat2"` + HandleSysExitUnlink *ebpf.ProgramSpec `ebpf:"handle_sys_exit_unlink"` + HandleSysExitUnlinkat *ebpf.ProgramSpec `ebpf:"handle_sys_exit_unlinkat"` +} + +// fileMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// fileVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type fileVariableSpecs struct { +} + +// fileObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type fileObjects struct { + filePrograms + fileMaps + fileVariables +} + +func (o *fileObjects) Close() error { + return _FileClose( + &o.filePrograms, + &o.fileMaps, + ) +} + +// fileMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type fileMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *fileMaps) Close() error { + return _FileClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// fileVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type fileVariables struct { +} + +// filePrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadFileObjects or ebpf.CollectionSpec.LoadAndAssign. +type filePrograms struct { + HandleSysEnterOpen *ebpf.Program `ebpf:"handle_sys_enter_open"` + HandleSysEnterOpenat *ebpf.Program `ebpf:"handle_sys_enter_openat"` + HandleSysEnterOpenat2 *ebpf.Program `ebpf:"handle_sys_enter_openat2"` + HandleSysEnterRename *ebpf.Program `ebpf:"handle_sys_enter_rename"` + HandleSysEnterRenameat *ebpf.Program `ebpf:"handle_sys_enter_renameat"` + HandleSysEnterRenameat2 *ebpf.Program `ebpf:"handle_sys_enter_renameat2"` + HandleSysEnterUnlink *ebpf.Program `ebpf:"handle_sys_enter_unlink"` + HandleSysEnterUnlinkat *ebpf.Program `ebpf:"handle_sys_enter_unlinkat"` + HandleSysExitOpen *ebpf.Program `ebpf:"handle_sys_exit_open"` + HandleSysExitOpenat *ebpf.Program `ebpf:"handle_sys_exit_openat"` + HandleSysExitOpenat2 *ebpf.Program `ebpf:"handle_sys_exit_openat2"` + HandleSysExitRename *ebpf.Program `ebpf:"handle_sys_exit_rename"` + HandleSysExitRenameat *ebpf.Program `ebpf:"handle_sys_exit_renameat"` + HandleSysExitRenameat2 *ebpf.Program `ebpf:"handle_sys_exit_renameat2"` + HandleSysExitUnlink *ebpf.Program `ebpf:"handle_sys_exit_unlink"` + HandleSysExitUnlinkat *ebpf.Program `ebpf:"handle_sys_exit_unlinkat"` +} + +func (p *filePrograms) Close() error { + return _FileClose( + p.HandleSysEnterOpen, + p.HandleSysEnterOpenat, + p.HandleSysEnterOpenat2, + p.HandleSysEnterRename, + p.HandleSysEnterRenameat, + p.HandleSysEnterRenameat2, + p.HandleSysEnterUnlink, + p.HandleSysEnterUnlinkat, + p.HandleSysExitOpen, + p.HandleSysExitOpenat, + p.HandleSysExitOpenat2, + p.HandleSysExitRename, + p.HandleSysExitRenameat, + p.HandleSysExitRenameat2, + p.HandleSysExitUnlink, + p.HandleSysExitUnlinkat, + ) +} + +func _FileClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed file_bpfel.o +var _FileBytes []byte diff --git a/internal/ebpf/file_bpfel.o b/internal/ebpf/file_bpfel.o new file mode 100644 index 000000000..80c6638f1 Binary files /dev/null and b/internal/ebpf/file_bpfel.o differ diff --git a/internal/ebpf/generate.sh b/internal/ebpf/generate.sh index e9ef3a51a..83cb87d0f 100755 --- a/internal/ebpf/generate.sh +++ b/internal/ebpf/generate.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Regenerate bpf2go bindings for the Linux eBPF process source. +# Regenerate bpf2go bindings for the Linux eBPF syscall telemetry source. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -32,5 +32,9 @@ generate execve ./c/execve.bpf.c generate exit ./c/exit.bpf.c generate clone ./c/clone.bpf.c generate prociter ./c/proc_iter.bpf.c +generate file ./c/file.bpf.c +generate net ./c/net.bpf.c +generate mem ./c/mem.bpf.c +generate ctl ./c/ctl.bpf.c echo "generated eBPF bindings" diff --git a/internal/ebpf/loader.go b/internal/ebpf/loader.go index 83cc1887a..1228f5cef 100644 --- a/internal/ebpf/loader.go +++ b/internal/ebpf/loader.go @@ -31,6 +31,8 @@ import ( "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/rlimit" + "github.com/rabbitstack/fibratus/pkg/config" + "github.com/rabbitstack/fibratus/pkg/event" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -40,6 +42,10 @@ type loader struct { exit *exitObjects clone *cloneObjects iter *prociterObjects + file *fileObjects + net *netObjects + mem *memObjects + ctl *ctlObjects links []link.Link iterLink *link.Iter once sync.Once @@ -66,6 +72,22 @@ func loadCollections() (*loader, error) { if err != nil { return nil, fmt.Errorf("loading prociter collection spec: %w", err) } + fileSpec, err := loadFile() + if err != nil { + return nil, fmt.Errorf("loading file collection spec: %w", err) + } + netSpec, err := loadNet() + if err != nil { + return nil, fmt.Errorf("loading net collection spec: %w", err) + } + memSpec, err := loadMem() + if err != nil { + return nil, fmt.Errorf("loading mem collection spec: %w", err) + } + ctlSpec, err := loadCtl() + if err != nil { + return nil, fmt.Errorf("loading ctl collection spec: %w", err) + } var execObjs execveObjects if err := execSpec.LoadAndAssign(&execObjs, nil); err != nil { @@ -73,9 +95,11 @@ func loadCollections() (*loader, error) { } replacements := map[string]*ebpf.Map{ - eventsMapName: execObjs.Events, - dropCountMapName: execObjs.DropCount, - scratchMapName: execObjs.Scratch, + eventsMapName: execObjs.Events, + dropCountMapName: execObjs.DropCount, + scratchMapName: execObjs.Scratch, + scratchHeapMapName: execObjs.ScratchHeap, + enabledMapName: execObjs.Enabled, } opts := &ebpf.CollectionOptions{MapReplacements: replacements} @@ -100,11 +124,57 @@ func loadCollections() (*loader, error) { return nil, fmt.Errorf("loading prociter objects: %w", err) } + var fileObjs fileObjects + if err := fileSpec.LoadAndAssign(&fileObjs, opts); err != nil { + _ = iterObjs.Close() + _ = cloneObjs.Close() + _ = exitObjs.Close() + _ = execObjs.Close() + return nil, fmt.Errorf("loading file objects: %w", err) + } + + var netObjs netObjects + if err := netSpec.LoadAndAssign(&netObjs, opts); err != nil { + _ = fileObjs.Close() + _ = iterObjs.Close() + _ = cloneObjs.Close() + _ = exitObjs.Close() + _ = execObjs.Close() + return nil, fmt.Errorf("loading net objects: %w", err) + } + + var memObjs memObjects + if err := memSpec.LoadAndAssign(&memObjs, opts); err != nil { + _ = netObjs.Close() + _ = fileObjs.Close() + _ = iterObjs.Close() + _ = cloneObjs.Close() + _ = exitObjs.Close() + _ = execObjs.Close() + return nil, fmt.Errorf("loading mem objects: %w", err) + } + + var ctlObjs ctlObjects + if err := ctlSpec.LoadAndAssign(&ctlObjs, opts); err != nil { + _ = memObjs.Close() + _ = netObjs.Close() + _ = fileObjs.Close() + _ = iterObjs.Close() + _ = cloneObjs.Close() + _ = exitObjs.Close() + _ = execObjs.Close() + return nil, fmt.Errorf("loading ctl objects: %w", err) + } + return &loader{ exec: &execObjs, exit: &exitObjs, clone: &cloneObjs, iter: &iterObjs, + file: &fileObjs, + net: &netObjs, + mem: &memObjs, + ctl: &ctlObjs, }, nil } @@ -120,6 +190,45 @@ func (l *loader) dropCount() uint64 { return drop } +func (l *loader) setEnabledTypes(cfg *config.EventSourceConfig) error { + if l.exec == nil || l.exec.Enabled == nil { + return fmt.Errorf("missing enabled map") + } + for typ, on := range enabledTypes(cfg) { + key := uint32(typ) + var val uint8 + if on { + val = 1 + } + if err := l.exec.Enabled.Put(&key, &val); err != nil { + return fmt.Errorf("enabling %s: %w", typ, err) + } + } + return nil +} + +func enabledTypes(cfg *config.EventSourceConfig) map[event.Type]bool { + file := cfg == nil || cfg.EnableFileIOEvents + netev := cfg == nil || cfg.EnableNetEvents + mem := cfg == nil || cfg.EnableMemEvents + return map[event.Type]bool{ + event.Execve: true, + event.Exit: true, + event.Clone: true, + event.Kill: true, + event.Ptrace: true, + event.Prctl: true, + event.Openat: file, + event.Unlink: file, + event.Rename: file, + event.Connect: netev, + event.Accept: netev, + event.Mmap: mem, + event.ProcessVMRead: mem, + event.ProcessVMWrite: mem, + } +} + // syscallsGroup is the tracefs group hosting raw syscall tracepoints. const syscallsGroup = "syscalls" @@ -128,7 +237,7 @@ const syscallsGroup = "syscalls" // in contrast to the one-shot iter/task snapshot program started by // runTaskIterator. The caller starts the ring buffer reader first so no // events are lost between attachment and consumption. -func (l *loader) attachPrograms() error { +func (l *loader) attachPrograms(cfg *config.EventSourceConfig) error { type tp struct { name string prog *ebpf.Program @@ -150,6 +259,48 @@ func (l *loader) attachPrograms() error { {"sys_enter_vfork", l.clone.HandleSysEnterVfork, true}, {"sys_exit_vfork", l.clone.HandleSysExitVfork, true}, } + if cfg == nil || cfg.EnableFileIOEvents { + // Legacy variants (open, unlink, rename) follow the fork/vfork + // precedent: some kernels refuse perf links on them. + tracepoints = append(tracepoints, + tp{"sys_enter_open", l.file.HandleSysEnterOpen, true}, + tp{"sys_exit_open", l.file.HandleSysExitOpen, true}, + tp{"sys_enter_openat", l.file.HandleSysEnterOpenat, false}, + tp{"sys_exit_openat", l.file.HandleSysExitOpenat, false}, + tp{"sys_enter_openat2", l.file.HandleSysEnterOpenat2, false}, + tp{"sys_exit_openat2", l.file.HandleSysExitOpenat2, false}, + tp{"sys_enter_unlink", l.file.HandleSysEnterUnlink, true}, + tp{"sys_exit_unlink", l.file.HandleSysExitUnlink, true}, + tp{"sys_enter_unlinkat", l.file.HandleSysEnterUnlinkat, false}, + tp{"sys_exit_unlinkat", l.file.HandleSysExitUnlinkat, false}, + tp{"sys_enter_rename", l.file.HandleSysEnterRename, true}, + tp{"sys_exit_rename", l.file.HandleSysExitRename, true}, + tp{"sys_enter_renameat", l.file.HandleSysEnterRenameat, false}, + tp{"sys_exit_renameat", l.file.HandleSysExitRenameat, false}, + tp{"sys_enter_renameat2", l.file.HandleSysEnterRenameat2, false}, + tp{"sys_exit_renameat2", l.file.HandleSysExitRenameat2, false}, + ) + } + if cfg == nil || cfg.EnableNetEvents { + tracepoints = append(tracepoints, + tp{"sys_enter_connect", l.net.HandleSysEnterConnect, false}, + tp{"sys_exit_connect", l.net.HandleSysExitConnect, false}, + tp{"sys_enter_accept", l.net.HandleSysEnterAccept, false}, + tp{"sys_exit_accept", l.net.HandleSysExitAccept, false}, + tp{"sys_enter_accept4", l.net.HandleSysEnterAccept4, false}, + tp{"sys_exit_accept4", l.net.HandleSysExitAccept4, false}, + ) + } + if cfg == nil || cfg.EnableMemEvents { + tracepoints = append(tracepoints, + tp{"sys_enter_mmap", l.mem.HandleSysEnterMmap, false}, + tp{"sys_exit_mmap", l.mem.HandleSysExitMmap, false}, + tp{"sys_enter_process_vm_readv", l.mem.HandleSysEnterProcessVmReadv, false}, + tp{"sys_exit_process_vm_readv", l.mem.HandleSysExitProcessVmReadv, false}, + tp{"sys_enter_process_vm_writev", l.mem.HandleSysEnterProcessVmWritev, false}, + tp{"sys_exit_process_vm_writev", l.mem.HandleSysExitProcessVmWritev, false}, + ) + } for _, t := range tracepoints { if t.prog == nil { return fmt.Errorf("missing program for %s/%s", syscallsGroup, t.name) @@ -168,12 +319,14 @@ func (l *loader) attachPrograms() error { // Successful clones and process exits are captured from scheduler // tracepoints. Clone syscall exit tracepoints fire in the parent, and // exit/exit_group never return, so their exit tracepoints never fire. + // kill/ptrace/prctl read arguments from pt_regs at tp_btf/sys_exit. tracing := []struct { name string prog *ebpf.Program }{ {"sched_process_fork", l.clone.HandleSchedProcessFork}, {"sched_process_exit", l.exit.HandleSchedProcessExit}, + {"sys_exit", l.ctl.HandleSysExit}, } for _, t := range tracing { if t.prog == nil { @@ -233,6 +386,26 @@ func (l *loader) Close() error { } // Replacement collections share maps owned by execve. Close only their // programs so the canonical maps are released once. + if l.ctl != nil { + if e := l.ctl.ctlPrograms.Close(); e != nil { + err = e + } + } + if l.mem != nil { + if e := l.mem.memPrograms.Close(); e != nil { + err = e + } + } + if l.net != nil { + if e := l.net.netPrograms.Close(); e != nil { + err = e + } + } + if l.file != nil { + if e := l.file.filePrograms.Close(); e != nil { + err = e + } + } if l.iter != nil { if e := l.iter.prociterPrograms.Close(); e != nil { err = e diff --git a/internal/ebpf/loader_test.go b/internal/ebpf/loader_test.go index 24f52cfe9..443fec4ea 100644 --- a/internal/ebpf/loader_test.go +++ b/internal/ebpf/loader_test.go @@ -26,10 +26,32 @@ import ( "os" "testing" + "github.com/rabbitstack/fibratus/pkg/config" + "github.com/rabbitstack/fibratus/pkg/event" "github.com/stretchr/testify/assert" "golang.org/x/sys/unix" ) +func TestEnabledTypes(t *testing.T) { + all := enabledTypes(nil) + assert.True(t, all[event.Execve]) + assert.True(t, all[event.Openat]) + assert.True(t, all[event.Connect]) + assert.True(t, all[event.Mmap]) + + off := enabledTypes(&config.EventSourceConfig{}) + assert.True(t, off[event.Execve]) + assert.True(t, off[event.Kill]) + assert.False(t, off[event.Openat]) + assert.False(t, off[event.Connect]) + assert.False(t, off[event.Mmap]) + + on := enabledTypes(&config.EventSourceConfig{EnableFileIOEvents: true, EnableNetEvents: true, EnableMemEvents: true}) + assert.True(t, on[event.Openat]) + assert.True(t, on[event.Accept]) + assert.True(t, on[event.ProcessVMRead]) +} + func TestIsAttachUnavailable(t *testing.T) { assert.False(t, isAttachUnavailable(nil)) assert.False(t, isAttachUnavailable(errors.New("verifier rejected"))) diff --git a/internal/ebpf/maps.go b/internal/ebpf/maps.go index 353876c85..c07a9de62 100644 --- a/internal/ebpf/maps.go +++ b/internal/ebpf/maps.go @@ -21,7 +21,9 @@ package ebpf const ( - eventsMapName = "events" - dropCountMapName = "drop_count" - scratchMapName = "scratch" + eventsMapName = "events" + dropCountMapName = "drop_count" + scratchMapName = "scratch" + scratchHeapMapName = "scratch_heap" + enabledMapName = "enabled" ) diff --git a/internal/ebpf/matrix_test.go b/internal/ebpf/matrix_test.go new file mode 100644 index 000000000..7e3dd91ee --- /dev/null +++ b/internal/ebpf/matrix_test.go @@ -0,0 +1,160 @@ +//go:build linux + +/* + * Copyright 2026 by Mostafa Moradian + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ebpf + +import ( + "testing" + + "github.com/rabbitstack/fibratus/pkg/event" + "github.com/rabbitstack/fibratus/pkg/event/params" + "github.com/rabbitstack/fibratus/pkg/ps" + pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" +) + +type matrixRow struct { + name string + typ event.Type + syscalls []string + hook string + params []string +} + +const hookSyscallIO = "tp/syscalls enter/exit" + +func syscallMatrix() []matrixRow { + return []matrixRow{ + {"execve", event.Execve, []string{"execve", "execveat"}, hookSyscallIO, []string{params.Exe, params.Retval}}, + {"exit", event.Exit, []string{"sched_process_exit"}, "tp_btf/sched_process_exit", []string{params.ExitStatus}}, + {"clone", event.Clone, []string{"clone", "clone3", "fork", "vfork"}, "tp/syscalls + tp_btf/sched_process_fork", []string{params.CloneFlags}}, + {"openat", event.Openat, []string{"open", "openat", "openat2"}, hookSyscallIO, []string{params.FilePath, params.DirFD, params.FileFlags, params.Retval}}, + {"unlink", event.Unlink, []string{"unlink", "unlinkat"}, hookSyscallIO, []string{params.FilePath, params.DirFD, params.Retval}}, + {"rename", event.Rename, []string{"rename", "renameat", "renameat2"}, hookSyscallIO, []string{params.FilePath, params.FileNewPath, params.Retval}}, + {"connect", event.Connect, []string{"connect"}, "tp/syscalls enter/exit; sockaddr copied at enter", []string{params.FD, params.SockFamily, params.Retval}}, + {"accept", event.Accept, []string{"accept", "accept4"}, "tp/syscalls enter/exit; sockaddr copied at exit", []string{params.FD, params.SockFamily, params.Retval}}, + {"mmap", event.Mmap, []string{"mmap"}, hookSyscallIO, []string{params.MemBaseAddress, params.MemRegionSize, params.MemProtect, params.MmapFlags, params.Retval}}, + {"process_vm_readv", event.ProcessVMRead, []string{"process_vm_readv"}, "tp/syscalls enter/exit; first remote iovec only", []string{params.TargetProcessID, params.MemBaseAddress, params.Retval}}, + {"process_vm_writev", event.ProcessVMWrite, []string{"process_vm_writev"}, "tp/syscalls enter/exit; first remote iovec only", []string{params.TargetProcessID, params.MemBaseAddress, params.Retval}}, + {"kill", event.Kill, []string{"kill", "tkill", "tgkill"}, "tp_btf/sys_exit", []string{params.TargetProcessID, params.Signal, params.Retval}}, + {"ptrace", event.Ptrace, []string{"ptrace"}, "tp_btf/sys_exit", []string{params.PtraceRequest, params.TargetProcessID, params.Retval}}, + {"prctl", event.Prctl, []string{"prctl"}, "tp_btf/sys_exit", []string{params.PrctlOption, params.Retval}}, + } +} + +func TestSyscallEventMatrix(t *testing.T) { + seen := map[event.Type]bool{} + for _, row := range syscallMatrix() { + assert.True(t, row.typ.Exists(), row.name) + assert.Equal(t, row.name, row.typ.String()) + assert.NotEmpty(t, row.syscalls) + assert.NotEmpty(t, row.hook) + seen[row.typ] = true + raw := rawEvent{Type: uint32(row.typ), Retval: 0, Filename: [256]byte{'/'}, Aux: [256]byte{'/'}} + if row.typ == event.Connect || row.typ == event.Accept { + raw.Aux[0] = byte(unix.AF_INET) + raw.Aux[1] = 0 + raw.Aux[2] = 0 + raw.Aux[3] = 80 + raw.Aux[4] = 127 + raw.Aux[5] = 0 + raw.Aux[6] = 0 + raw.Aux[7] = 1 + } + evt := raw.toEvent() + require.Equal(t, row.typ, evt.Type, row.name) + for _, name := range row.params { + _, err := evt.Params.Get(name) + assert.NoErrorf(t, err, "%s missing param %s", row.name, name) + } + } + for _, typ := range event.All() { + assert.Truef(t, seen[typ], "event type %s missing from the syscall matrix", typ) + } +} + +func TestParseSockaddr(t *testing.T) { + var inet [256]byte + inet[0] = byte(unix.AF_INET) + inet[2] = 0x00 + inet[3] = 0x50 + inet[4] = 10 + inet[5] = 1 + inet[6] = 2 + inet[7] = 3 + family, ip, port, unixPath := parseSockaddr(inet[:]) + assert.Equal(t, uint16(unix.AF_INET), family) + assert.Equal(t, "10.1.2.3", ip.String()) + assert.Equal(t, uint16(80), port) + assert.Empty(t, unixPath) + + var un [256]byte + un[0] = byte(unix.AF_UNIX) + copy(un[2:], "/tmp/fibratus.sock") + family, ip, port, unixPath = parseSockaddr(un[:]) + assert.Equal(t, uint16(unix.AF_UNIX), family) + assert.Nil(t, ip) + assert.Equal(t, "/tmp/fibratus.sock", unixPath) + assert.Zero(t, port) +} + +func TestMmapUpdatesProcessState(t *testing.T) { + snap := ps.NewSnapshotter() + ps := &pstypes.PS{PID: 9, Name: "target", StartBootTime: 1, Threads: map[uint64]pstypes.Thread{}} + snap.Put(ps) + + raw := rawEvent{Type: uint32(event.Mmap), PID: 9, TGID: 9, Retval: 0x1000, Arg0: 4096, Arg1: 8192, Arg2: 3, Arg3: 3, Flags: 0} + evt := raw.toEvent() + applyProcessState(snap, evt) + ok, got := snap.Find(9) + require.True(t, ok) + require.Len(t, got.Mmaps, 1) + assert.Equal(t, uint64(0x1000), uint64(got.Mmaps[0].BaseAddress)) + assert.Equal(t, uint64(8192), got.Mmaps[0].Size) + assert.Equal(t, "file", got.Mmaps[0].Type) +} + +func TestAnonymousMmapSkipped(t *testing.T) { + snap := ps.NewSnapshotter() + ps := &pstypes.PS{PID: 9, Name: "target", StartBootTime: 1, Threads: map[uint64]pstypes.Thread{}} + snap.Put(ps) + + raw := rawEvent{Type: uint32(event.Mmap), PID: 9, TGID: 9, Retval: 0x1000, Arg1: 8192, Arg2: 3, Arg3: ^uint64(0), Flags: 0x20} + evt := raw.toEvent() + applyProcessState(snap, evt) + ok, got := snap.Find(9) + require.True(t, ok) + assert.Empty(t, got.Mmaps) +} + +func TestFailedSyscallKeepsProcessState(t *testing.T) { + snap := ps.NewSnapshotter() + snap.Put(&pstypes.PS{PID: 11, Name: "keep", StartBootTime: 4}) + raw := rawEvent{Type: uint32(event.Openat), PID: 11, TGID: 11, Retval: -2} + copy(raw.Filename[:], "/no/such") + evt := raw.toEvent() + applyProcessState(snap, evt) + ok, got := snap.Find(11) + require.True(t, ok) + assert.Equal(t, "keep", got.Name) + assert.Empty(t, got.Mmaps) +} diff --git a/internal/ebpf/mem_bpfeb.go b/internal/ebpf/mem_bpfeb.go new file mode 100644 index 000000000..119afa03b --- /dev/null +++ b/internal/ebpf/mem_bpfeb.go @@ -0,0 +1,174 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (mips || mips64 || ppc64 || s390x) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type memScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadMem returns the embedded CollectionSpec for mem. +func loadMem() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_MemBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load mem: %w", err) + } + + return spec, err +} + +// loadMemObjects loads mem and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *memObjects +// *memPrograms +// *memMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadMemObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadMem() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// memSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memSpecs struct { + memProgramSpecs + memMapSpecs + memVariableSpecs +} + +// memProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memProgramSpecs struct { + HandleSysEnterMmap *ebpf.ProgramSpec `ebpf:"handle_sys_enter_mmap"` + HandleSysEnterProcessVmReadv *ebpf.ProgramSpec `ebpf:"handle_sys_enter_process_vm_readv"` + HandleSysEnterProcessVmWritev *ebpf.ProgramSpec `ebpf:"handle_sys_enter_process_vm_writev"` + HandleSysExitMmap *ebpf.ProgramSpec `ebpf:"handle_sys_exit_mmap"` + HandleSysExitProcessVmReadv *ebpf.ProgramSpec `ebpf:"handle_sys_exit_process_vm_readv"` + HandleSysExitProcessVmWritev *ebpf.ProgramSpec `ebpf:"handle_sys_exit_process_vm_writev"` +} + +// memMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// memVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memVariableSpecs struct { +} + +// memObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memObjects struct { + memPrograms + memMaps + memVariables +} + +func (o *memObjects) Close() error { + return _MemClose( + &o.memPrograms, + &o.memMaps, + ) +} + +// memMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *memMaps) Close() error { + return _MemClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// memVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memVariables struct { +} + +// memPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memPrograms struct { + HandleSysEnterMmap *ebpf.Program `ebpf:"handle_sys_enter_mmap"` + HandleSysEnterProcessVmReadv *ebpf.Program `ebpf:"handle_sys_enter_process_vm_readv"` + HandleSysEnterProcessVmWritev *ebpf.Program `ebpf:"handle_sys_enter_process_vm_writev"` + HandleSysExitMmap *ebpf.Program `ebpf:"handle_sys_exit_mmap"` + HandleSysExitProcessVmReadv *ebpf.Program `ebpf:"handle_sys_exit_process_vm_readv"` + HandleSysExitProcessVmWritev *ebpf.Program `ebpf:"handle_sys_exit_process_vm_writev"` +} + +func (p *memPrograms) Close() error { + return _MemClose( + p.HandleSysEnterMmap, + p.HandleSysEnterProcessVmReadv, + p.HandleSysEnterProcessVmWritev, + p.HandleSysExitMmap, + p.HandleSysExitProcessVmReadv, + p.HandleSysExitProcessVmWritev, + ) +} + +func _MemClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed mem_bpfeb.o +var _MemBytes []byte diff --git a/internal/ebpf/mem_bpfeb.o b/internal/ebpf/mem_bpfeb.o new file mode 100644 index 000000000..11508e9f1 Binary files /dev/null and b/internal/ebpf/mem_bpfeb.o differ diff --git a/internal/ebpf/mem_bpfel.go b/internal/ebpf/mem_bpfel.go new file mode 100644 index 000000000..708b6d7b9 --- /dev/null +++ b/internal/ebpf/mem_bpfel.go @@ -0,0 +1,174 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 || wasm) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type memScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadMem returns the embedded CollectionSpec for mem. +func loadMem() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_MemBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load mem: %w", err) + } + + return spec, err +} + +// loadMemObjects loads mem and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *memObjects +// *memPrograms +// *memMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadMemObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadMem() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// memSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memSpecs struct { + memProgramSpecs + memMapSpecs + memVariableSpecs +} + +// memProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memProgramSpecs struct { + HandleSysEnterMmap *ebpf.ProgramSpec `ebpf:"handle_sys_enter_mmap"` + HandleSysEnterProcessVmReadv *ebpf.ProgramSpec `ebpf:"handle_sys_enter_process_vm_readv"` + HandleSysEnterProcessVmWritev *ebpf.ProgramSpec `ebpf:"handle_sys_enter_process_vm_writev"` + HandleSysExitMmap *ebpf.ProgramSpec `ebpf:"handle_sys_exit_mmap"` + HandleSysExitProcessVmReadv *ebpf.ProgramSpec `ebpf:"handle_sys_exit_process_vm_readv"` + HandleSysExitProcessVmWritev *ebpf.ProgramSpec `ebpf:"handle_sys_exit_process_vm_writev"` +} + +// memMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// memVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type memVariableSpecs struct { +} + +// memObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memObjects struct { + memPrograms + memMaps + memVariables +} + +func (o *memObjects) Close() error { + return _MemClose( + &o.memPrograms, + &o.memMaps, + ) +} + +// memMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *memMaps) Close() error { + return _MemClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// memVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memVariables struct { +} + +// memPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadMemObjects or ebpf.CollectionSpec.LoadAndAssign. +type memPrograms struct { + HandleSysEnterMmap *ebpf.Program `ebpf:"handle_sys_enter_mmap"` + HandleSysEnterProcessVmReadv *ebpf.Program `ebpf:"handle_sys_enter_process_vm_readv"` + HandleSysEnterProcessVmWritev *ebpf.Program `ebpf:"handle_sys_enter_process_vm_writev"` + HandleSysExitMmap *ebpf.Program `ebpf:"handle_sys_exit_mmap"` + HandleSysExitProcessVmReadv *ebpf.Program `ebpf:"handle_sys_exit_process_vm_readv"` + HandleSysExitProcessVmWritev *ebpf.Program `ebpf:"handle_sys_exit_process_vm_writev"` +} + +func (p *memPrograms) Close() error { + return _MemClose( + p.HandleSysEnterMmap, + p.HandleSysEnterProcessVmReadv, + p.HandleSysEnterProcessVmWritev, + p.HandleSysExitMmap, + p.HandleSysExitProcessVmReadv, + p.HandleSysExitProcessVmWritev, + ) +} + +func _MemClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed mem_bpfel.o +var _MemBytes []byte diff --git a/internal/ebpf/mem_bpfel.o b/internal/ebpf/mem_bpfel.o new file mode 100644 index 000000000..486ecb389 Binary files /dev/null and b/internal/ebpf/mem_bpfel.o differ diff --git a/internal/ebpf/net.go b/internal/ebpf/net.go new file mode 100644 index 000000000..6d9d50ba4 --- /dev/null +++ b/internal/ebpf/net.go @@ -0,0 +1,85 @@ +//go:build linux + +/* + * Copyright 2026 by Mostafa Moradian + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ebpf + +import ( + "encoding/binary" + "net" + + "github.com/rabbitstack/fibratus/pkg/event" + "github.com/rabbitstack/fibratus/pkg/event/params" + "golang.org/x/sys/unix" +) + +func appendSockParams(evt *event.Event, r rawEvent) { + evt.Params.Append(params.FD, params.Int64, int64(r.Arg0)) + if r.Type == uint32(event.Accept) && r.Retval >= 0 { + evt.Params.Append(params.NewFD, params.Int64, r.Retval) + } + if r.Flags != 0 { + evt.Params.Append(params.SockFlags, params.Uint64, r.Flags) + } + + family, ip, port, unixPath := parseSockaddr(r.Aux[:]) + evt.Params.Append(params.SockFamily, params.Uint16, family) + if unixPath != "" { + evt.Params.Append(params.SockPath, params.Path, unixPath) + return + } + if ip == nil { + return + } + ipParam := params.IPv4 + if ip.To4() == nil { + ipParam = params.IPv6 + } + if r.Type == uint32(event.Connect) { + evt.Params.Append(params.NetDIP, ipParam, ip) + evt.Params.Append(params.NetDport, params.Port, port) + return + } + evt.Params.Append(params.NetSIP, ipParam, ip) + evt.Params.Append(params.NetSport, params.Port, port) +} + +func parseSockaddr(b []byte) (family uint16, ip net.IP, port uint16, unixPath string) { + if len(b) < 2 { + return 0, nil, 0, "" + } + family = binary.LittleEndian.Uint16(b[0:2]) + switch family { + case unix.AF_INET: + if len(b) >= 8 { + port = binary.BigEndian.Uint16(b[2:4]) + ip = net.IPv4(b[4], b[5], b[6], b[7]).To4() + } + case unix.AF_INET6: + if len(b) >= 24 { + port = binary.BigEndian.Uint16(b[2:4]) + ip = net.IP(append([]byte(nil), b[8:24]...)) + } + case unix.AF_UNIX: + if len(b) > 2 { + unixPath = cString(b[2:]) + } + } + return family, ip, port, unixPath +} diff --git a/internal/ebpf/net_bpfeb.go b/internal/ebpf/net_bpfeb.go new file mode 100644 index 000000000..e5dec87cb --- /dev/null +++ b/internal/ebpf/net_bpfeb.go @@ -0,0 +1,174 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (mips || mips64 || ppc64 || s390x) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type netScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadNet returns the embedded CollectionSpec for net. +func loadNet() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_NetBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load net: %w", err) + } + + return spec, err +} + +// loadNetObjects loads net and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *netObjects +// *netPrograms +// *netMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadNetObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadNet() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// netSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netSpecs struct { + netProgramSpecs + netMapSpecs + netVariableSpecs +} + +// netProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netProgramSpecs struct { + HandleSysEnterAccept *ebpf.ProgramSpec `ebpf:"handle_sys_enter_accept"` + HandleSysEnterAccept4 *ebpf.ProgramSpec `ebpf:"handle_sys_enter_accept4"` + HandleSysEnterConnect *ebpf.ProgramSpec `ebpf:"handle_sys_enter_connect"` + HandleSysExitAccept *ebpf.ProgramSpec `ebpf:"handle_sys_exit_accept"` + HandleSysExitAccept4 *ebpf.ProgramSpec `ebpf:"handle_sys_exit_accept4"` + HandleSysExitConnect *ebpf.ProgramSpec `ebpf:"handle_sys_exit_connect"` +} + +// netMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// netVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netVariableSpecs struct { +} + +// netObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netObjects struct { + netPrograms + netMaps + netVariables +} + +func (o *netObjects) Close() error { + return _NetClose( + &o.netPrograms, + &o.netMaps, + ) +} + +// netMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *netMaps) Close() error { + return _NetClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// netVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netVariables struct { +} + +// netPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netPrograms struct { + HandleSysEnterAccept *ebpf.Program `ebpf:"handle_sys_enter_accept"` + HandleSysEnterAccept4 *ebpf.Program `ebpf:"handle_sys_enter_accept4"` + HandleSysEnterConnect *ebpf.Program `ebpf:"handle_sys_enter_connect"` + HandleSysExitAccept *ebpf.Program `ebpf:"handle_sys_exit_accept"` + HandleSysExitAccept4 *ebpf.Program `ebpf:"handle_sys_exit_accept4"` + HandleSysExitConnect *ebpf.Program `ebpf:"handle_sys_exit_connect"` +} + +func (p *netPrograms) Close() error { + return _NetClose( + p.HandleSysEnterAccept, + p.HandleSysEnterAccept4, + p.HandleSysEnterConnect, + p.HandleSysExitAccept, + p.HandleSysExitAccept4, + p.HandleSysExitConnect, + ) +} + +func _NetClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed net_bpfeb.o +var _NetBytes []byte diff --git a/internal/ebpf/net_bpfeb.o b/internal/ebpf/net_bpfeb.o new file mode 100644 index 000000000..a97ce48a0 Binary files /dev/null and b/internal/ebpf/net_bpfeb.o differ diff --git a/internal/ebpf/net_bpfel.go b/internal/ebpf/net_bpfel.go new file mode 100644 index 000000000..da8e0c87a --- /dev/null +++ b/internal/ebpf/net_bpfel.go @@ -0,0 +1,174 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build (386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 || wasm) && linux + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + "structs" + + "github.com/cilium/ebpf" +) + +type netScratchValue struct { + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 +} + +// loadNet returns the embedded CollectionSpec for net. +func loadNet() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_NetBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load net: %w", err) + } + + return spec, err +} + +// loadNetObjects loads net and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *netObjects +// *netPrograms +// *netMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadNetObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadNet() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// netSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netSpecs struct { + netProgramSpecs + netMapSpecs + netVariableSpecs +} + +// netProgramSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netProgramSpecs struct { + HandleSysEnterAccept *ebpf.ProgramSpec `ebpf:"handle_sys_enter_accept"` + HandleSysEnterAccept4 *ebpf.ProgramSpec `ebpf:"handle_sys_enter_accept4"` + HandleSysEnterConnect *ebpf.ProgramSpec `ebpf:"handle_sys_enter_connect"` + HandleSysExitAccept *ebpf.ProgramSpec `ebpf:"handle_sys_exit_accept"` + HandleSysExitAccept4 *ebpf.ProgramSpec `ebpf:"handle_sys_exit_accept4"` + HandleSysExitConnect *ebpf.ProgramSpec `ebpf:"handle_sys_exit_connect"` +} + +// netMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netMapSpecs struct { + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` +} + +// netVariableSpecs contains global variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type netVariableSpecs struct { +} + +// netObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netObjects struct { + netPrograms + netMaps + netVariables +} + +func (o *netObjects) Close() error { + return _NetClose( + &o.netPrograms, + &o.netMaps, + ) +} + +// netMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netMaps struct { + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` +} + +func (m *netMaps) Close() error { + return _NetClose( + m.DropCount, + m.Enabled, + m.Events, + m.Scratch, + m.ScratchHeap, + ) +} + +// netVariables contains all global variables after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netVariables struct { +} + +// netPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadNetObjects or ebpf.CollectionSpec.LoadAndAssign. +type netPrograms struct { + HandleSysEnterAccept *ebpf.Program `ebpf:"handle_sys_enter_accept"` + HandleSysEnterAccept4 *ebpf.Program `ebpf:"handle_sys_enter_accept4"` + HandleSysEnterConnect *ebpf.Program `ebpf:"handle_sys_enter_connect"` + HandleSysExitAccept *ebpf.Program `ebpf:"handle_sys_exit_accept"` + HandleSysExitAccept4 *ebpf.Program `ebpf:"handle_sys_exit_accept4"` + HandleSysExitConnect *ebpf.Program `ebpf:"handle_sys_exit_connect"` +} + +func (p *netPrograms) Close() error { + return _NetClose( + p.HandleSysEnterAccept, + p.HandleSysEnterAccept4, + p.HandleSysEnterConnect, + p.HandleSysExitAccept, + p.HandleSysExitAccept4, + p.HandleSysExitConnect, + ) +} + +func _NetClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed net_bpfel.o +var _NetBytes []byte diff --git a/internal/ebpf/net_bpfel.o b/internal/ebpf/net_bpfel.o new file mode 100644 index 000000000..ca639ddaf Binary files /dev/null and b/internal/ebpf/net_bpfel.o differ diff --git a/internal/ebpf/prociter_bpfeb.go b/internal/ebpf/prociter_bpfeb.go index 4fe1862cf..b2a6fe6ef 100644 --- a/internal/ebpf/prociter_bpfeb.go +++ b/internal/ebpf/prociter_bpfeb.go @@ -14,10 +14,16 @@ import ( ) type prociterScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadProciter returns the embedded CollectionSpec for prociter. @@ -69,9 +75,11 @@ type prociterProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type prociterMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // prociterVariableSpecs contains global variables before they are loaded into the kernel. @@ -100,16 +108,20 @@ func (o *prociterObjects) Close() error { // // It can be passed to loadProciterObjects or ebpf.CollectionSpec.LoadAndAssign. type prociterMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *prociterMaps) Close() error { return _ProciterClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/prociter_bpfeb.o b/internal/ebpf/prociter_bpfeb.o index 772188f36..605d00f7e 100644 Binary files a/internal/ebpf/prociter_bpfeb.o and b/internal/ebpf/prociter_bpfeb.o differ diff --git a/internal/ebpf/prociter_bpfel.go b/internal/ebpf/prociter_bpfel.go index 7871faf59..060cb3e58 100644 --- a/internal/ebpf/prociter_bpfel.go +++ b/internal/ebpf/prociter_bpfel.go @@ -14,10 +14,16 @@ import ( ) type prociterScratchValue struct { - _ structs.HostLayout - Arg0 uint64 - Arg1 uint64 - Filename [256]uint8 + _ structs.HostLayout + Arg0 uint64 + Arg1 uint64 + Arg2 uint64 + Arg3 uint64 + Flags uint64 + Truncated uint32 + Pad uint32 + Filename [256]uint8 + Aux [256]uint8 } // loadProciter returns the embedded CollectionSpec for prociter. @@ -69,9 +75,11 @@ type prociterProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type prociterMapSpecs struct { - DropCount *ebpf.MapSpec `ebpf:"drop_count"` - Events *ebpf.MapSpec `ebpf:"events"` - Scratch *ebpf.MapSpec `ebpf:"scratch"` + DropCount *ebpf.MapSpec `ebpf:"drop_count"` + Enabled *ebpf.MapSpec `ebpf:"enabled"` + Events *ebpf.MapSpec `ebpf:"events"` + Scratch *ebpf.MapSpec `ebpf:"scratch"` + ScratchHeap *ebpf.MapSpec `ebpf:"scratch_heap"` } // prociterVariableSpecs contains global variables before they are loaded into the kernel. @@ -100,16 +108,20 @@ func (o *prociterObjects) Close() error { // // It can be passed to loadProciterObjects or ebpf.CollectionSpec.LoadAndAssign. type prociterMaps struct { - DropCount *ebpf.Map `ebpf:"drop_count"` - Events *ebpf.Map `ebpf:"events"` - Scratch *ebpf.Map `ebpf:"scratch"` + DropCount *ebpf.Map `ebpf:"drop_count"` + Enabled *ebpf.Map `ebpf:"enabled"` + Events *ebpf.Map `ebpf:"events"` + Scratch *ebpf.Map `ebpf:"scratch"` + ScratchHeap *ebpf.Map `ebpf:"scratch_heap"` } func (m *prociterMaps) Close() error { return _ProciterClose( m.DropCount, + m.Enabled, m.Events, m.Scratch, + m.ScratchHeap, ) } diff --git a/internal/ebpf/prociter_bpfel.o b/internal/ebpf/prociter_bpfel.o index 1be845f7d..175940972 100644 Binary files a/internal/ebpf/prociter_bpfel.o and b/internal/ebpf/prociter_bpfel.o differ diff --git a/internal/ebpf/source.go b/internal/ebpf/source.go index e335732cd..e692659f1 100644 --- a/internal/ebpf/source.go +++ b/internal/ebpf/source.go @@ -109,6 +109,11 @@ func (e *EventSource) Open(cfg *config.Config) error { } e.loader = ldr + if err := ldr.setEnabledTypes(&e.config.EventSource); err != nil { + _ = ldr.Close() + return err + } + rd, err := newRingReader(ldr.eventsMap()) if err != nil { _ = ldr.Close() @@ -118,7 +123,7 @@ func (e *EventSource) Open(cfg *config.Config) error { go e.consume() - if err := ldr.attachPrograms(); err != nil { + if err := ldr.attachPrograms(&e.config.EventSource); err != nil { e.Close() return err } diff --git a/internal/ebpf/source_integration_test.go b/internal/ebpf/source_integration_test.go index a33c43f56..f43c1feac 100644 --- a/internal/ebpf/source_integration_test.go +++ b/internal/ebpf/source_integration_test.go @@ -22,6 +22,8 @@ package ebpf import ( "os" + "os/exec" + "path/filepath" "testing" "time" @@ -81,3 +83,45 @@ func execLookPath() string { } return "/bin/sh" } + +func TestLiveSyscallTelemetry(t *testing.T) { + cfg := testConfig() + es := NewEventSource(ps.NewSnapshotter(), cfg, nil).(*EventSource) + if err := es.Open(cfg); err != nil { + t.Fatalf("opening process source: %v", err) + } + t.Cleanup(func() { _ = es.Close() }) + + dir := t.TempDir() + path := filepath.Join(dir, "x") + script := "echo hi > \"$1\" && mv \"$1\" \"$1.new\" && rm -f \"$1.new\" && kill -0 $$" + cmd := exec.Command("/bin/sh", "-c", script, "sh", path) + require.NoError(t, cmd.Run()) + + deadline := time.Now().Add(5 * time.Second) + saw := map[event.Type]bool{} + for time.Now().Before(deadline) && (!saw[event.Openat] || !saw[event.Rename] || !saw[event.Unlink] || !saw[event.Kill]) { + select { + case evt := <-es.Events(): + switch evt.Type { + case event.Openat, event.Rename, event.Unlink, event.Kill, event.Mmap: + saw[evt.Type] = true + } + case err := <-es.Errors(): + t.Fatalf("event source error: %v", err) + case <-time.After(50 * time.Millisecond): + } + } + if !saw[event.Openat] { + t.Fatal("did not observe openat") + } + if !saw[event.Rename] { + t.Fatal("did not observe rename") + } + if !saw[event.Unlink] { + t.Fatal("did not observe unlink") + } + if !saw[event.Kill] { + t.Fatal("did not observe kill") + } +} diff --git a/internal/ebpf/source_test.go b/internal/ebpf/source_test.go index c57fbaf18..7d01d2d1a 100644 --- a/internal/ebpf/source_test.go +++ b/internal/ebpf/source_test.go @@ -32,6 +32,9 @@ import ( func testConfig() *config.Config { cfg := &config.Config{ForwardMode: true} + cfg.EventSource.EnableFileIOEvents = true + cfg.EventSource.EnableNetEvents = true + cfg.EventSource.EnableMemEvents = true cfg.EventSource.Init() return cfg } diff --git a/internal/ebpf/spike/c/vmlinux.h b/internal/ebpf/spike/c/vmlinux.h index da94af6b5..c5a3f8ae2 100644 --- a/internal/ebpf/spike/c/vmlinux.h +++ b/internal/ebpf/spike/c/vmlinux.h @@ -73,6 +73,16 @@ enum bpf_map_type { #define TASK_COMM_LEN 16 #endif +/* x86-64 field names from kernel BTF. CO-RE relocates by name. */ +struct pt_regs { + unsigned long r10; + unsigned long r8; + unsigned long dx; + unsigned long si; + unsigned long di; + unsigned long orig_ax; +} __attribute__((preserve_access_index)); + struct seq_file; struct bpf_iter_meta { diff --git a/pkg/event/metainfo_linux.go b/pkg/event/metainfo_linux.go index 667f598a3..e00d08ee3 100644 --- a/pkg/event/metainfo_linux.go +++ b/pkg/event/metainfo_linux.go @@ -22,21 +22,28 @@ package event import "slices" -var events = map[Type]Info{ - Execve: {Name: "execve", Category: Process, Description: "Executes a program"}, - Exit: {Name: "exit", Category: Process, Description: "Exit all threads in a process"}, - Clone: {Name: "clone", Category: Process, Description: "Creates a child process or a thread"}, -} +var events map[Type]Info +var types map[string]Type -var types = map[string]Type{ - "execve": Execve, - "exit": Exit, - "clone": Clone, +func init() { + all := All() + events = make(map[Type]Info, len(all)) + types = make(map[string]Type, len(all)) + for _, typ := range all { + events[typ] = Info{Name: typ.String(), Category: typ.Category(), Description: typ.Description()} + types[typ.String()] = typ + } } // All returns all Linux event types. func All() []Type { - return []Type{Execve, Exit, Clone} + return []Type{ + Execve, Exit, Clone, + Openat, Unlink, Rename, + Connect, Accept, + Mmap, ProcessVMRead, ProcessVMWrite, + Kill, Ptrace, Prctl, + } } // MaxTypeID returns the largest Linux event type identifier. diff --git a/pkg/event/params/names_linux.go b/pkg/event/params/names_linux.go index edf79f1f0..ebb52580a 100644 --- a/pkg/event/params/names_linux.go +++ b/pkg/event/params/names_linux.go @@ -33,4 +33,40 @@ const ( Retval = "retval" // StartBootTime is the process start time measured from system boot, in nanoseconds. StartBootTime = "start_boot_time" + // DirFD is a directory file descriptor, including AT_FDCWD. + DirFD = "dirfd" + // NewDirFD is the destination directory file descriptor for renameat. + NewDirFD = "new_dirfd" + // FileNewPath is the destination path of a rename. + FileNewPath = "file_new_path" + // FileFlags is the raw openat, unlinkat, or renameat2 flags bitmask. + FileFlags = "file_flags" + // FileMode is the file creation mode supplied to openat. + FileMode = "file_mode" + // FD is a file descriptor. + FD = "fd" + // NewFD is the connected file descriptor returned by accept. + NewFD = "new_fd" + // SockFamily is the socket address family (AF_INET, AF_INET6, AF_UNIX). + SockFamily = "sock_family" + // SockFlags is the flags bitmask supplied to accept4. + SockFlags = "sock_flags" + // SockPath is the filesystem path of an AF_UNIX socket. + SockPath = "sock_path" + // MmapFlags is the raw mmap flags bitmask. + MmapFlags = "mmap_flags" + // MmapOffset is the file offset of a memory mapping. + MmapOffset = "mmap_offset" + // Signal is a POSIX signal number. + Signal = "signal" + // PtraceRequest is the ptrace request code. + PtraceRequest = "ptrace_request" + // PtraceAddr is the ptrace address argument. + PtraceAddr = "ptrace_addr" + // PtraceData is the ptrace data argument. + PtraceData = "ptrace_data" + // PrctlOption is the prctl option code. + PrctlOption = "prctl_option" + // Truncated is a bitmask of truncated variable-length fields. + Truncated = "truncated" ) diff --git a/pkg/event/types_linux.go b/pkg/event/types_linux.go index 8bb9b7140..883947e0b 100644 --- a/pkg/event/types_linux.go +++ b/pkg/event/types_linux.go @@ -41,6 +41,17 @@ const ( Execve Exit Clone + Openat + Unlink + Rename + Connect + Accept + Mmap + ProcessVMRead + ProcessVMWrite + Kill + Ptrace + Prctl ) // String returns the event type name. @@ -52,6 +63,28 @@ func (t Type) String() string { return "exit" case Clone: return "clone" + case Openat: + return "openat" + case Unlink: + return "unlink" + case Rename: + return "rename" + case Connect: + return "connect" + case Accept: + return "accept" + case Mmap: + return "mmap" + case ProcessVMRead: + return "process_vm_readv" + case ProcessVMWrite: + return "process_vm_writev" + case Kill: + return "kill" + case Ptrace: + return "ptrace" + case Prctl: + return "prctl" default: return "" } @@ -60,8 +93,14 @@ func (t Type) String() string { // Category returns the event type category. func (t Type) Category() Category { switch t { - case Execve, Exit, Clone: + case Execve, Exit, Clone, Kill, Ptrace, Prctl: return Process + case Openat, Unlink, Rename: + return File + case Connect, Accept: + return Net + case Mmap, ProcessVMRead, ProcessVMWrite: + return Mem default: return Unknown } @@ -79,6 +118,28 @@ func (t Type) Description() string { return "Exit all threads in a process" case Clone: return "Creates a child process or a thread" + case Openat: + return "Opens or creates a file" + case Unlink: + return "Removes a directory entry" + case Rename: + return "Renames a file" + case Connect: + return "Initiates a socket connection" + case Accept: + return "Accepts a socket connection" + case Mmap: + return "Maps files or devices into memory" + case ProcessVMRead: + return "Reads memory from another process" + case ProcessVMWrite: + return "Writes memory into another process" + case Kill: + return "Sends a signal to a process" + case Ptrace: + return "Traces or controls another process" + case Prctl: + return "Performs a process-control operation" default: return "" } @@ -95,7 +156,7 @@ func (t Type) Hash() uint32 { // Exists reports whether the event type is known. func (t Type) Exists() bool { switch t { - case Execve, Exit, Clone: + case Execve, Exit, Clone, Openat, Unlink, Rename, Connect, Accept, Mmap, ProcessVMRead, ProcessVMWrite, Kill, Ptrace, Prctl: return true default: return false diff --git a/pkg/event/types_linux_test.go b/pkg/event/types_linux_test.go new file mode 100644 index 000000000..9cee20881 --- /dev/null +++ b/pkg/event/types_linux_test.go @@ -0,0 +1,60 @@ +//go:build linux + +/* + * Copyright 2026 by Mostafa Moradian + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLinuxEventCatalog(t *testing.T) { + assert.Equal(t, uint16(1), uint16(Execve)) + assert.Equal(t, uint16(14), uint16(Prctl)) + assert.Equal(t, File, Openat.Category()) + assert.Equal(t, Net, Connect.Category()) + assert.Equal(t, Mem, Mmap.Category()) + assert.Equal(t, Process, Kill.Category()) + assert.Equal(t, "process_vm_readv", ProcessVMRead.String()) + assert.True(t, Openat.Exists()) + for _, typ := range All() { + assert.True(t, typ.Exists()) + assert.Equal(t, typ, NameToType(typ.String())) + assert.NotEmpty(t, typ.Description()) + assert.Equal(t, typ.Category(), TypeToEventInfo(typ).Category) + } + assert.Equal(t, uint16(Prctl), MaxTypeID()) +} + +func TestLinuxEventCatalogRejectsWindowsNames(t *testing.T) { + for _, name := range []string{ + "CreateProcess", + "TerminateProcess", + "ProcessRundown", + "CreateFile", + "CreateThread", + "LoadImage", + "RegOpenKey", + "RecvTCPv4", + } { + assert.Equal(t, UnknownType, NameToType(name), name) + } +}