feat(agent): process-exec probe — fentry on bprm_check_security (JEF-53)#26
Merged
Merged
Conversation
Add an eBPF process-exec probe and a Behavior::ProcessExec { path } — the
runtime signal for "unexpected process spawned" (Falco-rule parity, ADR-0014).
eBPF: fentry on bprm_check_security(struct linux_binprm *bprm), fired on every
execve once the binary is resolved. Reads bprm->filename (a kernel char*)
directly with bpf_probe_read_kernel_str — NOT bpf_d_path, which the verifier
rejects in hooks off the d_path allowlist (JEF-68). Emits a FileEvent with the
new KIND_EXEC = 4 (path carries the exec'd binary). On reserve failure calls
record_drop() (JEF-58).
behavior: new Behavior::ProcessExec; summary "executed {path}"; fingerprint_key
coarsens to the basename ("exec:{basename}") so exec churn doesn't bust the
verdict cache (mirrors LibraryLoaded's stable key).
userspace (observer.rs): RawEvent::Exec, a KIND_EXEC decode arm, into_behavior
→ ProcessExec, and the bprm_check fentry in the best-effort attach table — all
on the JEF-64 decoupled drain/worker path (no blocking I/O reintroduced).
engine: proof.rs corroborates() gets a non-corroborating ProcessExec arm —
wiring exec → corroboration is out of scope (JEF-49).
Tests: behavior fingerprint/summary basename test; observer KIND_EXEC →
RawEvent::Exec → ProcessExec decode test. Verifier-accept is only confirmable
on-node (the JEF-68 risk); the Docker builder confirms it compiles + links.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP
76dd0f8 to
28caa04
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new eBPF probe that captures process execs and emits a new
Behavior::ProcessExec { path }— the runtime signal for "unexpected process spawned" (Falco-rule parity, ADR-0014, JEF-53).How
eBPF probe (
agent/protector-agent-ebpf/src/main.rs): fentry onbprm_check_security(struct linux_binprm *bprm)— the LSM exec hook, fired on everyexecveonce the new binary is resolved. Readsbprm->filename(a kernelchar *) directly withbpf_probe_read_kernel_str— the same technique as the library-load probe, and deliberately notbpf_d_path(the verifier rejects it in hooks off the kernel's d_path allowlist — JEF-68). Emits aFileEventcarrying the exec'd path under a newKIND_EXEC = 4. On ring-reserve failure it callsrecord_drop()(JEF-58).behavior (
behavior/src/lib.rs): newBehavior::ProcessExec { path };summary→"executed {path}";fingerprint_keycoarsens to the basename (exec:{basename}) so repeated execs of the same binary collapse to one stable key and don't bust the verdict cache (mirrors howLibraryLoadedkeys on the lib name, not the full path).userspace (
agent/protector-agent/src/observer.rs): aRawEvent::Exec { pid, path }variant, aKIND_EXECdecode arm,into_behavior→ProcessExec, and thebprm_checkfentry added to the best-effort attach table. All on the JEF-64 decoupled drain/worker path — no blocking I/O reintroduced on the drain.engine (
engine/src/engine/reason/proof.rs):corroborates()gets a non-corroboratingProcessExecarm. Wiring exec → corroboration is explicitly out of scope (JEF-49).Validation
cargo +nightly checkon the eBPF crate — clean.docker build -f agent/Dockerfile --target builder— EXIT 0 (compiles + links the eBPF object incl. the new probe with bpf-linker, and the feature-gated--features ebpfuserspace).cargo build,cargo test,cargo clippy --all-targets -- -D warnings,cargo fmt— all green.cargo build,cargo test,cargo fmt— all green.Tests
behavior:ProcessExecfingerprint coarsens to basename (exec:bashfrom both/usr/bin/bashand/bin/bash) + summary.observer: aKIND_EXECFileEventdecodes toRawEvent::Execand maps toBehavior::ProcessExecwith the basename fingerprint.Concerns
bprm_check_security(the per-LSM callback). If that symbol isn't fentry-attachable on the target 6.8 kernel, the alternative is the wrappersecurity_bprm_check— a one-line change in the FENTRY_PROBES table + the#[fentry(function = ...)]attribute.🤖 Generated with Claude Code