shell bypass 403
/** @file ftrace_events.c @brief Events acquired via ftrace @details Copyright (c) 2024 Acronis International GmbH @author Bruce Wang ([email protected]) @since $Id: $ */ #include "ftrace_hooks/ftrace_events.h" // This is true for roughly Linux Kernel 3.7. #ifdef HAVE_FTRACE_OPS_FL_SAVE_REGS #ifdef KERNEL_MOCK #include <mock/mock.h> #endif #include <linux/dcache.h> #include <linux/fs.h> #include <linux/file.h> #include <linux/fcntl.h> #include <linux/ftrace.h> #include <linux/fsnotify.h> #include <linux/namei.h> #include <linux/skbuff.h> #include <linux/splice.h> #include <linux/types.h> #include <net/netlink.h> #include "audit_user.h" #include "compat.h" #include "debug.h" #include "exec_event.h" #include "exit_event.h" #include "file_contexts.h" #include "file_key_tools.h" #include "fsnotify_events.h" #include "fsnotify_listener.h" #include "fork_event.h" #include "fs_event.h" #include "lsm_hooks/lsm_common.h" #include "memory.h" #include "message.h" #include "path_tools.h" #include "reg_tools.h" #include "si_size.h" #include "si_templates.h" #include "si_writer_common.h" #include "syscall_common.h" #include "task_tools.h" #if 0 FSNOTIFY_V1 // version <= 5.8: static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir, const struct qstr *old_name, int isdir, struct inode *target, struct dentry *moved) { fsnotify_name(old_dir, old_dir_mask, source, old_name, fs_cookie); fsnotify_name(new_dir, new_dir_mask, source, new_name, fs_cookie); } static inline void fsnotify_create(struct inode *inode, struct dentry *dentry) { fsnotify_dirent(inode, dentry, FS_CREATE); } static inline void fsnotify_unlink(struct inode *dir, struct dentry *dentry) { fsnotify_dirent(dir, dentry, FS_DELETE); } static inline void fsnotify_dirent(struct inode *dir, struct dentry *dentry, __u32 mask) { fsnotify_name(dir, mask, d_inode(dentry), &dentry->d_name, 0); } static inline void fsnotify_name(struct inode *dir, __u32 mask, struct inode *child, const struct qstr *name, u32 cookie) { fsnotify(dir, mask, child, FSNOTIFY_EVENT_INODE, name, cookie); } int fsnotify(struct inode *to_tell, __u32 mask, const void *data, int data_type, const struct qstr *name, u32 cookie); #################### FSNOTIFY_V2 // 5.8 < version < 5.16 static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir, const struct qstr *old_name, int isdir, struct inode *target, struct dentry *moved) { fsnotify_name(old_dir, old_dir_mask, source, old_name, fs_cookie); fsnotify_name(new_dir, new_dir_mask, source, new_name, fs_cookie); } static inline void fsnotify_create(struct inode *inode, struct dentry *dentry) { fsnotify_dirent(inode, dentry, FS_CREATE); } static inline void fsnotify_unlink(struct inode *dir, struct dentry *dentry) { fsnotify_dirent(dir, dentry, FS_DELETE); } static inline void fsnotify_dirent(struct inode *dir, struct dentry *dentry, __u32 mask) { fsnotify_name(dir, mask, d_inode(dentry), &dentry->d_name, 0); } static inline void fsnotify_name(struct inode *dir, __u32 mask, struct inode *child, const struct qstr *name, u32 cookie) { fsnotify(mask, child, FSNOTIFY_EVENT_INODE, dir, name, NULL, cookie); } int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir, const struct qstr *file_name, struct inode *inode, u32 cookie); ########### FSNOTIFY_V3 (or V2) // version >= 5.16 static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir, const struct qstr *old_name, int isdir, struct inode *target, struct dentry *moved) { fsnotify_name(old_dir_mask, source, FSNOTIFY_EVENT_INODE, old_dir, old_name, fs_cookie); fsnotify_name(new_dir_mask, source, FSNOTIFY_EVENT_INODE, new_dir, new_name, fs_cookie); } static inline void fsnotify_delete(struct inode *dir, struct inode *inode, struct dentry *dentry) { fsnotify_name(mask, inode, FSNOTIFY_EVENT_INODE, dir, &dentry->d_name, 0); } static inline void fsnotify_create(struct inode *dir, struct dentry *dentry) { fsnotify_dirent(dir, dentry, FS_CREATE); } static inline void fsnotify_dirent(struct inode *dir, struct dentry *dentry, __u32 mask) { fsnotify_name(mask, dentry, FSNOTIFY_EVENT_DENTRY, dir, &dentry->d_name, 0); } static inline int fsnotify_name(__u32 mask, const void *data, int data_type, struct inode *dir, const struct qstr *name, u32 cookie) { return fsnotify(mask, data, data_type, dir, name, NULL, cookie); } int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir, const struct qstr *name, struct inode *inode, u32 cookie); #endif static void fsnotify_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { #ifndef HAVE_FSNOTIFY_PUBLIC_API uint32_t mask; const void *data; int data_type; const void *name_ptr; (void) ip; (void) parent_ip; (void) op; if (!HOOK_PROLOG()) return; #ifdef FSNOTIFY_MASK_SECOND mask = GET_KERNEL_ARGUMENT(regs, 1); data = (const void*)GET_KERNEL_ARGUMENT(regs, 2); data_type = (int) GET_KERNEL_ARGUMENT(regs, 3); #else mask = GET_KERNEL_ARGUMENT(regs, 0); data = (const void*)GET_KERNEL_ARGUMENT(regs, 1); data_type = (int) GET_KERNEL_ARGUMENT(regs, 2); #endif name_ptr = (const void*)GET_KERNEL_ARGUMENT(regs, 4); #ifdef HAVE_FSNOTIFY_QSTR name_ptr = name_ptr ? ((const struct qstr *)name_ptr)->name : NULL; #endif handle_fsnotify_event(mask, data, data_type, name_ptr); HOOK_EPILOG(); #else (void) regs; (void) op; (void) ip; (void) parent_ip; #endif } static void exec_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { struct task_struct* tsk = (struct task_struct *)GET_KERNEL_ARGUMENT(regs, 0); (void) ip; (void) parent_ip; (void) op; if (!HOOK_PROLOG()) { return; } exec_event(tsk); HOOK_EPILOG(); } static void exit_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { struct task_struct* tsk = (struct task_struct *)GET_KERNEL_ARGUMENT(regs, 0); (void) ip; (void) parent_ip; (void) op; if (!HOOK_PROLOG()) { return; } exit_event(tsk); HOOK_EPILOG(); } static void fork_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { struct task_struct* tsk = (struct task_struct *)GET_KERNEL_ARGUMENT(regs, 0); (void) ip; (void) parent_ip; (void) op; if (!HOOK_PROLOG()) { return; } fork_event(tsk); HOOK_EPILOG(); } static const uint8_t k_login_logout_fields[] = { SI_COMMON_FIELDS, FP_SI_PI_OBJECT_NAME, FP_SI_PI_FLAGS }; static void audit_handle_msg(struct sk_buff* skb, struct nlmsghdr *nlh) { u16 msg_type = nlh->nlmsg_type; (void) skb; if (msg_type == AUDIT_USER_LOGIN || msg_type == AUDIT_USER_LOGOUT) { int msg_len = nlmsg_len(nlh); msg_t* msg; size_t msg_size; uint64_t event_uid; SiSizedString data; task_info_t* task_info; if (msg_len < 2) return; { transport_ids_t ids; transport_global_get_ids(&ids); task_info = task_info_map_get(current); refresh_task(task_info, &ids); // not skipping event - always send login/logout events } data.value = (const char*) NLMSG_DATA(nlh); data.length = strnlen(data.value, msg_len); msg_size = SI_ESTIMATE_SIZE_CONST_PARAMS(k_login_logout_fields) + data.length; msg = msg_new(FP_SI_OT_NOTIFY_LOGIN_LOGOUT, 0, SI_CT_POST_CALLBACK, make_unique_pid(current), msg_size); if (msg) { event_uid = transport_global_sequence_next(); { si_property_writer_t writer; si_event_writer_init(&writer, &msg->event, msg_size); si_property_writer_write_common(&writer, event_uid, current->pid, current->tgid, task_info); si_property_writer_write_object_name(&writer, data); si_property_writer_write_flags(&writer, msg_type); si_event_writer_finalize(&msg->event, &writer); } send_msg_async_unref(msg); } if (task_info) task_info_put(task_info); } } static void audit_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { struct sk_buff* skb = (struct sk_buff*)GET_KERNEL_ARGUMENT(regs, 0); struct nlmsghdr *nlh; int len; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_LOGIN_LOGOUT); if (!(transport_global_get_combined_mask() & generatedEventsMask) || transport_process_belongs_to_control(current)) { return; } (void) ip; (void) parent_ip; (void) op; nlh = nlmsg_hdr(skb); len = skb->len; while (NLMSG_OK(nlh, len)) { audit_handle_msg(skb, nlh); nlh = NLMSG_NEXT(nlh, len); } } static void pre_write(const struct path *path, int flags, loff_t offset, size_t count) { transport_ids_t transport_ids; task_info_t* task_info; file_context_info_t file_context_info = {0}; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_WRITE); if (!(transport_global_get_combined_mask() & generatedEventsMask) || transport_process_belongs_to_control(current)) { return; } task_info = task_info_map_get(current); if (!task_info) return; transport_global_get_ids(&transport_ids); refresh_task(task_info, &transport_ids); if (task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) goto err_skipped; if (make_file_context_info(&file_context_info, path, flags, offset, offset + count, READ_ONCE(task_info->pid_version)) && check_write_cache(&transport_ids, &file_context_info, FILE_CONTEXT_WRITE_TABLE)) goto err_skipped; (void) fs_event_pre_write(task_info, flags, offset, count, path, &file_context_info.msg_info); err_skipped: task_info_put(task_info); } static void pre_read(const struct path *path, int flags, loff_t offset, size_t count) { transport_ids_t transport_ids; task_info_t* task_info; file_key_t key; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_FULL_READ); if (!(transport_global_get_combined_mask() & generatedEventsMask) || transport_process_belongs_to_control(current)) { return; } task_info = task_info_map_get(current); if (!task_info) return; transport_global_get_ids(&transport_ids); refresh_task(task_info, &transport_ids); if (task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) goto err_skipped; { file_context_info_t file_context_info = {0}; if (make_file_context_info(&file_context_info, path, flags, offset, offset + count, READ_ONCE(task_info->pid_version)) && check_and_update_read_cache(&transport_ids, &file_context_info)) { goto err_skipped; } } make_key(&key, path); fs_event_pre_full_read(task_info, &key, flags, offset, count); err_skipped: task_info_put(task_info); } static void rw_verify_area_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { int read_write = (int)GET_KERNEL_ARGUMENT(regs, 0); bool write = WRITE == read_write; bool read = READ == read_write; struct file *file = (struct file *)GET_KERNEL_ARGUMENT(regs, 1); const loff_t *ppos = (const loff_t *)GET_KERNEL_ARGUMENT(regs, 2); size_t count = (size_t)GET_KERNEL_ARGUMENT(regs, 3); loff_t pos; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) BUILD_BUG_ON(!__builtin_types_compatible_p(typeof(&rw_verify_area), int (*)(int, struct file*, const loff_t*, size_t))); #endif (void) ip; (void) parent_ip; (void) op; if (!file || !ppos) return; if (!read && !write) return; #ifdef FMODE_NONOTIFY if (file->f_mode & FMODE_NONOTIFY) return; #endif pos = *ppos; // I am not even going to consider these weird cases // rw_verify_area will check for those and I will do so too if (pos < 0) return; if ((ssize_t) count < 0) return; if ((loff_t) (pos + count) < 0) return; if (!file_is_valid(file)) return; if (!S_ISREG(file->f_path.dentry->d_inode->i_mode)) return; if (!HOOK_PROLOG()) return; if (write) pre_write(&file->f_path, file->f_flags, pos, count); if (read) pre_read(&file->f_path, file->f_flags, pos, count); HOOK_EPILOG(); } static void check_copy_file(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len) { transport_ids_t transport_ids; task_info_t* task_info; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_COPY); loff_t in_size; loff_t out_size; if (0 != pos_in || 0 != pos_out) return; if (!path_is_valid(&file_in->f_path) || !path_is_valid(&file_out->f_path)) return; in_size = i_size_read(file_in->f_path.dentry->d_inode); out_size = i_size_read(file_out->f_path.dentry->d_inode); if (in_size > len || out_size > len) return; if (!HOOK_PROLOG()) return; if (!(transport_global_get_combined_mask() & generatedEventsMask) || transport_process_belongs_to_control(current)) { goto out; } task_info = task_info_map_get(current); if (!task_info) goto out; transport_global_get_ids(&transport_ids); refresh_task(task_info, &transport_ids); if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) fs_event_pre_copyfile(task_info, &file_in->f_path, &file_out->f_path); task_info_put(task_info); out: HOOK_EPILOG(); } #ifdef HAVE_VFS_CLONE_FILE_RANGE static void clone_file_range_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { struct file *file_in = (struct file *)GET_KERNEL_ARGUMENT(regs, 0); loff_t pos_in = (loff_t)GET_KERNEL_ARGUMENT(regs, 1); struct file *file_out = (struct file *)GET_KERNEL_ARGUMENT(regs, 2); loff_t pos_out = (loff_t)GET_KERNEL_ARGUMENT(regs, 3); loff_t len = (loff_t)GET_KERNEL_ARGUMENT(regs, 4); BUILD_BUG_ON(!(__builtin_types_compatible_p(typeof(&vfs_clone_file_range), int (*)(struct file*, loff_t, struct file*, loff_t, u64)) || __builtin_types_compatible_p(typeof(&vfs_clone_file_range), loff_t (*)(struct file*, loff_t, struct file*, loff_t, loff_t, unsigned int)))); (void) ip; (void) parent_ip; (void) op; check_copy_file(file_in, pos_in, file_out, pos_out, len); } #endif #ifdef HAVE_VFS_COPY_FILE_RANGE static void copy_file_range_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { struct file *file_in = (struct file *)GET_KERNEL_ARGUMENT(regs, 0); loff_t pos_in = (loff_t)GET_KERNEL_ARGUMENT(regs, 1); struct file *file_out = (struct file *)GET_KERNEL_ARGUMENT(regs, 2); loff_t pos_out = (loff_t)GET_KERNEL_ARGUMENT(regs, 3); size_t len = (size_t)GET_KERNEL_ARGUMENT(regs, 4); BUILD_BUG_ON(!__builtin_types_compatible_p(typeof(&vfs_copy_file_range), ssize_t (*)(struct file*, loff_t, struct file *, loff_t, size_t, unsigned int))); (void) ip; (void) parent_ip; (void) op; check_copy_file(file_in, pos_in, file_out, pos_out, len); } #endif static void do_splice_direct_entry_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs) { struct file *in = (struct file *)GET_KERNEL_ARGUMENT(regs, 0); loff_t *ppos = (loff_t *)GET_KERNEL_ARGUMENT(regs, 1); struct file *out = (struct file *)GET_KERNEL_ARGUMENT(regs, 2); loff_t *opos = NULL; size_t len; (void) ip; (void) parent_ip; (void) op; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) BUILD_BUG_ON(!__builtin_types_compatible_p(typeof(&do_splice_direct), long (*)(struct file*, loff_t*, struct file*, loff_t*, size_t, unsigned int))); opos = (loff_t *)GET_KERNEL_ARGUMENT(regs, 3); len = (size_t)GET_KERNEL_ARGUMENT(regs, 4); #elif defined(HAVE_DO_SPLICE_DIRECT_IN_FS) if (__builtin_types_compatible_p(typeof(&do_splice_direct), long (*)(struct file*, loff_t*, struct file*, size_t, unsigned int))) { len = (size_t)GET_KERNEL_ARGUMENT(regs, 3); } else { opos = (loff_t *)GET_KERNEL_ARGUMENT(regs, 3); len = (size_t)GET_KERNEL_ARGUMENT(regs, 4); } #else opos = (loff_t *)GET_KERNEL_ARGUMENT(regs, 3); len = (size_t)GET_KERNEL_ARGUMENT(regs, 4); #endif if (!ppos || !opos) return; check_copy_file(in, *ppos, out, *opos, len); } typedef struct { struct ftrace_ops ops; unsigned long fn; const char* name; bool registered; } fp_probe_t; // !!! These must align exactly with ftrace_post_event_type_t !!! static fp_probe_t g_probes[] = { { .ops = { .func = (ftrace_func_t) fsnotify_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, .fn = (unsigned long) fsnotify, .name = NULL, .registered = false, }, { .ops = { .func = (ftrace_func_t) exec_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, .fn = 0, .name = "proc_exec_connector", .registered = false, }, { .ops = { .func = (ftrace_func_t) exit_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, .fn = 0, .name = "proc_exit_connector", .registered = false, }, { .ops = { .func = (ftrace_func_t) fork_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, .fn = 0, .name = "proc_fork_connector", .registered = false, }, { .ops = { .func = (ftrace_func_t) audit_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, .fn = 0, .name = "audit_receive", .registered = false, }, { .ops = { .func = (ftrace_func_t) rw_verify_area_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, // rw_verify_area is a public symbol in modern kernels but not in the older ones. // TODO: 'rw_verify_area' misses 'remap_verify_area' case so cloning is not handled. // Perhaps a simple solution could be hooking the clone functions like // 'vfs_clone_file_range' and 'vfs_dedupe_file_range_one'? // themselves? #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) .fn = (unsigned long) rw_verify_area, .name = NULL, #else .fn = 0, .name = "rw_verify_area", #endif .registered = false, }, { #ifdef HAVE_VFS_CLONE_FILE_RANGE .ops = { .func = (ftrace_func_t) clone_file_range_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, .fn = (unsigned long) vfs_clone_file_range, .name = NULL, .registered = false, #endif }, { #ifdef HAVE_VFS_COPY_FILE_RANGE .ops = { .func = (ftrace_func_t) copy_file_range_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, .fn = (unsigned long) vfs_copy_file_range, .name = NULL, .registered = false, #endif }, { .ops = { .func = (ftrace_func_t) do_splice_direct_entry_handler, .flags = FTRACE_OPS_FL_SAVE_REGS, }, // public symbol since 3.18 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) .fn = (unsigned long) do_splice_direct, .name = NULL, #else .fn = 0, .name = "do_splice_direct", #endif .registered = false, }, }; void register_ftrace_post_events(void) { int i = 0; for (; i < (int) ARRAY_SIZE(g_probes); i++) { fp_probe_t* probe = &g_probes[i]; struct ftrace_ops *ops = &probe->ops; int ret; #ifdef HAVE_FSNOTIFY_PUBLIC_API if (i == FTRACE_POST_EVENT_FSNOTIFY) { IPRINTF("fsnotify public API is used instead of ftrace"); continue; } #endif if (!probe->fn && !probe->name) { continue; } if (probe->name) { probe->fn = compat_kallsyms_lookup_name(probe->name); if (!probe->fn) { EPRINTF("Failed to find %s", probe->name); continue; } DPRINTF("%s -> %lu", probe->name, probe->fn); } ret = ftrace_set_filter_ip(ops, probe->fn, 0, 0); if (ret < 0) { EPRINTF("Failed to set filter ip"); continue; } ret = register_ftrace_function(ops); if (ret < 0) { EPRINTF("Failed to register ftrace"); continue; } IPRINTF("Planted ftrace %ps", (void*) probe->fn); g_probes[i].registered = true; } } void unregister_ftrace_post_events(void) { int i = 0; for (; i < (int) ARRAY_SIZE(g_probes); i++) { if (g_probes[i].registered) { fp_probe_t* probe = &g_probes[i]; struct ftrace_ops *ops = &probe->ops; IPRINTF("Remove ftrace for %ps", (void*) probe->fn); unregister_ftrace_function(ops); ftrace_set_filter_ip(ops, probe->fn, 1, 0); g_probes[i].registered = false; } } } bool ftrace_post_event_have(ftrace_post_event_type_t ev) { if (ev == FTRACE_POST_EVENT_FSNOTIFY && fsnotify_events_listener_registered()) return true; return g_probes[ev].registered; } #else void register_ftrace_post_events(void) { } void unregister_ftrace_post_events(void) { } bool ftrace_post_event_have(ftrace_post_event_type_t ev) { return false; } #endif