From a0d0a8fc9e4f3453d4a0221d17d4626af5d2e4e1 Mon Sep 17 00:00:00 2001 From: Mattia Meleleo Date: Thu, 4 Jan 2024 17:58:06 +0100 Subject: [PATCH] Add file content and owner modification probes --- GPL/Events/EbpfEventProto.h | 3 +- GPL/Events/File/File.h | 10 + GPL/Events/File/Probe.bpf.c | 241 ++++++++++++++++-- GPL/Events/State.h | 24 ++ non-GPL/Events/EventsTrace/EventsTrace.c | 3 + non-GPL/Events/Lib/EbpfEvents.c | 22 ++ testing/test_bins/create_rename_delete_file.c | 4 +- testing/testrunner/main.go | 1 + testing/testrunner/tests.go | 41 ++- 9 files changed, 314 insertions(+), 35 deletions(-) diff --git a/GPL/Events/EbpfEventProto.h b/GPL/Events/EbpfEventProto.h index 995e8ca8..994fdb29 100644 --- a/GPL/Events/EbpfEventProto.h +++ b/GPL/Events/EbpfEventProto.h @@ -180,7 +180,8 @@ enum ebpf_file_change_type { EBPF_FILE_CHANGE_TYPE_UNKNOWN = 0, EBPF_FILE_CHANGE_TYPE_CONTENT = 1, EBPF_FILE_CHANGE_TYPE_PERMISSIONS = 2, - EBPF_FILE_CHANGE_TYPE_XATTRS = 3, + EBPF_FILE_CHANGE_TYPE_OWNER = 3, + EBPF_FILE_CHANGE_TYPE_XATTRS = 4, }; struct ebpf_file_modify_event { diff --git a/GPL/Events/File/File.h b/GPL/Events/File/File.h index 71717ef9..6e4de5ad 100644 --- a/GPL/Events/File/File.h +++ b/GPL/Events/File/File.h @@ -10,6 +10,10 @@ #ifndef EBPF_EVENTPROBE_FILE_H #define EBPF_EVENTPROBE_FILE_H +#include "vmlinux.h" + +#include + #include "EbpfEventProto.h" #define PATH_MAX 4096 @@ -37,6 +41,12 @@ #define NANOSECONDS_IN_SECOND 1000000000 +static struct path *path_from_file(struct file *f) +{ + size_t off = bpf_core_field_offset(struct file, f_path); + return (struct path *)((char *)f + off); +} + static void ebpf_file_info__fill(struct ebpf_file_info *finfo, struct dentry *de) { struct inode *ino = BPF_CORE_READ(de, d_inode); diff --git a/GPL/Events/File/Probe.bpf.c b/GPL/Events/File/Probe.bpf.c index 345cd736..3ba704be 100644 --- a/GPL/Events/File/Probe.bpf.c +++ b/GPL/Events/File/Probe.bpf.c @@ -27,6 +27,9 @@ DECL_FUNC_ARG(vfs_rename, old_dentry); DECL_FUNC_ARG(vfs_rename, new_dentry); DECL_FUNC_RET(vfs_rename); DECL_FUNC_ARG_EXISTS(vfs_rename, rd); +/* do_truncate */ +DECL_FUNC_ARG(do_truncate, filp); +DECL_FUNC_RET(do_truncate); static int mntns(const struct task_struct *task) { @@ -462,41 +465,32 @@ int BPF_KRETPROBE(kretprobe__vfs_rename, int ret) return vfs_rename__exit(ret); } -SEC("kprobe/chmod_common") -int BPF_KPROBE(kprobe__chmod_common, const struct path *path, umode_t mode) -{ - struct ebpf_events_state state = {}; - state.chmod.path = (struct path *)path; - state.chmod.mode = mode; - ebpf_events_state__set(EBPF_EVENTS_STATE_CHMOD, &state); - return 0; -} - -static int chmod_common__exit(struct path *path, umode_t mode, int ret) +static void file_modify_event__emit(enum ebpf_file_change_type typ, struct path *path) { - if (ret) - goto out; - - if (ebpf_events_is_trusted_pid()) - goto out; - struct task_struct *task = (struct task_struct *)bpf_get_current_task(); struct ebpf_file_modify_event *event = get_event_buffer(); if (!event) { - bpf_printk("chmod_common__exit: failed to reserve event\n"); + bpf_printk("file_modify_event__emit: failed to reserve event\n"); goto out; } event->hdr.type = EBPF_EVENT_FILE_MODIFY; event->hdr.ts = bpf_ktime_get_ns(); - event->change_type = EBPF_FILE_CHANGE_TYPE_PERMISSIONS; + event->change_type = typ; ebpf_pid_info__fill(&event->pids, task); event->mntns = mntns(task); bpf_get_current_comm(event->comm, TASK_COMM_LEN); struct dentry *d = BPF_CORE_READ(path, dentry); ebpf_file_info__fill(&event->finfo, d); + switch (event->finfo.type) { + case EBPF_FILE_TYPE_FILE: + break; + default: + goto out; + } + // Variable length fields ebpf_vl_fields__init(&event->vl_fields); struct ebpf_varlen_field *field; @@ -516,13 +510,38 @@ static int chmod_common__exit(struct path *path, umode_t mode, int ret) bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); out: + return; +} + +SEC("kprobe/chmod_common") +int BPF_KPROBE(kprobe__chmod_common, const struct path *path, umode_t mode) +{ + struct ebpf_events_state state = {}; + state.chmod.path = (struct path *)path; + state.chmod.mode = mode; + ebpf_events_state__set(EBPF_EVENTS_STATE_CHMOD, &state); return 0; } +static void chmod_common__exit(struct path *path, int ret) +{ + if (ret) + goto out; + + if (ebpf_events_is_trusted_pid()) + goto out; + + file_modify_event__emit(EBPF_FILE_CHANGE_TYPE_PERMISSIONS, path); + +out: + return; +} + SEC("fexit/chmod_common") int BPF_PROG(fexit__chmod_common, const struct path *path, umode_t mode, int ret) { - return chmod_common__exit((struct path *)path, mode, ret); + chmod_common__exit((struct path *)path, ret); + return 0; } SEC("kretprobe/chmod_common") @@ -532,7 +551,187 @@ int BPF_KRETPROBE(kretprobe__chmod_common, int ret) if (!state) goto out; - chmod_common__exit(state->chmod.path, state->chmod.mode, ret); + chmod_common__exit(state->chmod.path, ret); + +out: + return 0; +} + +SEC("kprobe/do_truncate") +int BPF_KPROBE(kprobe__do_truncate) +{ + struct ebpf_events_state state = {}; + + struct file *filp; + if (FUNC_ARG_READ_PTREGS(filp, do_truncate, filp)) { + bpf_printk("kprobe__do_truncate: error reading filp\n"); + return 0; + } + + state.truncate.path = path_from_file(filp); + ebpf_events_state__set(EBPF_EVENTS_STATE_TRUNCATE, &state); + +out: + return 0; +} + +static void do_truncate__exit(struct path *path, int ret) +{ + if (ret) + goto out; + + if (ebpf_events_is_trusted_pid()) + goto out; + + file_modify_event__emit(EBPF_FILE_CHANGE_TYPE_CONTENT, path); + +out: + return; +} + +SEC("fexit/do_truncate") +int BPF_PROG(fexit__do_truncate) +{ + struct file *filp = FUNC_ARG_READ(___type(filp), do_truncate, filp); + int ret = FUNC_RET_READ(___type(ret), do_truncate); + do_truncate__exit(path_from_file(filp), ret); + return 0; +} + +SEC("kretprobe/do_truncate") +int BPF_KRETPROBE(kretprobe__do_truncate, int ret) +{ + struct ebpf_events_state *state = ebpf_events_state__get(EBPF_EVENTS_STATE_TRUNCATE); + if (!state) + goto out; + + do_truncate__exit(state->truncate.path, ret); + +out: + return 0; +} + +SEC("kprobe/vfs_write") +int BPF_KPROBE(kprobe__vfs_write, struct file *file) +{ + struct ebpf_events_state state = {}; + + state.write.path = path_from_file(file); + ebpf_events_state__set(EBPF_EVENTS_STATE_WRITE, &state); + + return 0; +} + +SEC("kprobe/vfs_writev") +int BPF_KPROBE(kprobe__vfs_writev, struct file *file) +{ + struct ebpf_events_state state = {}; + + state.writev.path = path_from_file(file); + ebpf_events_state__set(EBPF_EVENTS_STATE_WRITEV, &state); + + return 0; +} + +static void vfs_write__exit(struct path *path, ssize_t ret) +{ + if (ret <= 0) + goto out; + + if (ebpf_events_is_trusted_pid()) + goto out; + + file_modify_event__emit(EBPF_FILE_CHANGE_TYPE_CONTENT, path); + +out: + return; +} + +SEC("fexit/vfs_write") +int BPF_PROG( + fexit__vfs_write, struct file *file, const char *buf, size_t count, loff_t *pos, ssize_t ret) +{ + vfs_write__exit(path_from_file(file), ret); + return 0; +} + +SEC("fexit/vfs_writev") +int BPF_PROG(fexit__vfs_writev, + struct file *file, + const struct iovec *vec, + unsigned long vlen, + loff_t *pos, + rwf_t flags, + ssize_t ret) +{ + vfs_write__exit(path_from_file(file), ret); + return 0; +} + +SEC("kretprobe/vfs_write") +int BPF_KRETPROBE(kretprobe__vfs_write, ssize_t ret) +{ + struct ebpf_events_state *state = ebpf_events_state__get(EBPF_EVENTS_STATE_WRITE); + if (!state) + goto out; + + vfs_write__exit(state->write.path, ret); + +out: + return 0; +} + +SEC("kretprobe/vfs_writev") +int BPF_KRETPROBE(kretprobe__vfs_writev, ssize_t ret) +{ + struct ebpf_events_state *state = ebpf_events_state__get(EBPF_EVENTS_STATE_WRITEV); + if (!state) + goto out; + + vfs_write__exit(state->writev.path, ret); + +out: + return 0; +} + +SEC("kprobe/chown_common") +int BPF_KPROBE(kprobe__chown_common, struct path *path, uid_t user, gid_t group) +{ + struct ebpf_events_state state = {}; + state.chown.path = path; + ebpf_events_state__set(EBPF_EVENTS_STATE_CHOWN, &state); + return 0; +} + +static void chown_common__exit(struct path *path, int ret) +{ + if (ret) + goto out; + + if (ebpf_events_is_trusted_pid()) + goto out; + + file_modify_event__emit(EBPF_FILE_CHANGE_TYPE_OWNER, path); + +out: + return; +} + +SEC("fexit/chown_common") +int BPF_PROG(fexit__chown_common, struct path *path, uid_t user, gid_t group, int ret) +{ + chown_common__exit(path, ret); + return 0; +} + +SEC("kretprobe/chown_common") +int BPF_KRETPROBE(kretprobe__chown_common, int ret) +{ + struct ebpf_events_state *state = ebpf_events_state__get(EBPF_EVENTS_STATE_CHOWN); + if (!state) + goto out; + + chown_common__exit(state->chown.path, ret); out: return 0; diff --git a/GPL/Events/State.h b/GPL/Events/State.h index a4cf7e05..82197bc4 100644 --- a/GPL/Events/State.h +++ b/GPL/Events/State.h @@ -17,6 +17,10 @@ enum ebpf_events_state_op { EBPF_EVENTS_STATE_TCP_V4_CONNECT = 3, EBPF_EVENTS_STATE_TCP_V6_CONNECT = 4, EBPF_EVENTS_STATE_CHMOD = 5, + EBPF_EVENTS_STATE_TRUNCATE = 6, + EBPF_EVENTS_STATE_WRITE = 7, + EBPF_EVENTS_STATE_WRITEV = 8, + EBPF_EVENTS_STATE_CHOWN = 9, }; struct ebpf_events_key { @@ -60,6 +64,22 @@ struct ebpf_events_chmod_state { umode_t mode; }; +struct ebpf_events_truncate_state { + struct path *path; +}; + +struct ebpf_events_write_state { + struct path *path; +}; + +struct ebpf_events_writev_state { + struct path *path; +}; + +struct ebpf_events_chown_state { + struct path *path; +}; + struct ebpf_events_state { union { struct ebpf_events_unlink_state unlink; @@ -67,6 +87,10 @@ struct ebpf_events_state { struct ebpf_events_tcp_connect_state tcp_v4_connect; struct ebpf_events_tcp_connect_state tcp_v6_connect; struct ebpf_events_chmod_state chmod; + struct ebpf_events_truncate_state truncate; + struct ebpf_events_write_state write; + struct ebpf_events_writev_state writev; + struct ebpf_events_chown_state chown; }; }; diff --git a/non-GPL/Events/EventsTrace/EventsTrace.c b/non-GPL/Events/EventsTrace/EventsTrace.c index 51bc2122..a66321d7 100644 --- a/non-GPL/Events/EventsTrace/EventsTrace.c +++ b/non-GPL/Events/EventsTrace/EventsTrace.c @@ -531,6 +531,9 @@ static void out_file_modify(struct ebpf_file_modify_event *evt) case EBPF_FILE_CHANGE_TYPE_PERMISSIONS: out_string("change_type", "PERMISSIONS"); break; + case EBPF_FILE_CHANGE_TYPE_OWNER: + out_string("change_type", "OWNER"); + break; case EBPF_FILE_CHANGE_TYPE_XATTRS: out_string("change_type", "XATTRS"); break; diff --git a/non-GPL/Events/Lib/EbpfEvents.c b/non-GPL/Events/Lib/EbpfEvents.c index e58136ce..377f383c 100644 --- a/non-GPL/Events/Lib/EbpfEvents.c +++ b/non-GPL/Events/Lib/EbpfEvents.c @@ -281,6 +281,9 @@ static int probe_fill_relos(struct btf *btf, struct EventProbe_bpf *obj) if (BTF_FIELD_EXISTS(btf, iov_iter, __iov)) err = err ?: FILL_FIELD_OFFSET(obj, btf, iov_iter, __iov); + err = err ?: FILL_FUNC_ARG_IDX(obj, btf, do_truncate, filp); + err = err ?: FILL_FUNC_RET_IDX(obj, btf, do_truncate); + return err; } @@ -339,6 +342,16 @@ static inline int probe_set_autoload(struct btf *btf, struct EventProbe_bpf *obj err = err ?: bpf_program__set_autoload(obj->progs.fentry__tty_write, false); } + // vfs_writev BTF information is not available on all supported kernels. + // If BTF is not present we can't attach a fentry/ program to it, so + // fallback to a kprobe. + if (has_bpf_tramp && BTF_FUNC_EXISTS(btf, vfs_writev)) { + err = err ?: bpf_program__set_autoload(obj->progs.kprobe__vfs_writev, false); + err = err ?: bpf_program__set_autoload(obj->progs.kretprobe__vfs_writev, false); + } else { + err = err ?: bpf_program__set_autoload(obj->progs.fexit__vfs_writev, false); + } + // bpf trampolines are only implemented for x86. disable auto-loading of all // fentry/fexit progs if EBPF_FEATURE_BPF_TRAMP is not in `features` and // enable the k[ret]probe counterpart. @@ -358,6 +371,12 @@ static inline int probe_set_autoload(struct btf *btf, struct EventProbe_bpf *obj err = err ?: bpf_program__set_autoload(obj->progs.kprobe__tcp_close, false); err = err ?: bpf_program__set_autoload(obj->progs.kprobe__chmod_common, false); err = err ?: bpf_program__set_autoload(obj->progs.kretprobe__chmod_common, false); + err = err ?: bpf_program__set_autoload(obj->progs.kprobe__do_truncate, false); + err = err ?: bpf_program__set_autoload(obj->progs.kretprobe__do_truncate, false); + err = err ?: bpf_program__set_autoload(obj->progs.kprobe__vfs_write, false); + err = err ?: bpf_program__set_autoload(obj->progs.kretprobe__vfs_write, false); + err = err ?: bpf_program__set_autoload(obj->progs.kprobe__chown_common, false); + err = err ?: bpf_program__set_autoload(obj->progs.kretprobe__chown_common, false); } else { err = err ?: bpf_program__set_autoload(obj->progs.fentry__do_unlinkat, false); err = err ?: bpf_program__set_autoload(obj->progs.fentry__mnt_want_write, false); @@ -372,6 +391,9 @@ static inline int probe_set_autoload(struct btf *btf, struct EventProbe_bpf *obj err = err ?: bpf_program__set_autoload(obj->progs.fexit__tcp_v4_connect, false); err = err ?: bpf_program__set_autoload(obj->progs.fentry__tcp_close, false); err = err ?: bpf_program__set_autoload(obj->progs.fexit__chmod_common, false); + err = err ?: bpf_program__set_autoload(obj->progs.fexit__do_truncate, false); + err = err ?: bpf_program__set_autoload(obj->progs.fexit__vfs_write, false); + err = err ?: bpf_program__set_autoload(obj->progs.fexit__chown_common, false); } return err; diff --git a/testing/test_bins/create_rename_delete_file.c b/testing/test_bins/create_rename_delete_file.c index 86e8866f..549ee881 100644 --- a/testing/test_bins/create_rename_delete_file.c +++ b/testing/test_bins/create_rename_delete_file.c @@ -27,9 +27,11 @@ int main() FILE *f; CHECK(f = fopen(filename_orig, "w"), NULL); - CHECK(fclose(f), EOF); CHECK(rename(filename_orig, filename_new), -1); CHECK(chmod(filename_new, S_IRWXU | S_IRWXG | S_IRWXO), -1); + CHECK(fprintf(f, "test"), -1); + CHECK(ftruncate(fileno(f), 0), -1); + CHECK(fclose(f), EOF); CHECK(unlink(filename_new), -1); return 0; diff --git a/testing/testrunner/main.go b/testing/testrunner/main.go index e2981c01..f0c13232 100644 --- a/testing/testrunner/main.go +++ b/testing/testrunner/main.go @@ -24,6 +24,7 @@ func main() { RunEventsTest(TestFileCreate, "--file-create") RunEventsTest(TestFileDelete, "--file-delete") RunEventsTest(TestFileRename, "--file-rename") + RunEventsTest(TestFileModify, "--file-modify") RunEventsTest(TestTcpv4ConnectionAttempt, "--net-conn-attempt") RunEventsTest(TestTcpv4ConnectionAccept, "--net-conn-accept") diff --git a/testing/testrunner/tests.go b/testing/testrunner/tests.go index b1521eb4..71ab9476 100644 --- a/testing/testrunner/tests.go +++ b/testing/testrunner/tests.go @@ -215,7 +215,7 @@ func TestFileDelete(et *EventsTraceInstance) { AssertStringsEqual(fileDeleteEvent.Finfo.Type, "FILE") AssertUint64NotEqual(fileDeleteEvent.Finfo.Inode, 0) AssertUint64Equal(fileDeleteEvent.Finfo.Mode, 100777) - AssertUint64Equal(fileDeleteEvent.Finfo.Size, 0) + AssertUint64Equal(fileDeleteEvent.Finfo.Size, 4) AssertUint64Equal(fileDeleteEvent.Finfo.Uid, 0) AssertUint64Equal(fileDeleteEvent.Finfo.Gid, 0) } @@ -390,32 +390,49 @@ func TestFileDeleteContainer(et *EventsTraceInstance) { AssertStringsEqual(fileDeleteEvent.Path, binOutput.FileNameNew) } -func TestFileChmod(et *EventsTraceInstance) { +func TestFileModify(et *EventsTraceInstance) { outputStr := runTestBin("create_rename_delete_file") var binOutput struct { - ChildPid int64 `json:"child_pid"` - FileNameOrig string `json:"filename_orig"` - FileNameNew string `json:"filename_new"` + PidInfo TestPidInfo `json:"pid_info"` + FileNameOrig string `json:"filename_orig"` + FileNameNew string `json:"filename_new"` } if err := json.Unmarshal(outputStr, &binOutput); err != nil { TestFail("failed to unmarshal json", err) } - var fileModifyEvent FileModifyEvent + eventsCount := 3 // chmod, write, truncate + events := make([]FileModifyEvent, 0, eventsCount) for { + var event FileModifyEvent line := et.GetNextEventJson("FILE_MODIFY") - if err := json.Unmarshal([]byte(line), &fileModifyEvent); err != nil { + if err := json.Unmarshal([]byte(line), &event); err != nil { TestFail("failed to unmarshal JSON: ", err) } - if fileModifyEvent.Pids.Tgid == binOutput.ChildPid { - break + if event.Pids.Tid == binOutput.PidInfo.Tid { + events = append(events, event) + eventsCount-- + if eventsCount == 0 { + break + } } } - AssertStringsEqual(fileModifyEvent.Path, binOutput.FileNameNew) - AssertStringsEqual(fileModifyEvent.ChangeType, "PERMISSIONS") - AssertUint64Equal(fileModifyEvent.Finfo.Mode, 100777) + // chmod + AssertStringsEqual(events[0].Path, binOutput.FileNameNew) + AssertStringsEqual(events[0].ChangeType, "PERMISSIONS") + AssertUint64Equal(events[0].Finfo.Mode, 100777) + + // write + AssertStringsEqual(events[1].Path, binOutput.FileNameNew) + AssertStringsEqual(events[1].ChangeType, "CONTENT") + + // truncate + AssertStringsEqual(events[2].Path, binOutput.FileNameNew) + AssertStringsEqual(events[2].ChangeType, "CONTENT") + + AssertTrue(events[1].Finfo.Size != events[2].Finfo.Size) } func TestTtyWrite(et *EventsTraceInstance) {