| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Include vmlinux.h first to declare all kernel types. |
| #include "include/secagentd/vmlinux/vmlinux.h" |
| #include <bpf/bpf_helpers.h> |
| #include <bpf/bpf_tracing.h> |
| #include <bpf/bpf_core_read.h> |
| |
| // TODO(b/243453873): Workaround to get code completion working in CrosIDE. |
| #undef __cplusplus |
| #include "secagentd/bpf/process.h" |
| |
| const char LICENSE[] SEC("license") = "Dual BSD/GPL"; |
| |
| struct { |
| __uint(type, BPF_MAP_TYPE_RINGBUF); |
| __uint(max_entries, CROS_MAX_STRUCT_SIZE * 1024); |
| } rb SEC(".maps"); |
| |
| static inline __attribute__((always_inline)) bool is_kthread( |
| const struct task_struct* t) { |
| // From sched.h: |
| // #define PF_KTHREAD 0x00200000 |
| return (BPF_CORE_READ(t, flags) & 0x00200000); |
| } |
| |
| static inline __attribute__((always_inline)) void fill_ns_info( |
| struct cros_namespace_info* ns_info, const struct task_struct* t) { |
| ns_info->pid_ns = BPF_CORE_READ(t, nsproxy, pid_ns_for_children, ns.inum); |
| ns_info->mnt_ns = BPF_CORE_READ(t, nsproxy, mnt_ns, ns.inum); |
| ns_info->cgroup_ns = BPF_CORE_READ(t, nsproxy, cgroup_ns, ns.inum); |
| ns_info->ipc_ns = BPF_CORE_READ(t, nsproxy, ipc_ns, ns.inum); |
| ns_info->net_ns = BPF_CORE_READ(t, nsproxy, net_ns, ns.inum); |
| ns_info->user_ns = BPF_CORE_READ(t, nsproxy, uts_ns, user_ns, ns.inum); |
| ns_info->uts_ns = BPF_CORE_READ(t, nsproxy, uts_ns, ns.inum); |
| } |
| |
| static inline __attribute__((always_inline)) struct task_struct* |
| normalize_to_last_newns(const struct task_struct* t) { |
| const struct task_struct* ret = t; |
| // Arbitrarily selected limit to convince the verifier that the BPF will |
| // always halt. |
| for (int i = 0; i < 64; ++i) { |
| struct task_struct* parent = BPF_CORE_READ(ret, real_parent, group_leader); |
| if ((!parent) || (BPF_CORE_READ(parent, tgid) == 0) || |
| (BPF_CORE_READ(ret, nsproxy, mnt_ns, ns.inum) != |
| BPF_CORE_READ(parent, nsproxy, mnt_ns, ns.inum))) { |
| break; |
| } |
| ret = parent; |
| } |
| return ret; |
| } |
| |
| static inline __attribute__((always_inline)) void fill_image_info( |
| struct cros_image_info* image_info, |
| const struct linux_binprm* bprm, |
| const struct task_struct* t) { |
| // Fill in information from bprm's file inode. |
| image_info->inode = BPF_CORE_READ(bprm, file, f_inode, i_ino); |
| image_info->uid = BPF_CORE_READ(bprm, file, f_inode, i_uid.val); |
| image_info->gid = BPF_CORE_READ(bprm, file, f_inode, i_gid.val); |
| image_info->mode = BPF_CORE_READ(bprm, file, f_inode, i_mode); |
| image_info->mtime.tv_sec = BPF_CORE_READ(bprm, file, f_inode, i_mtime.tv_sec); |
| image_info->mtime.tv_nsec = |
| BPF_CORE_READ(bprm, file, f_inode, i_mtime.tv_nsec); |
| image_info->ctime.tv_sec = BPF_CORE_READ(bprm, file, f_inode, i_ctime.tv_sec); |
| image_info->ctime.tv_nsec = |
| BPF_CORE_READ(bprm, file, f_inode, i_ctime.tv_nsec); |
| // Mimic new_encode_dev() to get stat-like dev_id. |
| dev_t dev = BPF_CORE_READ(bprm, file, f_inode, i_sb, s_dev); |
| unsigned major = dev >> 20; |
| unsigned minor = dev & ((1 << 20) - 1); |
| image_info->inode_device_id = |
| (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12); |
| |
| // Fill in pathname from bprm. Interp is the actual binary that executed post |
| // symlink and interpreter resolution. |
| const char* interp_start = BPF_CORE_READ(bprm, interp); |
| bpf_probe_read_str(image_info->pathname, sizeof(image_info->pathname), |
| interp_start); |
| |
| // Fill in the mnt_ns from the task. |
| image_info->mnt_ns = BPF_CORE_READ(t, nsproxy, mnt_ns, ns.inum); |
| // Find an ancestral pid with the same mnt_ns. To increase the chances of its |
| // ns/mnt being available to userspace. |
| const struct task_struct* n = normalize_to_last_newns(t); |
| image_info->pid_for_setns = BPF_CORE_READ(n, tgid); |
| } |
| |
| static inline __attribute__((always_inline)) struct task_struct* |
| normalize_to_last_exec(const struct task_struct* t) { |
| const struct task_struct* ret = t; |
| // Arbitrarily selected limit to convince the verifier that the BPF will |
| // always halt. |
| for (int i = 0; i < 64; ++i) { |
| struct task_struct* parent = BPF_CORE_READ(ret, real_parent, group_leader); |
| if ((!parent) || (BPF_CORE_READ(ret, self_exec_id) != |
| BPF_CORE_READ(parent, self_exec_id))) { |
| break; |
| } |
| ret = parent; |
| } |
| return ret; |
| } |
| |
| static inline __attribute__((always_inline)) void fill_task_info( |
| struct cros_process_task_info* task_info, const struct task_struct* t) { |
| struct task_struct* parent = |
| normalize_to_last_exec(BPF_CORE_READ(t, real_parent, group_leader)); |
| task_info->ppid = BPF_CORE_READ(parent, tgid); |
| task_info->parent_start_time = BPF_CORE_READ(parent, start_boottime); |
| task_info->start_time = BPF_CORE_READ(t, group_leader, start_boottime); |
| task_info->pid = BPF_CORE_READ(t, tgid); |
| |
| task_info->uid = BPF_CORE_READ(t, real_cred, uid.val); |
| task_info->gid = BPF_CORE_READ(t, real_cred, gid.val); |
| |
| // Read argv from user memory. |
| const uintptr_t arg_start = (uintptr_t)BPF_CORE_READ(t, mm, arg_start); |
| const uintptr_t arg_end = (uintptr_t)BPF_CORE_READ(t, mm, arg_end); |
| if ((arg_end - arg_start) > sizeof(task_info->commandline)) { |
| task_info->commandline_len = sizeof(task_info->commandline); |
| } else { |
| task_info->commandline_len = (uint32_t)(arg_end - arg_start); |
| } |
| bpf_probe_read_user(task_info->commandline, task_info->commandline_len, |
| (const void*)arg_start); |
| if (task_info->commandline_len == sizeof(task_info->commandline)) { |
| task_info->commandline[task_info->commandline_len - 1] = '\0'; |
| } |
| } |
| |
| // trace_sched_process_exec is called by exec_binprm shortly after exec. It has |
| // the distinct advantage (over arguably more stable and security focused |
| // interfaces like bprm_committed_creds) of running in the context of the newly |
| // created Task. This makes it much easier for us to grab information about this |
| // new Task. |
| #if defined(USE_MIN_CORE_BTF) && USE_MIN_CORE_BTF == 1 |
| // tp_btf will make libbpf silently fall back to looking for a full vmlinux BTF. |
| // So use a raw tracepoint instead. |
| SEC("raw_tracepoint/sched_process_exec") |
| #else |
| SEC("tp_btf/sched_process_exec") |
| #endif // USE_MIN_CORE_BTF |
| int BPF_PROG(handle_sched_process_exec, |
| struct task_struct* current, |
| pid_t old_pid, |
| struct linux_binprm* bprm) { |
| if (is_kthread(current)) { |
| return 0; |
| } |
| // Reserve sample from BPF ringbuf. |
| struct cros_event* event = |
| (struct cros_event*)(bpf_ringbuf_reserve(&rb, sizeof(*event), 0)); |
| if (event == NULL) { |
| return 0; |
| } |
| event->type = kProcessEvent; |
| event->data.process_event.type = kProcessStartEvent; |
| struct cros_process_start* p = |
| &(event->data.process_event.data.process_start); |
| |
| fill_task_info(&p->task_info, current); |
| fill_ns_info(&p->spawn_namespace, current); |
| fill_image_info(&p->image_info, bprm, current); |
| |
| // Submit the event to the ring buffer for userspace processing. |
| bpf_ringbuf_submit(event, 0); |
| return 0; |
| } |
| |
| #if defined(USE_MIN_CORE_BTF) && USE_MIN_CORE_BTF == 1 |
| SEC("raw_tracepoint/sched_process_exit") |
| #else |
| SEC("tp_btf/sched_process_exit") |
| #endif // USE_MIN_CORE_BTF |
| int BPF_PROG(handle_sched_process_exit, struct task_struct* current) { |
| if (is_kthread(current)) { |
| return 0; |
| } |
| if ((BPF_CORE_READ(current, pid) != BPF_CORE_READ(current, tgid)) || |
| (current != normalize_to_last_exec(current))) { |
| // We didn't report an exec event for this task since it's either not a |
| // thread group leader or it's a !CLONE_THREAD clone that hasn't exec'd |
| // anything. So avoid reporting a terminate event for it. |
| return 0; |
| } |
| struct cros_event* event = |
| (struct cros_event*)(bpf_ringbuf_reserve(&rb, sizeof(*event), 0)); |
| if (event == NULL) { |
| return 0; |
| } |
| event->type = kProcessEvent; |
| event->data.process_event.type = kProcessExitEvent; |
| struct cros_process_exit* p = &(event->data.process_event.data.process_exit); |
| |
| fill_task_info(&p->task_info, current); |
| // Similar to list_empty(¤t->children). Though unsure how to get a |
| // reliable pointer to current->children. So instead of: |
| // (¤t->children == current->children.next) |
| // we check if: |
| // (current->children.next == current->children.next->next). |
| // The only way current->children.next would link to itself is if |
| // current->children.next were list head. The list head linking to itself |
| // implies that the list is empty. |
| struct list_head* first_child = BPF_CORE_READ(current, children.next); |
| p->is_leaf = |
| (!first_child || (first_child == BPF_CORE_READ(first_child, next))); |
| |
| bpf_ringbuf_submit(event, 0); |
| return 0; |
| } |