From daa1be7d622444c10ed086270938fe2f587d29b5 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Sun, 24 Mar 2024 11:26:15 +0200 Subject: [PATCH 01/17] Added check_syscall_source event This event checks, for preselected syscalls, the VMA from which the syscall was called. It determines if the VMA is the stack, the heap or an anonymous VMA (these are unusual for syscalls to be called from) and submits an event if so. --- pkg/ebpf/c/common/memory.h | 118 +++++++++++++++++++++++++++++++++++++ pkg/ebpf/c/maps.h | 8 +++ pkg/ebpf/c/tracee.bpf.c | 94 +++++++++++++++++++++++++++++ pkg/ebpf/c/types.h | 1 + pkg/ebpf/c/vmlinux.h | 32 +++++++--- pkg/events/core.go | 23 ++++++++ pkg/events/parse_args.go | 12 ++++ 7 files changed, 279 insertions(+), 9 deletions(-) diff --git a/pkg/ebpf/c/common/memory.h b/pkg/ebpf/c/common/memory.h index e91a2afa9287..51f2b9d51a9e 100644 --- a/pkg/ebpf/c/common/memory.h +++ b/pkg/ebpf/c/common/memory.h @@ -7,12 +7,17 @@ // PROTOTYPES +typedef long (*vma_callback_fn)(struct task_struct *task, struct vm_area_struct *vma, void *callback_ctx); + statfunc struct mm_struct *get_mm_from_task(struct task_struct *); statfunc unsigned long get_arg_start_from_mm(struct mm_struct *); statfunc unsigned long get_arg_end_from_mm(struct mm_struct *); statfunc unsigned long get_env_start_from_mm(struct mm_struct *); statfunc unsigned long get_env_end_from_mm(struct mm_struct *); statfunc unsigned long get_vma_flags(struct vm_area_struct *); +statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn, void *cb_ctx); +statfunc bool vma_is_stack(struct vm_area_struct *vma); +statfunc bool vma_is_heap(struct vm_area_struct *vma); // FUNCTIONS @@ -51,4 +56,117 @@ statfunc struct mount *real_mount(struct vfsmount *mnt) return container_of(mnt, struct mount, mnt); } +/** + * A busy process can have somewhere in the ballpark of 1000 VMAs. + * In an ideally balanced tree, this means that the max depth is ~10. + * A poorly balanced tree can have a leaf node that is up to twice as deep + * as another leaf node, which in the worst case scenario places its depth + * at 2*10 = 20. + * To be extra safe and accomodate for VMA counts higher than 1000, + * we define the max traversal depth as 25. + */ +#define MAX_VMA_RB_TREE_DEPTH 25 + +/** + * Given a task, find the first VMA which contains the given address, + * and call the specified callback function with the found VMA + * and the specified context. + * A callback function is required becuase this function potentially uses + * bpf_find_vma(), which requires a callback function. + * + * A generic callback function which receives a `struct vm_area_struct **` + * as its context and saves the found VMA to it is available in the main + * eBPF source file (tracee.bpf.c:find_vma_callback). + * + * See the check_syscall_source function for a usage example. + * + * DISCLAIMER: on systems with no MMU, multiple VMAs may contain the same address. + * Be aware that this function will call the callback only for the first VMA it finds. + */ +statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn, void *cb_ctx) +{ + /** + * From kernel version 6.1, the data structure with which VMAs + * are managed changed from an RB tree to a maple tree. + * In version 5.17 the "bpf_find_vma" helper was added. + * This means that if the helper does not exist, we can assume + * that the RB tree structure is used. + */ + + if (bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_find_vma)) { + bpf_find_vma(task, addr, cb_fn, cb_ctx, 0); + return; + } + + // bpf_find_vma doesn't exist, we can assume the VMAs are stored in an RB tree. + // This logic is based on the find_vma() function in mm/mmap.c + + struct vm_area_struct *vma = NULL; + struct rb_node *rb_node = BPF_CORE_READ(task, mm->mm_rb.rb_node); + +#pragma unroll + for (int i = 0; i < MAX_VMA_RB_TREE_DEPTH; i++) { + barrier(); // without this, the compiler refuses to unroll the loop + + if (rb_node == NULL) + break; + + struct vm_area_struct *tmp = container_of(rb_node, struct vm_area_struct, vm_rb); + unsigned long vm_start = BPF_CORE_READ(tmp, vm_start); + unsigned long vm_end = BPF_CORE_READ(tmp, vm_end); + + if (vm_end > addr) { + vma = tmp; + if (vm_start <= addr) + break; + rb_node = BPF_CORE_READ(rb_node, rb_left); + } + else + rb_node = BPF_CORE_READ(rb_node, rb_right); + } + + if (vma != NULL) + cb_fn(task, vma, cb_ctx); +} + +statfunc bool vma_is_stack(struct vm_area_struct *vma) +{ + struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); + if (vm_mm == NULL) + return false; + + u64 vm_start = BPF_CORE_READ(vma, vm_start); + u64 vm_end = BPF_CORE_READ(vma, vm_end); + u64 start_stack = BPF_CORE_READ(vm_mm, start_stack); + + // logic taken from include/linux/mm.h (vma_is_initial_stack) + if (vm_start <= start_stack && start_stack <= vm_end) + return true; + + return false; +} + +statfunc bool vma_is_heap(struct vm_area_struct *vma) +{ + struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); + if (vm_mm == NULL) + return false; + + u64 vm_start = BPF_CORE_READ(vma, vm_start); + u64 vm_end = BPF_CORE_READ(vma, vm_end); + u64 start_brk = BPF_CORE_READ(vm_mm, start_brk); + u64 brk = BPF_CORE_READ(vm_mm, brk); + + // logic taken from include/linux/mm.h (vma_is_initial_heap) + if (vm_start < brk && start_brk < vm_end) + return true; + + return false; +} + +statfunc bool vma_is_anon(struct vm_area_struct *vma) +{ + return BPF_CORE_READ(vma, vm_file) == NULL; +} + #endif diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h index 8ed74c1abaa7..36bba9d5a875 100644 --- a/pkg/ebpf/c/maps.h +++ b/pkg/ebpf/c/maps.h @@ -246,6 +246,14 @@ 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 stack traces #define MAX_STACK_ADDRESSES 1024 // max amount of diff stack trace addrs to buffer diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index fc9ba29cb9f0..d9fa411e782b 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -129,6 +129,9 @@ int sys_enter_init(struct bpf_raw_tracepoint_args *ctx) task_info->syscall_traced = true; } + // call syscall checker if relevant + bpf_tail_call(ctx, &check_syscall_source_tail, sys->id); + // if id is irrelevant continue to next tail call bpf_tail_call(ctx, &sys_enter_submit_tail, sys->id); @@ -5130,6 +5133,97 @@ int BPF_KPROBE(trace_security_task_setrlimit) return events_perf_submit(&p, 0); } +enum vma_type { + VMA_STACK, + VMA_HEAP, + VMA_ANON, + VMA_OTHER +}; + +statfunc enum vma_type get_vma_type(struct vm_area_struct *vma) +{ + if (vma_is_stack(vma)) + return VMA_STACK; + + if (vma_is_heap(vma)) + return VMA_HEAP; + + if (vma_is_anon(vma)) + return VMA_ANON; + + return VMA_OTHER; +} + +static long find_vma_callback(struct task_struct *task, struct vm_area_struct *vma, void *ctx) +{ + struct vm_area_struct **pvma = (struct vm_area_struct **)ctx; + *pvma = vma; + return 0; +} + +SEC("raw_tracepoint/check_syscall_source") +int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) +{ + program_data_t p = {}; + if (!init_program_data(&p, ctx)) + return 0; + + syscall_data_t *sys = &p.task_info->syscall_data; + + if (!should_trace(&p)) + goto out; + + if (!should_submit(CHECK_SYSCALL_SOURCE, p.event)) + goto out; + + struct task_struct *task = bpf_get_current_task_btf(); + if (task == NULL) + goto out; + + // TODO + // The only supported architectures (x86 and arm) always have ARCH_HAS_SYSCALL_WRAPPER. + // This shouldn't actually change anything for raw tracepoints - even without this config + // (a case that wasn't tested before) the registers should be accessible in the same way. + // This would mean that the argument extraction logic in sys_enter_init is wrong. + struct pt_regs *regs; + if (get_kconfig(ARCH_HAS_SYSCALL_WRAPPER)) + regs = (struct pt_regs *) ctx->args[0]; + else + // this is probably wrong + regs = (struct pt_regs *) ctx; + u64 ip = BPF_CORE_READ(regs, ip); + + struct vm_area_struct *vma = NULL; + find_vma(task, ip, find_vma_callback, &vma); + if (vma == NULL) + goto out; + + enum vma_type vma_type = get_vma_type(vma); + if (vma_type == VMA_OTHER) + goto out; + + p.event->context.ts = sys->ts; + + 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, &sys->id, sizeof(sys->id), 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); + save_to_submit_buf(&p.event->args_buf, &is_anon, sizeof(is_anon), 4); + + events_perf_submit(&p, CHECK_SYSCALL_SOURCE, 0); + +out: + // call remaining tails from sys_enter_init + bpf_tail_call(ctx, &sys_enter_submit_tail, sys->id); + bpf_tail_call(ctx, &sys_enter_tails, sys->id); + + return 0; +} + // clang-format off // Network Packets (works from ~5.2 and beyond) diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index 13c6c07ac4e1..a205f8d3736f 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -123,6 +123,7 @@ enum event_id_e PROCESS_EXECUTION_FAILED, SECURITY_PATH_NOTIFY, SET_FS_PWD, + CHECK_SYSCALL_SOURCE, HIDDEN_KERNEL_MODULE_SEEKER, MODULE_LOAD, MODULE_FREE, diff --git a/pkg/ebpf/c/vmlinux.h b/pkg/ebpf/c/vmlinux.h index b55c444db1cf..a273488003ce 100644 --- a/pkg/ebpf/c/vmlinux.h +++ b/pkg/ebpf/c/vmlinux.h @@ -284,7 +284,20 @@ struct signal_struct { atomic_t live; }; +struct rb_node { + struct rb_node *rb_right; + struct rb_node *rb_left; +} __attribute__((aligned(sizeof(long)))); + struct vm_area_struct { + union { + struct { + unsigned long vm_start; + unsigned long vm_end; + }; + }; + struct rb_node vm_rb; + struct mm_struct *vm_mm; long unsigned int vm_flags; struct file *vm_file; }; @@ -634,8 +647,17 @@ struct super_block { unsigned long s_magic; }; +struct rb_root { + struct rb_node *rb_node; +}; + struct mm_struct { struct { + struct rb_root mm_rb; + long unsigned int stack_vm; + long unsigned int start_brk; + long unsigned int brk; + long unsigned int start_stack; long unsigned int arg_start; long unsigned int arg_end; long unsigned int env_start; @@ -677,6 +699,7 @@ enum bpf_func_id BPF_FUNC_sk_storage_get = 107, BPF_FUNC_copy_from_user = 148, BPF_FUNC_for_each_map_elem = 164, + BPF_FUNC_find_vma = 180, }; #define MODULE_NAME_LEN (64 - sizeof(unsigned long)) @@ -717,19 +740,10 @@ struct module { struct module_memory mem[MOD_MEM_NUM_TYPES]; // kernel versions >= 6.4 }; -struct rb_node { - struct rb_node *rb_right; - struct rb_node *rb_left; -} __attribute__((aligned(sizeof(long)))); - struct latch_tree_node { struct rb_node node[2]; }; -struct rb_root { - struct rb_node *rb_node; -}; - typedef struct seqcount { unsigned sequence; } seqcount_t; diff --git a/pkg/events/core.go b/pkg/events/core.go index 1345e77539c4..22fe2ca328a1 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -105,6 +105,7 @@ const ( ProcessExecuteFailed SecurityPathNotify SetFsPwd + CheckSyscallSource HiddenKernelModuleSeeker ModuleLoad ModuleFree @@ -13072,6 +13073,28 @@ var CoreEvents = map[ID]Definition{ {Type: "u64", Name: "new_rlim_max"}, }, }, + CheckSyscallSource: { + id: CheckSyscallSource, + id32Bit: Sys32Undefined, + name: "check_syscall_source", + dependencies: Dependencies{ + probes: []Probe{ + {handle: probes.SyscallEnter__Internal, required: true}, + }, + tailCalls: []TailCall{ + {"sys_enter_init_tail", "sys_enter_init", []uint32{uint32(Execve)}}, + {"check_syscall_source_tail", "check_syscall_source", []uint32{uint32(Execve)}}, + }, + }, + sets: []string{}, + params: []trace.ArgMeta{ + {Type: "int", Name: "syscall"}, + {Type: "void*", Name: "pc"}, + {Type: "bool", Name: "is_stack"}, + {Type: "bool", Name: "is_heap"}, + {Type: "bool", Name: "is_anon_vma"}, + }, + }, // // Begin of Signal Events (Control Plane) // diff --git a/pkg/events/parse_args.go b/pkg/events/parse_args.go index c646d211fd05..5e17a30d50a0 100644 --- a/pkg/events/parse_args.go +++ b/pkg/events/parse_args.go @@ -293,6 +293,18 @@ func ParseArgs(event *trace.Event) error { parseOrEmptyString(objTypeArg, objTypeArgument, err) } } + case CheckSyscallSource: + if syscallArg := GetArg(event, "syscall"); syscallArg != nil { + if id, isInt32 := syscallArg.Value.(int32); isInt32 { + if Core.IsDefined(ID(id)) { + eventDefinition := Core.GetDefinitionByID(ID(id)) + if eventDefinition.IsSyscall() { + syscallArg.Value = eventDefinition.GetName() + syscallArg.Type = "string" + } + } + } + } } return nil From bd18fcc1de3b7cbde2a08c825ac612c39abe2e1a Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Mon, 25 Mar 2024 13:17:53 +0200 Subject: [PATCH 02/17] check_syscall_source only submits an event for unique sources A unique source is identified by a combination of process (tgid and group leader start time), vma address and syscall number. --- pkg/ebpf/c/common/memory.h | 6 ++++++ pkg/ebpf/c/maps.h | 8 ++++++++ pkg/ebpf/c/tracee.bpf.c | 15 +++++++++++++++ pkg/ebpf/c/types.h | 8 ++++++++ 4 files changed, 37 insertions(+) diff --git a/pkg/ebpf/c/common/memory.h b/pkg/ebpf/c/common/memory.h index 51f2b9d51a9e..c21be70a4dc7 100644 --- a/pkg/ebpf/c/common/memory.h +++ b/pkg/ebpf/c/common/memory.h @@ -15,6 +15,7 @@ statfunc unsigned long get_arg_end_from_mm(struct mm_struct *); statfunc unsigned long get_env_start_from_mm(struct mm_struct *); statfunc unsigned long get_env_end_from_mm(struct mm_struct *); statfunc unsigned long get_vma_flags(struct vm_area_struct *); +statfunc unsigned long get_vma_start(struct vm_area_struct *); statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn, void *cb_ctx); statfunc bool vma_is_stack(struct vm_area_struct *vma); statfunc bool vma_is_heap(struct vm_area_struct *vma); @@ -56,6 +57,11 @@ statfunc struct mount *real_mount(struct vfsmount *mnt) return container_of(mnt, struct mount, mnt); } +statfunc unsigned long get_vma_start(struct vm_area_struct *vma) +{ + return BPF_CORE_READ(vma, vm_start); +} + /** * A busy process can have somewhere in the ballpark of 1000 VMAs. * In an ideally balanced tree, this means that the max depth is ~10. diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h index 36bba9d5a875..103e1633a2e3 100644 --- a/pkg/ebpf/c/maps.h +++ b/pkg/ebpf/c/maps.h @@ -254,6 +254,14 @@ struct check_syscall_source_tail { __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); + __uint(max_entries, 4096); + __type(key, syscall_source_key_t); + __type(value, bool); +} syscall_source_map SEC(".maps"); + // store stack traces #define MAX_STACK_ADDRESSES 1024 // max amount of diff stack trace addrs to buffer diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index d9fa411e782b..45f6a6c1ec09 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5202,6 +5202,21 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) if (vma_type == VMA_OTHER) goto out; + // 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 = sys->id, + .tgid = p.task_info->context.pid, + .tgid_start_time = p.task_info->context.leader_start_time, + .vma_addr = get_vma_start(vma) + }; + bool val = true; + + // 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; + p.event->context.ts = sys->ts; bool is_stack = vma_type == VMA_STACK; diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index a205f8d3736f..f7cc89a279fd 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -562,4 +562,12 @@ struct sys_exit_tracepoint_args { long ret; }; +// key for the syscall source map +typedef struct { + uint syscall; + u32 tgid; + u64 tgid_start_time; + u64 vma_addr; +} syscall_source_key_t; + #endif From 59fb837834c3ebe8184ec88d2c76419a92cc74e2 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Wed, 27 Mar 2024 13:28:57 +0200 Subject: [PATCH 03/17] Added test for check_syscall_source event --- .github/workflows/pr.yaml | 1 + signatures/helpers/arguments_helpers.go | 14 +++ .../e2e-check_syscall_source.go | 102 +++++++++++++++ tests/e2e-inst-signatures/export.go | 1 + .../scripts/check_syscall_source.sh | 14 +++ .../scripts/sys_src_tester.c | 116 ++++++++++++++++++ tests/e2e-inst-test.sh | 2 +- 7 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 tests/e2e-inst-signatures/e2e-check_syscall_source.go create mode 100755 tests/e2e-inst-signatures/scripts/check_syscall_source.sh create mode 100644 tests/e2e-inst-signatures/scripts/sys_src_tester.c diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 6f6272b84e5d..566c861c4aac 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -73,6 +73,7 @@ env: DNS_DATA_SOURCE WRITABLE_DATA_SOURCE SET_FS_PWD + CHECK_SYSCALL_SOURCE jobs: # # DOC VERIFICATION diff --git a/signatures/helpers/arguments_helpers.go b/signatures/helpers/arguments_helpers.go index c975078a7e3f..2b5e17e89953 100644 --- a/signatures/helpers/arguments_helpers.go +++ b/signatures/helpers/arguments_helpers.go @@ -72,6 +72,20 @@ func GetTraceeIntArgumentByName(event trace.Event, argName string) (int, error) return 0, fmt.Errorf("can't convert argument %v to int", argName) } +// GetTraceeBoolArgumentByName gets the argument from `event` matching the `argName`, casted as bool. +func GetTraceeBoolArgumentByName(event trace.Event, argName string) (bool, error) { + arg, err := GetTraceeArgumentByName(event, argName, GetArgOps{DefaultArgs: false}) + if err != nil { + return false, err + } + argBool, ok := arg.Value.(bool) + if ok { + return argBool, nil + } + + return false, fmt.Errorf("can't convert argument %v to bool", argName) +} + // GetTraceeSliceStringArgumentByName gets the argument matching the "argName" given from the event "argv" field, casted as []string. func GetTraceeSliceStringArgumentByName(event trace.Event, argName string) ([]string, error) { arg, err := GetTraceeArgumentByName(event, argName, GetArgOps{DefaultArgs: false}) diff --git a/tests/e2e-inst-signatures/e2e-check_syscall_source.go b/tests/e2e-inst-signatures/e2e-check_syscall_source.go new file mode 100644 index 000000000000..1d2f41a253d9 --- /dev/null +++ b/tests/e2e-inst-signatures/e2e-check_syscall_source.go @@ -0,0 +1,102 @@ +package main + +import ( + "fmt" + + "github.com/aquasecurity/tracee/signatures/helpers" + "github.com/aquasecurity/tracee/types/detect" + "github.com/aquasecurity/tracee/types/protocol" + "github.com/aquasecurity/tracee/types/trace" +) + +type e2eCheckSyscallSource struct { + cb detect.SignatureHandler + foundStack bool + foundHeap bool + foundAnonVma bool +} + +func (sig *e2eCheckSyscallSource) Init(ctx detect.SignatureContext) error { + sig.cb = ctx.Callback + return nil +} + +func (sig *e2eCheckSyscallSource) GetMetadata() (detect.SignatureMetadata, error) { + return detect.SignatureMetadata{ + ID: "CHECK_SYSCALL_SOURCE", + EventName: "CHECK_SYSCALL_SOURCE", + Version: "0.1.0", + Name: "Check Syscall Source Test", + Description: "Instrumentation events E2E Tests: Check Syscall Source", + Tags: []string{"e2e", "instrumentation"}, + }, nil +} + +func (sig *e2eCheckSyscallSource) GetSelectedEvents() ([]detect.SignatureEventSelector, error) { + return []detect.SignatureEventSelector{ + {Source: "tracee", Name: "check_syscall_source"}, + }, nil +} + +func (sig *e2eCheckSyscallSource) OnEvent(event protocol.Event) error { + eventObj, ok := event.Payload.(trace.Event) + if !ok { + return fmt.Errorf("failed to cast event's payload") + } + + switch eventObj.EventName { + case "check_syscall_source": + syscall, err := helpers.GetTraceeStringArgumentByName(eventObj, "syscall") + if err != nil { + return err + } + isStack, err := helpers.GetTraceeBoolArgumentByName(eventObj, "is_stack") + if err != nil { + return err + } + isHeap, err := helpers.GetTraceeBoolArgumentByName(eventObj, "is_heap") + if err != nil { + return err + } + isAnonVma, err := helpers.GetTraceeBoolArgumentByName(eventObj, "is_anon_vma") + if err != nil { + return err + } + + // check expected values from test for detection + + if syscall != "execve" { + return nil + } + + if isStack { + sig.foundStack = true + } else if isHeap { + sig.foundHeap = true + } else if isAnonVma { + sig.foundAnonVma = true + } else { + return nil + } + + if !sig.foundStack || !sig.foundHeap || !sig.foundAnonVma { + return nil + } + + m, _ := sig.GetMetadata() + + sig.cb(&detect.Finding{ + SigMetadata: m, + Event: event, + Data: map[string]interface{}{}, + }) + } + + return nil +} + +func (sig *e2eCheckSyscallSource) OnSignal(s detect.Signal) error { + return nil +} + +func (sig *e2eCheckSyscallSource) Close() {} diff --git a/tests/e2e-inst-signatures/export.go b/tests/e2e-inst-signatures/export.go index df459de39c5f..6b88fc4c2069 100644 --- a/tests/e2e-inst-signatures/export.go +++ b/tests/e2e-inst-signatures/export.go @@ -22,6 +22,7 @@ var ExportedSignatures = []detect.Signature{ &e2eSecurityPathNotify{}, &e2eSetFsPwd{}, &e2eFtraceHook{}, + &e2eCheckSyscallSource{}, } var ExportedDataSources = []detect.DataSource{ diff --git a/tests/e2e-inst-signatures/scripts/check_syscall_source.sh b/tests/e2e-inst-signatures/scripts/check_syscall_source.sh new file mode 100755 index 000000000000..af27ae8b72f7 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/check_syscall_source.sh @@ -0,0 +1,14 @@ +#!/usr/bin/bash + +exit_err() { + echo -n "ERROR: " + echo "$@" + exit 1 +} + +prog=sys_src_tester +dir=tests/e2e-inst-signatures/scripts +gcc $dir/$prog.c -o $dir/$prog -z execstack || exit_err "could not compile $prog.c" +./$dir/$prog stack 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" +./$dir/$prog heap 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" +./$dir/$prog mmap 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" \ No newline at end of file diff --git a/tests/e2e-inst-signatures/scripts/sys_src_tester.c b/tests/e2e-inst-signatures/scripts/sys_src_tester.c new file mode 100644 index 000000000000..937ce29b2096 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/sys_src_tester.c @@ -0,0 +1,116 @@ +// gcc -o syscall_source_tester -z execstack syscall_source_tester.c + +#include +#include +#include +#include +#include +#include + +// execve("/bin/sh", ["/bin/sh", "-c", "exit"], NULL); +#if defined(__x86_64__) +#define SHELLCODE \ + "\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00" /* mov rax, 0x68732f6e69622f ; "/bin/sh\0" */ \ + "\x50" /* push rax */ \ + "\x48\x89\xe7" /* mov rdi, rsp */ \ + "\x48\xc7\xc0\x2d\x63\x00\x00" /* mov rax, 0x632d ; "-c\0" */ \ + "\x50" /* push rax */ \ + "\x48\x89\xe3" /* mov rbx, rsp */ \ + "\x48\xc7\xc0\x65\x78\x69\x74" /* mov rax, 0x74697865 ; "exit\0" */ \ + "\x50" /* push rax */ \ + "\x48\x89\xe1" /* mov rcx, rsp */ \ + "\x48\x31\xc0" /* xor rax, rax */ \ + "\x50" /* push rax */ \ + "\x51" /* push rcx */ \ + "\x53" /* push rbx */ \ + "\x57" /* push rdi */ \ + "\x48\x89\xe6" /* mov rsi, rsp */ \ + "\x48\x89\xc2" /* mov rdx, rax */ \ + "\x48\xc7\xc0\x3b\x00\x00\x00" /* mov rax, 59 ; __NR_execve */ \ + "\x0f\x05" /* syscall */ +#elif defined(__aarch64__) +#define SHELLCODE \ + "\xe3\x45\x8c\xd2" /* mov x3, #0x622F */ \ + "\x23\xcd\xad\xf2" /* movk x3, #0x6E69, lsl #16 */ \ + "\xe3\x65\xce\xf2" /* movk x3, #0x732F, lsl #32 */ \ + "\x03\x0d\xe0\xf2" /* movk x3, #0x68, lsl #48 ; x3 = "/bin/sh\0" */ \ + "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ + "\xe0\x03\x00\x91" /* mov x0, sp */ \ + "\xa3\x65\x8c\xd2" /* mov x3, #0x632d ; x3 = "-c\0" */ \ + "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ + "\xe4\x03\x00\x91" /* mov x4, sp */ \ + "\xa3\x0c\x8f\xd2" /* mov x3, #0x7865 */ \ + "\x23\x8d\xae\xf2" /* movk x3, #0x7469, lsl#16 ; x3 = "exit\0" */ \ + "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ + "\xe5\x03\x00\x91" /* mov x5, sp */ \ + "\xe3\x03\x1f\xaa" /* mov x3, xzr */ \ + "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ + "\xe5\x8f\x1f\xf8" /* str x5, [sp, #-8]! */ \ + "\xe4\x8f\x1f\xf8" /* str x4, [sp, #-8]! */ \ + "\xe0\x8f\x1f\xf8" /* str x0, [sp, #-8]! */ \ + "\xe1\x03\x00\x91" /* mov x1, sp */ \ + "\xe2\x03\x03\xaa" /* mov x2, x3 */ \ + "\xa8\x1b\x80\xd2" /* mov x8, #221 ; __NR_execve */ \ + "\x01\x00\x00\xd4" /* svc #0 */ +#else +#error Invalid architecture +#endif + +char shellcode[] = SHELLCODE; + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + printf("usage: ./syscall_source_tester [stack|heap|mmap]\n"); + goto fail; + } + + if (strcmp(argv[1], "stack") == 0) { + char shellcode_stack[] = SHELLCODE; + ((void (*)(void))shellcode_stack)(); + // cannot be reached + goto fail; + } + + if (strcmp(argv[1], "heap") == 0) { + void *shellcode_heap = malloc(sizeof(shellcode)); + if (shellcode_heap == NULL) { + perror("malloc failed"); + goto fail; + } + + memcpy(shellcode_heap, shellcode, sizeof(shellcode)); + + // set the heap memory as executable + if (mprotect((void *)((unsigned long long)shellcode_heap & ~(sysconf(_SC_PAGE_SIZE) - 1)), sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC) == -1) { + perror("mprotect failed"); + goto fail; + } + + // jump to the shellcode + ((void (*)(void))shellcode_heap)(); + + // cannot be reached + goto fail; + } + + if (strcmp(argv[1], "mmap") == 0) { + // create an anonymous mapping for the shellcode + void *shellcode_mmap = mmap(NULL, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (shellcode_mmap == MAP_FAILED) { + perror("mmap failed"); + goto fail; + } + + memcpy(shellcode_mmap, shellcode, sizeof(shellcode)); + + // jump to the shellcode + ((void (*)(void))shellcode_mmap)(); + + // cannot be reached + goto fail; + } + +fail: + exit(EXIT_FAILURE); +} diff --git a/tests/e2e-inst-test.sh b/tests/e2e-inst-test.sh index a1fc7f1e7fc6..69403f7d6566 100755 --- a/tests/e2e-inst-test.sh +++ b/tests/e2e-inst-test.sh @@ -139,7 +139,7 @@ for TEST in $TESTS; do --output option:parse-arguments \ --log file:$SCRIPT_TMP_DIR/tracee-log-$$ \ --signatures-dir "$SIG_DIR" \ - --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh \ + --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh,sys_src_tester \ --dnscache enable \ --grpc-listen-addr unix:/tmp/tracee.sock \ --events "$TEST" & From d051a0878c459f544c821a1e1aad83a0acc16a49 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Thu, 28 Mar 2024 13:17:28 +0200 Subject: [PATCH 04/17] Remove syscall wrapper logic Currently there are no supported platforms without the CONFIG_ARCH_HAS_SYSCALL_WRAPPER configuration. --- pkg/ebpf/c/tracee.bpf.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 45f6a6c1ec09..3fe751de00e6 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5180,17 +5180,7 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) if (task == NULL) goto out; - // TODO - // The only supported architectures (x86 and arm) always have ARCH_HAS_SYSCALL_WRAPPER. - // This shouldn't actually change anything for raw tracepoints - even without this config - // (a case that wasn't tested before) the registers should be accessible in the same way. - // This would mean that the argument extraction logic in sys_enter_init is wrong. - struct pt_regs *regs; - if (get_kconfig(ARCH_HAS_SYSCALL_WRAPPER)) - regs = (struct pt_regs *) ctx->args[0]; - else - // this is probably wrong - regs = (struct pt_regs *) ctx; + struct pt_regs *regs = (struct pt_regs *) ctx->args[0]; u64 ip = BPF_CORE_READ(regs, ip); struct vm_area_struct *vma = NULL; From 998736d44dc68ed7c56c05cba47c7bfee4ea3c19 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Thu, 28 Mar 2024 16:28:50 +0200 Subject: [PATCH 05/17] Moved tail call to sys_enter --- pkg/ebpf/c/tracee.bpf.c | 51 ++++++++++++++++++++++++++--------------- pkg/ebpf/c/types.h | 2 +- pkg/events/core.go | 3 +-- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 3fe751de00e6..8fa3e14e5f01 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -56,6 +56,11 @@ 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; } @@ -129,9 +134,6 @@ int sys_enter_init(struct bpf_raw_tracepoint_args *ctx) task_info->syscall_traced = true; } - // call syscall checker if relevant - bpf_tail_call(ctx, &check_syscall_source_tail, sys->id); - // if id is irrelevant continue to next tail call bpf_tail_call(ctx, &sys_enter_submit_tail, sys->id); @@ -5168,7 +5170,16 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) if (!init_program_data(&p, ctx)) return 0; - syscall_data_t *sys = &p.task_info->syscall_data; + // Get syscall ID + 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, so we can send to the correct handler + u32 *id_64 = bpf_map_lookup_elem(&sys_32_to_64_map, &id); + if (id_64 == 0) + return 0; + id = *id_64; + } if (!should_trace(&p)) goto out; @@ -5176,44 +5187,49 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) if (!should_submit(CHECK_SYSCALL_SOURCE, p.event)) goto out; - struct task_struct *task = bpf_get_current_task_btf(); - if (task == NULL) + struct task_struct *task_btf = bpf_get_current_task_btf(); + if (task_btf == NULL) goto out; + // 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 + // Find VMA which contains the instruction pointer struct vm_area_struct *vma = NULL; - find_vma(task, ip, find_vma_callback, &vma); + find_vma(task_btf, ip, find_vma_callback, &vma); if (vma == NULL) goto out; + // 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; - // build a key that identifies the combination of syscall, source VMA and process - // so we don't submit it multiple times + // 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 = sys->id, + .syscall = id, .tgid = p.task_info->context.pid, .tgid_start_time = p.task_info->context.leader_start_time, .vma_addr = get_vma_start(vma) }; bool val = true; - // try updating the map with the requirement that this key does not exist yet + // 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 + // This key already exists, no need to submit the same syscall-vma-process combination again goto out; - - p.event->context.ts = sys->ts; 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, &sys->id, sizeof(sys->id), 0); + save_to_submit_buf(&p.event->args_buf, &id, sizeof(id), 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); @@ -5222,9 +5238,8 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) events_perf_submit(&p, CHECK_SYSCALL_SOURCE, 0); out: - // call remaining tails from sys_enter_init - bpf_tail_call(ctx, &sys_enter_submit_tail, sys->id); - bpf_tail_call(ctx, &sys_enter_tails, sys->id); + // Call sys_enter_init_tail which we preceded + bpf_tail_call(ctx, &sys_enter_init_tail, id); return 0; } diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index f7cc89a279fd..a9e8c71e31ad 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -564,7 +564,7 @@ struct sys_exit_tracepoint_args { // key for the syscall source map typedef struct { - uint syscall; + u32 syscall; u32 tgid; u64 tgid_start_time; u64 vma_addr; diff --git a/pkg/events/core.go b/pkg/events/core.go index 22fe2ca328a1..cb6629c7b78d 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -13082,14 +13082,13 @@ var CoreEvents = map[ID]Definition{ {handle: probes.SyscallEnter__Internal, required: true}, }, tailCalls: []TailCall{ - {"sys_enter_init_tail", "sys_enter_init", []uint32{uint32(Execve)}}, {"check_syscall_source_tail", "check_syscall_source", []uint32{uint32(Execve)}}, }, }, sets: []string{}, params: []trace.ArgMeta{ {Type: "int", Name: "syscall"}, - {Type: "void*", Name: "pc"}, + {Type: "void*", Name: "ip"}, {Type: "bool", Name: "is_stack"}, {Type: "bool", Name: "is_heap"}, {Type: "bool", Name: "is_anon_vma"}, From fcd23a61f8ac498d0edba62b046d7872a899ef70 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Tue, 2 Apr 2024 16:29:05 +0300 Subject: [PATCH 06/17] Tester program fixes Added cache flushes to prevent issues on ARM CPUs Simplified shellcode to a simple exit(0). --- .../e2e-check_syscall_source.go | 2 +- .../scripts/sys_src_tester.c | 67 ++++++------------- 2 files changed, 22 insertions(+), 47 deletions(-) diff --git a/tests/e2e-inst-signatures/e2e-check_syscall_source.go b/tests/e2e-inst-signatures/e2e-check_syscall_source.go index 1d2f41a253d9..4bd6ec1cd71a 100644 --- a/tests/e2e-inst-signatures/e2e-check_syscall_source.go +++ b/tests/e2e-inst-signatures/e2e-check_syscall_source.go @@ -65,7 +65,7 @@ func (sig *e2eCheckSyscallSource) OnEvent(event protocol.Event) error { // check expected values from test for detection - if syscall != "execve" { + if syscall != "exit" { return nil } diff --git a/tests/e2e-inst-signatures/scripts/sys_src_tester.c b/tests/e2e-inst-signatures/scripts/sys_src_tester.c index 937ce29b2096..a34d981d0f8d 100644 --- a/tests/e2e-inst-signatures/scripts/sys_src_tester.c +++ b/tests/e2e-inst-signatures/scripts/sys_src_tester.c @@ -7,51 +7,17 @@ #include #include -// execve("/bin/sh", ["/bin/sh", "-c", "exit"], NULL); +// exit(0); #if defined(__x86_64__) #define SHELLCODE \ - "\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00" /* mov rax, 0x68732f6e69622f ; "/bin/sh\0" */ \ - "\x50" /* push rax */ \ - "\x48\x89\xe7" /* mov rdi, rsp */ \ - "\x48\xc7\xc0\x2d\x63\x00\x00" /* mov rax, 0x632d ; "-c\0" */ \ - "\x50" /* push rax */ \ - "\x48\x89\xe3" /* mov rbx, rsp */ \ - "\x48\xc7\xc0\x65\x78\x69\x74" /* mov rax, 0x74697865 ; "exit\0" */ \ - "\x50" /* push rax */ \ - "\x48\x89\xe1" /* mov rcx, rsp */ \ - "\x48\x31\xc0" /* xor rax, rax */ \ - "\x50" /* push rax */ \ - "\x51" /* push rcx */ \ - "\x53" /* push rbx */ \ - "\x57" /* push rdi */ \ - "\x48\x89\xe6" /* mov rsi, rsp */ \ - "\x48\x89\xc2" /* mov rdx, rax */ \ - "\x48\xc7\xc0\x3b\x00\x00\x00" /* mov rax, 59 ; __NR_execve */ \ - "\x0f\x05" /* syscall */ + "\x48\x31\xFF" /* xor rdi, rdi */ \ + "\x48\xC7\xC0\x3C\x00\x00\x00" /* mov rax, 60 ; __NR_exit */ \ + "\x0F\x05" /* syscall */ #elif defined(__aarch64__) #define SHELLCODE \ - "\xe3\x45\x8c\xd2" /* mov x3, #0x622F */ \ - "\x23\xcd\xad\xf2" /* movk x3, #0x6E69, lsl #16 */ \ - "\xe3\x65\xce\xf2" /* movk x3, #0x732F, lsl #32 */ \ - "\x03\x0d\xe0\xf2" /* movk x3, #0x68, lsl #48 ; x3 = "/bin/sh\0" */ \ - "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ - "\xe0\x03\x00\x91" /* mov x0, sp */ \ - "\xa3\x65\x8c\xd2" /* mov x3, #0x632d ; x3 = "-c\0" */ \ - "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ - "\xe4\x03\x00\x91" /* mov x4, sp */ \ - "\xa3\x0c\x8f\xd2" /* mov x3, #0x7865 */ \ - "\x23\x8d\xae\xf2" /* movk x3, #0x7469, lsl#16 ; x3 = "exit\0" */ \ - "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ - "\xe5\x03\x00\x91" /* mov x5, sp */ \ - "\xe3\x03\x1f\xaa" /* mov x3, xzr */ \ - "\xe3\x8f\x1f\xf8" /* str x3, [sp, #-8]! */ \ - "\xe5\x8f\x1f\xf8" /* str x5, [sp, #-8]! */ \ - "\xe4\x8f\x1f\xf8" /* str x4, [sp, #-8]! */ \ - "\xe0\x8f\x1f\xf8" /* str x0, [sp, #-8]! */ \ - "\xe1\x03\x00\x91" /* mov x1, sp */ \ - "\xe2\x03\x03\xaa" /* mov x2, x3 */ \ - "\xa8\x1b\x80\xd2" /* mov x8, #221 ; __NR_execve */ \ - "\x01\x00\x00\xd4" /* svc #0 */ + "\x00\x00\x80\xD2" /* mov x0, 0 */ \ + "\xA8\x0B\x80\xD2" /* mov x8, #93 ; __NR_exit */ \ + "\x01\x00\x00\xD4" /* svc #0 */ #else #error Invalid architecture #endif @@ -60,13 +26,14 @@ char shellcode[] = SHELLCODE; int main(int argc, char *argv[]) { - if (argc != 2) { - printf("usage: ./syscall_source_tester [stack|heap|mmap]\n"); - goto fail; - } + if (argc != 2) + goto usage; if (strcmp(argv[1], "stack") == 0) { char shellcode_stack[] = SHELLCODE; +#if defined(__aarch64__) + __builtin___clear_cache (&shellcode_stack, &shellcode_stack + sizeof(shellcode)); +#endif ((void (*)(void))shellcode_stack)(); // cannot be reached goto fail; @@ -82,12 +49,15 @@ int main(int argc, char *argv[]) memcpy(shellcode_heap, shellcode, sizeof(shellcode)); // set the heap memory as executable - if (mprotect((void *)((unsigned long long)shellcode_heap & ~(sysconf(_SC_PAGE_SIZE) - 1)), sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC) == -1) { + if (mprotect((void *)((unsigned long long)shellcode_heap & ~(sysconf(_SC_PAGE_SIZE) - 1)), 2 * sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC) == -1) { perror("mprotect failed"); goto fail; } // jump to the shellcode +#if defined(__aarch64__) + __builtin___clear_cache (&shellcode_heap, &shellcode_heap + sizeof(shellcode)); +#endif ((void (*)(void))shellcode_heap)(); // cannot be reached @@ -105,12 +75,17 @@ int main(int argc, char *argv[]) memcpy(shellcode_mmap, shellcode, sizeof(shellcode)); // jump to the shellcode +#if defined(__aarch64__) + __builtin___clear_cache(&shellcode_mmap, &shellcode_mmap + sizeof(shellcode)); +#endif ((void (*)(void))shellcode_mmap)(); // cannot be reached goto fail; } +usage: + printf("usage: ./syscall_source_tester [stack|heap|mmap]\n"); fail: exit(EXIT_FAILURE); } From edd536f5d4ac32c02e75e338e11da1c4296ea331 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Tue, 2 Apr 2024 16:34:32 +0300 Subject: [PATCH 07/17] Syscalls to be checked are now supplied at runtime using a filter Filtering on check_syscall_source.args.syscall results in the selected syscalls being added to the tail call map for the check_syscall program. --- pkg/ebpf/event_filters.go | 82 +++++++++++++++++++++++++++++++++++++++ pkg/ebpf/tracee.go | 6 +++ pkg/events/core.go | 2 +- pkg/filters/data.go | 3 +- 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 pkg/ebpf/event_filters.go diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go new file mode 100644 index 000000000000..40661b193a17 --- /dev/null +++ b/pkg/ebpf/event_filters.go @@ -0,0 +1,82 @@ +package ebpf + +import ( + "maps" + "strconv" + "unsafe" + + bpf "github.com/aquasecurity/libbpfgo" + "github.com/aquasecurity/tracee/pkg/errfmt" + "github.com/aquasecurity/tracee/pkg/events" + "github.com/aquasecurity/tracee/pkg/filters" +) + +type eventFilterHandler func(eventFilters map[string]filters.Filter, bpfModule *bpf.Module) error + +var eventFilterHandlers = map[events.ID]eventFilterHandler{ + events.CheckSyscallSource: populateMapsCheckSyscallSource, +} + +// populateEventFilterMaps populates maps with data from special event filters +func (t *Tracee) populateEventFilterMaps() error { + // Iterate through registerd event filter handlers + for eventID, handler := range eventFilterHandlers { + // Construct filters for this event + filters := map[string]filters.Filter{} + for p := range t.config.Policies.Map() { + f := p.ArgFilter.GetEventFilters(eventID) + if len(f) == 0 { + continue + } + maps.Copy(filters, f) + } + if len(filters) == 0 { + continue + } + + // Call handler + err := handler(filters, t.bpfModule) + if err != nil { + return err + } + } + return nil +} + +func populateMapsCheckSyscallSource(eventFilters map[string]filters.Filter, bpfModule *bpf.Module) 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) + if err != nil { + return errfmt.WrapError(err) + } + + err = checkSyscallSourceTail.Update(unsafe.Pointer(&syscallID), unsafe.Pointer(&checkSyscallSourceProgFD)) + if err != nil { + return errfmt.WrapError(err) + } + } + + return nil +} diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index f8967835fa2d..812c742c56c8 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -1212,6 +1212,12 @@ 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 } diff --git a/pkg/events/core.go b/pkg/events/core.go index cb6629c7b78d..97f1330e0c57 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -13082,7 +13082,7 @@ var CoreEvents = map[ID]Definition{ {handle: probes.SyscallEnter__Internal, required: true}, }, tailCalls: []TailCall{ - {"check_syscall_source_tail", "check_syscall_source", []uint32{uint32(Execve)}}, + {"check_syscall_source_tail", "check_syscall_source", []uint32{ /* Map will be populated at runtime according to event filter */ }}, }, }, sets: []string{}, diff --git a/pkg/filters/data.go b/pkg/filters/data.go index 2a54620245cf..379cfbf436f7 100644 --- a/pkg/filters/data.go +++ b/pkg/filters/data.go @@ -122,7 +122,8 @@ func (af *DataFilter) Parse(filterName string, operatorAndValues string, eventsN valueHandler := func(val string) (string, error) { switch id { case events.SysEnter, - events.SysExit: + events.SysExit, + events.CheckSyscallSource: if dataName == "syscall" { // handle either syscall name or syscall id _, err := strconv.Atoi(val) if err != nil { From 386135e342609a441564a0deea92c0a2a2084d90 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Tue, 2 Apr 2024 16:35:41 +0300 Subject: [PATCH 08/17] Updated test script to provide the required event filter for the check_syscall_source event --- tests/e2e-inst-test.sh | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/e2e-inst-test.sh b/tests/e2e-inst-test.sh index 69403f7d6566..2942f38c3308 100755 --- a/tests/e2e-inst-test.sh +++ b/tests/e2e-inst-test.sh @@ -129,20 +129,27 @@ for TEST in $TESTS; do rm -f $SCRIPT_TMP_DIR/build-$$ rm -f $SCRIPT_TMP_DIR/tracee-log-$$ - ./dist/tracee \ - --install-path $TRACEE_TMP_DIR \ - --cache cache-type=mem \ - --cache mem-cache-size=512 \ - --proctree source=both \ - --output option:sort-events \ - --output json:$SCRIPT_TMP_DIR/build-$$ \ - --output option:parse-arguments \ - --log file:$SCRIPT_TMP_DIR/tracee-log-$$ \ - --signatures-dir "$SIG_DIR" \ - --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh,sys_src_tester \ - --dnscache enable \ - --grpc-listen-addr unix:/tmp/tracee.sock \ - --events "$TEST" & + tracee_command="./dist/tracee \ + --install-path $TRACEE_TMP_DIR \ + --cache cache-type=mem \ + --cache mem-cache-size=512 \ + --proctree source=both \ + --output option:sort-events \ + --output json:$SCRIPT_TMP_DIR/build-$$ \ + --output option:parse-arguments \ + --log file:$SCRIPT_TMP_DIR/tracee-log-$$ \ + --signatures-dir "$SIG_DIR" \ + --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh,sys_src_tester \ + --dnscache enable \ + --grpc-listen-addr unix:/tmp/tracee.sock \ + --events "$TEST"" + + # Some tests might need event filters + if [ "$TEST" = "CHECK_SYSCALL_SOURCE" ]; then + tracee_command="$tracee_command --events check_syscall_source.args.syscall=exit" + fi + + $tracee_command & # Wait tracee to start From b6d1289b36fc1c7cb05478b79022793454b80856 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Tue, 2 Apr 2024 16:38:29 +0300 Subject: [PATCH 09/17] Extract task info manually for key A recent performance change (f806cb4b71b8f4082e182adf6c201c1e81f05753) results in task info not being populated until the event is submitted. --- pkg/ebpf/c/tracee.bpf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 8fa3e14e5f01..64e3ce6f5f45 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5174,7 +5174,7 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) 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, so we can send to the correct handler + // 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; @@ -5214,8 +5214,8 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) // source VMA and process so we don't submit it multiple times syscall_source_key_t key = { .syscall = id, - .tgid = p.task_info->context.pid, - .tgid_start_time = p.task_info->context.leader_start_time, + .tgid = get_task_ns_tgid(task), + .tgid_start_time = get_task_start_time(get_leader_task(task)), .vma_addr = get_vma_start(vma) }; bool val = true; From 944bf632aa1585b5d1add8935d91d19fae847a2c Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Wed, 3 Apr 2024 12:13:32 +0300 Subject: [PATCH 10/17] Cancel event if filter handler fails instead of preventing tracee from starting --- pkg/ebpf/event_filters.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go index 40661b193a17..142b7793d454 100644 --- a/pkg/ebpf/event_filters.go +++ b/pkg/ebpf/event_filters.go @@ -9,6 +9,7 @@ import ( "github.com/aquasecurity/tracee/pkg/errfmt" "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, bpfModule *bpf.Module) error @@ -37,7 +38,8 @@ func (t *Tracee) populateEventFilterMaps() error { // Call handler err := handler(filters, t.bpfModule) if err != nil { - return err + logger.Errorw("Failed to handle event filter for event " + events.Core.GetDefinitionByID(eventID).GetName() + ", err: " + err.Error()) + t.cancelEventFromEventState(eventID) } } return nil From 0a0bbc73493a209b35a169036b9ed434c373b695 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Wed, 3 Apr 2024 13:24:07 +0300 Subject: [PATCH 11/17] Added documentation for check_syscall_source --- .../builtin/extra/check_syscall_source.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/docs/events/builtin/extra/check_syscall_source.md diff --git a/docs/docs/events/builtin/extra/check_syscall_source.md b/docs/docs/events/builtin/extra/check_syscall_source.md new file mode 100644 index 000000000000..612ff6289929 --- /dev/null +++ b/docs/docs/events/builtin/extra/check_syscall_source.md @@ -0,0 +1,49 @@ +# check_syscall_source + +## Intro + +check_syscall_source - An event reporting a syscall that was invoked from an unusual code location. + +## Description + +In most cases, all code running in a process is placed in dedicated code regions (VMAs, or Virtual Memory Areas) that are mapped from executable files that contain the code. Thus, the locations that syscalls are invoked from should be in one of these code regions. + +When a syscall is invoked from an unusual location, this event is triggered. This may happen in the following scenarios: + +- A shellcode is executed from the stack, the heap or an anonymous (non-file-backed) memory region. + +- A packed program is executed, and is either statically linked or it calls syscalls directly (instead of using libc wrappers). + +This event relies on an event filter to specify which syscalls should be monitored, to reduce overhead. An example command line usage of this event: + +`tracee --events check_syscall_source.args.syscall=open,openat`. + +To reduce noise in cases where code with significant syscall activity is being detected, any unique combination of process, syscall and VMA that contains the invoking code will be submitted as an event only once. + +## Arguments + +* `syscall`:`int`[K] - the syscall which was invoked from an unusual location. The syscall name is parsed if the `parse-arguments` option is specified. +* `ip`:`void *`[K] - the address from which the syscall was invoked (instruction pointer of the instruction following the syscall instruction). +* `is_stack`:`bool`[K] - whether the syscall was invoked from the stack. Mutually exclusive with `is_heap` and `is_anon_vma`. +* `is_heap`:`bool`[K] - whether the syscall was invoked from the heap. Mutually exclusive with `is_stack` and `is_anon_vma`. +* `is_anon_vma`:`bool`[K] - whether the syscall was invoked from an anonymous (non-file-backed) VMA. Mutually exclusive with `is_stack` and `is_heap`. + +## Hooks + +### sys_enter + +#### Type + +tracepoint + +#### Purpose + +Utilizes a tail call from the existing tracepoint on `sys_enter`. The called function analyzes the location from which the syscall was invoked. The analysis occurs only if a policy has selected this syscall as a filter for this event. + +## Example Use Case + +Detect shellcodes. + +## Issues + +Unwanted events may occur in scenarios where legitimate programs run code from unusual locations. This may happen in the case of JITs that write code to anonymous VMAs. Although such code is not expected to invoke syscalls directly (instead relying on some runtime that is mapped from an executable file), exceptions may exist. From cb93049c1758aa7eea7a36c87feeb7850422f651 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Wed, 3 Apr 2024 13:46:57 +0300 Subject: [PATCH 12/17] Formatting fixes --- pkg/ebpf/c/common/memory.h | 23 ++++++++++++----------- pkg/ebpf/c/maps.h | 2 +- pkg/ebpf/c/tracee.bpf.c | 35 +++++++++++++++++------------------ pkg/ebpf/event_filters.go | 9 +++++---- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/pkg/ebpf/c/common/memory.h b/pkg/ebpf/c/common/memory.h index c21be70a4dc7..3f8142b2b057 100644 --- a/pkg/ebpf/c/common/memory.h +++ b/pkg/ebpf/c/common/memory.h @@ -7,7 +7,9 @@ // PROTOTYPES -typedef long (*vma_callback_fn)(struct task_struct *task, struct vm_area_struct *vma, void *callback_ctx); +typedef long (*vma_callback_fn)(struct task_struct *task, + struct vm_area_struct *vma, + void *callback_ctx); statfunc struct mm_struct *get_mm_from_task(struct task_struct *); statfunc unsigned long get_arg_start_from_mm(struct mm_struct *); @@ -79,13 +81,13 @@ statfunc unsigned long get_vma_start(struct vm_area_struct *vma) * and the specified context. * A callback function is required becuase this function potentially uses * bpf_find_vma(), which requires a callback function. - * + * * A generic callback function which receives a `struct vm_area_struct **` * as its context and saves the found VMA to it is available in the main * eBPF source file (tracee.bpf.c:find_vma_callback). - * + * * See the check_syscall_source function for a usage example. - * + * * DISCLAIMER: on systems with no MMU, multiple VMAs may contain the same address. * Be aware that this function will call the callback only for the first VMA it finds. */ @@ -116,7 +118,7 @@ statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn if (rb_node == NULL) break; - + struct vm_area_struct *tmp = container_of(rb_node, struct vm_area_struct, vm_rb); unsigned long vm_start = BPF_CORE_READ(tmp, vm_start); unsigned long vm_end = BPF_CORE_READ(tmp, vm_end); @@ -126,8 +128,7 @@ statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn if (vm_start <= addr) break; rb_node = BPF_CORE_READ(rb_node, rb_left); - } - else + } else rb_node = BPF_CORE_READ(rb_node, rb_right); } @@ -140,7 +141,7 @@ statfunc bool vma_is_stack(struct vm_area_struct *vma) struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); if (vm_mm == NULL) return false; - + u64 vm_start = BPF_CORE_READ(vma, vm_start); u64 vm_end = BPF_CORE_READ(vma, vm_end); u64 start_stack = BPF_CORE_READ(vm_mm, start_stack); @@ -148,7 +149,7 @@ statfunc bool vma_is_stack(struct vm_area_struct *vma) // logic taken from include/linux/mm.h (vma_is_initial_stack) if (vm_start <= start_stack && start_stack <= vm_end) return true; - + return false; } @@ -157,7 +158,7 @@ statfunc bool vma_is_heap(struct vm_area_struct *vma) struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); if (vm_mm == NULL) return false; - + u64 vm_start = BPF_CORE_READ(vma, vm_start); u64 vm_end = BPF_CORE_READ(vma, vm_end); u64 start_brk = BPF_CORE_READ(vm_mm, start_brk); @@ -166,7 +167,7 @@ statfunc bool vma_is_heap(struct vm_area_struct *vma) // logic taken from include/linux/mm.h (vma_is_initial_heap) if (vm_start < brk && start_brk < vm_end) return true; - + return false; } diff --git a/pkg/ebpf/c/maps.h b/pkg/ebpf/c/maps.h index 103e1633a2e3..56f153bb27ac 100644 --- a/pkg/ebpf/c/maps.h +++ b/pkg/ebpf/c/maps.h @@ -260,7 +260,7 @@ struct { __uint(max_entries, 4096); __type(key, syscall_source_key_t); __type(value, bool); -} syscall_source_map SEC(".maps"); +} syscall_source_map SEC(".maps"); // store stack traces #define MAX_STACK_ADDRESSES 1024 // max amount of diff stack trace addrs to buffer diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 64e3ce6f5f45..1e54cc3daf5a 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5135,7 +5135,8 @@ int BPF_KPROBE(trace_security_task_setrlimit) return events_perf_submit(&p, 0); } -enum vma_type { +enum vma_type +{ VMA_STACK, VMA_HEAP, VMA_ANON, @@ -5146,19 +5147,19 @@ statfunc enum vma_type get_vma_type(struct vm_area_struct *vma) { if (vma_is_stack(vma)) return VMA_STACK; - + if (vma_is_heap(vma)) return VMA_HEAP; - + if (vma_is_anon(vma)) return VMA_ANON; - + return VMA_OTHER; } static long find_vma_callback(struct task_struct *task, struct vm_area_struct *vma, void *ctx) { - struct vm_area_struct **pvma = (struct vm_area_struct **)ctx; + struct vm_area_struct **pvma = (struct vm_area_struct **) ctx; *pvma = vma; return 0; } @@ -5169,7 +5170,7 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) program_data_t p = {}; if (!init_program_data(&p, ctx)) return 0; - + // Get syscall ID struct task_struct *task = (struct task_struct *) bpf_get_current_task(); u32 id = ctx->args[1]; @@ -5180,10 +5181,10 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) return 0; id = *id_64; } - + if (!should_trace(&p)) goto out; - + if (!should_submit(CHECK_SYSCALL_SOURCE, p.event)) goto out; @@ -5198,30 +5199,28 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) #elif defined(bpf_target_arm64) u64 ip = BPF_CORE_READ(regs, pc); #endif - + // Find VMA which contains the instruction pointer struct vm_area_struct *vma = NULL; find_vma(task_btf, ip, find_vma_callback, &vma); if (vma == NULL) goto out; - + // 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; - + // 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, - .tgid = get_task_ns_tgid(task), - .tgid_start_time = get_task_start_time(get_leader_task(task)), - .vma_addr = get_vma_start(vma) - }; + syscall_source_key_t key = {.syscall = id, + .tgid = get_task_ns_tgid(task), + .tgid_start_time = get_task_start_time(get_leader_task(task)), + .vma_addr = get_vma_start(vma)}; bool val = true; // 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 */) + 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; diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go index 142b7793d454..7949210d1891 100644 --- a/pkg/ebpf/event_filters.go +++ b/pkg/ebpf/event_filters.go @@ -6,6 +6,7 @@ import ( "unsafe" bpf "github.com/aquasecurity/libbpfgo" + "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/pkg/filters" @@ -23,20 +24,20 @@ func (t *Tracee) populateEventFilterMaps() error { // Iterate through registerd event filter handlers for eventID, handler := range eventFilterHandlers { // Construct filters for this event - filters := map[string]filters.Filter{} + eventFilters := map[string]filters.Filter{} for p := range t.config.Policies.Map() { f := p.ArgFilter.GetEventFilters(eventID) if len(f) == 0 { continue } - maps.Copy(filters, f) + maps.Copy(eventFilters, f) } - if len(filters) == 0 { + if len(eventFilters) == 0 { continue } // Call handler - err := handler(filters, t.bpfModule) + err := handler(eventFilters, t.bpfModule) if err != nil { logger.Errorw("Failed to handle event filter for event " + events.Core.GetDefinitionByID(eventID).GetName() + ", err: " + err.Error()) t.cancelEventFromEventState(eventID) From fcb86884786cde56c474fb1495b623834b77e3a5 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Thu, 4 Apr 2024 13:05:51 +0300 Subject: [PATCH 13/17] Use new ArgVal signature helper in test --- pkg/ebpf/event_filters.go | 2 +- tests/e2e-inst-signatures/e2e-check_syscall_source.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go index 7949210d1891..d23f6ead6dbf 100644 --- a/pkg/ebpf/event_filters.go +++ b/pkg/ebpf/event_filters.go @@ -25,7 +25,7 @@ func (t *Tracee) populateEventFilterMaps() error { for eventID, handler := range eventFilterHandlers { // Construct filters for this event eventFilters := map[string]filters.Filter{} - for p := range t.config.Policies.Map() { + for _, p := range t.config.Policies.Map() { f := p.ArgFilter.GetEventFilters(eventID) if len(f) == 0 { continue diff --git a/tests/e2e-inst-signatures/e2e-check_syscall_source.go b/tests/e2e-inst-signatures/e2e-check_syscall_source.go index 4bd6ec1cd71a..b328f79726fa 100644 --- a/tests/e2e-inst-signatures/e2e-check_syscall_source.go +++ b/tests/e2e-inst-signatures/e2e-check_syscall_source.go @@ -50,15 +50,15 @@ func (sig *e2eCheckSyscallSource) OnEvent(event protocol.Event) error { if err != nil { return err } - isStack, err := helpers.GetTraceeBoolArgumentByName(eventObj, "is_stack") + isStack, err := helpers.ArgVal[bool](eventObj.Args, "is_stack") if err != nil { return err } - isHeap, err := helpers.GetTraceeBoolArgumentByName(eventObj, "is_heap") + isHeap, err := helpers.ArgVal[bool](eventObj.Args, "is_heap") if err != nil { return err } - isAnonVma, err := helpers.GetTraceeBoolArgumentByName(eventObj, "is_anon_vma") + isAnonVma, err := helpers.ArgVal[bool](eventObj.Args, "is_anon_vma") if err != nil { return err } From 8d718e2599726f8fc36ee49f1fcc23e0b8a79755 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Thu, 4 Apr 2024 17:47:27 +0300 Subject: [PATCH 14/17] invokeInitEvents: change emit to submit Init events should be created and submitted even if they should not be emitted. --- pkg/ebpf/tracee.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index 812c742c56c8..3c2abfaf87ab 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -1736,7 +1736,6 @@ func (t *Tracee) invokeInitEvents(out chan *trace.Event) { systemInfoEvent := events.InitNamespacesEvent() setMatchedPolicies(&systemInfoEvent, matchedPolicies, t.config.Policies) out <- &systemInfoEvent - _ = t.stats.EventCount.Increment() } // Initial existing containers events (1 event per container) @@ -1748,7 +1747,6 @@ func (t *Tracee) invokeInitEvents(out chan *trace.Event) { event := &(existingContainerEvents[i]) setMatchedPolicies(event, matchedPolicies, t.config.Policies) out <- event - _ = t.stats.EventCount.Increment() } } From ea76307244f0bcf0b6ee17743c728f69d82840eb Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Thu, 4 Apr 2024 17:55:02 +0300 Subject: [PATCH 15/17] Remove usage of bpf_find_vma The usage of this helper prevents the program from being loaded in older kernels, which prevents tracee from running. The alternative VMA lookup logic works only for RB trees (pre 6.1), so this event simply does not generate any output on newer kernels. --- pkg/ebpf/c/common/kconfig.h | 3 +- pkg/ebpf/c/common/memory.h | 52 ++++++------------- pkg/ebpf/c/tracee.bpf.c | 14 +---- pkg/ebpf/event_filters.go | 2 +- pkg/ebpf/initialization/kconfig.go | 4 ++ .../e2e-check_syscall_source.go | 31 +++++++++++ 6 files changed, 55 insertions(+), 51 deletions(-) diff --git a/pkg/ebpf/c/common/kconfig.h b/pkg/ebpf/c/common/kconfig.h index d28893496d1d..e0e53f5bbe43 100644 --- a/pkg/ebpf/c/common/kconfig.h +++ b/pkg/ebpf/c/common/kconfig.h @@ -9,7 +9,8 @@ enum kconfig_key_e { - ARCH_HAS_SYSCALL_WRAPPER = 1000U + ARCH_HAS_SYSCALL_WRAPPER = 1000U, + MMU = 1001U }; // PROTOTYPES diff --git a/pkg/ebpf/c/common/memory.h b/pkg/ebpf/c/common/memory.h index 3f8142b2b057..d8a7aca2375b 100644 --- a/pkg/ebpf/c/common/memory.h +++ b/pkg/ebpf/c/common/memory.h @@ -4,13 +4,10 @@ #include #include +#include // PROTOTYPES -typedef long (*vma_callback_fn)(struct task_struct *task, - struct vm_area_struct *vma, - void *callback_ctx); - statfunc struct mm_struct *get_mm_from_task(struct task_struct *); statfunc unsigned long get_arg_start_from_mm(struct mm_struct *); statfunc unsigned long get_arg_end_from_mm(struct mm_struct *); @@ -18,7 +15,7 @@ statfunc unsigned long get_env_start_from_mm(struct mm_struct *); statfunc unsigned long get_env_end_from_mm(struct mm_struct *); statfunc unsigned long get_vma_flags(struct vm_area_struct *); statfunc unsigned long get_vma_start(struct vm_area_struct *); -statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn, void *cb_ctx); +statfunc struct vm_area_struct *find_vma(struct task_struct *task, u64 addr); statfunc bool vma_is_stack(struct vm_area_struct *vma); statfunc bool vma_is_heap(struct vm_area_struct *vma); @@ -75,42 +72,26 @@ statfunc unsigned long get_vma_start(struct vm_area_struct *vma) */ #define MAX_VMA_RB_TREE_DEPTH 25 -/** - * Given a task, find the first VMA which contains the given address, - * and call the specified callback function with the found VMA - * and the specified context. - * A callback function is required becuase this function potentially uses - * bpf_find_vma(), which requires a callback function. - * - * A generic callback function which receives a `struct vm_area_struct **` - * as its context and saves the found VMA to it is available in the main - * eBPF source file (tracee.bpf.c:find_vma_callback). - * - * See the check_syscall_source function for a usage example. - * - * DISCLAIMER: on systems with no MMU, multiple VMAs may contain the same address. - * Be aware that this function will call the callback only for the first VMA it finds. - */ -statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn, void *cb_ctx) +// Given a task, find the first VMA which contains the given address. +statfunc struct vm_area_struct *find_vma(struct task_struct *task, u64 addr) { /** - * From kernel version 6.1, the data structure with which VMAs + * TODO: from kernel version 6.1, the data structure with which VMAs * are managed changed from an RB tree to a maple tree. - * In version 5.17 the "bpf_find_vma" helper was added. - * This means that if the helper does not exist, we can assume - * that the RB tree structure is used. + * We currently don't support finding VMAs on such systems. */ - - if (bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_find_vma)) { - bpf_find_vma(task, addr, cb_fn, cb_ctx, 0); - return; + struct mm_struct *mm = BPF_CORE_READ(task, mm); + if (!bpf_core_field_exists(mm->mm_rb)) + return NULL; + + // TODO: we don't support NOMMU systems yet (looking up VMAs on them requires walking the VMA + // linked list) + if (!get_kconfig(MMU)) { + return NULL; } - // bpf_find_vma doesn't exist, we can assume the VMAs are stored in an RB tree. - // This logic is based on the find_vma() function in mm/mmap.c - struct vm_area_struct *vma = NULL; - struct rb_node *rb_node = BPF_CORE_READ(task, mm->mm_rb.rb_node); + struct rb_node *rb_node = BPF_CORE_READ(mm, mm_rb.rb_node); #pragma unroll for (int i = 0; i < MAX_VMA_RB_TREE_DEPTH; i++) { @@ -132,8 +113,7 @@ statfunc void find_vma(struct task_struct *task, u64 addr, vma_callback_fn cb_fn rb_node = BPF_CORE_READ(rb_node, rb_right); } - if (vma != NULL) - cb_fn(task, vma, cb_ctx); + return vma; } statfunc bool vma_is_stack(struct vm_area_struct *vma) diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 1e54cc3daf5a..964d59edafea 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5157,13 +5157,6 @@ statfunc enum vma_type get_vma_type(struct vm_area_struct *vma) return VMA_OTHER; } -static long find_vma_callback(struct task_struct *task, struct vm_area_struct *vma, void *ctx) -{ - struct vm_area_struct **pvma = (struct vm_area_struct **) ctx; - *pvma = vma; - return 0; -} - SEC("raw_tracepoint/check_syscall_source") int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) { @@ -5188,10 +5181,6 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) if (!should_submit(CHECK_SYSCALL_SOURCE, p.event)) goto out; - struct task_struct *task_btf = bpf_get_current_task_btf(); - if (task_btf == NULL) - goto out; - // Get instruction pointer struct pt_regs *regs = (struct pt_regs *) ctx->args[0]; #if defined(bpf_target_x86) @@ -5201,8 +5190,7 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) #endif // Find VMA which contains the instruction pointer - struct vm_area_struct *vma = NULL; - find_vma(task_btf, ip, find_vma_callback, &vma); + struct vm_area_struct *vma = find_vma(task, ip); if (vma == NULL) goto out; diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go index d23f6ead6dbf..6d373cb4aa48 100644 --- a/pkg/ebpf/event_filters.go +++ b/pkg/ebpf/event_filters.go @@ -40,7 +40,7 @@ func (t *Tracee) populateEventFilterMaps() error { err := handler(eventFilters, t.bpfModule) if err != nil { logger.Errorw("Failed to handle event filter for event " + events.Core.GetDefinitionByID(eventID).GetName() + ", err: " + err.Error()) - t.cancelEventFromEventState(eventID) + t.eventsDependencies.RemoveEvent(eventID) } } return nil diff --git a/pkg/ebpf/initialization/kconfig.go b/pkg/ebpf/initialization/kconfig.go index b0efbc8c4f12..7690496dd4d3 100644 --- a/pkg/ebpf/initialization/kconfig.go +++ b/pkg/ebpf/initialization/kconfig.go @@ -1,6 +1,7 @@ package initialization import ( + "github.com/aquasecurity/libbpfgo/helpers" "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/logger" "github.com/aquasecurity/tracee/pkg/utils/environment" @@ -10,10 +11,12 @@ import ( // Add here all kconfig variables used within tracee.bpf.c const ( CONFIG_ARCH_HAS_SYSCALL_WRAPPER environment.KernelConfigOption = iota + environment.CUSTOM_OPTION_START + CONFIG_MMU helpers.KernelConfigOption = iota + helpers.CUSTOM_OPTION_START ) var kconfigUsed = map[environment.KernelConfigOption]string{ CONFIG_ARCH_HAS_SYSCALL_WRAPPER: "CONFIG_ARCH_HAS_SYSCALL_WRAPPER", + CONFIG_MMU: "CONFIG_MMU", } // LoadKconfigValues load all kconfig variables used within tracee.bpf.c @@ -33,6 +36,7 @@ func LoadKconfigValues(kc *environment.KernelConfig) (map[environment.KernelConf values[key] = environment.UNDEFINED } values[CONFIG_ARCH_HAS_SYSCALL_WRAPPER] = environment.BUILTIN // assume CONFIG_ARCH_HAS_SYSCALL_WRAPPER is a BUILTIN option + values[CONFIG_MMU] = helpers.BUILTIN // assume CONFIG_MMU is a BUILTIN option } else { for key := range kconfigUsed { values[key] = kc.GetValue(key) // undefined, builtin OR module diff --git a/tests/e2e-inst-signatures/e2e-check_syscall_source.go b/tests/e2e-inst-signatures/e2e-check_syscall_source.go index b328f79726fa..1ef649db97b0 100644 --- a/tests/e2e-inst-signatures/e2e-check_syscall_source.go +++ b/tests/e2e-inst-signatures/e2e-check_syscall_source.go @@ -3,6 +3,8 @@ package main import ( "fmt" + libbfgo "github.com/aquasecurity/libbpfgo/helpers" + "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/protocol" @@ -11,6 +13,7 @@ import ( type e2eCheckSyscallSource struct { cb detect.SignatureHandler + hasMapleTree bool foundStack bool foundHeap bool foundAnonVma bool @@ -18,6 +21,20 @@ type e2eCheckSyscallSource struct { func (sig *e2eCheckSyscallSource) Init(ctx detect.SignatureContext) error { sig.cb = ctx.Callback + + // Find if this system uses maple trees to manage VMAs. + // If so we don't expect any check_syscall_source event to be submitted. + ksyms, err := libbfgo.NewKernelSymbolTable() + if err != nil { + return err + } + _, err = ksyms.GetSymbolByName("mt_find") + if err != nil { + sig.hasMapleTree = false + } else { + sig.hasMapleTree = true + } + return nil } @@ -35,6 +52,7 @@ func (sig *e2eCheckSyscallSource) GetMetadata() (detect.SignatureMetadata, error func (sig *e2eCheckSyscallSource) GetSelectedEvents() ([]detect.SignatureEventSelector, error) { return []detect.SignatureEventSelector{ {Source: "tracee", Name: "check_syscall_source"}, + {Source: "tracee", Name: "init_namespaces"}, // This event always happens so we can pass the test on unsupported kernels }, nil } @@ -45,6 +63,19 @@ func (sig *e2eCheckSyscallSource) OnEvent(event protocol.Event) error { } switch eventObj.EventName { + case "init_namespaces": + // If the system uses maple trees we won't get any check_syscall_source events, pass the test + if sig.hasMapleTree { + m, _ := sig.GetMetadata() + + sig.cb(&detect.Finding{ + SigMetadata: m, + Event: event, + Data: map[string]interface{}{}, + }) + + return nil + } case "check_syscall_source": syscall, err := helpers.GetTraceeStringArgumentByName(eventObj, "syscall") if err != nil { From 49638e51a0599c9ac78bfed7813409ed48a7862e Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Sun, 7 Apr 2024 13:31:09 +0300 Subject: [PATCH 16/17] Remove unused signature helper --- pkg/ebpf/c/tracee.bpf.c | 15 +++++++-------- pkg/ebpf/event_filters.go | 9 +++++---- pkg/ebpf/tracee.go | 2 ++ signatures/helpers/arguments_helpers.go | 14 -------------- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 964d59edafea..a3557966ac81 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5160,11 +5160,9 @@ statfunc enum vma_type get_vma_type(struct vm_area_struct *vma) SEC("raw_tracepoint/check_syscall_source") int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) { - program_data_t p = {}; - if (!init_program_data(&p, ctx)) - return 0; - - // Get syscall ID + // 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)) { @@ -5175,10 +5173,11 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) id = *id_64; } - if (!should_trace(&p)) + program_data_t p = {}; + if (!init_program_data(&p, ctx, CHECK_SYSCALL_SOURCE)) goto out; - if (!should_submit(CHECK_SYSCALL_SOURCE, p.event)) + if (!evaluate_scope_filters(&p)) goto out; // Get instruction pointer @@ -5222,7 +5221,7 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx) save_to_submit_buf(&p.event->args_buf, &is_heap, sizeof(is_heap), 3); save_to_submit_buf(&p.event->args_buf, &is_anon, sizeof(is_anon), 4); - events_perf_submit(&p, CHECK_SYSCALL_SOURCE, 0); + events_perf_submit(&p, 0); out: // Call sys_enter_init_tail which we preceded diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go index 6d373cb4aa48..5cbf6e2d99a8 100644 --- a/pkg/ebpf/event_filters.go +++ b/pkg/ebpf/event_filters.go @@ -13,7 +13,7 @@ import ( "github.com/aquasecurity/tracee/pkg/logger" ) -type eventFilterHandler func(eventFilters map[string]filters.Filter, bpfModule *bpf.Module) error +type eventFilterHandler func(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error var eventFilterHandlers = map[events.ID]eventFilterHandler{ events.CheckSyscallSource: populateMapsCheckSyscallSource, @@ -24,8 +24,9 @@ func (t *Tracee) populateEventFilterMaps() error { // Iterate through registerd event filter handlers for eventID, handler := range eventFilterHandlers { // Construct filters for this event - eventFilters := map[string]filters.Filter{} - for _, p := range t.config.Policies.Map() { + eventFilters := map[string]filters.Filter[*filters.StringFilter]{} + for it := t.config.Policies.CreateAllIterator(); it.HasNext(); { + p := it.Next() f := p.ArgFilter.GetEventFilters(eventID) if len(f) == 0 { continue @@ -46,7 +47,7 @@ func (t *Tracee) populateEventFilterMaps() error { return nil } -func populateMapsCheckSyscallSource(eventFilters map[string]filters.Filter, bpfModule *bpf.Module) error { +func populateMapsCheckSyscallSource(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error { // Get syscalls to trace syscallsFilter, ok := eventFilters["syscall"].(*filters.StringFilter) if !ok { diff --git a/pkg/ebpf/tracee.go b/pkg/ebpf/tracee.go index 3c2abfaf87ab..812c742c56c8 100644 --- a/pkg/ebpf/tracee.go +++ b/pkg/ebpf/tracee.go @@ -1736,6 +1736,7 @@ func (t *Tracee) invokeInitEvents(out chan *trace.Event) { systemInfoEvent := events.InitNamespacesEvent() setMatchedPolicies(&systemInfoEvent, matchedPolicies, t.config.Policies) out <- &systemInfoEvent + _ = t.stats.EventCount.Increment() } // Initial existing containers events (1 event per container) @@ -1747,6 +1748,7 @@ func (t *Tracee) invokeInitEvents(out chan *trace.Event) { event := &(existingContainerEvents[i]) setMatchedPolicies(event, matchedPolicies, t.config.Policies) out <- event + _ = t.stats.EventCount.Increment() } } diff --git a/signatures/helpers/arguments_helpers.go b/signatures/helpers/arguments_helpers.go index 2b5e17e89953..c975078a7e3f 100644 --- a/signatures/helpers/arguments_helpers.go +++ b/signatures/helpers/arguments_helpers.go @@ -72,20 +72,6 @@ func GetTraceeIntArgumentByName(event trace.Event, argName string) (int, error) return 0, fmt.Errorf("can't convert argument %v to int", argName) } -// GetTraceeBoolArgumentByName gets the argument from `event` matching the `argName`, casted as bool. -func GetTraceeBoolArgumentByName(event trace.Event, argName string) (bool, error) { - arg, err := GetTraceeArgumentByName(event, argName, GetArgOps{DefaultArgs: false}) - if err != nil { - return false, err - } - argBool, ok := arg.Value.(bool) - if ok { - return argBool, nil - } - - return false, fmt.Errorf("can't convert argument %v to bool", argName) -} - // GetTraceeSliceStringArgumentByName gets the argument matching the "argName" given from the event "argv" field, casted as []string. func GetTraceeSliceStringArgumentByName(event trace.Event, argName string) ([]string, error) { arg, err := GetTraceeArgumentByName(event, argName, GetArgOps{DefaultArgs: false}) From eb312fe8c57eef2284b0d434fa16eef51b97c931 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Wed, 5 Jun 2024 13:02:41 +0300 Subject: [PATCH 17/17] Bugfix: 32-bit compat program false positive 32-bit programs on x86-64 may use a fast syscall method, whose code resides in the VDSO VMA. This VMA is not file backed, so it was incorrectly detected as anonymous memory. --- go.mod | 1 + go.sum | 2 ++ pkg/ebpf/c/common/common.h | 2 +- pkg/ebpf/c/common/memory.h | 15 +++++++++++++++ pkg/ebpf/c/tracee.bpf.c | 3 ++- pkg/ebpf/c/vmlinux.h | 12 ++++++++++++ pkg/ebpf/event_filters.go | 7 +++++-- pkg/ebpf/initialization/kconfig.go | 5 ++--- .../e2e-check_syscall_source.go | 6 +++--- 9 files changed, 43 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 9868b2e641a9..3373426b20ad 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/IBM/fluent-forward-go v0.2.2 github.com/Masterminds/sprig/v3 v3.2.3 github.com/aquasecurity/libbpfgo v0.7.0-libbpf-1.4 + github.com/aquasecurity/libbpfgo/helpers v0.4.5 github.com/aquasecurity/tracee/api v0.0.0-20240613134034-89d2d4fc7689 github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20240607205742-90c301111aee github.com/aquasecurity/tracee/types v0.0.0-20240607205742-90c301111aee diff --git a/go.sum b/go.sum index 7ecda3529cf3..5f942860dc30 100644 --- a/go.sum +++ b/go.sum @@ -406,6 +406,8 @@ github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVb github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aquasecurity/libbpfgo v0.7.0-libbpf-1.4 h1:rQ94U12Xlz2tncE8Rxnw3vpp/9hgUIEu3/Lv0/XQM0Q= github.com/aquasecurity/libbpfgo v0.7.0-libbpf-1.4/go.mod h1:iI7QCIZ3kXG0MR+FHsDZck6cYs1y1HyZP3sMObBg0sk= +github.com/aquasecurity/libbpfgo/helpers v0.4.5 h1:eCoLclL3yqv4N9jqGL3T/ckrLPms2r13C4V2xtU75yc= +github.com/aquasecurity/libbpfgo/helpers v0.4.5/go.mod h1:j/TQLmsZpOIdF3CnJODzYngG4yu1YoDCoRMELxkQSSA= github.com/aquasecurity/tracee/api v0.0.0-20240613134034-89d2d4fc7689 h1:mAOehSHrqAZ4lvn3AYgDxn+aDTKrv81ghNnGlteDB00= github.com/aquasecurity/tracee/api v0.0.0-20240613134034-89d2d4fc7689/go.mod h1:km0QNkaoOVxU/IYF/Pw/ju/2SO1mYn+HJOIyMDtnfkE= github.com/aquasecurity/tracee/signatures/helpers v0.0.0-20240607205742-90c301111aee h1:1KJy6Z2bSpmKQVPShU7hhbXgGVOgMwvzf9rjoWMTYEg= diff --git a/pkg/ebpf/c/common/common.h b/pkg/ebpf/c/common/common.h index a101ea92b8eb..e73edf6a13e4 100644 --- a/pkg/ebpf/c/common/common.h +++ b/pkg/ebpf/c/common/common.h @@ -62,7 +62,7 @@ static __inline int has_prefix(char *prefix, char *str, int n) } // prefix is too long - return 0; + return 1; } #endif diff --git a/pkg/ebpf/c/common/memory.h b/pkg/ebpf/c/common/memory.h index d8a7aca2375b..491617f2a5fe 100644 --- a/pkg/ebpf/c/common/memory.h +++ b/pkg/ebpf/c/common/memory.h @@ -18,6 +18,8 @@ statfunc unsigned long get_vma_start(struct vm_area_struct *); statfunc struct vm_area_struct *find_vma(struct task_struct *task, u64 addr); statfunc bool vma_is_stack(struct vm_area_struct *vma); statfunc bool vma_is_heap(struct vm_area_struct *vma); +statfunc bool vma_is_anon(struct vm_area_struct *vma); +statfunc bool vma_is_vdso(struct vm_area_struct *vma); // FUNCTIONS @@ -156,4 +158,17 @@ statfunc bool vma_is_anon(struct vm_area_struct *vma) return BPF_CORE_READ(vma, vm_file) == NULL; } +statfunc bool vma_is_vdso(struct vm_area_struct *vma) +{ + struct vm_special_mapping *special_mapping = + (struct vm_special_mapping *) BPF_CORE_READ(vma, vm_private_data); + if (special_mapping == NULL) + return false; + + // read only 6 characters (7 with NULL terminator), enough to compare with "[vdso]" + char mapping_name[7]; + bpf_probe_read_str(&mapping_name, 7, BPF_CORE_READ(special_mapping, name)); + return has_prefix("[vdso]", mapping_name, 6); +} + #endif diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index a3557966ac81..174095b7e437 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5151,8 +5151,9 @@ statfunc enum vma_type get_vma_type(struct vm_area_struct *vma) if (vma_is_heap(vma)) return VMA_HEAP; - if (vma_is_anon(vma)) + if (vma_is_anon(vma) && !vma_is_vdso(vma)) { return VMA_ANON; + } return VMA_OTHER; } diff --git a/pkg/ebpf/c/vmlinux.h b/pkg/ebpf/c/vmlinux.h index a273488003ce..4ed3e0e99dd5 100644 --- a/pkg/ebpf/c/vmlinux.h +++ b/pkg/ebpf/c/vmlinux.h @@ -289,6 +289,16 @@ struct rb_node { struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); +struct vm_area_struct; + +struct vm_operations_struct { + const char *(*name)(struct vm_area_struct *vma); +}; + +struct vm_special_mapping { + const char *name; +}; + struct vm_area_struct { union { struct { @@ -299,7 +309,9 @@ struct vm_area_struct { struct rb_node vm_rb; struct mm_struct *vm_mm; long unsigned int vm_flags; + const struct vm_operations_struct *vm_ops; struct file *vm_file; + void *vm_private_data; }; typedef unsigned int __kernel_gid32_t; diff --git a/pkg/ebpf/event_filters.go b/pkg/ebpf/event_filters.go index 5cbf6e2d99a8..a829fd76a188 100644 --- a/pkg/ebpf/event_filters.go +++ b/pkg/ebpf/event_filters.go @@ -27,7 +27,7 @@ func (t *Tracee) populateEventFilterMaps() error { eventFilters := map[string]filters.Filter[*filters.StringFilter]{} for it := t.config.Policies.CreateAllIterator(); it.HasNext(); { p := it.Next() - f := p.ArgFilter.GetEventFilters(eventID) + f := p.DataFilter.GetEventFilters(eventID) if len(f) == 0 { continue } @@ -41,7 +41,10 @@ func (t *Tracee) populateEventFilterMaps() error { err := handler(eventFilters, t.bpfModule) if err != nil { logger.Errorw("Failed to handle event filter for event " + events.Core.GetDefinitionByID(eventID).GetName() + ", err: " + err.Error()) - t.eventsDependencies.RemoveEvent(eventID) + err = t.eventsDependencies.RemoveEvent(eventID) + if err != nil { + return err + } } } return nil diff --git a/pkg/ebpf/initialization/kconfig.go b/pkg/ebpf/initialization/kconfig.go index 7690496dd4d3..d86e06d9016c 100644 --- a/pkg/ebpf/initialization/kconfig.go +++ b/pkg/ebpf/initialization/kconfig.go @@ -1,7 +1,6 @@ package initialization import ( - "github.com/aquasecurity/libbpfgo/helpers" "github.com/aquasecurity/tracee/pkg/errfmt" "github.com/aquasecurity/tracee/pkg/logger" "github.com/aquasecurity/tracee/pkg/utils/environment" @@ -11,7 +10,7 @@ import ( // Add here all kconfig variables used within tracee.bpf.c const ( CONFIG_ARCH_HAS_SYSCALL_WRAPPER environment.KernelConfigOption = iota + environment.CUSTOM_OPTION_START - CONFIG_MMU helpers.KernelConfigOption = iota + helpers.CUSTOM_OPTION_START + CONFIG_MMU environment.KernelConfigOption = iota + environment.CUSTOM_OPTION_START ) var kconfigUsed = map[environment.KernelConfigOption]string{ @@ -36,7 +35,7 @@ func LoadKconfigValues(kc *environment.KernelConfig) (map[environment.KernelConf values[key] = environment.UNDEFINED } values[CONFIG_ARCH_HAS_SYSCALL_WRAPPER] = environment.BUILTIN // assume CONFIG_ARCH_HAS_SYSCALL_WRAPPER is a BUILTIN option - values[CONFIG_MMU] = helpers.BUILTIN // assume CONFIG_MMU is a BUILTIN option + values[CONFIG_MMU] = environment.BUILTIN // assume CONFIG_MMU is a BUILTIN option } else { for key := range kconfigUsed { values[key] = kc.GetValue(key) // undefined, builtin OR module diff --git a/tests/e2e-inst-signatures/e2e-check_syscall_source.go b/tests/e2e-inst-signatures/e2e-check_syscall_source.go index 1ef649db97b0..3a198754ee89 100644 --- a/tests/e2e-inst-signatures/e2e-check_syscall_source.go +++ b/tests/e2e-inst-signatures/e2e-check_syscall_source.go @@ -3,7 +3,7 @@ package main import ( "fmt" - libbfgo "github.com/aquasecurity/libbpfgo/helpers" + libbpfgo "github.com/aquasecurity/libbpfgo/helpers" "github.com/aquasecurity/tracee/signatures/helpers" "github.com/aquasecurity/tracee/types/detect" @@ -24,11 +24,11 @@ func (sig *e2eCheckSyscallSource) Init(ctx detect.SignatureContext) error { // Find if this system uses maple trees to manage VMAs. // If so we don't expect any check_syscall_source event to be submitted. - ksyms, err := libbfgo.NewKernelSymbolTable() + ksyms, err := libbpfgo.NewKernelSymbolsMap() if err != nil { return err } - _, err = ksyms.GetSymbolByName("mt_find") + _, err = ksyms.GetSymbolByName("system", "mt_find") if err != nil { sig.hasMapleTree = false } else {