Skip to content

Commit

Permalink
feat(ebpf): make process_execute_failed not rely on sys_enter/exit
Browse files Browse the repository at this point in the history
  • Loading branch information
OriGlassman authored and randomname21 committed Aug 7, 2024
1 parent 0a32ea2 commit 2c74ffa
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 174 deletions.
2 changes: 1 addition & 1 deletion api/v1beta1/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ enum EventId {
module_load = 1082;
module_free = 1083;
execute_finished = 1084;
security_bprm_creds_for_exec = 1085;
process_execute_failed_internal = 1085;

// Events originated from user-space
net_packet_ipv4 = 2000;
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/events/builtin/extra/process_execute_failed.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Relevant from kernel version 5.8 onwards, matching the `security_bprm_creds_for_
## Example Use Case

```console
./tracee -e process_execution_failed
./tracee -e process_execute_failed
```

## Issues
Expand Down
80 changes: 42 additions & 38 deletions pkg/ebpf/c/tracee.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -4950,7 +4950,8 @@ statfunc int submit_process_execute_failed(struct pt_regs *ctx, program_data_t *
return -1;
}

statfunc int execute_failed_tail1(struct pt_regs *ctx, u32 tail_call_id)
SEC("kprobe/execute_failed_tail1")
int execute_failed_tail1(struct pt_regs *ctx)
{
program_data_t p = {};
if (!init_tailcall_program_data(&p, ctx))
Expand All @@ -4968,75 +4969,50 @@ statfunc int execute_failed_tail1(struct pt_regs *ctx, u32 tail_call_id)
int kernel_invoked = (get_task_parent_flags(task) & PF_KTHREAD) ? 1 : 0;
save_to_submit_buf(&p.event->args_buf, &kernel_invoked, sizeof(int), 9);

bpf_tail_call(ctx, &prog_array, tail_call_id);
bpf_tail_call(ctx, &prog_array, TAIL_PROCESS_EXECUTE_FAILED2);
return -1;
}

statfunc int execute_failed_tail2(struct pt_regs *ctx)
SEC("kprobe/execute_failed_tail2")
int execute_failed_tail2(struct pt_regs *ctx)
{
program_data_t p = {};
if (!init_tailcall_program_data(&p, ctx))
return -1;

syscall_data_t *sys = &p.task_info->syscall_data;
save_str_arr_to_buf(
&p.event->args_buf, (const char *const *) sys->args.args[1], 10); // userspace argv
struct pt_regs *task_regs = get_task_pt_regs((struct task_struct *) bpf_get_current_task());
u64 argv = PT_REGS_PARM2_CORE_SYSCALL(task_regs);
u64 envp = PT_REGS_PARM3_CORE_SYSCALL(task_regs);
save_str_arr_to_buf(&p.event->args_buf, (const char *const *) argv, 10); // userspace argv

if (p.config->options & OPT_EXEC_ENV) {
save_str_arr_to_buf(
&p.event->args_buf, (const char *const *) sys->args.args[2], 11); // userspace envp
save_str_arr_to_buf(&p.event->args_buf, (const char *const *) envp, 11); // userspace envp
}

int ret = PT_REGS_RC(ctx); // needs to be int
return events_perf_submit(&p, ret);
return events_perf_submit(&p, 0);
}

bool use_security_bprm_creds_for_exec = false;

SEC("kprobe/exec_binprm")
TRACE_ENT_FUNC(exec_binprm, EXEC_BINPRM);

SEC("kretprobe/exec_binprm")
int BPF_KPROBE(trace_ret_exec_binprm)
int BPF_KPROBE(trace_exec_binprm)
{
if (use_security_bprm_creds_for_exec) {
return 0;
}
args_t saved_args;
if (load_args(&saved_args, EXEC_BINPRM) != 0) {
// missed entry or not traced
return 0;
}
del_args(EXEC_BINPRM);

int ret_val = PT_REGS_RC(ctx);
if (ret_val == 0)
return 0; // not interested of successful execution - for that we have sched_process_exec

program_data_t p = {};
if (!init_program_data(&p, ctx, PROCESS_EXECUTION_FAILED))
if (!init_program_data(&p, ctx, PROCESS_EXECUTE_FAILED_INTERNAL))
return 0;
return submit_process_execute_failed(ctx, &p);
}

SEC("kretprobe/trace_execute_failed1")
int BPF_KPROBE(trace_execute_failed1)
{
return execute_failed_tail1(ctx, TAIL_PROCESS_EXECUTE_FAILED2);
}

SEC("kretprobe/trace_execute_failed2")
int BPF_KPROBE(trace_execute_failed2)
{
return execute_failed_tail2(ctx);
}

SEC("kprobe/security_bprm_creds_for_exec")
int BPF_KPROBE(trace_security_bprm_creds_for_exec)
{
use_security_bprm_creds_for_exec = true;
program_data_t p = {};
if (!init_program_data(&p, ctx, SECURITY_BPRM_CREDS_FOR_EXEC))
if (!init_program_data(&p, ctx, PROCESS_EXECUTE_FAILED_INTERNAL))
return 0;
return submit_process_execute_failed(ctx, &p);
}
Expand All @@ -5051,6 +5027,34 @@ int BPF_KPROBE(trace_execute_finished)
if (!evaluate_scope_filters(&p))
return 0;

// We can enrich the event with user provided arguments. If we have kernelspace arguments,
// the userspace arguments will be discarded.
struct pt_regs *task_regs = get_task_pt_regs((struct task_struct *) bpf_get_current_task());
u64 argv, envp;
void *path;

if (p.event->context.syscall == SYSCALL_EXECVEAT) { // TODO: what about compat?
int dirfd = PT_REGS_PARM1_CORE_SYSCALL(task_regs);
path = (void *) PT_REGS_PARM2_CORE_SYSCALL(task_regs);
argv = PT_REGS_PARM3_CORE_SYSCALL(task_regs);
envp = PT_REGS_PARM4_CORE_SYSCALL(task_regs);
int flags = PT_REGS_PARM5_CORE_SYSCALL(task_regs);

// send args unique to execevat
save_to_submit_buf(&p.event->args_buf, &dirfd, sizeof(int), 0);
save_to_submit_buf(&p.event->args_buf, &flags, sizeof(int), 4);
} else {
path = (void *) PT_REGS_PARM1_CORE_SYSCALL(task_regs);
argv = PT_REGS_PARM2_CORE_SYSCALL(task_regs);
envp = PT_REGS_PARM3_CORE_SYSCALL(task_regs);
}

save_str_to_buf(&p.event->args_buf, path, 1);
save_str_arr_to_buf(&p.event->args_buf, (const char *const *) argv, 2);
if (p.config->options & OPT_EXEC_ENV) {
save_str_arr_to_buf(&p.event->args_buf, (const char *const *) envp, 3);
}

long exec_ret = PT_REGS_RC(ctx);
return events_perf_submit(&p, exec_ret);
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/ebpf/c/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ enum event_id_e
FILE_MODIFICATION,
INOTIFY_WATCH,
SECURITY_BPF_PROG,
PROCESS_EXECUTION_FAILED,
PROCESS_EXECUTE_FAILED,
SECURITY_PATH_NOTIFY,
SET_FS_PWD,
HIDDEN_KERNEL_MODULE_SEEKER,
MODULE_LOAD,
MODULE_FREE,
EXECUTE_FINISHED,
SECURITY_BPRM_CREDS_FOR_EXEC,
PROCESS_EXECUTE_FAILED_INTERNAL,
SECURITY_TASK_SETRLIMIT,
SECURITY_SETTIME64,
MAX_EVENT_ID,
Expand Down
1 change: 0 additions & 1 deletion pkg/ebpf/probes/probe_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool) (*ProbeGroup, err
InotifyFindInodeRet: NewTraceProbe(KretProbe, "inotify_find_inode", "trace_ret_inotify_find_inode"),
BpfCheck: NewTraceProbe(KProbe, "bpf_check", "trace_bpf_check"),
ExecBinprm: NewTraceProbe(KProbe, "exec_binprm", "trace_exec_binprm"),
ExecBinprmRet: NewTraceProbe(KretProbe, "exec_binprm", "trace_ret_exec_binprm"),
SecurityPathNotify: NewTraceProbe(KProbe, "security_path_notify", "trace_security_path_notify"),
SecurityBprmCredsForExec: NewTraceProbe(KProbe, "security_bprm_creds_for_exec", "trace_security_bprm_creds_for_exec"),
SetFsPwd: NewTraceProbe(KProbe, "set_fs_pwd", "trace_set_fs_pwd"),
Expand Down
1 change: 0 additions & 1 deletion pkg/ebpf/probes/probes.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ const (
InotifyFindInodeRet
BpfCheck
ExecBinprm
ExecBinprmRet
SecurityPathNotify
SecurityBprmCredsForExec
SetFsPwd
Expand Down
2 changes: 1 addition & 1 deletion pkg/ebpf/tracee.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ func (t *Tracee) initDerivationTable() error {
DeriveFunction: executeFailedGen.ProcessExecuteFailed(),
},
},
events.SecurityBprmCredsForExec: {
events.ProcessExecuteFailedInternal: {
events.ProcessExecuteFailed: {
Enabled: shouldSubmit(events.ProcessExecuteFailed),
DeriveFunction: executeFailedGen.ProcessExecuteFailed(),
Expand Down
44 changes: 22 additions & 22 deletions pkg/events/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const (
ModuleLoad
ModuleFree
ExecuteFinished
SecurityBprmCredsForExec
ProcessExecuteFailedInternal
SecurityTaskSetrlimit
SecuritySettime64
MaxCommonID
Expand Down Expand Up @@ -12943,25 +12943,34 @@ var CoreEvents = map[ID]Definition{
{handle: probes.ExecuteAtFinishedCompatARM, required: false},
},
},
params: []trace.ArgMeta{
{Type: "int", Name: "dirfd"},
{Type: "const char*", Name: "pathname"},
{Type: "const char*const*", Name: "argv"},
{Type: "const char*const*", Name: "envp"},
{Type: "int", Name: "flags"},
},
},
SecurityBprmCredsForExec: {
id: SecurityBprmCredsForExec,
ProcessExecuteFailedInternal: {
id: ProcessExecuteFailedInternal,
id32Bit: Sys32Undefined,
name: "security_bprm_creds_for_exec",
name: "process_execute_failed_internal",
version: NewVersion(1, 0, 0),
sets: []string{"proc"},
internal: true,
dependencies: Dependencies{
ids: []ID{ExecuteFinished},
probes: []Probe{
{handle: probes.ExecBinprm, required: false},
{handle: probes.SecurityBprmCredsForExec, required: false}, // TODO: Change to required once fallbacks are supported
},
tailCalls: []TailCall{
{"prog_array", "trace_execute_failed1", []uint32{TailProcessExecuteFailed1}},
{"prog_array", "trace_execute_failed2", []uint32{TailProcessExecuteFailed2}},
{"prog_array", "execute_failed_tail1", []uint32{TailProcessExecuteFailed1}},
{"prog_array", "execute_failed_tail2", []uint32{TailProcessExecuteFailed2}},
},
},
params: []trace.ArgMeta{
{Type: "const char*", Name: "path"},
{Type: "const char*", Name: "pathname"},
{Type: "const char*", Name: "binary.path"},
{Type: "dev_t", Name: "binary.device_id"},
{Type: "unsigned long", Name: "binary.inode_number"},
Expand All @@ -12971,8 +12980,8 @@ var CoreEvents = map[ID]Definition{
{Type: "umode_t", Name: "stdin_type"},
{Type: "char*", Name: "stdin_path"},
{Type: "int", Name: "kernel_invoked"},
{Type: "const char*const*", Name: "binary.arguments"},
{Type: "const char*const*", Name: "environment"},
{Type: "const char*const*", Name: "argv"},
{Type: "const char*const*", Name: "envp"},
},
},
ProcessExecuteFailed: {
Expand All @@ -12982,19 +12991,10 @@ var CoreEvents = map[ID]Definition{
version: NewVersion(1, 0, 0),
sets: []string{"proc"},
dependencies: Dependencies{
ids: []ID{ExecuteFinished, SecurityBprmCredsForExec}, // For kernel version >= 5.8
probes: []Probe{
{handle: probes.ExecBinprm, required: false},
{handle: probes.ExecBinprmRet, required: false},
{handle: probes.SyscallEnter__Internal, required: true},
},
tailCalls: []TailCall{
{"prog_array", "trace_execute_failed1", []uint32{TailProcessExecuteFailed1}},
{"prog_array", "trace_execute_failed2", []uint32{TailProcessExecuteFailed2}},
},
ids: []ID{ProcessExecuteFailedInternal},
},
params: []trace.ArgMeta{
{Type: "const char*", Name: "path"},
{Type: "const char*", Name: "pathname"},
{Type: "const char*", Name: "binary.path"},
{Type: "dev_t", Name: "binary.device_id"},
{Type: "unsigned long", Name: "binary.inode_number"},
Expand All @@ -13004,8 +13004,8 @@ var CoreEvents = map[ID]Definition{
{Type: "umode_t", Name: "stdin_type"},
{Type: "char*", Name: "stdin_path"},
{Type: "int", Name: "kernel_invoked"},
{Type: "const char*const*", Name: "binary.arguments"},
{Type: "const char*const*", Name: "environment"},
{Type: "const char*const*", Name: "argv"},
{Type: "const char*const*", Name: "envp"},
},
},
FtraceHook: {
Expand Down
35 changes: 14 additions & 21 deletions pkg/events/derive/process_execute_failed.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package derive

import (
"fmt"

lru "github.com/hashicorp/golang-lru/v2"

"github.com/aquasecurity/tracee/pkg/events"
Expand Down Expand Up @@ -64,7 +63,7 @@ func (gen *ExecFailedGenerator) deriveEvent(event *trace.Event) (
*trace.Event, error,
) {
switch events.ID(event.EventID) {
case events.SecurityBprmCredsForExec:
case events.ProcessExecuteFailedInternal:
return gen.handleExecBaseEvent(event)
case events.ExecuteFinished:
return gen.handleExecFinished(event)
Expand All @@ -73,40 +72,34 @@ func (gen *ExecFailedGenerator) deriveEvent(event *trace.Event) (
}
}

// handleExecFinished will derive the event if all the event parts were received.
// Else it will cache the finished exec info for future use.
// handleExecFinished will add info on top of base event unless events came out of order. Sends an event in any case.
// Should be simplified once events reach from kernel-space to user-space are ordered!
func (gen *ExecFailedGenerator) handleExecFinished(event *trace.Event) (*trace.Event, error) {
defer gen.execEndInfo.Remove(event.HostProcessID)
execInfo := execEndInfo{
returnCode: event.ReturnValue,
timestamp: event.Timestamp,
}
if !isFailedExec(execInfo.returnCode) {

return nil, nil
}

e := event
securityExecEvent, ok := gen.baseEvents.Get(event.HostProcessID)
if ok {
gen.execEndInfo.Remove(event.HostProcessID)
if !isFailedExec(execInfo.returnCode) {
return nil, nil
}
return gen.generateEvent(securityExecEvent, execInfo)
e = securityExecEvent // There is a base event to use, use it!
}
gen.execEndInfo.Add(event.HostProcessID, execInfo)
return nil, nil
return gen.generateEvent(e, execInfo)
}

// handleExecBaseEvent will derive the event if the event parts were received, else will cache
// the base event for future use
func (gen *ExecFailedGenerator) handleExecBaseEvent(event *trace.Event) (*trace.Event, error) {
execInfo, ok := gen.execEndInfo.Get(event.HostProcessID)
// We don't have the execution end info - cache current event and wait for it to be received
// This is the expected flow, as the execution finished event come chronology after
if !ok {
gen.baseEvents.Add(event.HostProcessID, event)
return nil, nil
}
gen.execEndInfo.Remove(event.HostProcessID)
if !isFailedExec(execInfo.returnCode) {
return nil, nil
}
return gen.generateEvent(event, execInfo)
gen.baseEvents.Add(event.HostProcessID, event)
return nil, nil
}

// generateEvent create the ProcessExecuteFailed event from its parts
Expand Down
Loading

0 comments on commit 2c74ffa

Please sign in to comment.