From 5db3c652432300a9ac78677c25f93f8a81b5c038 Mon Sep 17 00:00:00 2001 From: Jeff Larson Date: Sun, 21 Jun 2026 12:59:30 -0700 Subject: [PATCH] fix(agent): process-exec probe attaches to security_bprm_check (JEF-53) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On-node (v0.3.41 deploy) the bprm_check probe failed to attach: "Unknown BTF type `bprm_check_security`" — that symbol isn't a BTF function on 6.8. The exported LSM call IS `security_bprm_check` (in BTF, like every other `security_*` probe we attach). Swap the fentry target + the attach-table row; bprm->filename read is unchanged. privilege-change (security_task_fix_setuid) already attached cleanly on-node; this fixes the one probe that didn't. eBPF nightly check + Docker builder link green. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP --- agent/common/src/lib.rs | 2 +- agent/protector-agent-ebpf/src/main.rs | 18 +++++++++--------- agent/protector-agent/src/observer.rs | 3 +-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/agent/common/src/lib.rs b/agent/common/src/lib.rs index e35688d..0bd1df6 100644 --- a/agent/common/src/lib.rs +++ b/agent/common/src/lib.rs @@ -19,7 +19,7 @@ pub const KIND_FILE_OPEN: u32 = 2; /// binary (fentry on `security_mmap_file`, PROT_EXEC). Carries the path; userspace emits /// a LibraryLoaded with the basename. Reuses [`FileEvent`] (kind discriminates). pub const KIND_LIBRARY_LOAD: u32 = 3; -/// A process was exec'd (fentry on `bprm_check_security`). Carries the exec'd binary's +/// A process was exec'd (fentry on `security_bprm_check`). Carries the exec'd binary's /// path, read from `linux_binprm->filename`; userspace emits a ProcessExec. Reuses /// [`FileEvent`] (kind discriminates) — the runtime signal for "unexpected process /// spawned" (Falco-rule parity, ADR-0014). diff --git a/agent/protector-agent-ebpf/src/main.rs b/agent/protector-agent-ebpf/src/main.rs index 37b982f..34c086c 100644 --- a/agent/protector-agent-ebpf/src/main.rs +++ b/agent/protector-agent-ebpf/src/main.rs @@ -28,9 +28,8 @@ use aya_ebpf::{ // The event layouts + kind discriminators are shared verbatim with the userspace loader // via this one crate, so the kernel↔userspace byte contract can't drift (ADR-0014). use protector_agent_common::{ - ConnEvent, EventHeader, FileEvent, KIND_CONNECT, KIND_EXEC, KIND_FILE_OPEN, KIND_LIBRARY_LOAD, - KIND_PRIV_CHANGE, PATH_CAP, PrivEvent, - + ConnEvent, EventHeader, FileEvent, PrivEvent, KIND_CONNECT, KIND_EXEC, KIND_FILE_OPEN, + KIND_LIBRARY_LOAD, KIND_PRIV_CHANGE, PATH_CAP, }; /// Ring buffer of behavioral events (all kinds) drained by userspace. @@ -239,20 +238,21 @@ fn try_fix_setuid(ctx: &FEntryContext) -> Result<(), i64> { Ok(()) } -/// fentry on `bprm_check_security(struct linux_binprm *bprm)` — the process-exec probe +/// fentry on `security_bprm_check(struct linux_binprm *bprm)` — the process-exec probe /// (ADR-0014, JEF-53). This LSM hook fires on every `execve` once the new binary is /// resolved, so `bprm->filename` is the path the kernel is about to exec. Emits a /// [`FileEvent`] (kind [`KIND_EXEC`]) carrying that path; userspace turns it into a -/// `ProcessExec`. Observe-only — `bprm_check_security` is the per-hook callback symbol on -/// 6.8 (`security_bprm_check` is the wrapper alternative if the symbol isn't attachable). -#[fentry(function = "bprm_check_security")] +/// `ProcessExec`. Observe-only. NOTE: the attach point is `security_bprm_check` (the +/// exported LSM call, in BTF — like the other `security_*` probes); the un-prefixed +/// `bprm_check_security` is NOT a BTF function on 6.8 (verified on-node: JEF-53 deploy). +#[fentry(function = "security_bprm_check")] pub fn bprm_check(ctx: FEntryContext) -> u32 { let _ = try_bprm_check(&ctx); 0 } fn try_bprm_check(ctx: &FEntryContext) -> Result<(), i64> { - // bprm_check_security's first argument is `struct linux_binprm *bprm`. + // security_bprm_check's first argument is `struct linux_binprm *bprm`. let bprm: *const vmlinux::linux_binprm = unsafe { ctx.arg(0) }; if bprm.is_null() { return Ok(()); @@ -263,7 +263,7 @@ fn try_bprm_check(ctx: &FEntryContext) -> Result<(), i64> { /// Emit the exec'd binary's path as a [`KIND_EXEC`] event. `bprm->filename` is a kernel /// `char *` (the resolved exec path), so — like the library-load probe — read the string -/// directly with `bpf_probe_read_kernel_str`. NOT `bpf_d_path`: `bprm_check_security` +/// directly with `bpf_probe_read_kernel_str`. NOT `bpf_d_path`: `security_bprm_check` /// isn't on the kernel's d_path allowlist, so the verifier would reject it (JEF-68). fn emit_exec_path(bprm: *const vmlinux::linux_binprm) { let pid = (aya_ebpf::helpers::bpf_get_current_pid_tgid() >> 32) as u32; diff --git a/agent/protector-agent/src/observer.rs b/agent/protector-agent/src/observer.rs index 21ce355..8066bf7 100644 --- a/agent/protector-agent/src/observer.rs +++ b/agent/protector-agent/src/observer.rs @@ -52,7 +52,6 @@ mod ebpf { use protector_agent_common::{ ConnEvent, EventHeader, FileEvent, KIND_CONNECT, KIND_EXEC, KIND_FILE_OPEN, KIND_LIBRARY_LOAD, KIND_PRIV_CHANGE, PATH_CAP, PrivEvent, - }; use protector_behavior::{Attribution, Behavior}; @@ -319,7 +318,7 @@ mod ebpf { ("file_open", "security_file_open"), ("mmap_file", "security_mmap_file"), ("fix_setuid", "security_task_fix_setuid"), - ("bprm_check", "bprm_check_security"), + ("bprm_check", "security_bprm_check"), ]; let btf = match Btf::from_sys_fs() { Ok(btf) => btf,