diff --git a/.github/workflows/packages-e2e-tests.yaml b/.github/workflows/packages-e2e-tests.yaml index ee693077f4a..a489ae23e36 100644 --- a/.github/workflows/packages-e2e-tests.yaml +++ b/.github/workflows/packages-e2e-tests.yaml @@ -1,7 +1,7 @@ name: Packages e2e Tests on: - pull_request: + pull_request_target: paths-ignore: - "**.md" - 'docs/**' diff --git a/bpf/process/bpf_generic_kprobe.c b/bpf/process/bpf_generic_kprobe.c index 49350f84a9d..8545ae4677b 100644 --- a/bpf/process/bpf_generic_kprobe.c +++ b/bpf/process/bpf_generic_kprobe.c @@ -302,3 +302,17 @@ generic_kprobe_override(void *ctx) map_delete_elem(&override_tasks, &id); return 0; } + +__attribute__((section("fmod_ret/security_task_prctl"), used)) long +generic_fmodret_override(void *ctx) +{ + __u64 id = get_current_pid_tgid(); + __s32 *error; + + error = map_lookup_elem(&override_tasks, &id); + if (!error) + return 0; + + map_delete_elem(&override_tasks, &id); + return (long)*error; +} diff --git a/cmd/tetragon/main.go b/cmd/tetragon/main.go index c8f82b62bae..f21bece4561 100644 --- a/cmd/tetragon/main.go +++ b/cmd/tetragon/main.go @@ -160,6 +160,8 @@ func tetragonExecute() error { log.WithField("version", version.Version).Info("Starting tetragon") log.WithField("config", viper.AllSettings()).Info("config settings") + log.Info("BPF detected features: ", bpf.LogFeatures()) + if option.Config.ForceLargeProgs && option.Config.ForceSmallProgs { log.Fatalf("Can't specify --force-small-progs and --force-large-progs together") } diff --git a/examples/tracingpolicy/override-security.yaml b/examples/tracingpolicy/override-security.yaml new file mode 100644 index 00000000000..2a84efe071b --- /dev/null +++ b/examples/tracingpolicy/override-security.yaml @@ -0,0 +1,16 @@ +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: "syswritefollowfdpsswd" +spec: + kprobes: + - call: "security_inode_mkdir" + syscall: false + selectors: + - matchBinaries: + - operator: "In" + values: + - "/usr/bin/bash" + - matchActions: + - action: Override + argError: -1 diff --git a/pkg/bpf/detect.go b/pkg/bpf/detect.go index a52bae1f30b..ae3f0eb5df2 100644 --- a/pkg/bpf/detect.go +++ b/pkg/bpf/detect.go @@ -25,6 +25,7 @@ var ( overrideHelper Feature kprobeMulti Feature buildid Feature + modifyReturn Feature ) func detectOverrideHelper() bool { @@ -108,3 +109,59 @@ func HasBuildId() bool { }) return buildid.detected } + +func detectModifyReturn() bool { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "probe_fmod_ret", + Type: ebpf.Tracing, + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + AttachType: ebpf.AttachModifyReturn, + License: "MIT", + AttachTo: "security_task_prctl", + }) + if err != nil { + return false + } + defer prog.Close() + + link, err := link.AttachTracing(link.TracingOptions{ + Program: prog, + }) + if err != nil { + return false + } + link.Close() + return true +} + +func HasModifyReturn() bool { + modifyReturn.init.Do(func() { + modifyReturn.detected = detectModifyReturn() + }) + return modifyReturn.detected +} + +func LogFeatures() string { + feats := "" + + if HasOverrideHelper() { + feats += "override_return " + } + if HasBuildId() { + feats += "buildid " + } + if HasKprobeMulti() { + feats += "kprobe_multi " + } + if HasModifyReturn() { + feats += "fmodret " + } + // cut the final ' ' + if len(feats) != 0 { + feats = feats[:len(feats)-1] + } + return feats +} diff --git a/pkg/observer/observertesthelper/observer_test_helper.go b/pkg/observer/observertesthelper/observer_test_helper.go index 02d657c95e3..b98428db0a5 100644 --- a/pkg/observer/observertesthelper/observer_test_helper.go +++ b/pkg/observer/observertesthelper/observer_test_helper.go @@ -271,6 +271,8 @@ func getDefaultObserver(tb testing.TB, ctx context.Context, base *sensors.Sensor testDone(tb, obs) }) + logger.GetLogger().Info("BPF detected features: ", bpf.LogFeatures()) + obs.PerfConfig = bpf.DefaultPerfEventConfig() obs.PerfConfig.MapName = filepath.Join(bpf.MapPrefixPath(), "tcpmon_map") return obs, nil diff --git a/pkg/sensors/program/loader.go b/pkg/sensors/program/loader.go index 47633441031..8860352e0b4 100644 --- a/pkg/sensors/program/loader.go +++ b/pkg/sensors/program/loader.go @@ -125,15 +125,30 @@ func RawTracepointAttach(load *Program) AttachFunc { } } +func disableProg(coll *ebpf.CollectionSpec, name string) { + if spec, ok := coll.Programs[name]; ok { + spec.Type = ebpf.UnspecifiedProgram + } +} + func KprobeOpen(load *Program) OpenFunc { return func(coll *ebpf.CollectionSpec) error { // The generic_kprobe_override program is part of bpf_generic_kprobe.o object, // so let's disable it if the override is not configured. Otherwise it gets // loaded and bpftool will show it. if !load.Override { - progOverrideSpec, ok := coll.Programs["generic_kprobe_override"] - if ok { - progOverrideSpec.Type = ebpf.UnspecifiedProgram + disableProg(coll, "generic_kprobe_override") + disableProg(coll, "generic_fmodret_override") + } else { + if load.OverrideFmodRet { + spec, ok := coll.Programs["generic_fmodret_override"] + if !ok { + return fmt.Errorf("failed to find generic_fmodret_override") + } + spec.AttachTo = load.Attach + disableProg(coll, "generic_kprobe_override") + } else { + disableProg(coll, "generic_fmodret_override") } } return nil @@ -161,35 +176,96 @@ func kprobeAttach(load *Program, prog *ebpf.Program, spec *ebpf.ProgramSpec, sym }, nil } -func KprobeAttach(load *Program, bpfDir string) AttachFunc { - return func(coll *ebpf.Collection, collSpec *ebpf.CollectionSpec, - prog *ebpf.Program, spec *ebpf.ProgramSpec) (unloader.Unloader, error) { +func kprobeAttachOverride(load *Program, bpfDir string, + coll *ebpf.Collection, collSpec *ebpf.CollectionSpec) error { - if load.Override { - progOverrideSpec, ok := collSpec.Programs["generic_kprobe_override"] - if ok { - progOverrideSpec.Type = ebpf.UnspecifiedProgram - } + spec, ok := collSpec.Programs["generic_kprobe_override"] + if !ok { + return fmt.Errorf("spec for generic_kprobe_override program not found") + } - progOverride, ok := coll.Programs["generic_kprobe_override"] - if !ok { - return nil, fmt.Errorf("program for section '%s' not found", load.Label) - } + prog, ok := coll.Programs["generic_kprobe_override"] + if !ok { + return fmt.Errorf("program generic_kprobe_override not found") + } - progOverride, err := progOverride.Clone() - if err != nil { - return nil, fmt.Errorf("failed to clone program '%s': %w", load.Label, err) - } + prog, err := prog.Clone() + if err != nil { + return fmt.Errorf("failed to clone generic_kprobe_override program: %w", err) + } - pinPath := filepath.Join(bpfDir, fmt.Sprint(load.PinPath, "-override")) + 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) - } + if err := prog.Pin(pinPath); err != nil { + return fmt.Errorf("pinning '%s' to '%s' failed: %w", load.Label, pinPath, err) + } - load.unloaderOverride, err = kprobeAttach(load, progOverride, progOverrideSpec, load.Attach) - if err != nil { - logger.GetLogger().Warnf("Failed to attach override program: %w", err) + load.unloaderOverride, err = kprobeAttach(load, prog, spec, load.Attach) + if err != nil { + logger.GetLogger().Warnf("Failed to attach override program: %w", err) + } + + return nil +} + +func fmodretAttachOverride(load *Program, bpfDir string, + coll *ebpf.Collection, collSpec *ebpf.CollectionSpec) error { + + spec, ok := collSpec.Programs["generic_fmodret_override"] + if !ok { + return fmt.Errorf("spec for generic_fmodret_override program not found") + } + + prog, ok := coll.Programs["generic_fmodret_override"] + if !ok { + return fmt.Errorf("program generic_fmodret_override not found") + } + + prog, err := prog.Clone() + if err != nil { + return fmt.Errorf("failed to clone generic_fmodret_override program: %w", err) + } + + pinPath := filepath.Join(bpfDir, fmt.Sprint(load.PinPath, "-override")) + + if err := prog.Pin(pinPath); err != nil { + return fmt.Errorf("pinning '%s' to '%s' failed: %w", load.Label, pinPath, err) + } + + linkFn := func() (link.Link, error) { + return link.AttachTracing(link.TracingOptions{ + Program: prog, + }) + } + + lnk, err := linkFn() + if err != nil { + return fmt.Errorf("attaching '%s' failed: %w", spec.Name, err) + } + + load.unloaderOverride = &unloader.RelinkUnloader{ + UnloadProg: unloader.PinUnloader{Prog: prog}.Unload, + IsLinked: true, + Link: lnk, + RelinkFn: linkFn, + } + + return nil +} + +func KprobeAttach(load *Program, bpfDir string) AttachFunc { + return func(coll *ebpf.Collection, collSpec *ebpf.CollectionSpec, + prog *ebpf.Program, spec *ebpf.ProgramSpec) (unloader.Unloader, error) { + + if load.Override { + if load.OverrideFmodRet { + if err := fmodretAttachOverride(load, bpfDir, coll, collSpec); err != nil { + return nil, err + } + } else { + if err := kprobeAttachOverride(load, bpfDir, coll, collSpec); err != nil { + return nil, err + } } } diff --git a/pkg/sensors/program/program.go b/pkg/sensors/program/program.go index 4f739a32c74..2d0f786caee 100644 --- a/pkg/sensors/program/program.go +++ b/pkg/sensors/program/program.go @@ -74,7 +74,8 @@ type Program struct { ErrorFatal bool // Needs override bpf program - Override bool + Override bool + OverrideFmodRet bool // Type is the type of BPF program. For example, tc, skb, tracepoint, // etc. diff --git a/pkg/sensors/tracing/generickprobe.go b/pkg/sensors/tracing/generickprobe.go index 2744d009423..dec123d165a 100644 --- a/pkg/sensors/tracing/generickprobe.go +++ b/pkg/sensors/tracing/generickprobe.go @@ -621,6 +621,19 @@ func addKprobe(funcName string, f *v1alpha1.KProbeSpec, in *addKprobeIn, selMaps } } + isSecurityFunc := strings.HasPrefix(funcName, "security_") + + if selectors.HasOverride(f) { + if isSecurityFunc && in.useMulti { + return nil, fmt.Errorf("Error: can't override '%s' function with kprobe_multi, use --disable-kprobe-multi option", + funcName) + } + if isSecurityFunc && !bpf.HasModifyReturn() { + return nil, fmt.Errorf("Error: can't override '%s' function without fmodret support", + funcName) + } + } + argRetprobe = nil // holds pointer to arg for return handler // Parse Arguments @@ -806,6 +819,9 @@ func addKprobe(funcName string, f *v1alpha1.KProbeSpec, in *addKprobeIn, selMaps "generic_kprobe"). SetLoaderData(kprobeEntry.tableId) load.Override = kprobeEntry.hasOverride + if load.Override { + load.OverrideFmodRet = isSecurityFunc && bpf.HasModifyReturn() + } out.progs = append(out.progs, load) fdinstall := program.MapBuilderPin("fdinstall_map", sensors.PathJoin(in.sensorPath, "fdinstall_map"), load) @@ -1001,6 +1017,7 @@ func loadMultiKprobeSensor(ids []idtable.EntryID, bpfDir, mapDir string, load *p } load.Override = len(data.Overrides) > 0 + load.OverrideFmodRet = false load.SetAttachData(data) if err := program.LoadMultiKprobeProgram(bpfDir, mapDir, load, verbose); err == nil { diff --git a/pkg/sensors/tracing/kprobe_test.go b/pkg/sensors/tracing/kprobe_test.go index 7e3b97b97d0..7769ca49e42 100644 --- a/pkg/sensors/tracing/kprobe_test.go +++ b/pkg/sensors/tracing/kprobe_test.go @@ -2207,6 +2207,64 @@ spec: runKprobeOverride(t, openAtHook, checker, file.Name(), syscall.ENOENT, false) } +func TestKprobeOverrideSecurity(t *testing.T) { + if !bpf.HasModifyReturn() { + t.Skip("skipping fmod_ret support is not available") + } + + pidStr := strconv.Itoa(int(observertesthelper.GetMyPid())) + + file, err := os.CreateTemp(t.TempDir(), "kprobe-override-") + if err != nil { + t.Fatalf("writeFile(%s): err %s", testConfigFile, err) + } + defer assert.NoError(t, file.Close()) + + openAtHook := ` +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: "sys-openat-override" +spec: + kprobes: + - call: "security_file_open" + syscall: false + return: true + args: + - index: 0 + type: "file" + returnArg: + type: "int" + selectors: + - matchPIDs: + - operator: In + followForks: true + values: + - ` + pidStr + ` + matchArgs: + - index: 0 + operator: "Equal" + values: + - "` + file.Name() + `" + matchActions: + - action: Override + argError: -2 +` + + kpChecker := ec.NewProcessKprobeChecker(""). + WithFunctionName(sm.Full("security_file_open")). + WithArgs(ec.NewKprobeArgumentListMatcher(). + WithOperator(lc.Ordered). + WithValues( + ec.NewKprobeArgumentChecker().WithFileArg(ec.NewKprobeFileChecker().WithPath(sm.Full(file.Name()))), + )). + WithReturn(ec.NewKprobeArgumentChecker().WithIntArg(-2)). + WithAction(tetragon.KprobeAction_KPROBE_ACTION_OVERRIDE) + checker := ec.NewUnorderedEventChecker(kpChecker) + + runKprobeOverride(t, openAtHook, checker, file.Name(), syscall.ENOENT, false) +} + func TestKprobeOverrideNopostAction(t *testing.T) { pidStr := strconv.Itoa(int(observertesthelper.GetMyPid()))