Repository navigation
BPF Hooks Reference
All five probes live in internal/bpf/provctl.bpf.c and submit to one
BPF_RINGBUF map (events). Every probe first checks self_pid (an
ARRAY map holding provctl's own pid, set once at startup) and returns
early if the calling pid matches — otherwise the daemon would endlessly
trace its own file opens, log writes, and database access.
| Hook | Attach type | Emits | Key fields |
|---|---|---|---|
sched_process_fork |
tracepoint (tp/sched/sched_process_fork) |
EVENT_FORK |
ppid (parent), pid (new child), comm (child's, read via __data_loc) |
sched_process_exec |
tracepoint (tp/sched/sched_process_exec) |
EVENT_EXEC |
pid, ppid (via task_struct->real_parent->tgid), uid/gid, comm, filename (resolved exec path, via __data_loc) |
sched_process_exit |
tracepoint (tp/sched/sched_process_exit) |
EVENT_EXIT |
pid, exit_code (only emitted for the group leader — thread exits are filtered out as noise) |
security_file_open |
fentry (fentry/security_file_open) |
EVENT_FILE_OPEN |
pid, filename (resolved via bpf_d_path()) |
tcp_v4_connect |
fentry (fentry/tcp_v4_connect) |
EVENT_CONNECT (family=AF_INET) |
daddr_v4, dport, read straight from the sockaddr_in the caller passed |
tcp_v6_connect |
fentry (fentry/tcp_v6_connect) |
EVENT_CONNECT (family=AF_INET6) |
daddr_v6, dport, read from sockaddr_in6
|
-
Tracepoints (
sched_process_*) are a stable kernel ABI — theirtrace_event_raw_*context struct layout doesn't change across kernel versions the way internal function signatures can. Process lifecycle events are exactly the stable, well-known case tracepoints exist for. -
fentry (
security_file_open,tcp_v4/v6_connect) is used where we need arguments a tracepoint doesn't expose (the actualstruct file *or the connect-timesockaddr). fentry is BTF-based (via CO-RE), lower overhead than a kprobe, and — since we're purely observational, not blocking or modifying behavior — doesn't need the LSM attach path thatsecurity_file_openis normally associated with.
security_file_open is the LSM hook every file open funnels through
regardless of which syscall got there (open, openat, openat2,
execve's own implicit open, etc.) — one hook instead of covering every
open-ish syscall separately.
Hooking on entry means we read the destination straight from the
sockaddr the caller supplied, before connect() has a chance to fail,
block, or (for non-blocking sockets) return before the handshake
completes. We don't need the eventual sock state — the caller's intent
is the provenance signal ("this process tried to reach this address"),
regardless of whether the connection ultimately succeeded.
events is sized at 1 << 24 (16 MiB). Under a slow consumer or an
extreme burst, bpf_ringbuf_reserve() can fail and the probe silently
drops that one event (checked via if (!e) return 0; in every probe) —
this is deliberate backpressure (drop, don't block the traced process) but
does mean very bursty workloads can have gaps. See
Known Limitations & Roadmap.