Skip to content

Commit

Permalink
Remove dependency on sys_enter
Browse files Browse the repository at this point in the history
Instead, use generic syscall kprobes that are attached dynamically according to syscall filter.
  • Loading branch information
oshaked1 committed Oct 8, 2024
1 parent 9376b56 commit ba0457e
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 82 deletions.
8 changes: 0 additions & 8 deletions pkg/ebpf/c/maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,6 @@ struct sys_exit_init_tail {

typedef struct sys_exit_init_tail sys_exit_init_tail_t;

// store program for performing syscall checking logic
struct check_syscall_source_tail {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, MAX_EVENT_ID);
__type(key, u32);
__type(value, u32);
} check_syscall_source_tail SEC(".maps");

// store syscalls with abnormal source per VMA per process
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
Expand Down
57 changes: 20 additions & 37 deletions pkg/ebpf/c/tracee.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ int tracepoint__raw_syscalls__sys_enter(struct bpf_raw_tracepoint_args *ctx)
id = *id_64;
}

// Call syscall checker if registered for this syscall.
// If so, it will make sure the following tail is called.
bpf_tail_call(ctx, &check_syscall_source_tail, id);

bpf_tail_call(ctx, &sys_enter_init_tail, id);
return 0;
}
Expand Down Expand Up @@ -5189,50 +5185,41 @@ statfunc enum vma_type get_vma_type(struct vm_area_struct *vma)
return VMA_OTHER;
}

SEC("raw_tracepoint/check_syscall_source")
int check_syscall_source(struct bpf_raw_tracepoint_args *ctx)
SEC("kprobe/check_syscall_source")
int BPF_KPROBE(check_syscall_source)
{
// Get syscall ID.
// NOTE: this must happen first before any logic that may fail,
// because we must know the syscall ID for the tail call we preceded.
struct task_struct *task = (struct task_struct *) bpf_get_current_task();
u32 id = ctx->args[1];
if (is_compat(task)) {
// Translate 32bit syscalls to 64bit syscalls
u32 *id_64 = bpf_map_lookup_elem(&sys_32_to_64_map, &id);
if (id_64 == 0)
return 0;
id = *id_64;
}

program_data_t p = {};
if (!init_program_data(&p, ctx, CHECK_SYSCALL_SOURCE))
goto out;
return 0;

if (!evaluate_scope_filters(&p))
goto out;
return 0;

// Get instruction pointer
struct pt_regs *regs = (struct pt_regs *) ctx->args[0];
#if defined(bpf_target_x86)
u64 ip = BPF_CORE_READ(regs, ip);
#elif defined(bpf_target_arm64)
u64 ip = BPF_CORE_READ(regs, pc);
#endif
struct pt_regs *regs = ctx;
if (get_kconfig(ARCH_HAS_SYSCALL_WRAPPER))
regs = (struct pt_regs *) PT_REGS_PARM1(ctx);
u64 ip = PT_REGS_IP_CORE(regs);

// Find VMA which contains the instruction pointer
struct task_struct *task = (struct task_struct *) bpf_get_current_task();
if (unlikely(task == NULL))
return 0;
struct vm_area_struct *vma = find_vma(task, ip);
if (vma == NULL)
goto out;
if (unlikely(vma == NULL))
return 0;

// Get VMA type and make sure it's abnormal (stack/heap/anonymous VMA)
enum vma_type vma_type = get_vma_type(vma);
if (vma_type == VMA_OTHER)
goto out;
return 0;

// Get syscall ID
u32 syscall = get_syscall_id_from_regs(regs);

// Build a key that identifies the combination of syscall,
// source VMA and process so we don't submit it multiple times
syscall_source_key_t key = {.syscall = id,
syscall_source_key_t key = {.syscall = syscall,
.tgid = get_task_ns_tgid(task),
.tgid_start_time = get_task_start_time(get_leader_task(task)),
.vma_addr = get_vma_start(vma)};
Expand All @@ -5241,24 +5228,20 @@ int check_syscall_source(struct bpf_raw_tracepoint_args *ctx)
// Try updating the map with the requirement that this key does not exist yet
if ((int) bpf_map_update_elem(&syscall_source_map, &key, &val, BPF_NOEXIST) == -17 /* EEXIST */)
// This key already exists, no need to submit the same syscall-vma-process combination again
goto out;
return 0;

bool is_stack = vma_type == VMA_STACK;
bool is_heap = vma_type == VMA_HEAP;
bool is_anon = vma_type == VMA_ANON;

save_to_submit_buf(&p.event->args_buf, &id, sizeof(id), 0);
save_to_submit_buf(&p.event->args_buf, &syscall, sizeof(syscall), 0);
save_to_submit_buf(&p.event->args_buf, &ip, sizeof(ip), 1);
save_to_submit_buf(&p.event->args_buf, &is_stack, sizeof(is_stack), 2);
save_to_submit_buf(&p.event->args_buf, &is_heap, sizeof(is_heap), 3);
save_to_submit_buf(&p.event->args_buf, &is_anon, sizeof(is_anon), 4);

events_perf_submit(&p, 0);

out:
// Call sys_enter_init_tail which we preceded
bpf_tail_call(ctx, &sys_enter_init_tail, id);

return 0;
}

Expand Down
63 changes: 32 additions & 31 deletions pkg/ebpf/event_filters.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package ebpf

import (
"errors"
"fmt"
"maps"
"strconv"
"unsafe"

bpf "github.com/aquasecurity/libbpfgo"

"github.com/aquasecurity/tracee/pkg/errfmt"
"github.com/aquasecurity/tracee/pkg/ebpf/probes"
"github.com/aquasecurity/tracee/pkg/events"
"github.com/aquasecurity/tracee/pkg/filters"
"github.com/aquasecurity/tracee/pkg/logger"
)

type eventFilterHandler func(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error
type eventFilterHandler func(t *Tracee, eventFilters map[string]filters.Filter[*filters.StringFilter]) error

var eventFilterHandlers = map[events.ID]eventFilterHandler{
events.CheckSyscallSource: populateMapsCheckSyscallSource,
events.CheckSyscallSource: attachCheckSyscallSourceProbes,
}

// populateEventFilterMaps populates maps with data from special event filters
Expand All @@ -43,7 +42,7 @@ func (t *Tracee) populateEventFilterMaps() error {
}

// Call handler
err := handler(eventFilters, t.bpfModule)
err := handler(t, eventFilters)
if err != nil {
logger.Errorw("Failed to handle event filters", "event", events.Core.GetDefinitionByID(eventID).GetName(), "error", err)
err = t.eventsDependencies.RemoveEvent(eventID)
Expand All @@ -55,38 +54,40 @@ func (t *Tracee) populateEventFilterMaps() error {
return nil
}

func populateMapsCheckSyscallSource(eventFilters map[string]filters.Filter[*filters.StringFilter], bpfModule *bpf.Module) error {
func attachCheckSyscallSourceProbes(t *Tracee, eventFilters map[string]filters.Filter[*filters.StringFilter]) error {
// Get syscalls to trace
syscallsFilter, ok := eventFilters["syscall"].(*filters.StringFilter)
if !ok {
return nil
}
syscalls := syscallsFilter.Equal()

// Get map and program for check_syscall_source tailcall
checkSyscallSourceTail, err := bpfModule.GetMap("check_syscall_source_tail")
if err != nil {
return errfmt.Errorf("could not get BPF map \"check_syscall_source_tail\": %v", err)
}
checkSyscallSourceProg, err := bpfModule.GetProgram("check_syscall_source")
if err != nil {
return errfmt.Errorf("could not get BPF program \"check_syscall_source\": %v", err)
}
checkSyscallSourceProgFD := checkSyscallSourceProg.FileDescriptor()
if checkSyscallSourceProgFD < 0 {
return errfmt.Errorf("could not get BPF program FD for \"check_syscall_source\": %v", err)
}

// Add each syscall to the tail call map
for _, syscall := range syscalls {
syscallID, err := strconv.Atoi(syscall)
syscalls := make([]string, 0)
for _, entry := range syscallsFilter.Equal() {
syscallID, err := strconv.Atoi(entry)
if err != nil {
return errfmt.WrapError(err)
return err
}
if !events.Core.IsDefined(events.ID(syscallID)) {
return fmt.Errorf("syscall id %d is not defined", syscallID)
}
syscalls = append(syscalls, events.Core.GetDefinitionByID(events.ID(syscallID)).GetName())
}

err = checkSyscallSourceTail.Update(unsafe.Pointer(&syscallID), unsafe.Pointer(&checkSyscallSourceProgFD))
if err != nil {
return errfmt.WrapError(err)
// Create probe group
probeMap := make(map[probes.Handle]probes.Probe)
for i, syscall := range syscalls {
probeMap[probes.Handle(i)] = probes.NewTraceProbe(probes.SyscallEnter, syscall, "check_syscall_source")
}
t.checkSyscallSourceProbes = probes.NewProbeGroup(t.bpfModule, probeMap)

// Attach probes
for i, syscall := range syscalls {
if err := t.checkSyscallSourceProbes.Attach(probes.Handle(i), t.kernelSymbols); err != nil {
var errs error
errs = errors.Join(errs, fmt.Errorf("failed to attach check_syscall_source probe for syscall %s: %v", syscall, err))
if err := t.eventsDependencies.RemoveEvent(events.CheckSyscallSource); err != nil {
errs = errors.Join(errs, err)
}
return errs
}
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/ebpf/tracee.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ type Tracee struct {
// This does not mean they are required for tracee to function.
// TODO: remove this in favor of dependency manager nodes
requiredKsyms []string
// Probes created for check_syscall_source event
checkSyscallSourceProbes *probes.ProbeGroup
}

func (t *Tracee) Stats() *metrics.Stats {
Expand Down Expand Up @@ -1492,6 +1494,12 @@ func (t *Tracee) Close() {
logger.Errorw("failed to detach probes when closing tracee", "err", err)
}
}
if t.checkSyscallSourceProbes != nil {
err := t.checkSyscallSourceProbes.DetachAll()
if err != nil {
logger.Errorw("failed to detach check_syscall_source probes when closing tracee", "err", err)
}
}
if t.bpfModule != nil {
t.bpfModule.Close()
}
Expand Down
11 changes: 5 additions & 6 deletions pkg/events/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -13047,15 +13047,14 @@ var CoreEvents = map[ID]Definition{
id: CheckSyscallSource,
id32Bit: Sys32Undefined,
name: "check_syscall_source",
sets: []string{},
dependencies: Dependencies{
probes: []Probe{
{handle: probes.SyscallEnter__Internal, required: true},
},
tailCalls: []TailCall{
{"check_syscall_source_tail", "check_syscall_source", []uint32{ /* Map will be populated at runtime according to event filter */ }},
capabilities: Capabilities{
ebpf: []cap.Value{
cap.SYSLOG,
},
},
},
sets: []string{},
params: []trace.ArgMeta{
{Type: "int", Name: "syscall"},
{Type: "void*", Name: "ip"},
Expand Down

0 comments on commit ba0457e

Please sign in to comment.