Skip to content

Commit

Permalink
tetragon: extract path from dentry using CO:RE
Browse files Browse the repository at this point in the history
Signed-off-by: David Windsor <dawindso@cisco.com>
  • Loading branch information
dwindsor committed Jul 18, 2024
1 parent 4d73b14 commit b450367
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 21 deletions.
35 changes: 35 additions & 0 deletions bpf/process/bpf_process_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "bpf_cgroup.h"
#include "bpf_cred.h"
#include "bpf_tracing.h"

#define ENAMETOOLONG 36 /* File name too long */

Expand Down Expand Up @@ -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)
{
Expand Down
16 changes: 16 additions & 0 deletions bpf/process/types/basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ enum {

net_dev_ty = 39,

dentry_type = 40,

nop_s64_ty = -10,
nop_u64_ty = -11,
nop_u32_ty = -12,
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion contrib/tester-progs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 31 additions & 0 deletions contrib/tester-progs/symlink-tester.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#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;
}

14 changes: 14 additions & 0 deletions examples/tracingpolicy/security_inode_follow_link.yaml
Original file line number Diff line number Diff line change
@@ -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"
14 changes: 14 additions & 0 deletions pkg/api/tracingapi/client_kprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/btf/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 *":
Expand Down
4 changes: 4 additions & 0 deletions pkg/generictypes/generictypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const (

GenericNetDev = 39

GenericDentryType = 40

GenericNopType = -1
GenericInvalidType = -2
)
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -150,6 +153,7 @@ var GenericTypeToStringTable = map[int]string{
GenericLinuxBinprmType: "linux_binprm",
GenericDataLoc: "data_loc",
GenericNetDev: "net_device",
GenericDentryType: "dentry",
GenericInvalidType: "",
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/grpc/tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{

Check failure on line 270 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeDentry

Check failure on line 270 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeDentry

Check failure on line 270 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeDentry

Check failure on line 270 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeDentry

Check failure on line 270 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeDentry

Check failure on line 270 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / generated-files

undefined: tetragon.KprobeDentry

Check failure on line 270 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / analyze

undefined: tetragon.KprobeDentry
Name: e.Value,
}
a.Arg = &tetragon.KprobeArgument_DentryArg{DentryArg: lArg}

Check failure on line 273 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeArgument_DentryArg)

Check failure on line 273 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeArgument_DentryArg

Check failure on line 273 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeArgument_DentryArg)

Check failure on line 273 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeArgument_DentryArg)

Check failure on line 273 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: tetragon.KprobeArgument_DentryArg)

Check failure on line 273 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / generated-files

undefined: tetragon.KprobeArgument_DentryArg

Check failure on line 273 in pkg/grpc/tracing/tracing.go

View workflow job for this annotation

GitHub Actions / analyze

undefined: tetragon.KprobeArgument_DentryArg
a.Label = e.Label
default:
logger.GetLogger().WithField("arg", e).Warnf("unexpected type: %T", e)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/selectors/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions pkg/sensors/tracing/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
113 changes: 94 additions & 19 deletions pkg/sensors/tracing/kprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit b450367

Please sign in to comment.