Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(events): fix process_execute_failed missing symbol for new kernels #3983

Merged
merged 2 commits into from Apr 29, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pr.yaml
Expand Up @@ -61,6 +61,7 @@ env:
DNS
HTTP
INSTTESTS: >
PROCESS_EXECUTE_FAILED
VFS_WRITE
FILE_MODIFICATION
HOOKED_SYSCALL
Expand Down
24 changes: 21 additions & 3 deletions docs/docs/events/builtin/extra/process_execute_failed.md
Expand Up @@ -28,13 +28,30 @@ while providing as much as possible the arguments as used by the kernel.
#### Type
kprobe
#### Purpose
Fetch the arguments of exec_binprm
To retrieve the arguments of exec_binprm.
Used for kernels older than 5.8.

### exec_binprm
#### Type
kretprobe
#### Purpose
Fetch the return value of exec_binprm
To retrieve the return value of exec_binprm and generate the event.
Used for kernels older than 5.8.

### security_bprm_creds_for_exec
#### Type
kprobe
#### Purpose
To retrieve the arguments for the event.
Relevant from kernel version 5.8 onwards, as the function was added in that kernel.

### sys_enter
#### Type
tracepoint
#### Purpose
To obtain the return code of the execution, determining whether to generate the event.
For a failed execution, an event will be generated using the information from the `security_bprm_creds_for_exec` hook.
Relevant from kernel version 5.8 onwards, matching the `security_bprm_creds_for_exec` hook.

## Example Use Case

Expand All @@ -43,7 +60,8 @@ Fetch the return value of exec_binprm
```

## Issues
Currently, only covers failed executions that are happening within exec_binprm. Other failures may occur at an earlier stage.
The `exec_binprm` symbol is not available in some systems, potentially resulting in the failure to load the event in kernels older than 5.8.
For kernels older than 5.8, the event only encompasses failed executions occurring within `exec_binprm`. Other failures may occur at an earlier stage. Newer versions do not account for failures before `security_bprm_creds_for_exec`, which precedes `exec_binprm`.

## Related Events
execve,execveat,bprm_check,sched_process_exec
2 changes: 2 additions & 0 deletions pkg/ebpf/c/maps.h
Expand Up @@ -23,6 +23,8 @@ enum tail_call_id_e
TAIL_HIDDEN_KERNEL_MODULE_KSET,
TAIL_HIDDEN_KERNEL_MODULE_MOD_TREE,
TAIL_HIDDEN_KERNEL_MODULE_NEW_MOD_ONLY,
TAIL_SECURITY_BPRM_CREDS_FOR_EXEC1,
TAIL_SECURITY_BPRM_CREDS_FOR_EXEC2,
MAX_TAIL_CALL
};

Expand Down
126 changes: 90 additions & 36 deletions pkg/ebpf/c/tracee.bpf.c
Expand Up @@ -4890,64 +4890,44 @@ int BPF_KPROBE(trace_ret_inotify_find_inode)
return events_perf_submit(&p, 0);
}

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

SEC("kretprobe/exec_binprm")
int BPF_KPROBE(trace_ret_exec_binprm)
statfunc int execute_failed_tail0(struct pt_regs *ctx, program_data_t *p, u32 tail_call_id)
{
args_t saved_args;
if (load_args(&saved_args, EXEC_BINPRM) != 0) {
// missed entry or not traced
if (!evaluate_scope_filters(p))
return 0;
}
del_args(EXEC_BINPRM);

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

if (!evaluate_scope_filters(&p))
return 0;

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

struct linux_binprm *bprm = (struct linux_binprm *) saved_args.args[0];
struct linux_binprm *bprm = (struct linux_binprm *) PT_REGS_PARM1(ctx);
if (bprm == NULL) {
return -1;
}

struct file *file = get_file_ptr_from_bprm(bprm);

const char *path = get_binprm_filename(bprm);
save_str_to_buf(&p.event->args_buf, (void *) path, 0);
save_str_to_buf(&p->event->args_buf, (void *) path, 0);

void *binary_path = get_path_str(__builtin_preserve_access_index(&file->f_path));
save_str_to_buf(&p.event->args_buf, binary_path, 1);
save_str_to_buf(&p->event->args_buf, binary_path, 1);

dev_t binary_device_id = get_dev_from_file(file);
save_to_submit_buf(&p.event->args_buf, &binary_device_id, sizeof(dev_t), 2);
save_to_submit_buf(&p->event->args_buf, &binary_device_id, sizeof(dev_t), 2);

unsigned long binary_inode_number = get_inode_nr_from_file(file);
save_to_submit_buf(&p.event->args_buf, &binary_inode_number, sizeof(unsigned long), 3);
save_to_submit_buf(&p->event->args_buf, &binary_inode_number, sizeof(unsigned long), 3);

u64 binary_ctime = get_ctime_nanosec_from_file(file);
save_to_submit_buf(&p.event->args_buf, &binary_ctime, sizeof(u64), 4);
save_to_submit_buf(&p->event->args_buf, &binary_ctime, sizeof(u64), 4);

umode_t binary_inode_mode = get_inode_mode_from_file(file);
save_to_submit_buf(&p.event->args_buf, &binary_inode_mode, sizeof(umode_t), 5);
save_to_submit_buf(&p->event->args_buf, &binary_inode_mode, sizeof(umode_t), 5);

const char *interpreter_path = get_binprm_interp(bprm);
save_str_to_buf(&p.event->args_buf, (void *) interpreter_path, 6);
save_str_to_buf(&p->event->args_buf, (void *) interpreter_path, 6);

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

SEC("kretprobe/trace_ret_exec_binprm1")
int BPF_KPROBE(trace_ret_exec_binprm1)
statfunc int execute_failed_tail1(struct pt_regs *ctx, u32 tail_call_id)
{
program_data_t p = {};
if (!init_tailcall_program_data(&p, ctx))
Expand All @@ -4965,12 +4945,11 @@ int BPF_KPROBE(trace_ret_exec_binprm1)
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_EXEC_BINPRM2);
bpf_tail_call(ctx, &prog_array, tail_call_id);
return -1;
}

SEC("kretprobe/trace_ret_exec_binprm2")
int BPF_KPROBE(trace_ret_exec_binprm2)
statfunc int execute_failed_tail2(struct pt_regs *ctx)
{
program_data_t p = {};
if (!init_tailcall_program_data(&p, ctx))
Expand All @@ -4986,10 +4965,85 @@ int BPF_KPROBE(trace_ret_exec_binprm2)
}

int ret = PT_REGS_RC(ctx); // needs to be int

return events_perf_submit(&p, ret);
}

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)
{
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))
return 0;
return execute_failed_tail0(ctx, &p, TAIL_EXEC_BINPRM1);
}

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

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))
return 0;
return execute_failed_tail0(ctx, &p, TAIL_SECURITY_BPRM_CREDS_FOR_EXEC1);
}

SEC("kretprobe/trace_security_bprm_creds_for_exec1")
int BPF_KPROBE(trace_security_bprm_creds_for_exec1)
{
return execute_failed_tail1(ctx, TAIL_SECURITY_BPRM_CREDS_FOR_EXEC2);
}

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

SEC("tracepoint/execute_finished")
int execute_finished(struct sys_exit_tracepoint_args *args)
{
program_data_t p = {};
if (!init_program_data(&p, args, EXECUTE_FINISHED))
return -1;

if (!evaluate_scope_filters(&p))
return 0;

long exec_ret = args->ret;
return events_perf_submit(&p, exec_ret);
}

SEC("kprobe/security_path_notify")
int BPF_KPROBE(trace_security_path_notify)
{
Expand Down
8 changes: 8 additions & 0 deletions pkg/ebpf/c/types.h
Expand Up @@ -125,6 +125,8 @@ enum event_id_e
HIDDEN_KERNEL_MODULE_SEEKER,
MODULE_LOAD,
MODULE_FREE,
EXECUTE_FINISHED,
SECURITY_BPRM_CREDS_FOR_EXEC,
MAX_EVENT_ID,
NO_EVENT_SUBMIT,
};
Expand Down Expand Up @@ -546,4 +548,10 @@ enum file_modification_op
typedef __u64 stack_trace_t[MAX_STACK_DEPTH];
typedef u32 file_type_t;

struct sys_exit_tracepoint_args {
u64 __pad;
int __syscall_nr;
long ret;
};

#endif
3 changes: 3 additions & 0 deletions pkg/ebpf/probes/probe_group.go
Expand Up @@ -221,6 +221,7 @@ func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool, kSyms *helpers.Ke
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"),
TpProbeRegPrioMayExist: NewTraceProbe(KProbe, "tracepoint_probe_register_prio_may_exist", "trace_tracepoint_probe_register_prio_may_exist"),
ModuleLoad: NewTraceProbe(RawTracepoint, "module:module_load", "tracepoint__module__module_load"),
ModuleFree: NewTraceProbe(RawTracepoint, "module:module_free", "tracepoint__module__module_free"),
Expand All @@ -229,6 +230,8 @@ func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool, kSyms *helpers.Ke
SignalSchedProcessFork: NewTraceProbe(RawTracepoint, "sched:sched_process_fork", "sched_process_fork_signal"),
SignalSchedProcessExec: NewTraceProbe(RawTracepoint, "sched:sched_process_exec", "sched_process_exec_signal"),
SignalSchedProcessExit: NewTraceProbe(RawTracepoint, "sched:sched_process_exit", "sched_process_exit_signal"),
ExecuteFinished: NewTraceProbe(Tracepoint, "syscalls:sys_exit_execve", "execute_finished"),
ExecuteAtFinished: NewTraceProbe(Tracepoint, "syscalls:sys_exit_execveat", "execute_finished"),
}

if !netEnabled {
Expand Down
3 changes: 3 additions & 0 deletions pkg/ebpf/probes/probes.go
Expand Up @@ -128,6 +128,7 @@ const (
ExecBinprm
ExecBinprmRet
SecurityPathNotify
SecurityBprmCredsForExec
HiddenKernelModuleSeeker
TpProbeRegPrioMayExist
HiddenKernelModuleVerifier
Expand All @@ -138,4 +139,6 @@ const (
SignalSchedProcessFork
SignalSchedProcessExec
SignalSchedProcessExit
ExecuteFinished
ExecuteAtFinished
)
42 changes: 32 additions & 10 deletions pkg/ebpf/tracee.go
Expand Up @@ -634,6 +634,12 @@ func (t *Tracee) initDerivationTable() error {
}
symbolsCollisions := derive.SymbolsCollision(t.contSymbolsLoader, t.config.Policies)

executeFailedGen, err := derive.InitProcessExecuteFailedGenerator()
if err != nil {
logger.Errorw("failed to init derive function for ProcessExecuteFiled", "error", err)
return nil
}

t.eventDerivations = derive.Table{
events.CgroupMkdir: {
events.ContainerCreate: {
Expand Down Expand Up @@ -692,6 +698,18 @@ func (t *Tracee) initDerivationTable() error {
),
},
},
events.ExecuteFinished: {
events.ProcessExecuteFailed: {
Enabled: shouldSubmit(events.ProcessExecuteFailed),
DeriveFunction: executeFailedGen.ProcessExecuteFailed(),
},
},
events.SecurityBprmCredsForExec: {
events.ProcessExecuteFailed: {
Enabled: shouldSubmit(events.ProcessExecuteFailed),
DeriveFunction: executeFailedGen.ProcessExecuteFailed(),
},
},
//
// Network Packet Derivations
//
Expand Down Expand Up @@ -1474,7 +1492,7 @@ func (t *Tracee) getSelfLoadedPrograms(kprobesOnly bool) map[string]int {
// userland process itself, and not from the kernel. These events usually serve as informational
// events for the signatures engine/logic.
func (t *Tracee) invokeInitEvents(out chan *trace.Event) {
var emit uint64
var matchedPolicies uint64

setMatchedPolicies := func(event *trace.Event, matchedPolicies uint64, pols *policy.Policies) {
event.PoliciesVersion = pols.Version()
Expand All @@ -1483,35 +1501,39 @@ func (t *Tracee) invokeInitEvents(out chan *trace.Event) {
event.MatchedPolicies = pols.MatchedNames(matchedPolicies)
}

policiesMatch := func(state events.EventState) uint64 {
return state.Emit | state.Submit
}
AlonZivony marked this conversation as resolved.
Show resolved Hide resolved

// Initial namespace events

emit = t.eventsState[events.InitNamespaces].Emit
if emit > 0 {
matchedPolicies = policiesMatch(t.eventsState[events.InitNamespaces])
if matchedPolicies > 0 {
systemInfoEvent := events.InitNamespacesEvent()
setMatchedPolicies(&systemInfoEvent, emit, t.config.Policies)
setMatchedPolicies(&systemInfoEvent, matchedPolicies, t.config.Policies)
out <- &systemInfoEvent
_ = t.stats.EventCount.Increment()
}

// Initial existing containers events (1 event per container)

emit = t.eventsState[events.ExistingContainer].Emit
if emit > 0 {
matchedPolicies = policiesMatch(t.eventsState[events.ExistingContainer])
if matchedPolicies > 0 {
existingContainerEvents := events.ExistingContainersEvents(t.containers, t.config.NoContainersEnrich)
for i := range existingContainerEvents {
event := &(existingContainerEvents[i])
setMatchedPolicies(event, emit, t.config.Policies)
setMatchedPolicies(event, matchedPolicies, t.config.Policies)
out <- event
_ = t.stats.EventCount.Increment()
}
}

// Ftrace hook event

emit = t.eventsState[events.FtraceHook].Emit
if emit > 0 {
matchedPolicies = policiesMatch(t.eventsState[events.FtraceHook])
if matchedPolicies > 0 {
ftraceBaseEvent := events.GetFtraceBaseEvent()
setMatchedPolicies(ftraceBaseEvent, emit, t.config.Policies)
setMatchedPolicies(ftraceBaseEvent, matchedPolicies, t.config.Policies)
logger.Debugw("started ftraceHook goroutine")

// TODO: Ideally, this should be inside the goroutine and be computed before each run,
Expand Down