Conversation
The application-profile recorder in ReportFileExec derives the path it
stores into the AP from `args[0]`, while the rule-side resolver
(`parse.get_exec_path` in pkg/rulemanager/cel/libraries/parse/parse.go)
falls back to `comm` when `args[0]` is empty. The asymmetry causes
"Unexpected process launched" (R0001) and other ap.was_executed-based
rules to fire on processes that are present in the application profile.
Trigger: fexecve / execveat with AT_EMPTY_PATH. modern libpam (>= 1.5)
invokes its helpers (unix_chkpwd, unix_update, ...) via fexecve to avoid
TOCTOU on the helper path. The kernel implements fexecve as
execveat(fd, "", argv, envp, AT_EMPTY_PATH) — pathname is empty by
design.
Inspektor Gadget's trace_exec puts the syscall pathname into args[0]
and reads argv from index 1 (gadgets/trace_exec/program.bpf.c:146-153).
For fexecve/execveat empty-pathname, this produces args = ["", argv[1]]
in the agent's exec event. The recorder then sets path = args[0] = ""
and the AP entry is unreachable to ap.was_executed("unix_chkpwd")
(which the rule-side resolver computes via the empty-args[0] -> comm
fallback).
Fix: derive the recorder's path the same way the rule-side does — prefer
exepath (the kernel-authoritative exe_file path, immune to argv[0]
spoofing too), then argv[0] when non-empty, then comm.
Concrete impact in production: 408 of 1976 Bonial I013 incidents on
production scoring-api APs are exactly this case — cron user-context
setup invokes pam_unix -> unix_chkpwd via fexecve, AP records path: ""
with args ["", "root"], rule looks up "unix_chkpwd" via comm fallback,
no match.
The new resolveExecPath helper is also more defensive against argv[0]
spoofing in general — exepath comes from task->mm->exe_file in the BPF
side and cannot be controlled by user code.
Verified locally on a kind cluster with kubescape v0.3.94: a pod that
loops execve (control) and execveat-AT_EMPTY_PATH (bug) reproduces the
production-shape AP entry on the unfixed code path.
Signed-off-by: Ben <ben@armosec.io>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughA new helper function ChangesExec Path Resolution Refactoring
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
Performance Benchmark ResultsNode-Agent Resource Usage
Dedup Effectiveness (AFTER only)
Event Counters
|
matthyx
left a comment
There was a problem hiding this comment.
The rule-side resolvers still derive the lookup key from args[0] / comm only in pkg/utils/events.go and pkg/rulemanager/cel/libraries/parse/parse.go. In the fexecve / execveat(..., AT_EMPTY_PATH) case that means the application profile can store /usr/sbin/unix_chkpwd, while parse.get_exec_path(...) still asks ap.was_executed("unix_chkpwd"), so the lookup still misses and the false positive remains.
I would add an integration test to be sure...
Yes, I am working on fixing the CELs now |
Problem
The application-profile recorder (
ReportFileExecinpkg/containerprofilemanager/v1/event_reporting.go) derives the path it stores into the AP fromargs[0], while the rule-side resolver (parse.get_exec_pathinpkg/rulemanager/cel/libraries/parse/parse.go) falls back tocommwhenargs[0]is empty. The asymmetry causes any rule built onap.was_executed(R0001 Unexpected process launched and friends) to fire on processes that ARE present in the application profile.Trigger:
fexecve/execveatwithAT_EMPTY_PATHModern libpam (≥ 1.5) invokes its helpers (
unix_chkpwd,unix_update, …) viafexecveto avoid TOCTOU on the helper path. The kernel implementsfexecveasexecveat(fd, "", argv, envp, AT_EMPTY_PATH)— pathname is empty by design.Inspektor Gadget's
trace_execputs the syscall'spathnameintoargs[0]and readsargvfrom index 1 (gadgets/trace_exec/program.bpf.c:146-153). Forfexecve/execveat-with-empty-pathname this producesargs = ["", argv[1]]in the agent's exec event.The current recorder then does:
→ AP entry:
Path: "", Args: ["", "root"].At runtime, the rule's
parse.get_exec_pathfalls back tocomm = "unix_chkpwd"(becauseargsList[0] == "").ap.was_executed("unix_chkpwd")then walks the AP execs comparingexec.Path == "unix_chkpwd"— never finds the empty-path entry — and the rule fires.Fix
Make the recorder symmetric with the rule-side resolver:
event.GetExePath()— the kernel-authoritative path (task->mm->exe_filein BPF, immune toargv[0]spoofing too).args[0]when non-empty.commotherwise.Extracted into a small
resolveExecPathhelper so the logic is unit-testable.Concrete impact
In a Bonial customer snapshot, 408 of 1976 R0001 incidents (20.6%) on
scoring-apiproduction workloads are exactly this case — cron user-context setup invokespam_unix→unix_chkpwdviafexecve, the AP recordsPath: ""withArgs: ["", "root"], and the rule fires every time. With this fix the recorded entry becomesPath: /usr/sbin/unix_chkpwd, Args: ["", "root"]and matches what the rule looks up.Same pattern applies to any rule using
ap.was_executed.Reproducer
Confirmed locally on a kind cluster with kubescape + node-agent. A pod that loops between
execve(control:pathnamenon-empty) andexecveat-with-AT_EMPTY_PATH(bug:pathname="") reproduces both AP entry shapes on the unfixed code; with the fix applied, both produce a usablePathfield.Note: Python's
os.execvrejects emptyargv[0]— bypass withctypesdirect libc, or callexecveatviasyscall(__NR_execveat, fd, "", ...)from C.Test
Added
resolveExecPathunit tests covering canonical exec, fexecve with non-emptyargv[0], fexecve with emptyargv[0](older PAM convention), bare-comm fallback, and anargv[0]-spoofing case whereexepathcorrectly wins.Summary by CodeRabbit
Release Notes
Bug Fixes
Tests