From 4c70e9e684712f1ad212fc11abc16f51e9318d01 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Mon, 7 Oct 2024 18:26:31 +0300 Subject: [PATCH] Remove dependency on sys_enter Instead, use generic syscall kprobes that are attached dynamically according to syscall filter. --- pkg/ebpf/c/maps.h | 8 ----- pkg/ebpf/c/tracee.bpf.c | 57 ++++++++++++---------------------- pkg/ebpf/event_filters.go | 64 +++++++++++++++++++-------------------- pkg/ebpf/tracee.go | 24 +++++++++++---- pkg/events/core.go | 10 +----- 5 files changed, 70 insertions(+), 93 deletions(-) diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h index f07483915f4a..17f36dae37bd 100644 --- a/pkg/ebpf/c/maps.h +++ b/pkg/ebpf/c/maps.h @@ -265,14 +265,6 @@ struct sys_exit_init_tail { typedef struct sys_exit_init_tail sys_exit_init_tail_t; -// store program for performing syscall checking logic -struct check_syscall_source_tail { - __uint(type, BPF_MAP_TYPE_PROG_ARRAY); - __uint(max_entries, MAX_EVENT_ID); - __type(key, u32); - __type(value, u32); -} check_syscall_source_tail SEC(".maps"); - // store syscalls with abnormal source per VMA per process struct { __uint(type, BPF_MAP_TYPE_LRU_HASH); diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 68d2b04c0d4f..85d8ab49d02e 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -57,10 +57,6 @@ int tracepoint__raw_syscalls__sys_enter(struct bpf_raw_tracepoint_args *ctx) id = *id_64; } - // Call syscall checker if registered for this syscall. - // If so, it will make sure the following tail is called. - bpf_tail_call(ctx, &check_syscall_source_tail, id); - bpf_tail_call(ctx, &sys_enter_init_tail, id); return 0; } @@ -5209,50 +5205,41 @@ statfunc enum vma_type get_vma_type(struct vm_area_struct *vma) return VMA_OTHER; } -SEC("raw_tracepoint/check_syscall_source") -int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) +SEC("kprobe/check_syscall_source") +int BPF_KPROBE(check_syscall_source) { - // Get syscall ID. - // NOTE: this must happen first before any logic that may fail, - // because we must know the syscall ID for the tail call we preceded. - struct task_struct *task = (struct task_struct *) bpf_get_current_task(); - u32 id = ctx->args[1]; - if (is_compat(task)) { - // Translate 32bit syscalls to 64bit syscalls - u32 *id_64 = bpf_map_lookup_elem(&sys_32_to_64_map, &id); - if (id_64 == 0) - return 0; - id = *id_64; - } - program_data_t p = {}; if (!init_program_data(&p, ctx, CHECK_SYSCALL_SOURCE)) - goto out; + return 0; if (!evaluate_scope_filters(&p)) - goto out; + return 0; // Get instruction pointer - struct pt_regs *regs = (struct pt_regs *) ctx->args[0]; -#if defined(bpf_target_x86) - u64 ip = BPF_CORE_READ(regs, ip); -#elif defined(bpf_target_arm64) - u64 ip = BPF_CORE_READ(regs, pc); -#endif + struct pt_regs *regs = ctx; + if (get_kconfig(ARCH_HAS_SYSCALL_WRAPPER)) + regs = (struct pt_regs *) PT_REGS_PARM1(ctx); + u64 ip = PT_REGS_IP_CORE(regs); // Find VMA which contains the instruction pointer + struct task_struct *task = (struct task_struct *) bpf_get_current_task(); + if (unlikely(task == NULL)) + return 0; struct vm_area_struct *vma = find_vma(task, ip); - if (vma == NULL) - goto out; + if (unlikely(vma == NULL)) + return 0; // Get VMA type and make sure it's abnormal (stack/heap/anonymous VMA) enum vma_type vma_type = get_vma_type(vma); if (vma_type == VMA_OTHER) - goto out; + return 0; + + // Get syscall ID + u32 syscall = get_syscall_id_from_regs(regs); // Build a key that identifies the combination of syscall, // source VMA and process so we don't submit it multiple times - syscall_source_key_t key = {.syscall = id, + syscall_source_key_t key = {.syscall = syscall, .tgid = get_task_ns_tgid(task), .tgid_start_time = get_task_start_time(get_leader_task(task)), .vma_addr = get_vma_start(vma)}; @@ -5261,13 +5248,13 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) // Try updating the map with the requirement that this key does not exist yet if ((int) bpf_map_update_elem(&syscall_source_map, &key, &val, BPF_NOEXIST) == -17 /* EEXIST */) // This key already exists, no need to submit the same syscall-vma-process combination again - goto out; + return 0; bool is_stack = vma_type == VMA_STACK; bool is_heap = vma_type == VMA_HEAP; bool is_anon = vma_type == VMA_ANON; - save_to_submit_buf(&p.event->args_buf, &id, sizeof(id), 0); + save_to_submit_buf(&p.event->args_buf, &syscall, sizeof(syscall), 0); save_to_submit_buf(&p.event->args_buf, &ip, sizeof(ip), 1); save_to_submit_buf(&p.event->args_buf, &is_stack, sizeof(is_stack), 2); save_to_submit_buf(&p.event->args_buf, &is_heap, sizeof(is_heap), 3); @@ -5275,10 +5262,6 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) events_perf_submit(&p, 0); -out: - // Call sys_enter_init_tail which we preceded - bpf_tail_call(ctx, &sys_enter_init_tail, id); - return 0; } diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go index 38b174824514..a6ba4d7c9b85 100644 --- a/pkg/ebpf/event_filters.go +++ b/pkg/ebpf/event_filters.go @@ -1,26 +1,26 @@ package ebpf import ( + "fmt" "maps" "strconv" - "unsafe" - bpf "github.com/aquasecurity/libbpfgo" - - "github.com/aquasecurity/tracee/pkg/errfmt" + "github.com/aquasecurity/tracee/pkg/ebpf/probes" "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/pkg/filters" "github.com/aquasecurity/tracee/pkg/logger" ) -type eventFilterHandler func(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error +type eventFilterHandler func(t *Tracee, eventFilters map[string]filters.Filter[*filters.StringFilter]) error var eventFilterHandlers = map[events.ID]eventFilterHandler{ - events.CheckSyscallSource: populateMapsCheckSyscallSource, + events.CheckSyscallSource: attachCheckSyscallSourceProbes, } -// populateEventFilterMaps populates maps with data from special event filters -func (t *Tracee) populateEventFilterMaps() error { +// handleEventFilters performs eBPF related actions according to special event filters. +// For example, an event can use one of its filters to populate eBPF maps, or perhaps +// attach eBPF programs according to the filters. +func (t *Tracee) handleEventFilters() error { // Iterate through registerd event filter handlers for eventID, handler := range eventFilterHandlers { // Make sure this event is selected @@ -43,7 +43,7 @@ func (t *Tracee) populateEventFilterMaps() error { } // Call handler - err := handler(eventFilters, t.bpfModule) + err := handler(t, eventFilters) if err != nil { logger.Errorw("Failed to handle event filters", "event", events.Core.GetDefinitionByID(eventID).GetName(), "error", err) err = t.eventsDependencies.RemoveEvent(eventID) @@ -55,38 +55,36 @@ func (t *Tracee) populateEventFilterMaps() error { return nil } -func populateMapsCheckSyscallSource(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error { +func attachCheckSyscallSourceProbes(t *Tracee, eventFilters map[string]filters.Filter[*filters.StringFilter]) error { // Get syscalls to trace syscallsFilter, ok := eventFilters["syscall"].(*filters.StringFilter) if !ok { return nil } - syscalls := syscallsFilter.Equal() - - // Get map and program for check_syscall_source tailcall - checkSyscallSourceTail, err := bpfModule.GetMap("check_syscall_source_tail") - if err != nil { - return errfmt.Errorf("could not get BPF map \"check_syscall_source_tail\": %v", err) - } - checkSyscallSourceProg, err := bpfModule.GetProgram("check_syscall_source") - if err != nil { - return errfmt.Errorf("could not get BPF program \"check_syscall_source\": %v", err) - } - checkSyscallSourceProgFD := checkSyscallSourceProg.FileDescriptor() - if checkSyscallSourceProgFD < 0 { - return errfmt.Errorf("could not get BPF program FD for \"check_syscall_source\": %v", err) - } - - // Add each syscall to the tail call map - for _, syscall := range syscalls { - syscallID, err := strconv.Atoi(syscall) + syscalls := make([]string, 0) + for _, entry := range syscallsFilter.Equal() { + syscallID, err := strconv.Atoi(entry) if err != nil { - return errfmt.WrapError(err) + return err } + if !events.Core.IsDefined(events.ID(syscallID)) { + return fmt.Errorf("syscall id %d is not defined", syscallID) + } + syscalls = append(syscalls, events.Core.GetDefinitionByID(events.ID(syscallID)).GetName()) + } - err = checkSyscallSourceTail.Update(unsafe.Pointer(&syscallID), unsafe.Pointer(&checkSyscallSourceProgFD)) - if err != nil { - return errfmt.WrapError(err) + // Create probe group + probeMap := make(map[probes.Handle]probes.Probe) + for i, syscall := range syscalls { + probeMap[probes.Handle(i)] = probes.NewTraceProbe(probes.SyscallEnter, syscall, "check_syscall_source") + } + t.checkSyscallSourceProbes = probes.NewProbeGroup(t.bpfModule, probeMap) + + // Attach probes + for i, syscall := range syscalls { + if err := t.checkSyscallSourceProbes.Attach(probes.Handle(i), t.kernelSymbols); err != nil { + // Report attachment errors but don't fail, because it may be a syscall that doesn't exist on this system + logger.Warnw("Failed to attach check_syscall_source kprobe", "syscall", syscall, "error", err) } } diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index 87004b42a581..8c7e54cd0d68 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -125,6 +125,8 @@ type Tracee struct { // This does not mean they are required for tracee to function. // TODO: remove this in favor of dependency manager nodes requiredKsyms []string + // Probes created for check_syscall_source event + checkSyscallSourceProbes *probes.ProbeGroup } func (t *Tracee) Stats() *metrics.Stats { @@ -518,6 +520,16 @@ func (t *Tracee) Init(ctx gocontext.Context) error { }, } + // Perform extra initializtion steps required by specific events according to their arguments + err = capabilities.GetInstance().EBPF( + func() error { + return t.handleEventFilters() + }, + ) + if err != nil { + return errfmt.WrapError(err) + } + return nil } @@ -1107,12 +1119,6 @@ func (t *Tracee) populateBPFMaps() error { } } - // Populate maps according to BPF-level event argument filters - err = t.populateEventFilterMaps() - if err != nil { - return errfmt.WrapError(err) - } - return nil } @@ -1492,6 +1498,12 @@ func (t *Tracee) Close() { logger.Errorw("failed to detach probes when closing tracee", "err", err) } } + if t.checkSyscallSourceProbes != nil { + err := t.checkSyscallSourceProbes.DetachAll() + if err != nil { + logger.Errorw("failed to detach check_syscall_source probes when closing tracee", "err", err) + } + } if t.bpfModule != nil { t.bpfModule.Close() } diff --git a/pkg/events/core.go b/pkg/events/core.go index 3a9bb302e2cb..346dfd8a624a 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -13065,15 +13065,7 @@ var CoreEvents = map[ID]Definition{ id: CheckSyscallSource, id32Bit: Sys32Undefined, name: "check_syscall_source", - dependencies: Dependencies{ - probes: []Probe{ - {handle: probes.SyscallEnter__Internal, required: true}, - }, - tailCalls: []TailCall{ - {"check_syscall_source_tail", "check_syscall_source", []uint32{ /* Map will be populated at runtime according to event filter */ }}, - }, - }, - sets: []string{}, + sets: []string{}, params: []trace.ArgMeta{ {Type: "int", Name: "syscall"}, {Type: "void*", Name: "ip"},