diff --git a/bpf/process/bpf_process_event.h b/bpf/process/bpf_process_event.h index 501137c646d..67bf066584b 100644 --- a/bpf/process/bpf_process_event.h +++ b/bpf/process/bpf_process_event.h @@ -8,6 +8,7 @@ #include "bpf_cgroup.h" #include "bpf_cred.h" +#include "bpf_tracing.h" #define ENAMETOOLONG 36 /* File name too long */ @@ -363,6 +364,40 @@ d_path_local(const struct path *path, int *buflen, int *error) return buffer; } +FUNC_INLINE char *path_from_dentry(struct dentry *dentry, char *buf, int *buflen) +{ + if (d_unlinked(dentry)) { + int error = prepend(&buf, buflen, " (deleted)", 10); + + if (error) // will never happen as prepend will never return a value != 0 + return NULL; + } + + // Construct struct path element with task->nsproxy->mnt_ns->root + struct task_struct *task; + struct nsproxy *ns; + struct mnt_namespace *mnt_ns; + struct vfsmount *root; + + task = (struct task_struct *)get_current_task(); + probe_read(&ns, sizeof(ns), _(&task->nsproxy)); + probe_read(&mnt_ns, sizeof(mnt_ns), _(&ns->mnt_ns)); + probe_read(&root, sizeof(root), _(&mnt_ns->root)); + + struct path target = { + .mnt = root, + .dentry = dentry + }; + + int flags; + + buf = d_path_local(&target, buflen, &flags); + if (!buf) + return NULL; + + return buf; +} + FUNC_INLINE __u32 getcwd(struct msg_process *curr, __u32 offset, __u32 proc_pid) { diff --git a/bpf/process/types/basic.h b/bpf/process/types/basic.h index 74a0d44c849..1080e6bf0d5 100644 --- a/bpf/process/types/basic.h +++ b/bpf/process/types/basic.h @@ -80,6 +80,8 @@ enum { net_dev_ty = 39, + dentry_type = 40, + nop_s64_ty = -10, nop_u64_ty = -11, nop_u32_ty = -12, @@ -221,6 +223,10 @@ struct msg_linux_binprm { char path[MAX_STRING]; } __attribute__((packed)); +struct msg_dentry { + char name[MAX_STRING]; +} __attribute__((packed)); + #ifdef __MULTI_KPROBE FUNC_INLINE __u32 get_index(void *ctx) { @@ -1509,6 +1515,8 @@ FUNC_INLINE size_t type_to_min_size(int type, int argm) return sizeof(struct msg_linux_binprm); case net_dev_ty: return IFNAMSIZ; + case dentry_type: + return sizeof(struct msg_dentry); // nop or something else we do not process here default: return 0; @@ -2480,6 +2488,14 @@ read_call_arg(void *ctx, struct msg_generic_kprobe *e, int index, int type, path_arg = _(&file->f_path); goto do_copy_path; } break; + case dentry_type: { + struct dentry *dentry = (struct dentry *)arg; + char pathbuf[MAX_STRING]; + int len = 0; + char *path = path_from_dentry(dentry, pathbuf, &len); + + size = copy_strings(args, path, MAX_STRING); + }; break; #endif case filename_ty: { struct filename *file; diff --git a/contrib/tester-progs/Makefile b/contrib/tester-progs/Makefile index 1e3f43c968e..dd355190322 100644 --- a/contrib/tester-progs/Makefile +++ b/contrib/tester-progs/Makefile @@ -23,7 +23,8 @@ PROGS = sigkill-tester \ getcpu \ direct-write-tester \ change-capabilities \ - user-stacktrace + user-stacktrace \ + symlink-tester # For now enforcer-tester is compiled to 32-bit only on x86_64 as we want # to test 32-bit binaries and system calls compatibility layer. diff --git a/contrib/tester-progs/symlink-tester.c b/contrib/tester-progs/symlink-tester.c new file mode 100644 index 00000000000..01536397027 --- /dev/null +++ b/contrib/tester-progs/symlink-tester.c @@ -0,0 +1,31 @@ +#include +#include +#include + +#define TARGET_PROG "/usr/bin/id" + +int main() { + const char *src = TARGET_PROG; + const char *dst = "/tmp/id"; + + if (symlink(src, dst) == -1) { + perror("Error creating symlink"); + return 1; + } + + char *const argv[] = {TARGET_PROG, NULL}; + char *const envp[] = {NULL}; + if (execve(dst, argv, envp) == -1) { + perror("Error executing command"); + unlink(dst); + return 1; + } + + if (unlink(dst) == -1) { + perror("Error deleting symlink"); + return 1; + } + + return 0; +} + diff --git a/examples/tracingpolicy/security_inode_follow_link.yaml b/examples/tracingpolicy/security_inode_follow_link.yaml new file mode 100644 index 00000000000..5e7655881ed --- /dev/null +++ b/examples/tracingpolicy/security_inode_follow_link.yaml @@ -0,0 +1,14 @@ +apiVersion: cilium.io/v1alpha1 +kind: TracingPolicy +metadata: + name: "follow-symlink" +spec: + kprobes: + - call: "security_inode_follow_link" + syscall: false + args: + - index: 0 + type: "dentry" + returnArg: + index: 0 + type: "int" diff --git a/pkg/api/tracingapi/client_kprobe.go b/pkg/api/tracingapi/client_kprobe.go index d95bb4663f6..62a3fd9a5f3 100644 --- a/pkg/api/tracingapi/client_kprobe.go +++ b/pkg/api/tracingapi/client_kprobe.go @@ -409,6 +409,20 @@ func (m MsgGenericKprobeArgLinuxBinprm) IsReturnArg() bool { return (m.Index == ReturnArgIndex) } +type MsgGenericKprobeArgDentry struct { + Index uint64 + Value string + Label string +} + +func (m MsgGenericKprobeArgDentry) GetIndex() uint64 { + return m.Index +} + +func (m MsgGenericKprobeArgDentry) IsReturnArg() bool { + return (m.Index == ReturnArgIndex) +} + type MsgGenericUserNamespace struct { Level int32 Uid uint32 diff --git a/pkg/btf/validation.go b/pkg/btf/validation.go index ec8a55e58e2..35595f5eb71 100644 --- a/pkg/btf/validation.go +++ b/pkg/btf/validation.go @@ -375,6 +375,11 @@ func typesCompatible(specTy string, kernelTy string) bool { case "struct net_device *": return true } + case "dentry": + switch kernelTy { + case "struct dentry *": + return true + } case "kernel_cap_t", "cap_inheritable", "cap_permitted", "cap_effective": switch kernelTy { case "struct kernel_cap_t *": diff --git a/pkg/generictypes/generictypes.go b/pkg/generictypes/generictypes.go index d9f0d99e44c..a1a372915b1 100644 --- a/pkg/generictypes/generictypes.go +++ b/pkg/generictypes/generictypes.go @@ -57,6 +57,8 @@ const ( GenericNetDev = 39 + GenericDentryType = 40 + GenericNopType = -1 GenericInvalidType = -2 ) @@ -108,6 +110,7 @@ var GenericStringToType = map[string]int{ "linux_binprm": GenericLinuxBinprmType, "data_loc": GenericDataLoc, "net_device": GenericNetDev, + "dentry": GenericDentryType, } var GenericTypeToStringTable = map[int]string{ @@ -150,6 +153,7 @@ var GenericTypeToStringTable = map[int]string{ GenericLinuxBinprmType: "linux_binprm", GenericDataLoc: "data_loc", GenericNetDev: "net_device", + GenericDentryType: "dentry", GenericInvalidType: "", } diff --git a/pkg/grpc/tracing/tracing.go b/pkg/grpc/tracing/tracing.go index 1b35fdba258..68151578257 100644 --- a/pkg/grpc/tracing/tracing.go +++ b/pkg/grpc/tracing/tracing.go @@ -266,6 +266,12 @@ func getKprobeArgument(arg tracingapi.MsgGenericKprobeArg) *tetragon.KprobeArgum } a.Arg = &tetragon.KprobeArgument_LinuxBinprmArg{LinuxBinprmArg: lArg} a.Label = e.Label + case api.MsgGenericKprobeArgDentry: + lArg := &tetragon.KprobeDentry{ + Name: e.Value, + } + a.Arg = &tetragon.KprobeArgument_DentryArg{DentryArg: lArg} + a.Label = e.Label default: logger.GetLogger().WithField("arg", e).Warnf("unexpected type: %T", e) } diff --git a/pkg/selectors/kernel.go b/pkg/selectors/kernel.go index 2f6d66b79f3..210e0479249 100644 --- a/pkg/selectors/kernel.go +++ b/pkg/selectors/kernel.go @@ -774,7 +774,7 @@ func ParseMatchArg(k *KernelSelectorState, arg *v1alpha1.ArgSelector, sig []v1al } case SelectorOpEQ, SelectorOpNEQ: switch ty { - case gt.GenericFdType, gt.GenericFileType, gt.GenericPathType, gt.GenericStringType, gt.GenericCharBuffer, gt.GenericLinuxBinprmType, gt.GenericDataLoc, gt.GenericNetDev: + case gt.GenericFdType, gt.GenericFileType, gt.GenericPathType, gt.GenericStringType, gt.GenericCharBuffer, gt.GenericLinuxBinprmType, gt.GenericDataLoc, gt.GenericNetDev, gt.GenericDentryType: err := writeMatchStrings(k, arg.Values, ty) if err != nil { return fmt.Errorf("writeMatchStrings error: %w", err) diff --git a/pkg/sensors/tracing/args.go b/pkg/sensors/tracing/args.go index f9a673cf596..4d5a22e10eb 100644 --- a/pkg/sensors/tracing/args.go +++ b/pkg/sensors/tracing/args.go @@ -540,6 +540,20 @@ func getArg(r *bytes.Reader, a argPrinter) api.MsgGenericKprobeArg { arg.Permission = mode arg.Label = a.label return arg + case gt.GenericDentryType: + var arg api.MsgGenericKprobeArgDentry + + arg.Index = uint64(a.index) + arg.Value, err = parseString(r) + if err != nil { + if errors.Is(err, errParseStringSize) { + arg.Value = "/" + } else { + logger.GetLogger().WithError(err).Warn("error parsing arg type dentry") + } + } + arg.Label = a.label + return arg default: logger.GetLogger().WithError(err).WithField("event-type", a.ty).Warnf("Unknown event type") } diff --git a/pkg/sensors/tracing/kprobe_test.go b/pkg/sensors/tracing/kprobe_test.go index 3a0c007cbf3..1acf2a2ff7f 100644 --- a/pkg/sensors/tracing/kprobe_test.go +++ b/pkg/sensors/tracing/kprobe_test.go @@ -4235,45 +4235,45 @@ spec: func TestLoadKprobeSensor(t *testing.T) { var sensorProgs = []tus.SensorProg{ // kprobe - 0: tus.SensorProg{Name: "generic_kprobe_event", Type: ebpf.Kprobe}, - 1: tus.SensorProg{Name: "generic_kprobe_setup_event", Type: ebpf.Kprobe}, - 2: tus.SensorProg{Name: "generic_kprobe_process_event", Type: ebpf.Kprobe}, - 3: tus.SensorProg{Name: "generic_kprobe_filter_arg", Type: ebpf.Kprobe}, - 4: tus.SensorProg{Name: "generic_kprobe_process_filter", Type: ebpf.Kprobe}, - 5: tus.SensorProg{Name: "generic_kprobe_actions", Type: ebpf.Kprobe}, - 6: tus.SensorProg{Name: "generic_kprobe_output", Type: ebpf.Kprobe}, + 0: {Name: "generic_kprobe_event", Type: ebpf.Kprobe}, + 1: {Name: "generic_kprobe_setup_event", Type: ebpf.Kprobe}, + 2: {Name: "generic_kprobe_process_event", Type: ebpf.Kprobe}, + 3: {Name: "generic_kprobe_filter_arg", Type: ebpf.Kprobe}, + 4: {Name: "generic_kprobe_process_filter", Type: ebpf.Kprobe}, + 5: {Name: "generic_kprobe_actions", Type: ebpf.Kprobe}, + 6: {Name: "generic_kprobe_output", Type: ebpf.Kprobe}, // retkprobe - 7: tus.SensorProg{Name: "generic_retkprobe_event", Type: ebpf.Kprobe}, - 8: tus.SensorProg{Name: "generic_retkprobe_filter_arg", Type: ebpf.Kprobe}, - 9: tus.SensorProg{Name: "generic_retkprobe_actions", Type: ebpf.Kprobe}, - 10: tus.SensorProg{Name: "generic_retkprobe_output", Type: ebpf.Kprobe}, + 7: {Name: "generic_retkprobe_event", Type: ebpf.Kprobe}, + 8: {Name: "generic_retkprobe_filter_arg", Type: ebpf.Kprobe}, + 9: {Name: "generic_retkprobe_actions", Type: ebpf.Kprobe}, + 10: {Name: "generic_retkprobe_output", Type: ebpf.Kprobe}, } var sensorMaps = []tus.SensorMap{ // all kprobe programs - tus.SensorMap{Name: "process_call_heap", Progs: []uint{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}, + {Name: "process_call_heap", Progs: []uint{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}, // all but generic_kprobe_output - tus.SensorMap{Name: "kprobe_calls", Progs: []uint{0, 1, 2, 3, 4, 5}}, + {Name: "kprobe_calls", Progs: []uint{0, 1, 2, 3, 4, 5}}, // generic_retkprobe_event - tus.SensorMap{Name: "retkprobe_calls", Progs: []uint{7, 8, 9}}, + {Name: "retkprobe_calls", Progs: []uint{7, 8, 9}}, // generic_kprobe_process_filter,generic_kprobe_filter_arg, // generic_kprobe_actions,generic_kprobe_output - tus.SensorMap{Name: "filter_map", Progs: []uint{3, 4, 5}}, + {Name: "filter_map", Progs: []uint{3, 4, 5}}, // generic_kprobe_actions - tus.SensorMap{Name: "override_tasks", Progs: []uint{5}}, + {Name: "override_tasks", Progs: []uint{5}}, // all kprobe but generic_kprobe_process_filter,generic_retkprobe_event - tus.SensorMap{Name: "config_map", Progs: []uint{0, 1, 2}}, + {Name: "config_map", Progs: []uint{0, 1, 2}}, // generic_kprobe_process_event*,generic_kprobe_actions,retkprobe - tus.SensorMap{Name: "fdinstall_map", Progs: []uint{1, 2, 5, 7, 9}}, + {Name: "fdinstall_map", Progs: []uint{1, 2, 5, 7, 9}}, // generic_kprobe_event - tus.SensorMap{Name: "tg_conf_map", Progs: []uint{0}}, + {Name: "tg_conf_map", Progs: []uint{0}}, } if kernels.EnableLargeProgs() { @@ -5988,6 +5988,81 @@ spec: } } +func TestDentryExtractPath(t *testing.T) { + + testutils.CaptureLog(t, logger.GetLogger().(*logrus.Logger)) + ctx, cancel := context.WithTimeout(context.Background(), tus.Conf().CmdWaitTime) + defer cancel() + + if err := observer.InitDataCache(1024); err != nil { + t.Fatalf("observertesthelper.InitDataCache: %s", err) + } + + option.Config.HubbleLib = tus.Conf().TetragonLib + tus.LoadSensor(t, base.GetInitialSensor()) + tus.LoadSensor(t, testsensor.GetTestSensor()) + sm := tus.GetTestSensorManager(ctx, t) + + testSymlink := testutils.RepoRootPath("contrib/tester-progs/symlink-tester") + dentryTracingPolicy := tracingpolicy.GenericTracingPolicy{ + Metadata: v1.ObjectMeta{ + Name: "dentry-extract-path", + }, + Spec: v1alpha1.TracingPolicySpec{ + Options: []v1alpha1.OptionSpec{ + { + Name: "disable-kprobe-multi", + Value: "1", + }, + }, + KProbes: []v1alpha1.KProbeSpec{ + { + Call: "security_inode_follow_link", + Syscall: false, + Return: false, + Args: []v1alpha1.KProbeArg{ + { + Index: 0, + Type: "dentry", + }, + }, + Selectors: []v1alpha1.KProbeSelector{ + { + MatchBinaries: []v1alpha1.BinarySelector{ + { + Operator: "In", + Values: []string{testSymlink}, + }, + }, + }, + }, + }, + }, + }, + } + + err := sm.Manager.AddTracingPolicy(ctx, &dentryTracingPolicy) + assert.NoError(t, err) + + command := exec.Command(testSymlink) + + ops := func() { + err = command.Start() + assert.NoError(t, err) + } + + events := perfring.RunTestEvents(t, ctx, ops) + + for _, ev := range events { + if kprobe, ok := ev.(*tracing.MsgGenericKprobeUnix); ok { + if int(kprobe.Msg.ProcessKey.Pid) == command.Process.Pid && kprobe.FuncName == "security_inode_follow_link" { + return + } + } + } + t.Error("dentry error") +} + func TestLinuxBinprmExtractPath(t *testing.T) { if !kernels.EnableLargeProgs() { t.Skip("Older kernels do not support matchArgs with linux_binprm")