diff --git a/GPL/Events/EbpfEventProto.h b/GPL/Events/EbpfEventProto.h index 8485fac8..d9a77087 100644 --- a/GPL/Events/EbpfEventProto.h +++ b/GPL/Events/EbpfEventProto.h @@ -55,6 +55,7 @@ enum ebpf_varlen_field_type { EBPF_VL_FIELD_NEW_PATH, EBPF_VL_FIELD_TTY_OUT, EBPF_VL_FIELD_PIDS_SS_CGROUP_PATH, + EBPF_VL_FIELD_SYMLINK_TARGET_PATH, }; // Convenience macro to iterate all the variable length fields in an event @@ -117,34 +118,60 @@ struct ebpf_tty_dev { struct ebpf_tty_termios termios; } __attribute__((packed)); +enum ebpf_file_type { + EBPF_FILE_TYPE_UNKNOWN = 0, + EBPF_FILE_TYPE_FILE = 1, + EBPF_FILE_TYPE_DIR = 2, + EBPF_FILE_TYPE_SYMLINK = 3, + EBPF_FILE_TYPE_CHARACTER_DEVICE = 4, + EBPF_FILE_TYPE_BLOCK_DEVICE = 5, + EBPF_FILE_TYPE_NAMED_PIPE = 6, + EBPF_FILE_TYPE_SOCKET = 7, +}; + +struct ebpf_file_info { + enum ebpf_file_type type; + uint64_t inode; + uint16_t mode; + uint64_t size; + uint32_t uid; + uint32_t gid; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; +} __attribute__((packed)); + // Full events follow struct ebpf_file_delete_event { struct ebpf_event_header hdr; struct ebpf_pid_info pids; + struct ebpf_file_info finfo; uint32_t mntns; char comm[TASK_COMM_LEN]; - // Variable length fields: path + // Variable length fields: path, symlink_target_path struct ebpf_varlen_fields_start vl_fields; } __attribute__((packed)); struct ebpf_file_create_event { struct ebpf_event_header hdr; struct ebpf_pid_info pids; + struct ebpf_file_info finfo; uint32_t mntns; char comm[TASK_COMM_LEN]; - // Variable length fields: path + // Variable length fields: path, symlink_target_path struct ebpf_varlen_fields_start vl_fields; } __attribute__((packed)); struct ebpf_file_rename_event { struct ebpf_event_header hdr; struct ebpf_pid_info pids; + struct ebpf_file_info finfo; uint32_t mntns; char comm[TASK_COMM_LEN]; - // Variable length fields: old_path, new_path + // Variable length fields: old_path, new_path, symlink_target_path struct ebpf_varlen_fields_start vl_fields; } __attribute__((packed)); diff --git a/GPL/Events/File/File.h b/GPL/Events/File/File.h new file mode 100644 index 00000000..71717ef9 --- /dev/null +++ b/GPL/Events/File/File.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause + +/* + * Copyright (C) 2021 Elasticsearch BV + * + * This software is dual-licensed under the BSD 2-Clause and GPL v2 licenses. + * You may choose either one of them if you use this software. + */ + +#ifndef EBPF_EVENTPROBE_FILE_H +#define EBPF_EVENTPROBE_FILE_H + +#include "EbpfEventProto.h" + +#define PATH_MAX 4096 + +// include/uapi/linux/stat.h +#define S_IFMT 00170000 +#define S_IFSOCK 0140000 +#define S_IFLNK 0120000 +#define S_IFREG 0100000 +#define S_IFBLK 0060000 +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 +#define S_IFIFO 0010000 +#define S_ISUID 0004000 +#define S_ISGID 0002000 +#define S_ISVTX 0001000 + +#define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK) +#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) +#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) +#define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR) +#define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK) +#define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO) +#define S_ISSOCK(m) (((m)&S_IFMT) == S_IFSOCK) + +#define NANOSECONDS_IN_SECOND 1000000000 + +static void ebpf_file_info__fill(struct ebpf_file_info *finfo, struct dentry *de) +{ + struct inode *ino = BPF_CORE_READ(de, d_inode); + + finfo->inode = BPF_CORE_READ(ino, i_ino); + finfo->mode = BPF_CORE_READ(ino, i_mode); + finfo->size = BPF_CORE_READ(ino, i_size); + finfo->uid = BPF_CORE_READ(ino, i_uid.val); + finfo->gid = BPF_CORE_READ(ino, i_gid.val); + finfo->atime = BPF_CORE_READ(ino, i_atime.tv_sec) * NANOSECONDS_IN_SECOND + + BPF_CORE_READ(ino, i_atime.tv_nsec); + finfo->mtime = BPF_CORE_READ(ino, i_mtime.tv_sec) * NANOSECONDS_IN_SECOND + + BPF_CORE_READ(ino, i_mtime.tv_nsec); + finfo->ctime = BPF_CORE_READ(ino, i_ctime.tv_sec) * NANOSECONDS_IN_SECOND + + BPF_CORE_READ(ino, i_ctime.tv_nsec); + + if (S_ISREG(finfo->mode)) { + finfo->type = EBPF_FILE_TYPE_FILE; + } else if (S_ISDIR(finfo->mode)) { + finfo->type = EBPF_FILE_TYPE_DIR; + } else if (S_ISLNK(finfo->mode)) { + finfo->type = EBPF_FILE_TYPE_SYMLINK; + } else if (S_ISCHR(finfo->mode)) { + finfo->type = EBPF_FILE_TYPE_CHARACTER_DEVICE; + } else if (S_ISBLK(finfo->mode)) { + finfo->type = EBPF_FILE_TYPE_BLOCK_DEVICE; + } else if (S_ISFIFO(finfo->mode)) { + finfo->type = EBPF_FILE_TYPE_NAMED_PIPE; + } else if (S_ISSOCK(finfo->mode)) { + finfo->type = EBPF_FILE_TYPE_SOCKET; + } else { + finfo->type = EBPF_FILE_TYPE_UNKNOWN; + } +} + +#endif // EBPF_EVENTPROBE_FILE_H diff --git a/GPL/Events/File/Probe.bpf.c b/GPL/Events/File/Probe.bpf.c index f5e1c093..555bb2ca 100644 --- a/GPL/Events/File/Probe.bpf.c +++ b/GPL/Events/File/Probe.bpf.c @@ -13,6 +13,7 @@ #include #include +#include "File.h" #include "Helpers.h" #include "PathResolver.h" #include "State.h" @@ -123,10 +124,11 @@ static int vfs_unlink__exit(int ret) ebpf_pid_info__fill(&event->pids, task); struct path p; - p.dentry = state->unlink.de; + p.dentry = &state->unlink.de; p.mnt = state->unlink.mnt; event->mntns = mntns(task); bpf_get_current_comm(event->comm, TASK_COMM_LEN); + ebpf_file_info__fill(&event->finfo, p.dentry); // Variable length fields ebpf_vl_fields__init(&event->vl_fields); @@ -138,6 +140,12 @@ static int vfs_unlink__exit(int ret) size = ebpf_resolve_path_to_string(field->data, &p, task); ebpf_vl_field__set_size(&event->vl_fields, field, size); + // symlink_target_path + field = ebpf_vl_field__add(&event->vl_fields, EBPF_VL_FIELD_SYMLINK_TARGET_PATH); + char *link = BPF_CORE_READ(p.dentry, d_inode, i_link); + size = read_kernel_str_or_empty_str(field->data, PATH_MAX, link); + ebpf_vl_field__set_size(&event->vl_fields, field, size); + bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); // Certain filesystems (eg. overlayfs) call vfs_unlink twice during the same @@ -170,7 +178,10 @@ static int vfs_unlink__enter(struct dentry *de) goto out; } - state->unlink.de = de; + if (bpf_core_read(&state->unlink.de, sizeof(struct dentry), de)) { + bpf_printk("vfs_unlink__enter: failed to read dentry\n"); + goto out; + } state->unlink.step = UNLINK_STATE_DENTRY_SET; out: @@ -224,6 +235,7 @@ static int do_filp_open__exit(struct file *f) ebpf_pid_info__fill(&event->pids, task); event->mntns = mntns(task); bpf_get_current_comm(event->comm, TASK_COMM_LEN); + ebpf_file_info__fill(&event->finfo, p.dentry); // Variable length fields ebpf_vl_fields__init(&event->vl_fields); @@ -235,6 +247,12 @@ static int do_filp_open__exit(struct file *f) size = ebpf_resolve_path_to_string(field->data, &p, task); ebpf_vl_field__set_size(&event->vl_fields, field, size); + // symlink_target_path + field = ebpf_vl_field__add(&event->vl_fields, EBPF_VL_FIELD_SYMLINK_TARGET_PATH); + char *link = BPF_CORE_READ(p.dentry, d_inode, i_link); + size = read_kernel_str_or_empty_str(field->data, PATH_MAX, link); + ebpf_vl_field__set_size(&event->vl_fields, field, size); + bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); } @@ -316,6 +334,7 @@ static int vfs_rename__enter(struct dentry *old_dentry, struct dentry *new_dentr ebpf_resolve_path_to_string(ss->rename.new_path, &p, task); state->rename.step = RENAME_STATE_PATHS_SET; + state->rename.de = old_dentry; out: return 0; @@ -388,12 +407,15 @@ static int vfs_rename__exit(int ret) goto out; struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + // NOTE: this temp variable is necessary to keep the verifier happy + struct dentry *de = (struct dentry *)state->rename.de; event->hdr.type = EBPF_EVENT_FILE_RENAME; event->hdr.ts = bpf_ktime_get_ns(); ebpf_pid_info__fill(&event->pids, task); event->mntns = mntns(task); bpf_get_current_comm(event->comm, TASK_COMM_LEN); + ebpf_file_info__fill(&event->finfo, de); // Variable length fields ebpf_vl_fields__init(&event->vl_fields); @@ -410,6 +432,12 @@ static int vfs_rename__exit(int ret) size = read_kernel_str_or_empty_str(field->data, PATH_MAX, ss->rename.new_path); ebpf_vl_field__set_size(&event->vl_fields, field, size); + // symlink_target_path + field = ebpf_vl_field__add(&event->vl_fields, EBPF_VL_FIELD_SYMLINK_TARGET_PATH); + char *link = BPF_CORE_READ(de, d_inode, i_link); + size = read_kernel_str_or_empty_str(field->data, PATH_MAX, link); + ebpf_vl_field__set_size(&event->vl_fields, field, size); + bpf_ringbuf_output(&ringbuf, event, EVENT_SIZE(event), 0); // Certain filesystems (eg. overlayfs) call vfs_rename twice during the same diff --git a/GPL/Events/State.h b/GPL/Events/State.h index 8ad6cb2b..89d6980b 100644 --- a/GPL/Events/State.h +++ b/GPL/Events/State.h @@ -32,7 +32,10 @@ enum ebpf_events_unlink_state_step { struct ebpf_events_unlink_state { enum ebpf_events_unlink_state_step step; struct vfsmount *mnt; - struct dentry *de; + // NOTE: for this specific hook, storing a dentry pointer + // doesn't work because the content will be emptied out + // in the exit function. + struct dentry de; }; enum ebpf_events_rename_state_step { @@ -44,6 +47,7 @@ enum ebpf_events_rename_state_step { struct ebpf_events_rename_state { enum ebpf_events_rename_state_step step; struct vfsmount *mnt; + struct dentry *de; }; struct ebpf_events_tcp_connect_state { diff --git a/non-GPL/Events/EventsTrace/EventsTrace.c b/non-GPL/Events/EventsTrace/EventsTrace.c index 02c5e781..5a5fd715 100644 --- a/non-GPL/Events/EventsTrace/EventsTrace.c +++ b/non-GPL/Events/EventsTrace/EventsTrace.c @@ -209,6 +209,11 @@ static void out_int(const char *name, const long value) printf("\"%s\":%ld", name, value); } +static void out_octal(const char *name, const short unsigned value) +{ + printf("\"%s\":%o", name, value); +} + static void out_escaped_string(const char *value) { for (size_t i = 0; i < strlen(value); i++) { @@ -302,6 +307,64 @@ static void out_cred_info(const char *name, struct ebpf_cred_info *cred_info) out_object_end(); } +static void out_file_info(const char *name, struct ebpf_file_info *finfo) +{ + printf("\"%s\":", name); + out_object_start(); + + switch (finfo->type) { + case EBPF_FILE_TYPE_DIR: + out_string("type", "DIR"); + break; + case EBPF_FILE_TYPE_FILE: + out_string("type", "FILE"); + break; + case EBPF_FILE_TYPE_SYMLINK: + out_string("type", "SYMLINK"); + break; + case EBPF_FILE_TYPE_CHARACTER_DEVICE: + out_string("type", "CHARACTER_DEVICE"); + break; + case EBPF_FILE_TYPE_BLOCK_DEVICE: + out_string("type", "BLOCK_DEVICE"); + break; + case EBPF_FILE_TYPE_NAMED_PIPE: + out_string("type", "NAMED_PIPE"); + break; + case EBPF_FILE_TYPE_SOCKET: + out_string("type", "SOCKET"); + break; + case EBPF_FILE_TYPE_UNKNOWN: + out_string("type", "UNKNOWN"); + break; + } + out_comma(); + + out_uint("inode", finfo->inode); + out_comma(); + + out_octal("mode", finfo->mode); + out_comma(); + + out_uint("size", finfo->size); + out_comma(); + + out_int("uid", finfo->uid); + out_comma(); + + out_int("gid", finfo->gid); + out_comma(); + + out_uint("atime", finfo->atime); + out_comma(); + + out_uint("mtime", finfo->mtime); + out_comma(); + + out_uint("ctime", finfo->ctime); + out_object_end(); +} + static void out_null_delimited_string_array(const char *name, char *buf, size_t buf_size) { // buf is an array (argv, env etc.) with multiple values delimited by a '\0' @@ -337,6 +400,9 @@ static void out_file_delete(struct ebpf_file_delete_event *evt) out_comma(); out_string("comm", (const char *)&evt->comm); + out_comma(); + + out_file_info("file_info", &evt->finfo); struct ebpf_varlen_field *field; FOR_EACH_VARLEN_FIELD(evt->vl_fields, field) @@ -346,6 +412,9 @@ static void out_file_delete(struct ebpf_file_delete_event *evt) case EBPF_VL_FIELD_PATH: out_string("path", field->data); break; + case EBPF_VL_FIELD_SYMLINK_TARGET_PATH: + out_string("symlink_target_path", field->data); + break; default: fprintf(stderr, "Unexpected variable length field: %d\n", field->type); break; @@ -369,6 +438,9 @@ static void out_file_create(struct ebpf_file_create_event *evt) out_comma(); out_string("comm", (const char *)&evt->comm); + out_comma(); + + out_file_info("file_info", &evt->finfo); struct ebpf_varlen_field *field; FOR_EACH_VARLEN_FIELD(evt->vl_fields, field) @@ -378,6 +450,9 @@ static void out_file_create(struct ebpf_file_create_event *evt) case EBPF_VL_FIELD_PATH: out_string("path", field->data); break; + case EBPF_VL_FIELD_SYMLINK_TARGET_PATH: + out_string("symlink_target_path", field->data); + break; default: fprintf(stderr, "Unexpected variable length field: %d\n", field->type); break; @@ -401,6 +476,9 @@ static void out_file_rename(struct ebpf_file_rename_event *evt) out_comma(); out_string("comm", (const char *)&evt->comm); + out_comma(); + + out_file_info("file_info", &evt->finfo); struct ebpf_varlen_field *field; FOR_EACH_VARLEN_FIELD(evt->vl_fields, field) @@ -413,6 +491,9 @@ static void out_file_rename(struct ebpf_file_rename_event *evt) case EBPF_VL_FIELD_NEW_PATH: out_string("new_path", field->data); break; + case EBPF_VL_FIELD_SYMLINK_TARGET_PATH: + out_string("symlink_target_path", field->data); + break; default: fprintf(stderr, "Unexpected variable length field: %d\n", field->type); break; diff --git a/testing/testrunner/tests.go b/testing/testrunner/tests.go index 8d28de12..46a14cbb 100644 --- a/testing/testrunner/tests.go +++ b/testing/testrunner/tests.go @@ -177,6 +177,13 @@ func TestFileCreate(et *EventsTraceInstance) { AssertPidInfoEqual(binOutput.PidInfo, fileCreateEvent.Pids) AssertStringsEqual(fileCreateEvent.Path, binOutput.FileNameOrig) + // File Info + AssertStringsEqual(fileCreateEvent.Finfo.Type, "FILE") + AssertUint64NotEqual(fileCreateEvent.Finfo.Inode, 0) + AssertUint64Equal(fileCreateEvent.Finfo.Mode, 100644) + AssertUint64Equal(fileCreateEvent.Finfo.Size, 0) + AssertUint64Equal(fileCreateEvent.Finfo.Uid, 0) + AssertUint64Equal(fileCreateEvent.Finfo.Gid, 0) } func TestFileDelete(et *EventsTraceInstance) { @@ -204,6 +211,13 @@ func TestFileDelete(et *EventsTraceInstance) { AssertPidInfoEqual(binOutput.PidInfo, fileDeleteEvent.Pids) AssertStringsEqual(fileDeleteEvent.Path, binOutput.FileNameNew) + // File Info + AssertStringsEqual(fileDeleteEvent.Finfo.Type, "FILE") + AssertUint64NotEqual(fileDeleteEvent.Finfo.Inode, 0) + AssertUint64Equal(fileDeleteEvent.Finfo.Mode, 100644) + AssertUint64Equal(fileDeleteEvent.Finfo.Size, 0) + AssertUint64Equal(fileDeleteEvent.Finfo.Uid, 0) + AssertUint64Equal(fileDeleteEvent.Finfo.Gid, 0) } func TestFileRename(et *EventsTraceInstance) { @@ -232,6 +246,13 @@ func TestFileRename(et *EventsTraceInstance) { AssertPidInfoEqual(binOutput.PidInfo, fileRenameEvent.Pids) AssertStringsEqual(fileRenameEvent.OldPath, binOutput.FileNameOrig) AssertStringsEqual(fileRenameEvent.NewPath, binOutput.FileNameNew) + // File Info + AssertStringsEqual(fileRenameEvent.Finfo.Type, "FILE") + AssertUint64NotEqual(fileRenameEvent.Finfo.Inode, 0) + AssertUint64Equal(fileRenameEvent.Finfo.Mode, 100644) + AssertUint64Equal(fileRenameEvent.Finfo.Size, 0) + AssertUint64Equal(fileRenameEvent.Finfo.Uid, 0) + AssertUint64Equal(fileRenameEvent.Finfo.Gid, 0) } func TestSetuid(et *EventsTraceInstance) { diff --git a/testing/testrunner/utils.go b/testing/testrunner/utils.go index a70a8a55..2ad86c1b 100644 --- a/testing/testrunner/utils.go +++ b/testing/testrunner/utils.go @@ -78,6 +78,17 @@ type NetInfo struct { NetNs int64 `json:"network_namespace"` } +type FileInfo struct { + Type string `json:"type"` + Inode uint64 `json:"inode"` + Mode uint64 `json:"mode"` + Size uint64 `json:"size"` + Uid uint64 `json:"uid"` + Gid uint64 `json:"gid"` + Mtime uint64 `json:"mtime"` + Ctime uint64 `json:"ctime"` +} + type ProcessForkEvent struct { ParentPids PidInfo `json:"parent_pids"` ChildPids PidInfo `json:"child_pids"` @@ -95,19 +106,22 @@ type ProcessExecEvent struct { } type FileCreateEvent struct { - Pids PidInfo `json:"pids"` - Path string `json:"path"` + Pids PidInfo `json:"pids"` + Path string `json:"path"` + Finfo FileInfo `json:"file_info"` } type FileDeleteEvent struct { - Pids PidInfo `json:"pids"` - Path string `json:"path"` + Pids PidInfo `json:"pids"` + Path string `json:"path"` + Finfo FileInfo `json:"file_info"` } type FileRenameEvent struct { - Pids PidInfo `json:"pids"` - OldPath string `json:"old_path"` - NewPath string `json:"new_path"` + Pids PidInfo `json:"pids"` + OldPath string `json:"old_path"` + NewPath string `json:"new_path"` + Finfo FileInfo `json:"file_info"` } type SetUidEvent struct { @@ -234,6 +248,12 @@ func AssertUint64Equal(a, b uint64) { } } +func AssertUint64NotEqual(a, b uint64) { + if a == b { + TestFail(fmt.Sprintf("Test assertion failed 0x%016x == 0x%016x", a, b)) + } +} + func PrintBPFDebugOutput() { file, err := os.Open("/sys/kernel/debug/tracing/trace") if err != nil {