From bc295bd71b754af4091bd3acf67c64a17043d4c4 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Tue, 25 Jul 2023 05:07:38 -0400 Subject: [PATCH 1/7] tetragon: Skip loading of override prog for kprobe multi object In case the override helper is not supported we can get verifier failure when loading kprobe multi object. Adding the code to skip the loading of override program the same way we do for normal kprobes. Fixes: 840b12d39247 ("tetragon: Move override setup into kprobe open/attach functions") Signed-off-by: Jiri Olsa --- pkg/sensors/program/loader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index 95c33d81bdf..80ec9762155 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -367,6 +367,7 @@ func LoadUprobeProgram(bpfDir, mapDir string, load *Program, verbose int) error func LoadMultiKprobeProgram(bpfDir, mapDir string, load *Program, verbose int) error { opts := &loadOpts{ attach: MultiKprobeAttach(load), + open: KprobeOpen(load), ci: &customInstall{fmt.Sprintf("%s-kp_calls", load.PinPath), "kprobe"}, } return loadProgram(bpfDir, []string{mapDir}, load, opts, verbose) From 7729ec1e186aced8e79653f89ab4b75b44e794c3 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Tue, 11 Jul 2023 14:37:28 +0000 Subject: [PATCH 2/7] tetragon: Add kprobe.multi section for override program We will need to load override program with kprobe.multi interface in following changes. Signed-off-by: Jiri Olsa --- bpf/process/bpf_generic_kprobe.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpf/process/bpf_generic_kprobe.c b/bpf/process/bpf_generic_kprobe.c index f762ea201bb..49350f84a9d 100644 --- a/bpf/process/bpf_generic_kprobe.c +++ b/bpf/process/bpf_generic_kprobe.c @@ -117,9 +117,11 @@ generic_kprobe_start_process_filter(void *ctx) } #ifdef __MULTI_KPROBE -#define MAIN "kprobe.multi/generic_kprobe" +#define MAIN "kprobe.multi/generic_kprobe" +#define OVERRIDE "kprobe.multi/generic_kprobe_override" #else -#define MAIN "kprobe/generic_kprobe" +#define MAIN "kprobe/generic_kprobe" +#define OVERRIDE "kprobe/generic_kprobe_override" #endif /* Generic kprobe pseudocode is the following @@ -286,7 +288,7 @@ generic_kprobe_output(void *ctx) return generic_output(ctx, (struct bpf_map_def *)&process_call_heap); } -__attribute__((section("kprobe/override"), used)) int +__attribute__((section(OVERRIDE), used)) int generic_kprobe_override(void *ctx) { __u64 id = get_current_pid_tgid(); From 4b39b9a61ecc5533ef22842846b029889acf2c2a Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Tue, 11 Jul 2023 15:13:05 +0000 Subject: [PATCH 3/7] tetragon: Add loader support for kprobe.multi override Adding support to load override helper for kprobe.multi attached kprobes. Signed-off-by: Jiri Olsa --- pkg/sensors/program/loader.go | 81 +++++++++++++++++++++++++--------- pkg/sensors/program/program.go | 5 ++- 2 files changed, 63 insertions(+), 23 deletions(-) diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index 80ec9762155..71464bba8f5 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -276,7 +276,31 @@ func LSMAttach() AttachFunc { } } -func MultiKprobeAttach(load *Program) AttachFunc { +func multiKprobeAttach(load *Program, prog *ebpf.Program, + spec *ebpf.ProgramSpec, opts link.KprobeMultiOptions) (unloader.Unloader, error) { + + var lnk link.Link + var err error + + if load.RetProbe { + lnk, err = link.KretprobeMulti(prog, opts) + } else { + lnk, err = link.KprobeMulti(prog, opts) + } + if err != nil { + return nil, fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) + } + return unloader.ChainUnloader{ + unloader.PinUnloader{ + Prog: prog, + }, + unloader.LinkUnloader{ + Link: lnk, + }, + }, nil +} + +func MultiKprobeAttach(load *Program, bpfDir string) AttachFunc { return func(coll *ebpf.Collection, collSpec *ebpf.CollectionSpec, prog *ebpf.Program, spec *ebpf.ProgramSpec) (unloader.Unloader, error) { @@ -284,30 +308,45 @@ func MultiKprobeAttach(load *Program) AttachFunc { if !ok { return nil, fmt.Errorf("attaching '%s' failed: wrong attach data", spec.Name) } + + if load.Override { + progOverrideSpec, ok := collSpec.Programs["generic_kprobe_override"] + if ok { + progOverrideSpec.Type = ebpf.UnspecifiedProgram + } + + progOverride, ok := coll.Programs["generic_kprobe_override"] + if !ok { + return nil, fmt.Errorf("program for section '%s' not found", load.Label) + } + + progOverride, err := progOverride.Clone() + if err != nil { + return nil, fmt.Errorf("failed to clone program '%s': %w", load.Label, err) + } + + pinPath := filepath.Join(bpfDir, fmt.Sprint(load.PinPath, "-override")) + + if err := progOverride.Pin(pinPath); err != nil { + return nil, fmt.Errorf("pinning '%s' to '%s' failed: %w", load.Label, pinPath, err) + } + + opts := link.KprobeMultiOptions{ + Symbols: data.Overrides, + } + + load.unloaderOverride, err = multiKprobeAttach(load, progOverride, progOverrideSpec, opts) + if err != nil { + logger.GetLogger().Warnf("Failed to attach override program: %w", err) + } + } + opts := link.KprobeMultiOptions{ Symbols: data.Symbols, Cookies: data.Cookies, } - var lnk link.Link - var err error - - if load.RetProbe { - lnk, err = link.KretprobeMulti(prog, opts) - } else { - lnk, err = link.KprobeMulti(prog, opts) - } - if err != nil { - return nil, fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) - } - return unloader.ChainUnloader{ - unloader.PinUnloader{ - Prog: prog, - }, - unloader.LinkUnloader{ - Link: lnk, - }, - }, nil + return multiKprobeAttach(load, prog, spec, opts) } } @@ -366,7 +405,7 @@ func LoadUprobeProgram(bpfDir, mapDir string, load *Program, verbose int) error func LoadMultiKprobeProgram(bpfDir, mapDir string, load *Program, verbose int) error { opts := &loadOpts{ - attach: MultiKprobeAttach(load), + attach: MultiKprobeAttach(load, bpfDir), open: KprobeOpen(load), ci: &customInstall{fmt.Sprintf("%s-kp_calls", load.PinPath), "kprobe"}, } diff --git a/pkg/sensors/program/program.go b/pkg/sensors/program/program.go index 5f61f6a857f..4f739a32c74 100644 --- a/pkg/sensors/program/program.go +++ b/pkg/sensors/program/program.go @@ -43,8 +43,9 @@ type MapLoad struct { } type MultiKprobeAttachData struct { - Symbols []string - Cookies []uint64 + Symbols []string + Cookies []uint64 + Overrides []string } type UprobeAttachData struct { From 4452343050f5a4206ee1bc0fd65cdfd362c5816d Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 24 Jul 2023 13:13:50 +0000 Subject: [PATCH 4/7] tetragon: Add override support for generic multi kprobe Adding override support for kprobes attached with kprobe.multi interface. Signed-off-by: Jiri Olsa --- pkg/sensors/tracing/generickprobe.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/sensors/tracing/generickprobe.go b/pkg/sensors/tracing/generickprobe.go index f1f974f6cea..0079161b27c 100644 --- a/pkg/sensors/tracing/generickprobe.go +++ b/pkg/sensors/tracing/generickprobe.go @@ -118,6 +118,9 @@ type genericKprobe struct { // policyName is the name of the policy that this tracepoint belongs to policyName string + + // is there override defined for the kprobe + hasOverride bool } // pendingEvent is an event waiting to be merged with another event. @@ -497,8 +500,6 @@ func createGenericKprobeSensor( } } - hasOverride := selectors.HasOverride(f) - // Write attributes into BTF ptr for use with load if !setRetprobe { setRetprobe = f.Return @@ -529,6 +530,7 @@ func createGenericKprobeSensor( pendingEvents: nil, tableId: idtable.UninitializedEntryID, policyName: policyName, + hasOverride: selectors.HasOverride(f), } // Parse Filters into kernel filter logic @@ -569,7 +571,7 @@ func createGenericKprobeSensor( pinProg, "generic_kprobe"). SetLoaderData(kprobeEntry.tableId) - load.Override = hasOverride + load.Override = kprobeEntry.hasOverride progs = append(progs, load) fdinstall := program.MapBuilderPin("fdinstall_map", sensors.PathJoin(sensorPath, "fdinstall_map"), load) @@ -728,8 +730,13 @@ func loadMultiKprobeSensor(ids []idtable.EntryID, bpfDir, mapDir string, load *p data.Symbols = append(data.Symbols, gk.funcName) data.Cookies = append(data.Cookies, uint64(index)) + + if gk.hasOverride && !load.RetProbe { + data.Overrides = append(data.Overrides, gk.funcName) + } } + load.Override = len(data.Overrides) > 0 load.SetAttachData(data) if err := program.LoadMultiKprobeProgram(bpfDir, mapDir, load, verbose); err == nil { From 2ba8944e3f2145c77853887d2880ba499201c3c8 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Thu, 13 Jul 2023 12:34:53 +0000 Subject: [PATCH 5/7] tetragon: Use multi kprobe even for single kprobe Let's use multi object even for single kprobe, the kprobe-multi interface should be faster than generic kprobe attach. Signed-off-by: Jiri Olsa --- pkg/sensors/tracing/generickprobe.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/sensors/tracing/generickprobe.go b/pkg/sensors/tracing/generickprobe.go index 0079161b27c..022de3a2d7d 100644 --- a/pkg/sensors/tracing/generickprobe.go +++ b/pkg/sensors/tracing/generickprobe.go @@ -367,10 +367,8 @@ func createGenericKprobeSensor( // use multi kprobe only if: // - it's not disabled by user // - there's support detected - // - multiple kprobes are defined useMulti = !option.Config.DisableKprobeMulti && - bpf.HasKprobeMulti() && - len(kprobes) > 1 + bpf.HasKprobeMulti() for i := range kprobes { f := &kprobes[i] From c4ac6be5666063604a67c23e39a585d93320be8b Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Tue, 25 Jul 2023 12:06:24 +0000 Subject: [PATCH 6/7] tetragon: Be verbose about call being overridden Adding verbose out about call being overridden for both single and multi kprobes, like: logcapture.go:25: time="2023-07-25T12:22:44Z" level=info msg="Added multi kprobe" function=__x64_sys_openat override=true return=true logcapture.go:25: time="2023-07-25T12:22:44Z" level=info msg="Added multi kprobe" function=__x64_sys_linkat override=true return=false logcapture.go:25: time="2023-07-25T12:22:44Z" level=info msg="Added multi kprobe" function=__x64_sys_symlinkat override=true return=false logcapture.go:25: time="2023-07-25T12:22:44Z" level=info msg="Added multi kprobe" function=__x64_sys_renameat override=false return=false Signed-off-by: Jiri Olsa --- pkg/sensors/tracing/generickprobe.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/sensors/tracing/generickprobe.go b/pkg/sensors/tracing/generickprobe.go index 022de3a2d7d..46cce7ddb1e 100644 --- a/pkg/sensors/tracing/generickprobe.go +++ b/pkg/sensors/tracing/generickprobe.go @@ -555,6 +555,7 @@ func createGenericKprobeSensor( logger.GetLogger(). WithField("return", setRetprobe). WithField("function", kprobeEntry.funcName). + WithField("override", kprobeEntry.hasOverride). Infof("Added multi kprobe") continue } @@ -643,6 +644,7 @@ func createGenericKprobeSensor( } logger.GetLogger().WithField("flags", flagsString(config.Flags)). + WithField("override", kprobeEntry.hasOverride). Infof("Added generic kprobe sensor: %s -> %s", load.Name, load.Attach) } From 0e3cc8b91fdeb05fb0dc17211349d5cbd26fe335 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Tue, 25 Jul 2023 11:54:47 +0000 Subject: [PATCH 7/7] tetragon: Add override test for multiple kprobes Add override test for multiple kprobes to verify the kprobe multi interface handles override properly. The test hooks on 4 syscalls and override 3 of them. sys_openat override with -1 sys_linkat override with -2 sys_symlinkat override with -3 sys_renameat no override Signed-off-by: Jiri Olsa --- pkg/sensors/tracing/kprobe_test.go | 252 +++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) diff --git a/pkg/sensors/tracing/kprobe_test.go b/pkg/sensors/tracing/kprobe_test.go index 7f14cbd6f60..e9179f19203 100644 --- a/pkg/sensors/tracing/kprobe_test.go +++ b/pkg/sensors/tracing/kprobe_test.go @@ -2312,6 +2312,258 @@ spec: runKprobeOverrideSignal(t, openAtHook, checker, file.Name(), syscall.ENOENT, true, syscall.SIGUSR1) } +func runKprobeOverrideMulti(t *testing.T, hook string, checker ec.MultiEventChecker, + testFile, testLink string, errOpen, errHardlink, errSymlink error) { + var doneWG, readyWG sync.WaitGroup + defer doneWG.Wait() + + ctx, cancel := context.WithTimeout(context.Background(), tus.Conf().CmdWaitTime) + defer cancel() + + configHook := []byte(hook) + err := os.WriteFile(testConfigFile, configHook, 0644) + if err != nil { + t.Fatalf("writeFile(%s): err %s", testConfigFile, err) + } + + obs, err := observer.GetDefaultObserverWithFile(t, ctx, testConfigFile, tus.Conf().TetragonLib, observer.WithMyPid()) + if err != nil { + t.Fatalf("GetDefaultObserverWithFile error: %s", err) + } + observer.LoopEvents(ctx, t, &doneWG, &readyWG, obs) + readyWG.Wait() + + fd, err := syscall.Open(testFile, syscall.O_RDWR, 0x777) + if fd >= 0 { + t.Logf("syscall.Open succeeded\n") + syscall.Close(fd) + t.Fatal() + } + + if !errors.Is(err, errOpen) { + t.Fatalf("syscall.Open wrong error %v\n", err) + } + + err = syscall.Link(testFile, testLink) + if err == nil { + t.Fatalf("syscall.Link succeeded\n") + } + + if !errors.Is(err, errHardlink) { + t.Fatalf("syscall.Link wrong error %v\n", err) + } + + err = syscall.Symlink(testFile, testLink) + if err == nil { + t.Fatalf("syscall.Symlink succeeded\n") + } + + if !errors.Is(err, errSymlink) { + t.Fatalf("syscall.Symlink wrong error %v\n", err) + } + + err = syscall.Rename(testFile, testLink) + if err != nil { + t.Fatalf("syscall.Rename failed\n") + } + + err = jsonchecker.JsonTestCheck(t, checker) + assert.NoError(t, err) +} + +func TestKprobeOverrideMulti(t *testing.T) { + if !kernels.EnableLargeProgs() { + t.Skip() + } + + if !bpf.HasOverrideHelper() { + t.Skip("skipping override test, bpf_override_return helper not available") + } + + pidStr := strconv.Itoa(int(observer.GetMyPid())) + + file, err := os.CreateTemp(t.TempDir(), "kprobe-override-") + if err != nil { + t.Fatalf("CreateTemp failed: %s", err) + } + defer assert.NoError(t, file.Close()) + + link, err := os.CreateTemp(t.TempDir(), "kprobe-override-") + if err != nil { + t.Fatalf("CreateTemp failed: %s", err) + } + defer assert.NoError(t, link.Close()) + + // The test hooks on 4 syscalls and override 3 of them. + // + // sys_openat override with -1 + // sys_linkat override with -2 + // sys_symlinkat override with -3 + // sys_renameat no override + + multiHook := ` +apiVersion: cilium.io/v1alpha1 +metadata: + name: "sys-openat-signal-override" +spec: + kprobes: + - call: "sys_openat" + return: true + syscall: true + args: + - index: 0 + type: int + - index: 1 + type: "string" + - index: 2 + type: "int" + returnArg: + type: "int" + selectors: + - matchPIDs: + - operator: In + followForks: true + values: + - ` + pidStr + ` + matchArgs: + - index: 1 + operator: "Equal" + values: + - "` + file.Name() + `\0" + matchActions: + - action: Override + argError: -1 + - call: "sys_linkat" + return: true + syscall: true + args: + - index: 0 + type: "int" + - index: 1 + type: "string" + - index: 2 + type: "int" + - index: 3 + type: "string" + - index: 4 + type: "int" + returnArg: + type: "int" + selectors: + - matchPIDs: + - operator: In + followForks: true + values: + - ` + pidStr + ` + matchArgs: + - index: 1 + operator: "Equal" + values: + - "` + file.Name() + `\0" + matchActions: + - action: Override + argError: -2 + - call: "sys_symlinkat" + syscall: true + args: + - index: 0 + type: "string" + - index: 1 + type: "int" + - index: 2 + type: "string" + selectors: + - matchPIDs: + - operator: In + followForks: true + values: + - ` + pidStr + ` + matchArgs: + - index: 0 + operator: "Equal" + values: + - "` + file.Name() + `\0" + matchActions: + - action: Override + argError: -3 + - call: "sys_renameat" + syscall: true + args: + - index: 0 + type: "int" + - index: 1 + type: "string" + - index: 2 + type: "int" + - index: 3 + type: "string" + selectors: + - matchPIDs: + - operator: In + followForks: true + values: + - ` + pidStr + ` + matchArgs: + - index: 1 + operator: "Equal" + values: + - "` + file.Name() + `\0" +` + + openChecker := ec.NewProcessKprobeChecker(""). + WithFunctionName(sm.Full(arch.AddSyscallPrefixTestHelper(t, "sys_openat"))). + WithArgs(ec.NewKprobeArgumentListMatcher(). + WithOperator(lc.Ordered). + WithValues( + ec.NewKprobeArgumentChecker(), + ec.NewKprobeArgumentChecker().WithStringArg(sm.Contains(file.Name())), + ec.NewKprobeArgumentChecker(), + )). + WithReturn(ec.NewKprobeArgumentChecker().WithIntArg(-1)). + WithAction(tetragon.KprobeAction_KPROBE_ACTION_OVERRIDE) + + symlinkChecker := ec.NewProcessKprobeChecker(""). + WithFunctionName(sm.Full(arch.AddSyscallPrefixTestHelper(t, "sys_linkat"))). + WithArgs(ec.NewKprobeArgumentListMatcher(). + WithOperator(lc.Ordered). + WithValues( + ec.NewKprobeArgumentChecker(), + ec.NewKprobeArgumentChecker().WithStringArg(sm.Contains(file.Name())), + ec.NewKprobeArgumentChecker(), + ec.NewKprobeArgumentChecker().WithStringArg(sm.Contains(link.Name())), + ec.NewKprobeArgumentChecker(), + )). + WithReturn(ec.NewKprobeArgumentChecker().WithIntArg(-2)). + WithAction(tetragon.KprobeAction_KPROBE_ACTION_OVERRIDE) + + hardlinkChecker := ec.NewProcessKprobeChecker(""). + WithFunctionName(sm.Full(arch.AddSyscallPrefixTestHelper(t, "sys_symlinkat"))). + WithArgs(ec.NewKprobeArgumentListMatcher(). + WithOperator(lc.Ordered). + WithValues( + ec.NewKprobeArgumentChecker().WithStringArg(sm.Contains(file.Name())), + ec.NewKprobeArgumentChecker(), + ec.NewKprobeArgumentChecker().WithStringArg(sm.Contains(link.Name())), + )). + WithAction(tetragon.KprobeAction_KPROBE_ACTION_OVERRIDE) + + renameChecker := ec.NewProcessKprobeChecker(""). + WithFunctionName(sm.Full(arch.AddSyscallPrefixTestHelper(t, "sys_renameat"))). + WithArgs(ec.NewKprobeArgumentListMatcher(). + WithOperator(lc.Ordered). + WithValues( + ec.NewKprobeArgumentChecker(), + ec.NewKprobeArgumentChecker().WithStringArg(sm.Contains(file.Name())), + ec.NewKprobeArgumentChecker(), + ec.NewKprobeArgumentChecker().WithStringArg(sm.Contains(link.Name())), + )). + WithAction(tetragon.KprobeAction_KPROBE_ACTION_POST) + + checker := ec.NewUnorderedEventChecker(openChecker, symlinkChecker, hardlinkChecker, renameChecker) + + runKprobeOverrideMulti(t, multiHook, checker, file.Name(), link.Name(), syscall.EPERM, syscall.ENOENT, syscall.ESRCH) +} + func runKprobe_char_iovec(t *testing.T, configHook string, checker *ec.UnorderedEventChecker, fdw, fdr int, buffer []byte) { var doneWG, readyWG sync.WaitGroup