Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions pkg/containerprofilemanager/v1/event_reporting.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,33 @@ func (cpm *ContainerProfileManager) ReportCapability(containerID, capability str
cpm.logEventError(err, "capability", containerID)
}

// resolveExecPath derives the path to record for an exec event. It is kept
// symmetric with the rule-side resolver in
// pkg/rulemanager/cel/libraries/parse/parse.go (parse.get_exec_path): prefer
// the kernel-authoritative exepath, then argv[0] when non-empty, then comm.
// Using args[0] unconditionally produces an empty Path when the syscall has
// an empty pathname (fexecve / execveat AT_EMPTY_PATH — the libpam helper
// invocation pattern), while the rule-side resolver falls back to comm —
// leaving the AP entry unreachable to ap.was_executed and producing spurious
// "Unexpected process launched" alerts.
func resolveExecPath(exepath, comm string, args []string) string {
if exepath != "" {
return exepath
}
if len(args) > 0 && args[0] != "" {
return args[0]
}
return comm
}

// ReportFileExec reports a file execution event for a container
func (cpm *ContainerProfileManager) ReportFileExec(containerID string, event utils.ExecEvent) {
err := cpm.withContainer(containerID, func(data *containerData) (int, error) {
if data.execs == nil {
data.execs = &maps.SafeMap[string, []string]{}
}
path := event.GetComm()
args := event.GetArgs()
if len(args) > 0 {
path = args[0]
}
path := resolveExecPath(event.GetExePath(), event.GetComm(), args)

// Use SHA256 hash of the exec to identify it uniquely
execIdentifier := utils.CalculateSHA256FileExecHash(path, args)
Expand Down
57 changes: 57 additions & 0 deletions pkg/containerprofilemanager/v1/event_reporting_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package containerprofilemanager

import "testing"

func TestResolveExecPath(t *testing.T) {
tests := []struct {
name string
exepath string
comm string
args []string
want string
}{
{
name: "exepath present (canonical exec)",
exepath: "/usr/sbin/unix_chkpwd",
comm: "unix_chkpwd",
args: []string{"/usr/sbin/unix_chkpwd", "root"},
want: "/usr/sbin/unix_chkpwd",
},
{
name: "fexecve / execveat AT_EMPTY_PATH — pathname empty, argv[0] non-empty",
exepath: "",
comm: "unix_chkpwd",
args: []string{"unix_chkpwd", "root"},
want: "unix_chkpwd",
},
{
name: "fexecve with empty argv[0] (older PAM convention)",
exepath: "",
comm: "unix_chkpwd",
args: []string{"", "root"},
want: "unix_chkpwd",
},
{
name: "no exepath, no args — fall back to comm",
exepath: "",
comm: "some_proc",
args: nil,
want: "some_proc",
},
{
name: "exepath wins even when argv[0] disagrees (argv[0] spoofing)",
exepath: "/usr/bin/curl",
comm: "curl",
args: []string{"sshd", "-i"},
want: "/usr/bin/curl",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := resolveExecPath(tt.exepath, tt.comm, tt.args)
if got != tt.want {
t.Errorf("resolveExecPath(%q, %q, %v) = %q, want %q", tt.exepath, tt.comm, tt.args, got, tt.want)
}
})
}
}
Loading