diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 4f7dd9815d09..aded8893bf53 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -78,6 +78,7 @@ env: SECURITY_PATH_NOTIFY SET_FS_PWD SUSPICIOUS_SYSCALL_SOURCE + STACK_PIVOT jobs: # # DOC VERIFICATION diff --git a/docs/docs/events/builtin/extra/stack_pivot.md b/docs/docs/events/builtin/extra/stack_pivot.md new file mode 100644 index 000000000000..f90217038dc2 --- /dev/null +++ b/docs/docs/events/builtin/extra/stack_pivot.md @@ -0,0 +1,50 @@ +# stack_pivot + +## Intro + +stack_pivot - An event reporting a syscall that was invoked while the user's stack pointer doesn't point to the stack. + +## Description + +All native code executed makes use of the stack, a region of memory used for storage of function-local data, like function parameters, return address, and local variables. + +A stack overflow vulnerability is a security vulnerability that allows an attacker to write data past the end of a stack allocated buffer, allowing him to overwrite other stack data. This kind of vulnerability could be exploited by overwriting the function return address to a location chosen by the attacker, causing the code at that location to run when the vulnerable function returns. An attacker can write multiple return addresses to the stack such that small code sequences, called gadgets, are executed in a chain dictated by the attacker. This exploitation method is called ROP (return oriented programming). + +One potential limitation of such an exploit is the amount of data the attacker is able to write to the stack - in some cases, it may not be enough to write the full sequence of gadget addresses required to achieve the attacker's goal. To overcome this limitation, the attacker can use the stack pivot technique. This technique involves a gadget that writes an attacker controlled value to the stack pointer, effectively moving the stack to a new location that the attacker is able to write to (and thus achieving a longer ROP chain). + +This event attempts to detect the usage of this technique by checking the stack pointer at the invocation of selected syscalls and detecting cases where it does not point to the original stack. + +This event relies on an event parameter to specify which syscalls should be monitored, to reduce overhead. An example command line usage of this event: + +`tracee --events stack_pivot.args.syscall=open,openat`. + +## Arguments + +- `syscall`:`int`[K] - the syscall which was invoked while the stack pointer doesn't point to the orignal stack. The syscall name is parsed if the `parse-arguments` option is specified. This argument is also used as a parameter to select which syscalls should be checked. +- `sp`:`void *`[K] - the stack pointer at the time of syscall invocation +- `vma_type`:`char *`[K] - a string describing the type of the VMA which contains the address that the stack pointer points to +- `vma_start`:`void *`[K] - the start address of the VMA which contains the address that the stack pointer points to +- `vma_size`:`unsigned long`[K] - the size of the VMA which contains the address that the stack pointer points to +- `vma_flags`:`unsigned long`[K] - the flags of the VMA which contains the address that the stack pointer points to. The flag names are parsed if the `parse-arguments` option is specified. + +## Hooks + +### Individual syscalls + +#### Type + +kprobe + +#### Purpose + +A kprobe is placed on each syscall that was selected using a parameter for this event. The kprobe function analyzes the location pointed to by the stack pointer. + +## Example Use Case + +Detect ROP exploits that use the stack pivot technique. + +## Issues + +The kernel manages the stack for the main thread of each process, but additional threads must create and manage their own stacks. The kernel has no notion of a thread stack, so in order to detect that an address belongs to a thread stack and avoid false positives, thread stacks are tracked by tracee by storing the memory region pointed to by the stack pointer at the time of a new thread's creation. This means that threads created before tracee started are not tracked, and we have no way to differentiate between a regular anonymous memory region and one allocated for the stack in such threads. To avoid false positives, anonymous memory regions are ignored for untracked threads, which may result in false negatives. + + diff --git a/docs/docs/events/builtin/extra/suspicious_syscall_source.md b/docs/docs/events/builtin/extra/suspicious_syscall_source.md index ba4d365253e4..b20b2e5100ef 100644 --- a/docs/docs/events/builtin/extra/suspicious_syscall_source.md +++ b/docs/docs/events/builtin/extra/suspicious_syscall_source.md @@ -24,7 +24,7 @@ To reduce noise in cases where code with significant syscall activity is being d * `syscall`:`int`[K] - the syscall which was invoked from an unusual location. The syscall name is parsed if the `parse-arguments` option is specified. This argument is also used as a parameter to select which syscalls should be checked. * `ip`:`void *`[K] - the address from which the syscall was invoked (instruction pointer of the instruction following the syscall instruction). -* `vma_type`:`char *`[K] - the type of the VMA which contains the code that triggered the syscall (one of *stack*/*heap*/*anonymous*) +* `vma_type`:`char *`[K] - a string describing the type of the VMA which contains the code that triggered the syscall * `vma_start`:`void *`[K] - the start address of the VMA which contains the code that triggered the syscall * `vma_size`:`unsigned long`[K] - the size of the VMA which contains the code that triggered the syscall * `vma_flags`:`unsigned long`[K] - the flags of the VMA which contains the code that triggered the syscall. The flag names are parsed if the `parse-arguments` option is specified. diff --git a/pkg/ebpf/c/common/context.h b/pkg/ebpf/c/common/context.h index 09841fbb85ff..5732173c198d 100644 --- a/pkg/ebpf/c/common/context.h +++ b/pkg/ebpf/c/common/context.h @@ -20,6 +20,7 @@ statfunc int init_program_data(program_data_t *, void *, u32); statfunc int init_tailcall_program_data(program_data_t *, void *); statfunc bool reset_event(event_data_t *, u32); statfunc void reset_event_args_buf(event_data_t *); +statfunc bool thread_stack_tracked(task_info_t *); // FUNCTIONS @@ -262,4 +263,9 @@ statfunc bool reset_event(event_data_t *event, u32 event_id) return true; } +statfunc bool thread_stack_tracked(task_info_t *task_info) +{ + return task_info->stack.start != 0 && task_info->stack.end != 0; +} + #endif diff --git a/pkg/ebpf/c/common/memory.h b/pkg/ebpf/c/common/memory.h index 6eadf9972d1f..9bcf0f52b550 100644 --- a/pkg/ebpf/c/common/memory.h +++ b/pkg/ebpf/c/common/memory.h @@ -8,12 +8,12 @@ enum vma_type { VMA_FILE_BACKED, - VMA_STACK, + VMA_ANON, + VMA_MAIN_STACK, + VMA_THREAD_STACK, VMA_HEAP, VMA_GOLANG_HEAP, - VMA_THREAD_STACK, VMA_VDSO, - VMA_ANON, VMA_UNKNOWN, }; @@ -27,8 +27,8 @@ statfunc unsigned long get_env_end_from_mm(struct mm_struct *); statfunc unsigned long get_vma_flags(struct vm_area_struct *); statfunc struct vm_area_struct *find_vma(void *ctx, struct task_struct *task, u64 addr); statfunc bool vma_is_file_backed(struct vm_area_struct *vma); -statfunc bool vma_is_initial_stack(struct vm_area_struct *vma); -statfunc bool vma_is_initial_heap(struct vm_area_struct *vma); +statfunc bool vma_is_main_stack(struct vm_area_struct *vma); +statfunc bool vma_is_main_heap(struct vm_area_struct *vma); statfunc bool vma_is_anon(struct vm_area_struct *vma); statfunc bool vma_is_golang_heap(struct vm_area_struct *vma); statfunc bool vma_is_thread_stack(task_info_t *task_info, struct vm_area_struct *vma); @@ -133,7 +133,7 @@ statfunc bool vma_is_file_backed(struct vm_area_struct *vma) return BPF_CORE_READ(vma, vm_file) != NULL; } -statfunc bool vma_is_initial_stack(struct vm_area_struct *vma) +statfunc bool vma_is_main_stack(struct vm_area_struct *vma) { struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); if (vm_mm == NULL) @@ -150,7 +150,7 @@ statfunc bool vma_is_initial_stack(struct vm_area_struct *vma) return false; } -statfunc bool vma_is_initial_heap(struct vm_area_struct *vma) +statfunc bool vma_is_main_heap(struct vm_area_struct *vma) { struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm); if (vm_mm == NULL) @@ -231,10 +231,10 @@ statfunc enum vma_type get_vma_type(task_info_t *task_info, struct vm_area_struc if (vma_is_file_backed(vma)) return VMA_FILE_BACKED; - if (vma_is_initial_stack(vma)) - return VMA_STACK; + if (vma_is_main_stack(vma)) + return VMA_MAIN_STACK; - if (vma_is_initial_heap(vma)) + if (vma_is_main_heap(vma)) return VMA_HEAP; if (vma_is_anon(vma)) { @@ -253,4 +253,28 @@ statfunc enum vma_type get_vma_type(task_info_t *task_info, struct vm_area_struc return VMA_UNKNOWN; } +statfunc const char *get_vma_type_str(enum vma_type vma_type) +{ + switch (vma_type) { + case VMA_FILE_BACKED: + return "file backed"; + case VMA_ANON: + return "anonymous"; + case VMA_MAIN_STACK: + return "main stack"; + case VMA_THREAD_STACK: + return "thread stack"; + case VMA_HEAP: + return "heap"; + case VMA_GOLANG_HEAP: + // Goroutine stacks are allocated on the golang heap + return "golang heap/stack"; + case VMA_VDSO: + return "vdso"; + case VMA_UNKNOWN: + default: + return "unknown"; + } +} + #endif diff --git a/pkg/ebpf/c/tracee.bpf.c b/pkg/ebpf/c/tracee.bpf.c index 1146c440b287..764f3d7da12c 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -575,7 +575,7 @@ statfunc void update_thread_stack(void *ctx, task_info_t *task_info, struct task if (get_task_flags(task) & PF_KTHREAD || BPF_CORE_READ(task, pid) == BPF_CORE_READ(task, tgid)) task_info->stack = (address_range_t){0}; - // Get user SP of new thread + // Get user SP of new thread #if defined(bpf_target_x86) struct fork_frame *fork_frame = (struct fork_frame *) BPF_CORE_READ(task, thread.sp); u64 thread_sp = BPF_CORE_READ(fork_frame, regs.sp); @@ -592,10 +592,8 @@ statfunc void update_thread_stack(void *ctx, task_info_t *task_info, struct task return; // Add the VMA address range to the task info - task_info->stack = (address_range_t){ - .start = BPF_CORE_READ(vma, vm_start), - .end = BPF_CORE_READ(vma, vm_end) - }; + task_info->stack = + (address_range_t){.start = BPF_CORE_READ(vma, vm_start), .end = BPF_CORE_READ(vma, vm_end)}; } // trace/events/sched.h: TP_PROTO(struct task_struct *parent, struct task_struct *child) @@ -638,7 +636,8 @@ int tracepoint__sched__sched_process_fork(struct bpf_raw_tracepoint_args *ctx) task->context.start_time = child_start_time; // Track thread stack if needed - if (event_is_selected(SUSPICIOUS_SYSCALL_SOURCE, p.event->context.policies_version)) + if (event_is_selected(SUSPICIOUS_SYSCALL_SOURCE, p.event->context.policies_version) || + event_is_selected(STACK_PIVOT, p.event->context.policies_version)) update_thread_stack(ctx, task, child); // Update the proc_info_map with the new process's info (from parent) @@ -5287,6 +5286,13 @@ struct { __type(value, u32); } suspicious_syscall_source_syscalls SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_EVENT_ID); + __type(key, u32); + __type(value, u32); +} stack_pivot_syscalls SEC(".maps"); + statfunc void check_suspicious_syscall_source(void *ctx, struct pt_regs *regs, u32 syscall) { program_data_t p = {}; @@ -5311,9 +5317,6 @@ statfunc void check_suspicious_syscall_source(void *ctx, struct pt_regs *regs, u if (vma_is_file_backed(vma)) return; - // Get VMA type - enum vma_type vma_type = get_vma_type(p.task_info, vma); - // 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 = syscall, @@ -5327,40 +5330,66 @@ statfunc void check_suspicious_syscall_source(void *ctx, struct pt_regs *regs, u // This key already exists, no need to submit the same syscall-vma-process combination again return; - char *vma_type_str; + const char *vma_type_str = get_vma_type_str(get_vma_type(p.task_info, vma)); + unsigned long vma_start = BPF_CORE_READ(vma, vm_start); + unsigned long vma_size = BPF_CORE_READ(vma, vm_end) - vma_start; + unsigned long vma_flags = BPF_CORE_READ(vma, vm_flags); - switch (vma_type) { - case VMA_STACK: - vma_type_str = "main stack"; - break; - case VMA_THREAD_STACK: - vma_type_str = "thread stack"; - break; - case VMA_HEAP: - vma_type_str = "heap"; - break; - case VMA_GOLANG_HEAP: - // Goroutine stacks are allocated on the golang heap - vma_type_str = "golang heap/stack"; - break; - case VMA_ANON: - vma_type_str = "anonymous"; - break; - case VMA_VDSO: - vma_type_str = "vdso"; - break; - default: - vma_type_str = "unknown"; - break; - } + 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_str_to_buf(&p.event->args_buf, (void *) vma_type_str, 2); + save_to_submit_buf(&p.event->args_buf, &vma_start, sizeof(vma_start), 3); + save_to_submit_buf(&p.event->args_buf, &vma_size, sizeof(vma_size), 4); + save_to_submit_buf(&p.event->args_buf, &vma_flags, sizeof(vma_flags), 5); + events_perf_submit(&p, 0); +} + +statfunc void check_stack_pivot(void *ctx, struct pt_regs *regs, u32 syscall) +{ + program_data_t p = {}; + + if (!init_program_data(&p, ctx, STACK_PIVOT)) + return; + + if (!evaluate_scope_filters(&p)) + return; + + // Get stack pointer + u64 sp = PT_REGS_SP_CORE(regs); + + // Find VMA which contains the stack pointer + struct task_struct *task = (struct task_struct *) bpf_get_current_task(); + if (unlikely(task == NULL)) + return; + struct vm_area_struct *vma = find_vma(ctx, task, sp); + if (unlikely(vma == NULL)) + return; + + // Check if the stack pointer points to the stack region. + // + // Goroutine stacks are allocated on golang's heap, which means that an + // exploit performing a stack pivot on a go program will result in a false + // negative if the new stack location is on golang's heap. + // + // To identify thread stacks, they need to be tracked when new threads are + // created. This means that we cannot identify stacks of threads that were + // created before tracee started. To avoid false positives, we ignore events + // where the stack pointer's VMA might be a thread stack but it was not + // tracked for this thread. This may result in false negatives. + enum vma_type vma_type = get_vma_type(p.task_info, vma); + if (vma_type == VMA_MAIN_STACK || vma_type == VMA_GOLANG_HEAP || vma_type == VMA_THREAD_STACK || + (vma_type == VMA_ANON && !thread_stack_tracked(p.task_info))) + return; + + const char *vma_type_str = get_vma_type_str(vma_type); unsigned long vma_start = BPF_CORE_READ(vma, vm_start); unsigned long vma_size = BPF_CORE_READ(vma, vm_end) - vma_start; unsigned long vma_flags = BPF_CORE_READ(vma, vm_flags); 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_str_to_buf(&p.event->args_buf, vma_type_str, 2); + save_to_submit_buf(&p.event->args_buf, &sp, sizeof(sp), 1); + save_str_to_buf(&p.event->args_buf, (void *) vma_type_str, 2); save_to_submit_buf(&p.event->args_buf, &vma_start, sizeof(vma_start), 3); save_to_submit_buf(&p.event->args_buf, &vma_size, sizeof(vma_size), 4); save_to_submit_buf(&p.event->args_buf, &vma_flags, sizeof(vma_flags), 5); @@ -5382,6 +5411,9 @@ int BPF_KPROBE(syscall_checker) if (bpf_map_lookup_elem(&suspicious_syscall_source_syscalls, &syscall) != NULL) check_suspicious_syscall_source(ctx, regs, syscall); + if (bpf_map_lookup_elem(&stack_pivot_syscalls, &syscall) != NULL) + check_stack_pivot(ctx, regs, syscall); + return 0; } diff --git a/pkg/ebpf/c/types.h b/pkg/ebpf/c/types.h index b477b7cb5642..521f5593c4c1 100644 --- a/pkg/ebpf/c/types.h +++ b/pkg/ebpf/c/types.h @@ -125,6 +125,7 @@ enum event_id_e SECURITY_PATH_NOTIFY, SET_FS_PWD, SUSPICIOUS_SYSCALL_SOURCE, + STACK_PIVOT, HIDDEN_KERNEL_MODULE_SEEKER, MODULE_LOAD, MODULE_FREE, @@ -231,9 +232,10 @@ typedef struct { typedef struct task_info { task_context_t context; syscall_data_t syscall_data; - bool syscall_traced; // indicates that syscall_data is valid - u8 container_state; // the state of the container the task resides in - address_range_t stack; // stack area, only relevant for tasks that aren't group leaders (threads) + bool syscall_traced; // indicates that syscall_data is valid + u8 container_state; // the state of the container the task resides in + address_range_t + stack; // stack area, only relevant for tasks that aren't group leaders (threads) } task_info_t; typedef struct file_id { diff --git a/pkg/ebpf/event_parameters.go b/pkg/ebpf/event_parameters.go index 78a03753e102..4a205572a7c7 100644 --- a/pkg/ebpf/event_parameters.go +++ b/pkg/ebpf/event_parameters.go @@ -17,6 +17,7 @@ type eventParameterHandler func(t *Tracee, eventParams []map[string]filters.Filt var eventParameterHandlers = map[events.ID]eventParameterHandler{ events.SuspiciousSyscallSource: prepareSuspiciousSyscallSource, + events.StackPivot: prepareStackPivot, } // handleEventParameters performs initialization actions according to event parameters, @@ -151,3 +152,7 @@ func registerSyscallChecker(t *Tracee, eventParams []map[string]filters.Filter[* func prepareSuspiciousSyscallSource(t *Tracee, eventParams []map[string]filters.Filter[*filters.StringFilter]) error { return registerSyscallChecker(t, eventParams, "syscall", "suspicious_syscall_source_syscalls") } + +func prepareStackPivot(t *Tracee, eventParams []map[string]filters.Filter[*filters.StringFilter]) error { + return registerSyscallChecker(t, eventParams, "syscall", "stack_pivot_syscalls") +} diff --git a/pkg/events/core.go b/pkg/events/core.go index dbc7bb30f05f..7cb67f000b3e 100644 --- a/pkg/events/core.go +++ b/pkg/events/core.go @@ -107,6 +107,7 @@ const ( SecurityPathNotify SetFsPwd SuspiciousSyscallSource + StackPivot HiddenKernelModuleSeeker ModuleLoad ModuleFree @@ -13082,6 +13083,26 @@ var CoreEvents = map[ID]Definition{ {Type: "unsigned long", Name: "vma_flags"}, }, }, + StackPivot: { + id: StackPivot, + id32Bit: Sys32Undefined, + name: "stack_pivot", + dependencies: Dependencies{ + probes: []Probe{ + {handle: probes.SchedProcessFork, required: false}, // for thread stack tracking + {handle: probes.SchedProcessExec, required: false}, // for thread stack tracking + }, + }, + sets: []string{}, + fields: []trace.ArgMeta{ + {Type: "int", Name: "syscall"}, + {Type: "void*", Name: "sp"}, + {Type: "char*", Name: "vma_type"}, + {Type: "void*", Name: "vma_start"}, + {Type: "unsigned long", Name: "vma_size"}, + {Type: "unsigned long", Name: "vma_flags"}, + }, + }, // // Begin of Signal Events (Control Plane) // diff --git a/pkg/events/parse_args.go b/pkg/events/parse_args.go index e57433cdb7e0..533af6069f4b 100644 --- a/pkg/events/parse_args.go +++ b/pkg/events/parse_args.go @@ -220,7 +220,7 @@ func ParseArgs(event *trace.Event) error { parseFsNotifyObjType(objTypeArg, uint64(objType)) } } - case SuspiciousSyscallSource: + case SuspiciousSyscallSource, StackPivot: if syscallArg := GetArg(event, "syscall"); syscallArg != nil { if id, isInt32 := syscallArg.Value.(int32); isInt32 { if Core.IsDefined(ID(id)) { diff --git a/pkg/filters/data.go b/pkg/filters/data.go index 48cc2f315fc0..ea4f0e335ff9 100644 --- a/pkg/filters/data.go +++ b/pkg/filters/data.go @@ -173,7 +173,8 @@ func (f *DataFilter) Parse(id events.ID, fieldName string, operatorAndValues str case events.SysEnter, events.SysExit, - events.SuspiciousSyscallSource: + events.SuspiciousSyscallSource, + events.StackPivot: if fieldName == "syscall" { // handle either syscall name or syscall id _, err := strconv.Atoi(val) if err != nil { diff --git a/tests/e2e-inst-signatures/e2e-stack_pivot.go b/tests/e2e-inst-signatures/e2e-stack_pivot.go new file mode 100644 index 000000000000..50419d671921 --- /dev/null +++ b/tests/e2e-inst-signatures/e2e-stack_pivot.go @@ -0,0 +1,82 @@ +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 e2eStackPivot struct { + cb detect.SignatureHandler + falsePositive bool +} + +func (sig *e2eStackPivot) Init(ctx detect.SignatureContext) error { + sig.cb = ctx.Callback + + return nil +} + +func (sig *e2eStackPivot) GetMetadata() (detect.SignatureMetadata, error) { + return detect.SignatureMetadata{ + ID: "STACK_PIVOT", + EventName: "STACK_PIVOT", + Version: "0.1.0", + Name: "Stack Pivot Test", + Description: "Instrumentation events E2E Tests: Stack Pivot", + Tags: []string{"e2e", "instrumentation"}, + }, nil +} + +func (sig *e2eStackPivot) GetSelectedEvents() ([]detect.SignatureEventSelector, error) { + return []detect.SignatureEventSelector{ + {Source: "tracee", Name: "stack_pivot"}, + }, nil +} + +func (sig *e2eStackPivot) 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 "stack_pivot": + syscall, err := helpers.ArgVal[string](eventObj.Args, "syscall") + if err != nil { + return err + } + vmaType, err := helpers.ArgVal[string](eventObj.Args, "vma_type") + if err != nil { + return err + } + + // Make sure this is the exact event we're looking for + if eventObj.ProcessName == "stack_pivot" && syscall == "exit_group" && vmaType == "heap" { + // Make sure there was no false positive + if !sig.falsePositive { + m, _ := sig.GetMetadata() + + sig.cb(&detect.Finding{ + SigMetadata: m, + Event: event, + Data: map[string]interface{}{}, + }) + } + } else { + // False positive, mark it so that the test will fail + sig.falsePositive = true + } + } + + return nil +} + +func (sig *e2eStackPivot) OnSignal(s detect.Signal) error { + return nil +} + +func (sig *e2eStackPivot) Close() {} diff --git a/tests/e2e-inst-signatures/export.go b/tests/e2e-inst-signatures/export.go index 4cddd8abd6d9..5b480a39820d 100644 --- a/tests/e2e-inst-signatures/export.go +++ b/tests/e2e-inst-signatures/export.go @@ -23,6 +23,7 @@ var ExportedSignatures = []detect.Signature{ &e2eSetFsPwd{}, &e2eFtraceHook{}, &e2eSuspiciousSyscallSource{}, + &e2eStackPivot{}, } var ExportedDataSources = []detect.DataSource{ diff --git a/tests/e2e-inst-signatures/scripts/stack_pivot.c b/tests/e2e-inst-signatures/scripts/stack_pivot.c new file mode 100644 index 000000000000..6689af03735d --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/stack_pivot.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +void* thread_func(void* arg) { + // Try triggering a false posivie + getpid(); + + return NULL; +} + +int main() { + // Start a thread that will call getpid(), in an attempt to trigger a false positive + pthread_t thread; + if (pthread_create(&thread, NULL, thread_func, NULL) != 0) { + perror("pthread_create failed"); + return 1; + } + + // Sleep a bit to have give potential false positives a chance to show themselves + sleep(15); + + // Allocate a block of memory on the heap + void *heap_memory = malloc(1024); + if (heap_memory == NULL) { + perror("malloc failed"); + return 1; + } + + // Set stack pointer to the allocated heap memory (top of the block) + void *new_sp = heap_memory + 1024; +#if defined(__x86_64__) + __asm__ volatile ( + "mov %0, %%rsp\n" + : + : "r"(new_sp) + ); +#elif defined(__aarch64__) + __asm__ volatile ( + "mov sp, %0\n" + : + : "r"(new_sp) + ); +#else + #error "Unsupported architecture" +#endif + + // Trigger the stack pivot event by invoking exit_group() while the stack pointer is pointing to the heap + exit(0); +} \ No newline at end of file diff --git a/tests/e2e-inst-signatures/scripts/stack_pivot.sh b/tests/e2e-inst-signatures/scripts/stack_pivot.sh new file mode 100755 index 000000000000..f114dbd4de5c --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/stack_pivot.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +exit_err() { + echo -n "ERROR: " + echo "$@" + exit 1 +} + +prog=stack_pivot +dir=tests/e2e-inst-signatures/scripts +gcc $dir/$prog.c -pthread -o $dir/$prog || exit_err "could not compile $prog.c" +./$dir/$prog 2>&1 > /tmp/$prog.log || exit_err "could not run $prog" \ No newline at end of file diff --git a/tests/e2e-inst-test.sh b/tests/e2e-inst-test.sh index 39a06395ee02..ec1ccd0e7eac 100755 --- a/tests/e2e-inst-test.sh +++ b/tests/e2e-inst-test.sh @@ -128,9 +128,9 @@ for TEST in $TESTS; do continue fi ;; - SUSPICIOUS_SYSCALL_SOURCE) + SUSPICIOUS_SYSCALL_SOURCE|STACK_PIVOT) if cat /proc/kallsyms | grep -qP "trace.*vma_store"; then - info "skip suspicious_syscall_source test on kernel $(uname -r) (VMAs stored in maple tree)" + info "skip $TEST test on kernel $(uname -r) (VMAs stored in maple tree)" continue fi ;; @@ -151,15 +151,27 @@ 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,sys_src_tester \ --dnscache enable \ --grpc-listen-addr unix:/tmp/tracee.sock \ --events "$TEST"" + # Some tests might look for false positives and thus we shouldn't limit the scope for them + if [ "$TEST" != "STACK_PIVOT" ]; then + tracee_command="$tracee_command --scope comm=echo,mv,ls,tracee,proctreetester,ping,ds_writer,fsnotify_tester,process_execute,tracee-ebpf,writev,set_fs_pwd.sh,sys_src_tester" + fi + # Some tests might need event parameters - if [ "$TEST" = "SUSPICIOUS_SYSCALL_SOURCE" ]; then + case $TEST in + SUSPICIOUS_SYSCALL_SOURCE) tracee_command="$tracee_command --events suspicious_syscall_source.args.syscall=exit" - fi + ;; + STACK_PIVOT) + # The expected event is triggered using the exit_group syscall. + # Also add various high-frequency sycalls so that false positives have a chance to trigger. + # Also add getpid, which the tester program uses in an attempt to trigger a false positive + tracee_command="$tracee_command --events stack_pivot.args.syscall=exit_group,getpid,write,openat,mmap,execve,fork,clone,recvmsg,gettid,epoll_wait,poll,recvfrom" + ;; + esac $tracee_command &