diff --git a/bpf/Makefile b/bpf/Makefile index 477d110b605..2e1ce7f86a7 100644 --- a/bpf/Makefile +++ b/bpf/Makefile @@ -21,7 +21,7 @@ PROCESS = bpf_execve_event.o bpf_execve_event_v53.o bpf_fork.o bpf_exit.o bpf_ge bpf_multi_kprobe_v61.o bpf_multi_retkprobe_v61.o \ bpf_generic_uprobe_v61.o \ bpf_loader.o \ - bpf_killer.o bpf_multi_killer.o + bpf_killer.o bpf_multi_killer.o bpf_fmodret_killer.o CGROUP = bpf_cgroup_mkdir.o bpf_cgroup_rmdir.o bpf_cgroup_release.o BPFTEST = bpf_lseek.o bpf_globals.o @@ -75,6 +75,30 @@ objs/%.ll: $(ALIGNCHECKERDIR)%.c $(DEPSDIR)%.d: $(ALIGNCHECKERDIR)%.c $(CLANG) $(CLANG_FLAGS) -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@ + +# Killer programs: bpf_killer, bpf_multi_killer, bpf_fmodret_killer + +## bpf_killer: __BPF_OVERRIDE_RETURN, but no __MULTI_KPROBE +objs/bpf_killer.ll: process/bpf_killer.c + $(CLANG) $(CLANG_FLAGS) -D__BPF_OVERRIDE_RETURN -c $< -o $@ + +$(DEPSDIR)bpf_killer.d: process/bpf_killer.c + $(CLANG) $(CLANG_FLAGS) -D__BPF_OVERRIDE_RETURN -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@ + +## bpf_multi_killer: __BPF_OVERRIDE_RETURN and __MULTI_KPROBE +objs/bpf_multi_killer.ll: process/bpf_killer.c + $(CLANG) $(CLANG_FLAGS) -D__BPF_OVERRIDE_RETURN -D__MULTI_KPROBE -c $< -o $@ + +$(DEPSDIR)/bpf_multi_killer.d: process/bpf_killer.c + $(CLANG) $(CLANG_FLAGS) -D__BPF_OVERRIDE_RETURN -D__MULTI_KPROBE -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@ + +## bpf_fmodret_killer no bpf_override_return: we need fmod_ret +objs/bpf_fmodret_killer.ll: process/bpf_killer.c + $(CLANG) $(CLANG_FLAGS) -c $< -o $@ + +$(DEPSDIR)/bpf_fmodret_killer.d: process/bpf_killer.c + $(CLANG) $(CLANG_FLAGS) -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@ + # PROCESSDIR objs/%.ll: $(PROCESSDIR)%.c $(CLANG) $(CLANG_FLAGS) -c $< -o $@ @@ -88,11 +112,6 @@ objs/%_v53.ll: $(DEPSDIR)%.d: $(PROCESSDIR)%.c $(CLANG) $(CLANG_FLAGS) -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@ -objs/bpf_multi_killer.ll: process/bpf_killer.c - $(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__MULTI_KPROBE -c $< -o $@ - -$(DEPSDIR)/bpf_multi_killer.d: process/bpf_killer.c - $(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__MULTI_KPROBE -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@ $(DEPSDIR)%_v53.d: $(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@ diff --git a/bpf/lib/bpf_task.h b/bpf/lib/bpf_task.h index f63c2e00440..f4e2e9bc13a 100644 --- a/bpf/lib/bpf_task.h +++ b/bpf/lib/bpf_task.h @@ -158,12 +158,13 @@ static inline __attribute__((always_inline)) struct execve_map_value * event_find_curr(__u32 *ppid, bool *walked) { struct task_struct *task = (struct task_struct *)get_current_task(); - __u32 pid = get_current_pid_tgid() >> 32; struct execve_map_value *value = 0; int i; + __u32 pid; #pragma unroll for (i = 0; i < 4; i++) { + probe_read(&pid, sizeof(pid), _(&task->tgid)); value = execve_map_get_noinit(pid); if (value && value->key.ktime != 0) break; @@ -172,7 +173,6 @@ event_find_curr(__u32 *ppid, bool *walked) probe_read(&task, sizeof(task), _(&task->real_parent)); if (!task) break; - probe_read(&pid, sizeof(pid), _(&task->tgid)); } *ppid = pid; return value; diff --git a/bpf/process/bpf_killer.c b/bpf/process/bpf_killer.c index 7ff4d0024ce..91eda228818 100644 --- a/bpf/process/bpf_killer.c +++ b/bpf/process/bpf_killer.c @@ -2,14 +2,8 @@ char _license[] __attribute__((section("license"), used)) = "Dual BSD/GPL"; -#ifdef __MULTI_KPROBE -#define MAIN "kprobe.multi/killer" -#else -#define MAIN "kprobe/killer" -#endif - -__attribute__((section(MAIN), used)) int -killer(void *ctx) +static inline __attribute__((always_inline)) int +do_killer(void *ctx) { __u64 id = get_current_pid_tgid(); struct killer_data *data; @@ -18,11 +12,42 @@ killer(void *ctx) if (!data) return 0; - if (data->error) - override_return(ctx, data->error); if (data->signal) send_signal(data->signal); map_delete_elem(&killer_data, &id); + return data->error; +} + +#if defined(__BPF_OVERRIDE_RETURN) + +#ifdef __MULTI_KPROBE +#define MAIN "kprobe.multi/killer" +#else +#define MAIN "kprobe/killer" +#endif + +__attribute__((section(MAIN), used)) int +multi_kprobe_killer(void *ctx) +{ + long ret; + + ret = do_killer(ctx); + if (ret) + override_return(ctx, ret); + return 0; } + +#else /* !__BPF_OVERRIDE_RETURN */ + +/* Putting security_task_prctl in here to pass contrib/verify/verify.sh test, + * in normal run the function is set by tetragon dynamically. + */ +__attribute__((section("fmod_ret/security_task_prctl"), used)) long +fmodret_killer(void *ctx) +{ + return do_killer(ctx); +} + +#endif diff --git a/contrib/tester-progs/.gitignore b/contrib/tester-progs/.gitignore index db3a507daa5..fe0e46e31bc 100644 --- a/contrib/tester-progs/.gitignore +++ b/contrib/tester-progs/.gitignore @@ -17,3 +17,5 @@ threads-tester bench-reader threads-exit killer-tester +killer-tester-32 +/getcpu diff --git a/contrib/tester-progs/Makefile b/contrib/tester-progs/Makefile index 9009cb64197..c906352314e 100644 --- a/contrib/tester-progs/Makefile +++ b/contrib/tester-progs/Makefile @@ -19,7 +19,8 @@ PROGS = sigkill-tester \ bench-reader \ threads-exit \ killer-tester \ - drop-privileges + drop-privileges \ + getcpu # For now killer-tester is compiled to 32-bit only on x86_64 as we want # to test 32-bit binaries and system calls compatibility layer. @@ -79,6 +80,9 @@ killer-tester-32: killer-tester.c lseek-pipe: FORCE go build -o lseek-pipe ./go/lseek-pipe +getcpu: FORCE + go build -o getcpu ./go/getcpu + .PHONY: clean clean: rm -f $(PROGS) diff --git a/contrib/tester-progs/go/getcpu/getcpu.go b/contrib/tester-progs/go/getcpu/getcpu.go new file mode 100644 index 00000000000..40176cfdba1 --- /dev/null +++ b/contrib/tester-progs/go/getcpu/getcpu.go @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Tetragon + +package main + +import ( + "os" + "unsafe" + + "golang.org/x/sys/unix" +) + +func main() { + var cpu, node int + _, _, err := unix.Syscall( + unix.SYS_GETCPU, + uintptr(unsafe.Pointer(&cpu)), + uintptr(unsafe.Pointer(&node)), + 0, + ) + os.Exit(int(err)) +} diff --git a/pkg/bpf/detect.go b/pkg/bpf/detect.go index e728f78c273..ac168c33638 100644 --- a/pkg/bpf/detect.go +++ b/pkg/bpf/detect.go @@ -15,6 +15,8 @@ import ( "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/features" "github.com/cilium/ebpf/link" + "github.com/cilium/tetragon/pkg/arch" + "github.com/cilium/tetragon/pkg/logger" "golang.org/x/sys/unix" ) @@ -24,9 +26,10 @@ type Feature struct { } var ( - kprobeMulti Feature - buildid Feature - modifyReturn Feature + kprobeMulti Feature + buildid Feature + modifyReturn Feature + modifyReturnSyscall Feature ) func HasOverrideHelper() bool { @@ -119,6 +122,40 @@ func detectModifyReturn() bool { return true } +func detectModifyReturnSyscall() bool { + sysGetcpu, err := arch.AddSyscallPrefix("sys_getcpu") + if err != nil { + return false + } + logger.GetLogger().Infof("probing detectModifyReturnSyscall using %s", sysGetcpu) + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_sys_fmod_ret", + Type: ebpf.Tracing, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachModifyReturn, + AttachTo: sysGetcpu, + License: "MIT", + }) + if err != nil { + logger.GetLogger().WithError(err).Info("detectModifyReturnSyscall: failed to load") + return false + } + defer prog.Close() + + link, err := link.AttachTracing(link.TracingOptions{ + Program: prog, + }) + if err != nil { + logger.GetLogger().WithError(err).Info("detectModifyReturnSyscall, failed to attach") + return false + } + link.Close() + return true +} + func HasModifyReturn() bool { modifyReturn.init.Do(func() { modifyReturn.detected = detectModifyReturn() @@ -126,11 +163,18 @@ func HasModifyReturn() bool { return modifyReturn.detected } +func HasModifyReturnSyscall() bool { + modifyReturnSyscall.init.Do(func() { + modifyReturnSyscall.detected = detectModifyReturnSyscall() + }) + return modifyReturnSyscall.detected +} + func HasProgramLargeSize() bool { return features.HaveLargeInstructions() == nil } func LogFeatures() string { - return fmt.Sprintf("override_return: %t, buildid: %t, kprobe_multi: %t, fmodret: %t, signal: %t, large: %t", - HasOverrideHelper(), HasBuildId(), HasKprobeMulti(), HasModifyReturn(), HasSignalHelper(), HasProgramLargeSize()) + return fmt.Sprintf("override_return: %t, buildid: %t, kprobe_multi: %t, fmodret: %t, fmodret_syscall: %t, signal: %t, large: %t", + HasOverrideHelper(), HasBuildId(), HasKprobeMulti(), HasModifyReturn(), HasModifyReturnSyscall(), HasSignalHelper(), HasProgramLargeSize()) } diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index a054da29eb5..dd0d84d5f3e 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -528,6 +528,42 @@ func LoadMultiKprobeProgram(bpfDir, mapDir string, load *Program, verbose int) e return loadProgram(bpfDir, []string{mapDir}, load, opts, verbose) } +func LoadFmodRetProgram(bpfDir, mapDir string, load *Program, progName string, verbose int) error { + opts := &loadOpts{ + attach: func( + coll *ebpf.Collection, + collSpec *ebpf.CollectionSpec, + prog *ebpf.Program, + spec *ebpf.ProgramSpec, + ) (unloader.Unloader, error) { + linkFn := func() (link.Link, error) { + return link.AttachTracing(link.TracingOptions{ + Program: prog, + }) + } + lnk, err := linkFn() + if err != nil { + return nil, fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) + } + return &unloader.RelinkUnloader{ + UnloadProg: unloader.PinUnloader{Prog: prog}.Unload, + IsLinked: true, + Link: lnk, + RelinkFn: linkFn, + }, nil + }, + open: func(coll *ebpf.CollectionSpec) error { + progSpec, ok := coll.Programs[progName] + if !ok { + return fmt.Errorf("progName %s not in collecition spec programs: %+v", progName, coll.Programs) + } + progSpec.AttachTo = load.Attach + return nil + }, + } + return loadProgram(bpfDir, []string{mapDir}, load, opts, verbose) +} + func LoadTracingProgram(bpfDir, mapDir string, load *Program, verbose int) error { opts := &loadOpts{ attach: TracingAttach(), diff --git a/pkg/sensors/tracing/generickprobe.go b/pkg/sensors/tracing/generickprobe.go index 3015fc2700f..eff1bd61e29 100644 --- a/pkg/sensors/tracing/generickprobe.go +++ b/pkg/sensors/tracing/generickprobe.go @@ -553,18 +553,17 @@ func createGenericKprobeSensor( kprobes := spec.KProbes lists := spec.Lists - options, err := getKprobeOptions(spec.Options) + specOpts, err := getSpecOptions(spec.Options) if err != nil { - return nil, fmt.Errorf("failed to set options: %s", err) + return nil, fmt.Errorf("failed to get spec options: %s", err) } // use multi kprobe only if: // - it's not disabled by spec option // - it's not disabled by command line option // - there's support detected - if !options.DisableKprobeMulti { - useMulti = !option.Config.DisableKprobeMulti && - bpf.HasKprobeMulti() + if !specOpts.DisableKprobeMulti { + useMulti = !option.Config.DisableKprobeMulti && bpf.HasKprobeMulti() } if useMulti { diff --git a/pkg/sensors/tracing/killer.go b/pkg/sensors/tracing/killer.go index 7cc191e0691..108dbd2894a 100644 --- a/pkg/sensors/tracing/killer.go +++ b/pkg/sensors/tracing/killer.go @@ -20,20 +20,32 @@ import ( "github.com/cilium/tetragon/pkg/tracingpolicy" ) -type killerSensor struct{} +const ( + killerDataMapName = "killer_data" +) -func init() { - killer := &killerSensor{} - sensors.RegisterProbeType("killer", killer) - sensors.RegisterPolicyHandlerAtInit("killer", killerSensor{}) +type killerHandler struct { + configured bool + syscallsSyms []string +} + +func newKillerHandler() *killerHandler { + return &killerHandler{ + configured: false, + } } var ( - configured = false - syscallsSyms []string + // global killer handler + gKillerHandler = newKillerHandler() ) -func (k killerSensor) PolicyHandler( +func init() { + sensors.RegisterProbeType("killer", gKillerHandler) + sensors.RegisterPolicyHandlerAtInit("killer", gKillerHandler) +} + +func (kh *killerHandler) PolicyHandler( policy tracingpolicy.TracingPolicy, _ policyfilter.PolicyID, ) (*sensors.Sensor, error) { @@ -48,26 +60,27 @@ func (k killerSensor) PolicyHandler( } if len(spec.Killers) > 0 { name := fmt.Sprintf("killer-sensor-%d", atomic.AddUint64(&sensorCounter, 1)) - return createKillerSensor(spec.Killers, spec.Lists, name) + return kh.createKillerSensor(spec.Killers, spec.Lists, spec.Options, name) } return nil, nil } -func loadSingleKillerSensor(bpfDir, mapDir string, load *program.Program, verbose int) error { - if err := program.LoadKprobeProgramAttachMany(bpfDir, mapDir, load, syscallsSyms, verbose); err == nil { +func (kh *killerHandler) loadSingleKillerSensor( + bpfDir, mapDir string, load *program.Program, verbose int, +) error { + if err := program.LoadKprobeProgramAttachMany(bpfDir, mapDir, load, kh.syscallsSyms, verbose); err == nil { logger.GetLogger().Infof("Loaded killer sensor: %s", load.Attach) } else { return err } - return nil } -func loadMultiKillerSensor(bpfDir, mapDir string, load *program.Program, verbose int) error { +func (kh *killerHandler) loadMultiKillerSensor(bpfDir, mapDir string, load *program.Program, verbose int) error { data := &program.MultiKprobeAttachData{} - data.Symbols = append(data.Symbols, syscallsSyms...) + data.Symbols = append(data.Symbols, kh.syscallsSyms...) load.SetAttachData(data) @@ -79,23 +92,58 @@ func loadMultiKillerSensor(bpfDir, mapDir string, load *program.Program, verbose return nil } -func (k *killerSensor) LoadProbe(args sensors.LoadProbeArgs) error { +func (kh *killerHandler) LoadProbe(args sensors.LoadProbeArgs) error { + if args.Load.Label == "kprobe.multi/killer" { + return kh.loadMultiKillerSensor(args.BPFDir, args.MapDir, args.Load, args.Verbose) + } if args.Load.Label == "kprobe/killer" { - return loadSingleKillerSensor(args.BPFDir, args.MapDir, args.Load, args.Verbose) + return kh.loadSingleKillerSensor(args.BPFDir, args.MapDir, args.Load, args.Verbose) + } + + if strings.HasPrefix(args.Load.Label, "fmod_ret/") { + return program.LoadFmodRetProgram(args.BPFDir, args.MapDir, args.Load, "fmodret_killer", args.Verbose) + } + + return fmt.Errorf("killer loader: unknown label: %s", args.Load.Label) +} + +// select proper override method based on configuration and spec options +func selectOverrideMethod(specOpts *specOptions) (OverrideMethod, error) { + overrideMethod := specOpts.OverrideMethod + switch overrideMethod { + case OverrideMethodDefault: + // by default, first try OverrideReturn and if this does not work try fmod_ret + if bpf.HasOverrideHelper() { + overrideMethod = OverrideMethodReturn + } else if bpf.HasModifyReturnSyscall() { + overrideMethod = OverrideMethodFmodRet + } else { + return OverrideMethodInvalid, fmt.Errorf("no override helper or mod_ret support: cannot load killer") + } + case OverrideMethodReturn: + if !bpf.HasOverrideHelper() { + return OverrideMethodInvalid, fmt.Errorf("option override return set, but it is not supported") + } + case OverrideMethodFmodRet: + if !bpf.HasModifyReturnSyscall() { + return OverrideMethodInvalid, fmt.Errorf("option fmod_ret set, but it is not supported") + } } - return loadMultiKillerSensor(args.BPFDir, args.MapDir, args.Load, args.Verbose) + + return overrideMethod, nil } -func unloadKiller() error { - configured = false - syscallsSyms = []string{} +func (kh *killerHandler) unload() error { + kh.configured = false + kh.syscallsSyms = []string{} logger.GetLogger().Infof("Cleaning up killer") return nil } -func createKillerSensor( +func (kh *killerHandler) createKillerSensor( killers []v1alpha1.KillerSpec, lists []v1alpha1.ListSpec, + opts []v1alpha1.OptionSpec, name string, ) (*sensors.Sensor, error) { @@ -103,11 +151,10 @@ func createKillerSensor( return nil, fmt.Errorf("failed: we support only single killer sensor") } - if configured { + if kh.configured { return nil, fmt.Errorf("failed: killer sensor is already configured") } - - configured = true + kh.configured = true killer := killers[0] @@ -125,7 +172,7 @@ func createKillerSensor( if !isSyscallListType(list.Type) { return nil, fmt.Errorf("Error list '%s' is not syscall type", listName) } - syscallsSyms = append(syscallsSyms, list.Values...) + kh.syscallsSyms = append(kh.syscallsSyms, list.Values...) continue } @@ -133,45 +180,70 @@ func createKillerSensor( if err != nil { return nil, err } - syscallsSyms = append(syscallsSyms, pfxSym) + kh.syscallsSyms = append(kh.syscallsSyms, pfxSym) } // register killer sensor var load *program.Program var progs []*program.Program var maps []*program.Map + specOpts, err := getSpecOptions(opts) + if err != nil { + return nil, fmt.Errorf("failed to get spec options: %s", err) + } - useMulti := !option.Config.DisableKprobeMulti && bpf.HasKprobeMulti() - - attach := fmt.Sprintf("%d syscalls: %s", len(syscallsSyms), syscallsSyms) - prog := sensors.PathJoin(name, "killer_kprobe") + if !bpf.HasSignalHelper() { + return nil, fmt.Errorf("killer sensor requires signal helper which is not available") + } - if useMulti { - load = program.Builder( - path.Join(option.Config.HubbleLib, "bpf_multi_killer.o"), - attach, - "kprobe.multi/killer", - prog, - "killer") + // select proper override method based on configuration and spec options + overrideMethod, err := selectOverrideMethod(specOpts) + if err != nil { + return nil, err + } - } else { + pinPath := sensors.PathJoin(name, "killer_kprobe") + switch overrideMethod { + case OverrideMethodReturn: + useMulti := !specOpts.DisableKprobeMulti && !option.Config.DisableKprobeMulti && bpf.HasKprobeMulti() + logger.GetLogger().Infof("killer: using override return (multi-kprobe: %t)", useMulti) + label := "kprobe/killer" + prog := "bpf_killer.o" + if useMulti { + label = "kprobe.multi/killer" + prog = "bpf_multi_killer.o" + } + attach := fmt.Sprintf("%d syscalls: %s", len(kh.syscallsSyms), kh.syscallsSyms) load = program.Builder( - path.Join(option.Config.HubbleLib, "bpf_killer.o"), + path.Join(option.Config.HubbleLib, prog), attach, - "kprobe/killer", - prog, + label, + pinPath, "killer") + progs = append(progs, load) + case OverrideMethodFmodRet: + // for fmod_ret, we need one program per syscall + logger.GetLogger().Infof("killer: using fmod_ret") + for _, syscallSym := range kh.syscallsSyms { + load = program.Builder( + path.Join(option.Config.HubbleLib, "bpf_fmodret_killer.o"), + syscallSym, + "fmod_ret/security_task_prctl", + pinPath, + "killer") + progs = append(progs, load) + } + default: + return nil, fmt.Errorf("unexpected override method: %d", overrideMethod) } - killerDataMap := program.MapBuilderPin("killer_data", "killer_data", load) - - progs = append(progs, load) + killerDataMap := program.MapBuilderPin(killerDataMapName, killerDataMapName, load) maps = append(maps, killerDataMap) return &sensors.Sensor{ Name: "__killer__", Progs: progs, Maps: maps, - PostUnloadHook: unloadKiller, + PostUnloadHook: gKillerHandler.unload, }, nil } diff --git a/pkg/sensors/tracing/killer_amd64_test.go b/pkg/sensors/tracing/killer_amd64_test.go index 9383ff0e8fb..ff47bb0cabd 100644 --- a/pkg/sensors/tracing/killer_amd64_test.go +++ b/pkg/sensors/tracing/killer_amd64_test.go @@ -11,7 +11,6 @@ import ( "testing" "github.com/cilium/tetragon/api/v1/tetragon" - "github.com/cilium/tetragon/pkg/bpf" "github.com/cilium/tetragon/pkg/syscallinfo/i386" "github.com/cilium/tetragon/pkg/testutils" @@ -20,48 +19,14 @@ import ( ) func TestKillerOverride32(t *testing.T) { - if !bpf.HasOverrideHelper() { - t.Skip("skipping killer test, bpf_override_return helper not available") - } - if !bpf.HasSignalHelper() { - t.Skip("skipping killer test, bpf_send_signal helper not available") - } + testKillerCheckSkip(t) test := testutils.RepoRootPath("contrib/tester-progs/killer-tester-32") - configHook := ` -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "kill-syscalls" -spec: - lists: - - name: "mine" - type: "syscalls" - values: - - "__ia32_sys_prctl" - killers: - - syscalls: - - "list:mine" - tracepoints: - - subsystem: "raw_syscalls" - event: "sys_enter" - args: - - index: 4 - type: "syscall64" - selectors: - - matchArgs: - - index: 0 - operator: "InMap" - values: - - "list:mine" - matchBinaries: - - operator: "In" - values: - - "` + test + `" - matchActions: - - action: "NotifyKiller" - argError: -17 # EEXIST -` + yaml := NewKillerSpecBuilder("killer-override"). + WithSyscallList("__ia32_sys_prctl"). + WithMatchBinaries(test). + WithOverrideValue(-17). // EEXIST + MustYAML() tpChecker := ec.NewProcessTracepointChecker(""). WithArgs(ec.NewKprobeArgumentListMatcher(). @@ -79,52 +44,19 @@ spec: } } - testKiller(t, configHook, test, "", checker, checkerFunc) + testKiller(t, yaml, test, "", checker, checkerFunc) } func TestKillerSignal32(t *testing.T) { - if !bpf.HasOverrideHelper() { - t.Skip("skipping killer test, bpf_override_return helper not available") - } - if !bpf.HasSignalHelper() { - t.Skip("skipping killer test, bpf_send_signal helper not available") - } + testKillerCheckSkip(t) test := testutils.RepoRootPath("contrib/tester-progs/killer-tester-32") - configHook := ` -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "kill-syscalls" -spec: - lists: - - name: "mine" - type: "syscalls" - values: - - "__ia32_sys_prctl" - killers: - - syscalls: - - "list:mine" - tracepoints: - - subsystem: "raw_syscalls" - event: "sys_enter" - args: - - index: 4 - type: "syscall64" - selectors: - - matchArgs: - - index: 0 - operator: "InMap" - values: - - "list:mine" - matchBinaries: - - operator: "In" - values: - - "` + test + `" - matchActions: - - action: "NotifyKiller" - argSig: 9 # SIGKILL -` + yaml := NewKillerSpecBuilder("killer-signal"). + WithSyscallList("__ia32_sys_prctl"). + WithMatchBinaries(test). + WithOverrideValue(-17). // EEXIST + WithKill(9). // SigKill + MustYAML() tpChecker := ec.NewProcessTracepointChecker(""). WithArgs(ec.NewKprobeArgumentListMatcher(). @@ -142,56 +74,20 @@ spec: } } - testKiller(t, configHook, test, "", checker, checkerFunc) + testKiller(t, yaml, test, "", checker, checkerFunc) } func TestKillerOverrideBothBits(t *testing.T) { - if !bpf.HasOverrideHelper() { - t.Skip("skipping killer test, bpf_override_return helper not available") - } - if !bpf.HasSignalHelper() { - t.Skip("skipping killer test, bpf_send_signal helper not available") - } + testKillerCheckSkip(t) test32 := testutils.RepoRootPath("contrib/tester-progs/killer-tester-32") test64 := testutils.RepoRootPath("contrib/tester-progs/killer-tester") - configHook := ` -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "kill-syscalls" -spec: - lists: - - name: "mine" - type: "syscalls" - values: - - "sys_prctl" - - "__ia32_sys_prctl" - killers: - - syscalls: - - "list:mine" - tracepoints: - - subsystem: "raw_syscalls" - event: "sys_enter" - args: - - index: 4 - type: "syscall64" - selectors: - - matchArgs: - - index: 0 - operator: "InMap" - values: - - "list:mine" - matchBinaries: - - operator: "In" - values: - - "` + test32 + `" - - "` + test64 + `" - matchActions: - - action: "NotifyKiller" - argError: -17 # EEXIST -` + yaml := NewKillerSpecBuilder("killer-override"). + WithSyscallList("__ia32_sys_prctl", "sys_prctl"). + WithMatchBinaries(test32, test64). + WithOverrideValue(-17). // EEXIST + MustYAML() tpChecker32 := ec.NewProcessTracepointChecker(""). WithArgs(ec.NewKprobeArgumentListMatcher(). @@ -217,5 +113,5 @@ spec: } } - testKiller(t, configHook, test64, test32, checker, checkerFunc) + testKiller(t, yaml, test64, test32, checker, checkerFunc) } diff --git a/pkg/sensors/tracing/killer_builder.go b/pkg/sensors/tracing/killer_builder.go new file mode 100644 index 00000000000..ade59a370cf --- /dev/null +++ b/pkg/sensors/tracing/killer_builder.go @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Tetragon + +package tracing + +import ( + "fmt" + "log" + + k8sv1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" + + "github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1" + "github.com/cilium/tetragon/pkg/option" +) + +type KillerSpecBuilder struct { + name string + syscalls [][]string + kill *uint32 + override *int32 + binaries []string + overrideMethod string + multiKprobe *bool +} + +func NewKillerSpecBuilder(name string) *KillerSpecBuilder { + return &KillerSpecBuilder{ + name: name, + } +} + +func (ksb *KillerSpecBuilder) WithSyscallList(calls ...string) *KillerSpecBuilder { + ksb.syscalls = append(ksb.syscalls, calls) + return ksb +} + +func (ksb *KillerSpecBuilder) WithKill(sig uint32) *KillerSpecBuilder { + ksb.kill = &sig + return ksb +} + +func (ksb *KillerSpecBuilder) WithMultiKprobe() *KillerSpecBuilder { + multi := true + ksb.multiKprobe = &multi + return ksb +} + +func (ksb *KillerSpecBuilder) WithoutMultiKprobe() *KillerSpecBuilder { + multi := false + ksb.multiKprobe = &multi + return ksb +} + +func (ksb *KillerSpecBuilder) WithOverrideValue(ret int32) *KillerSpecBuilder { + ksb.override = &ret + return ksb +} + +func (ksb *KillerSpecBuilder) WithMatchBinaries(bins ...string) *KillerSpecBuilder { + ksb.binaries = append(ksb.binaries, bins...) + return ksb +} + +func (ksb *KillerSpecBuilder) WithOverrideReturn() *KillerSpecBuilder { + ksb.overrideMethod = valOverrideReturn + return ksb +} + +func (ksb *KillerSpecBuilder) WithFmodRet() *KillerSpecBuilder { + ksb.overrideMethod = valFmodRet + return ksb + +} + +func (ksb *KillerSpecBuilder) WithDefaultOverride() *KillerSpecBuilder { + ksb.overrideMethod = "" + return ksb +} + +func (ksb *KillerSpecBuilder) MustBuild() *v1alpha1.TracingPolicy { + spec, err := ksb.Build() + if err != nil { + log.Fatalf("MustBuild failed with %v", err) + } + return spec +} + +func (ksb *KillerSpecBuilder) MustYAML() string { + tp, err := ksb.Build() + if err != nil { + log.Fatalf("MustYAML: build failed with %v", err) + } + + b, err := yaml.Marshal(tp) + if err != nil { + log.Fatalf("MustYAML: marshal failed with %v", err) + } + return string(b) +} + +func (ksb *KillerSpecBuilder) Build() (*v1alpha1.TracingPolicy, error) { + + var listNames []string + var lists []v1alpha1.ListSpec + var killers []v1alpha1.KillerSpec + var matchBinaries []v1alpha1.BinarySelector + var options []v1alpha1.OptionSpec + + for i, syscallList := range ksb.syscalls { + var name string + if len(ksb.syscalls) > 1 { + name = fmt.Sprintf("%s-%d", ksb.name, i+1) + } else { + name = ksb.name + } + listName := fmt.Sprintf("list:%s", name) + listNames = append(listNames, listName) + lists = append(lists, v1alpha1.ListSpec{ + Name: name, + Type: "syscalls", + Values: syscallList, + Pattern: nil, + Validated: false, + }) + killers = append(killers, v1alpha1.KillerSpec{ + Syscalls: []string{listName}, + }) + } + + actions := []v1alpha1.ActionSelector{{Action: "NotifyKiller"}} + act := &actions[0] + if ksb.kill == nil && ksb.override == nil { + return nil, fmt.Errorf("need either override or kill to notify killer") + } + if ksb.kill != nil { + act.ArgSig = *ksb.kill + } + if ksb.override != nil { + act.ArgError = *ksb.override + } + + if len(ksb.binaries) > 0 { + matchBinaries = []v1alpha1.BinarySelector{{ + Operator: "In", + Values: ksb.binaries, + }} + } + + if ksb.overrideMethod != "" { + options = append(options, v1alpha1.OptionSpec{ + Name: keyOverrideMethod, + Value: ksb.overrideMethod, + }) + } + + if ksb.multiKprobe != nil { + options = append(options, v1alpha1.OptionSpec{ + Name: option.KeyDisableKprobeMulti, + Value: fmt.Sprintf("%t", *ksb.multiKprobe), + }) + } + + // NB: We might want to add options for these in the future + syscallIDTy := "syscall64" + operator := "InMap" + + return &v1alpha1.TracingPolicy{ + TypeMeta: k8sv1.TypeMeta{ + Kind: "TracingPolicy", + APIVersion: "cilium.io/v1alpha1", + }, + ObjectMeta: k8sv1.ObjectMeta{ + Name: ksb.name, + }, + Spec: v1alpha1.TracingPolicySpec{ + Lists: lists, + Tracepoints: []v1alpha1.TracepointSpec{{ + Subsystem: "raw_syscalls", + Event: "sys_enter", + Args: []v1alpha1.KProbeArg{{ + Index: 4, + Type: syscallIDTy, + }}, + Selectors: []v1alpha1.KProbeSelector{{ + MatchArgs: []v1alpha1.ArgSelector{{ + Index: 0, + Operator: operator, + Values: listNames, + }}, + MatchActions: actions, + MatchBinaries: matchBinaries, + }}, + }}, + Killers: killers, + Options: options, + }, + }, nil +} diff --git a/pkg/sensors/tracing/killer_test.go b/pkg/sensors/tracing/killer_test.go index fd2eac6f407..920c14efbcc 100644 --- a/pkg/sensors/tracing/killer_test.go +++ b/pkg/sensors/tracing/killer_test.go @@ -21,8 +21,18 @@ import ( "github.com/cilium/tetragon/pkg/testutils" tus "github.com/cilium/tetragon/pkg/testutils/sensors" "github.com/stretchr/testify/assert" + "golang.org/x/sys/unix" ) +func testKillerCheckSkip(t *testing.T) { + if !bpf.HasSignalHelper() { + t.Skip("skipping killer test, bpf_send_signal helper not available") + } + if !bpf.HasOverrideHelper() && !bpf.HasModifyReturnSyscall() { + t.Skip("skipping test, neither bpf_override_return nor fmod_ret for syscalls is available") + } +} + func testKiller(t *testing.T, configHook string, test string, test2 string, checker *eventchecker.UnorderedEventChecker, @@ -63,54 +73,21 @@ func testKiller(t *testing.T, configHook string, } func TestKillerOverride(t *testing.T) { - if !bpf.HasOverrideHelper() { - t.Skip("skipping killer test, bpf_override_return helper not available") - } - if !bpf.HasSignalHelper() { - t.Skip("skipping killer test, bpf_send_signal helper not available") + testKillerCheckSkip(t) + + test := testutils.RepoRootPath("contrib/tester-progs/getcpu") + builder := func() *KillerSpecBuilder { + return NewKillerSpecBuilder("killer-override"). + WithSyscallList("sys_getcpu"). + WithMatchBinaries(test). + WithOverrideValue(-17) // EEXIST } - test := testutils.RepoRootPath("contrib/tester-progs/killer-tester") - configHook := ` -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "kill-syscalls" -spec: - lists: - - name: "mine" - type: "syscalls" - values: - - "sys_prctl" - killers: - - syscalls: - - "list:mine" - tracepoints: - - subsystem: "raw_syscalls" - event: "sys_enter" - args: - - index: 4 - type: "syscall64" - selectors: - - matchArgs: - - index: 0 - operator: "InMap" - values: - - "list:mine" - matchBinaries: - - operator: "In" - values: - - "` + test + `" - matchActions: - - action: "NotifyKiller" - argError: -17 # EEXIST -` - tpChecker := ec.NewProcessTracepointChecker(""). WithArgs(ec.NewKprobeArgumentListMatcher(). WithOperator(lc.Ordered). WithValues( - ec.NewKprobeArgumentChecker().WithSizeArg(syscall.SYS_PRCTL), + ec.NewKprobeArgumentChecker().WithSizeArg(unix.SYS_GETCPU), )). WithAction(tetragon.KprobeAction_KPROBE_ACTION_NOTIFYKILLER) @@ -122,52 +99,37 @@ spec: } } - testKiller(t, configHook, test, "", checker, checkerFunc) + t.Run("override_helper", func(t *testing.T) { + if !bpf.HasOverrideHelper() { + t.Skip("override_helper not supported") + } + + t.Run("multi kprobe", func(t *testing.T) { + if !bpf.HasKprobeMulti() { + t.Skip("no multi-kprobe support") + } + yaml := builder().WithOverrideReturn().WithMultiKprobe().MustYAML() + testKiller(t, yaml, test, "", checker, checkerFunc) + }) + + t.Run("kprobe (no multi)", func(t *testing.T) { + yaml := builder().WithOverrideReturn().WithoutMultiKprobe().MustYAML() + testKiller(t, yaml, test, "", checker, checkerFunc) + }) + }) + t.Run("fmod_ret", func(t *testing.T) { + if !bpf.HasModifyReturn() { + t.Skip("fmod_ret not supported") + } + yaml := builder().WithFmodRet().MustYAML() + testKiller(t, yaml, test, "", checker, checkerFunc) + }) } func TestKillerSignal(t *testing.T) { - if !bpf.HasOverrideHelper() { - t.Skip("skipping killer test, bpf_override_return helper not available") - } - if !bpf.HasSignalHelper() { - t.Skip("skipping killer test, bpf_send_signal helper not available") - } + testKillerCheckSkip(t) test := testutils.RepoRootPath("contrib/tester-progs/killer-tester") - configHook := ` -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "kill-syscalls" -spec: - lists: - - name: "mine" - type: "syscalls" - values: - - "sys_prctl" - killers: - - syscalls: - - "list:mine" - tracepoints: - - subsystem: "raw_syscalls" - event: "sys_enter" - args: - - index: 4 - type: "syscall64" - selectors: - - matchArgs: - - index: 0 - operator: "InMap" - values: - - "list:mine" - matchBinaries: - - operator: "In" - values: - - "` + test + `" - matchActions: - - action: "NotifyKiller" - argSig: 9 # SIGKILL -` tpChecker := ec.NewProcessTracepointChecker(""). WithArgs(ec.NewKprobeArgumentListMatcher(). @@ -185,51 +147,39 @@ spec: } } - testKiller(t, configHook, test, "", checker, checkerFunc) -} - -func TestKillerMulti(t *testing.T) { - if !bpf.HasOverrideHelper() { - t.Skip("skipping killer test, bpf_override_return helper not available") + builder := func() *KillerSpecBuilder { + return NewKillerSpecBuilder("killer-signal"). + WithSyscallList("sys_prctl"). + WithMatchBinaries(test). + WithOverrideValue(-17). // EEXIST + WithKill(9) // SigKill } - crd := ` -apiVersion: cilium.io/v1alpha1 -kind: TracingPolicy -metadata: - name: "kill-syscalls" -spec: - lists: - - name: "mine1" - type: "syscalls" - values: - - "sys_prctl" - - name: "mine2" - type: "syscalls" - values: - - "sys_prctl" - killers: - - syscalls: - - "list:mine1" - - syscalls: - - "list:mine2" - tracepoints: - - subsystem: "raw_syscalls" - event: "sys_enter" - args: - - index: 4 - type: "syscall64" - selectors: - - matchArgs: - - index: 0 - operator: "InMap" - values: - - "list:mine1" - matchActions: - - action: "NotifyKiller" - argSig: 9 # SIGKILL -` - - err := checkCrd(t, crd) + t.Run("multi kprobe", func(t *testing.T) { + if !bpf.HasKprobeMulti() { + t.Skip("no multi-kprobe support") + } + if !bpf.HasOverrideHelper() { + t.Skip("no override helper, so cannot use multi kprobes") + } + + yaml := builder().WithMultiKprobe().MustYAML() + testKiller(t, yaml, test, "", checker, checkerFunc) + }) + + t.Run("kprobe (no multi)", func(t *testing.T) { + yaml := builder().WithoutMultiKprobe().MustYAML() + testKiller(t, yaml, test, "", checker, checkerFunc) + }) + +} + +func TestKillerMultiNotSupported(t *testing.T) { + yaml := NewKillerSpecBuilder("killer-multi"). + WithSyscallList("sys_prctl"). + WithSyscallList("sys_dup"). + WithOverrideValue(-17). // EEXIST + MustYAML() + err := checkCrd(t, yaml) assert.Error(t, err) } diff --git a/pkg/sensors/tracing/options.go b/pkg/sensors/tracing/options.go index 1445d6f68a7..ac34b853ed9 100644 --- a/pkg/sensors/tracing/options.go +++ b/pkg/sensors/tracing/options.go @@ -12,27 +12,70 @@ import ( "github.com/cilium/tetragon/pkg/option" ) -type kprobeOptions struct { +type OverrideMethod int + +const ( + keyOverrideMethod = "override-method" + valFmodRet = "fmod-ret" + valOverrideReturn = "override-return" +) + +const ( + OverrideMethodDefault OverrideMethod = iota + OverrideMethodReturn + OverrideMethodFmodRet + OverrideMethodInvalid +) + +func overrideMethodParse(s string) OverrideMethod { + switch s { + case valFmodRet: + return OverrideMethodFmodRet + case valOverrideReturn: + return OverrideMethodReturn + default: + return OverrideMethodInvalid + } +} + +type specOptions struct { DisableKprobeMulti bool + OverrideMethod OverrideMethod } type opt struct { - set func(val string, options *kprobeOptions) error + set func(val string, options *specOptions) error +} + +func newDefaultSpecOptions() *specOptions { + return &specOptions{ + DisableKprobeMulti: false, + OverrideMethod: OverrideMethodDefault, + } } // Allowed kprobe options var opts = map[string]opt{ option.KeyDisableKprobeMulti: opt{ - set: func(str string, options *kprobeOptions) (err error) { + set: func(str string, options *specOptions) (err error) { options.DisableKprobeMulti, err = strconv.ParseBool(str) return err }, }, + keyOverrideMethod: opt{ + set: func(str string, options *specOptions) (err error) { + m := overrideMethodParse(str) + if m == OverrideMethodInvalid { + return fmt.Errorf("invalid override method: '%s'", str) + } + options.OverrideMethod = m + return nil + }, + }, } -func getKprobeOptions(specs []v1alpha1.OptionSpec) (*kprobeOptions, error) { - options := &kprobeOptions{} - +func getSpecOptions(specs []v1alpha1.OptionSpec) (*specOptions, error) { + options := newDefaultSpecOptions() for _, spec := range specs { opt, ok := opts[spec.Name] if ok { @@ -42,6 +85,5 @@ func getKprobeOptions(specs []v1alpha1.OptionSpec) (*kprobeOptions, error) { logger.GetLogger().Infof("Set option %s = %s", spec.Name, spec.Value) } } - return options, nil }