From a6c4e35c3e9928e77640c82737564a86ff54c472 Mon Sep 17 00:00:00 2001 From: Ofek Shaked Date: Thu, 4 Apr 2024 17:55:02 +0300 Subject: [PATCH] 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 | 3 ++ .../e2e-check_syscall_source.go | 31 +++++++++++ 6 files changed, 54 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 23d4936644ec..e797a5d01566 100644 --- a/pkg/ebpf/c/tracee.bpf.c +++ b/pkg/ebpf/c/tracee.bpf.c @@ -5161,13 +5161,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) { @@ -5192,10 +5185,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) @@ -5205,8 +5194,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 3a9ad31ba4b7..b9d0bbb0909d 100644 --- a/pkg/ebpf/initialization/kconfig.go +++ b/pkg/ebpf/initialization/kconfig.go @@ -11,10 +11,12 @@ import ( // Add here all kconfig variables used within tracee.bpf.c const ( CONFIG_ARCH_HAS_SYSCALL_WRAPPER helpers.KernelConfigOption = iota + helpers.CUSTOM_OPTION_START + CONFIG_MMU helpers.KernelConfigOption = iota + helpers.CUSTOM_OPTION_START ) var kconfigUsed = map[helpers.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 @@ -34,6 +36,7 @@ func LoadKconfigValues(kc *helpers.KernelConfig) (map[helpers.KernelConfigOption values[key] = helpers.UNDEFINED } values[CONFIG_ARCH_HAS_SYSCALL_WRAPPER] = helpers.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 {