Skip to content

BPF Hooks Reference

thefoulowl edited this page Sep 4, 2026 · 1 revision

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

Why tracepoint vs. fentry

  • Tracepoints (sched_process_*) are a stable kernel ABI — their trace_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 actual struct file * or the connect-time sockaddr). 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 that security_file_open is normally associated with.

Why security_file_open and not vfs_open/a syscall

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.

Why hook tcp_v4_connect/tcp_v6_connect on entry

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.

Ring buffer sizing

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.

Clone this wiki locally