Skip to content

Commit

Permalink
Remove usage of bpf_find_vma
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
oshaked1 committed Apr 4, 2024
1 parent 38a05f7 commit c45b5fc
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 50 deletions.
3 changes: 2 additions & 1 deletion pkg/ebpf/c/common/kconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

enum kconfig_key_e
{
ARCH_HAS_SYSCALL_WRAPPER = 1000U
ARCH_HAS_SYSCALL_WRAPPER = 1000U,
MMU = 1001U
};

// PROTOTYPES
Expand Down
52 changes: 16 additions & 36 deletions pkg/ebpf/c/common/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@
#include <vmlinux.h>

#include <common/common.h>
#include <common/kconfig.h>

// 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 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);

Expand Down Expand Up @@ -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++) {
Expand All @@ -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)
Expand Down
14 changes: 1 addition & 13 deletions pkg/ebpf/c/tracee.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -5154,13 +5154,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)
{
Expand All @@ -5185,10 +5178,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)
Expand All @@ -5198,8 +5187,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;

Expand Down
3 changes: 3 additions & 0 deletions pkg/ebpf/initialization/kconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
31 changes: 31 additions & 0 deletions tests/e2e-inst-signatures/e2e-check_syscall_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -11,13 +13,28 @@ import (

type e2eCheckSyscallSource struct {
cb detect.SignatureHandler
hasMapleTree bool
foundStack bool
foundHeap bool
foundAnonVma bool
}

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
}

Expand All @@ -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
}

Expand All @@ -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 {
Expand Down

0 comments on commit c45b5fc

Please sign in to comment.