shell bypass 403
/** @file lsm_pre_events.c @brief LSM based hooks @details Copyright (c) 2024 Acronis International GmbH @author Bruce Wang ([email protected]) @since $Id: $ */ #ifdef CONFIG_SECURITY #include <linux/list.h> #include <linux/mman.h> #include <linux/net.h> #include <linux/types.h> #include "compat.h" #include "file_handle_tools.h" #include "ftrace_hooks/fsnotify_listener.h" #include "ftrace_hooks/ftrace_events.h" #include "fs_event.h" #include "lsm_common.h" #include "memory.h" #include "path_tools.h" #include "si_templates.h" #include "si_writer_common.h" #include "subtype.h" #include "syscall_common.h" #include "task_info_map.h" #include "task_tools.h" #include "transport.h" #include "transport_protocol.h" #include "write_protection.h" static int _lsm_file_open(struct file *file) { long ret = 0; const struct path *path; task_info_t *task_info; transport_ids_t transport_ids; file_context_info_t file_context_info = {0}; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_OPEN) | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_OPEN); subtypes_t generatedSubtypes; if (!file) { return 0; } #ifdef FMODE_NONOTIFY if (file->f_mode & FMODE_NONOTIFY) return 0; #endif if (!HOOK_PROLOG()) { return 0; } path = &file->f_path; if (path->mnt && sb_ok(path->mnt->mnt_sb)) fsnotify_events_listen_sb(path->mnt->mnt_sb); if (!(transport_global_get_combined_mask() & (generatedEventsMask | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_PROCESS_EXEC)))) goto out; generatedSubtypes = fs_open_subtypes(file->f_flags, path->dentry->d_inode); if (!transport_global_subtype_needed(generatedSubtypes.sync) && !transport_global_subtype_needed(generatedSubtypes.notify)) goto out; if (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 (!path_is_valid(path)) { DPRINTF("path is not valid"); goto err_exit; } if (make_file_context_info(&file_context_info, path, file->f_flags, 0, 0, READ_ONCE(task_info->pid_version)) && check_open_cache(&transport_ids, &file_context_info)) { goto err_exit; } if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) { long block = fs_event_pre_open(task_info, file->f_flags, path, &file_context_info.msg_info); // TODO: Why is block changed to EACCES? ret = block ? -EACCES : 0; DPRINTF("finished 'fs_event_pre_open(flags=0o%o/0x%x)", file->f_flags, file->f_flags); } err_exit: task_info_put(task_info); out: HOOK_EPILOG(); return ret; } #ifdef HAVE_FILE_OPEN #pragma message("HAVE_FILE_OPEN") #ifdef FILE_OPEN_WITH_CRED #pragma message("FILE_OPEN_WITH_CRED") static int lsm_file_open(struct file *file, const struct cred *cred) { (void) cred; return _lsm_file_open(file); } #else static int lsm_file_open(struct file *file) { return _lsm_file_open(file); } #endif #else static int lsm_dentry_open(struct file *file, const struct cred *cred) { (void) cred; return _lsm_file_open(file); } #endif #ifdef HAVE_MMAP_FILE static int lsm_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { long ret = 0; const struct path *path; task_info_t *task_info; transport_ids_t transport_ids; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_MMAP) | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_MMAP); const bool writable = (flags & MAP_SHARED) && (prot & PROT_WRITE); subtypes_t subtypes; if (!file) { return 0; } if (!file_is_valid(file)) { return 0; } #ifdef FMODE_NONOTIFY if (file->f_mode & FMODE_NONOTIFY) return 0; #endif if (!HOOK_PROLOG()) { return 0; } if (!(transport_global_get_combined_mask() & generatedEventsMask)) goto out; path = &file->f_path; subtypes = fs_mmap_subtypes(writable, path->dentry->d_inode); if (!transport_global_subtype_needed(subtypes.notify) && !transport_global_subtype_needed(subtypes.sync)) goto out; if (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)) { long block = fs_event_pre_mmap(task_info, file->f_flags, path, reqprot, prot, flags); // TODO: Why is block changed to EACCES? ret = block ? -EACCES : 0; DPRINTF("finished 'fs_event_pre_mmap(flags=0o%o/0x%x)", file->f_flags, file->f_flags); } task_info_put(task_info); out: HOOK_EPILOG(); return ret; } #endif #ifdef HAVE_CONST_PATH_IN_SECURITY #define LSM_PATH_PTR const struct path* #else #define LSM_PATH_PTR struct path* #endif #ifdef CONFIG_SECURITY_PATH static int _lsm_path_rename(const struct path *old_dir, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry, unsigned int flags) { long ret = 0; struct path oldpath = (struct path){}; struct path newpath = (struct path){}; task_info_t *task_info; transport_ids_t transport_ids; bool new_path_is_valid = false; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_RENAME) | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_RENAME); if (!old_dir || !old_dentry || !new_dir || !new_dentry) { return 0; } if (!HOOK_PROLOG()) { return 0; } 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); oldpath.mnt = old_dir->mnt; oldpath.dentry = old_dentry; if (!path_is_valid(&oldpath)) { DPRINTF("path is invalid"); goto err_exit; } // TODO: There should be a check for negative dentry newpath.mnt = new_dir->mnt; newpath.dentry = new_dentry; new_path_is_valid = path_is_valid(&newpath); fsnotify_events_listen_sb(oldpath.mnt->mnt_sb); DPRINTF("calling fs_event_pre_rename(flags=%u)", flags); if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) { long block = fs_event_pre_rename(task_info, flags, &oldpath, &newpath, new_path_is_valid); ret = block ? -EACCES : 0; } DPRINTF("finished 'fs_event_pre_rename(flags=%u)", flags); err_exit: task_info_put(task_info); out: HOOK_EPILOG(); return ret; } #ifdef RENAME_HAS_FLAGS static int lsm_path_rename(LSM_PATH_PTR old_dir, struct dentry *old_dentry, LSM_PATH_PTR new_dir, struct dentry *new_dentry, unsigned int flags) { return _lsm_path_rename(old_dir, old_dentry, new_dir, new_dentry, flags); } #else static int lsm_path_rename(LSM_PATH_PTR old_dir, struct dentry *old_dentry, LSM_PATH_PTR new_dir, struct dentry *new_dentry) { return _lsm_path_rename(old_dir, old_dentry, new_dir, new_dentry, 0); } #endif static int handle_unlink(const struct path *dir, struct dentry *dentry, int flag) { long ret = 0; struct path path = (struct path){}; task_info_t *task_info; transport_ids_t transport_ids; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_UNLINK) | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_UNLINK); if (!dir || !dentry) { return 0; } if (!HOOK_PROLOG()) { return 0; } 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); path.mnt = dir->mnt; path.dentry = dentry; if (!path_is_valid(&path)) { DPRINTF("path is invalid"); goto err_exit; } fsnotify_events_listen_sb(path.mnt->mnt_sb); DPRINTF("calling fs_event_pre_unlink(flag=%d)", flag); if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) { long block = fs_event_pre_unlink(task_info, &path); ret = block ? -EACCES : 0; } DPRINTF("finished 'fs_event_pre_unlink(flag=%d)", flag); err_exit: task_info_put(task_info); out: HOOK_EPILOG(); return ret; } static int lsm_path_unlink(LSM_PATH_PTR dir, struct dentry *dentry) { return handle_unlink(dir, dentry, 0); } static int lsm_path_rmdir(LSM_PATH_PTR dir, struct dentry *dentry) { return handle_unlink(dir, dentry, AT_REMOVEDIR); } static int lsm_path_link(struct dentry *old_dentry, LSM_PATH_PTR new_dir, struct dentry *new_dentry) { long ret = 0; struct path oldpath = (struct path){}; struct path newpath = (struct path){}; task_info_t *task_info; transport_ids_t transport_ids; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_LINK) | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_LINK); if (!old_dentry || !new_dir || !new_dentry) { return 0; } if (!HOOK_PROLOG()) { return 0; } 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); oldpath.mnt = new_dir->mnt; oldpath.dentry = old_dentry; if (!path_is_valid(&oldpath)) { DPRINTF("path is invalid"); goto err_exit; } newpath.mnt = new_dir->mnt; newpath.dentry = new_dentry; DPRINTF("calling fs_event_pre_link()"); if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) { long block = fs_event_pre_link(task_info, &oldpath, &newpath); ret = block ? -EACCES : 0; } DPRINTF("finished 'fs_event_pre_link()"); err_exit: task_info_put(task_info); out: HOOK_EPILOG(); return ret; } static int lsm_path_chmod(LSM_PATH_PTR path, umode_t mode) { task_info_t *task_info; transport_ids_t transport_ids; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CHMOD); if (!path || !path_is_valid(path)) { return 0; } if (!HOOK_PROLOG()) { return 0; } 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_chmod(task_info, path, mode); } task_info_put(task_info); out: HOOK_EPILOG(); return 0; } static int lsm_path_chown(LSM_PATH_PTR path, COMPAT_KUID_T uid, COMPAT_KGID_T gid) { task_info_t *task_info; transport_ids_t transport_ids; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_CHOWN); if (!path || !path_is_valid(path)) { return 0; } if (!HOOK_PROLOG()) { return 0; } 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_chown(task_info, path, from_kuid_compat(uid), from_kgid_compat(gid)); } task_info_put(task_info); out: HOOK_EPILOG(); return 0; } static int lsm_path_mkdir(LSM_PATH_PTR dir, struct dentry *dentry, umode_t mode) { task_info_t *task_info; transport_ids_t transport_ids; struct path path; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_MKDIR); if (!dir || !dentry) { return 0; } if (!path_is_valid(dir)) { return 0; } if (!HOOK_PROLOG()) { return 0; } // Mind that 'path' will refer to 'negative' dentry so do not attempt to check it for validity path.mnt = dir->mnt; fsnotify_events_listen_sb(path.mnt->mnt_sb); path.dentry = dentry; 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_mkdir(task_info, &path, mode); } task_info_put(task_info); out: HOOK_EPILOG(); return 0; } #endif #if defined(CONFIG_SECURITY_PATH) || defined(HAVE_FILE_TRUNCATE) static int lsm_path_truncate(LSM_PATH_PTR path) { long ret = 0; task_info_t *task_info; transport_ids_t transport_ids; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_SYNC_FILE_PRE_TRUNCATE) | MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_FILE_PRE_TRUNCATE); if (!path) { return 0; } if (!HOOK_PROLOG()) { return 0; } 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 (!path_is_valid(path)) { goto err_exit; } DPRINTF("calling fs_event_pre_truncate()"); if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) { long block = fs_event_pre_truncate(task_info, path); ret = block ? -EACCES : 0; } DPRINTF("finished 'fs_event_pre_truncate()"); err_exit: task_info_put(task_info); out: HOOK_EPILOG(); return ret; } #endif #ifdef HAVE_FILE_TRUNCATE static int lsm_file_truncate(struct file *file) { if (!file) return 0; #ifdef FMODE_NONOTIFY if (file->f_mode & FMODE_NONOTIFY) return 0; #endif return lsm_path_truncate(&file->f_path); } #endif static int lsm_settime(const TIMESPEC *ts, const struct timezone *tz) { static const uint8_t k_set_time_fields[] = { SI_COMMON_FIELDS, FP_SI_PI_SYSTEM_TIME_OLD_TIMESTAMP, FP_SI_PI_SYSTEM_TIME_NEW_TIMESTAMP }; transport_ids_t transport_ids; task_info_t *task_info; const uint64_t generatedEventsMask = MSG_TYPE_TO_EVENT_MASK(FP_SI_OT_NOTIFY_SETTIME); (void) tz; if (!ts) { return 0; } if (!HOOK_PROLOG()) { return 0; } 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); if (!task_info_can_skip(task_info, &transport_ids, generatedEventsMask)) { uint64_t unique_pid = make_unique_pid(current); uint64_t event_uid; uint32_t event_size; msg_t *msg = NULL; event_size = SI_ESTIMATE_SIZE_CONST_PARAMS(k_set_time_fields); msg = msg_new(FP_SI_OT_NOTIFY_SETTIME, 0, SI_CT_PRE_CALLBACK, unique_pid, event_size); if (!msg) goto err_exit; event_uid = transport_global_sequence_next(); { SiTimeMicroseconds us; si_property_writer_t writer; si_event_writer_init(&writer, &msg->event, event_size); si_property_writer_write_common(&writer, event_uid, current->pid, current->tgid, task_info); us.microseconds = ktime_to_us(ktime_get_real()); si_property_writer_write_system_time_old_timestamp(&writer, us); us.microseconds = ts->tv_sec * 1000000 + ts->tv_nsec / 1000; si_property_writer_write_system_time_new_timestamp(&writer, us); si_event_writer_finalize(&msg->event, &writer); } msg->task_info = task_info_get(task_info); msg->id = event_uid; send_msg_async_unref_unchecked(msg); } err_exit: task_info_put(task_info); out: HOOK_EPILOG(); return 0; } #ifdef HAVE_FSNOTIFY_PUBLIC_API static int lsm_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode) { (void) mode; if (!HOOK_PROLOG()) return 0; if (dir && sb_ok(dir->i_sb)) fsnotify_events_listen_sb(dir->i_sb); if (dentry && sb_ok(dentry->d_sb)) fsnotify_events_listen_sb(dentry->d_sb); HOOK_EPILOG(); return 0; } #endif #if defined(HAVE_SECURITY_HOOK_LIST) || defined(HAVE_LSM_STATIC_CALL) // MARK: Page RO unprotect // Must be at least 6x amount of callbacks in lsm_defs_x.h. Currently 256 is enough // The most amount of pages are altered during hooklist: head+next+prev unprotect across page boundary #define MAX_UNPROTECTED_PAGES 256 typedef struct { struct page* page; unsigned long address; bool changed; } unprotected_page_t; static unprotected_page_t s_unprotected_pages[MAX_UNPROTECTED_PAGES]; static unsigned int s_unprotected_pages_count = 0; #ifndef HAVE_SECURITY_HOOK_LIST // When hook list is not used, pages can be static because they never change. // Unfortunately, this is not the case with hook list because it can be changed at runtime (although unlikely) // For safety sake, pages will be recollected every time. static bool s_all_pages_collected = 0; #endif static void unprotect(void* ptr) { unsigned long address = (((unsigned long) ptr) / PAGE_SIZE) * PAGE_SIZE; unsigned int level; struct page *page; int unprotected_idx; int i; #ifndef HAVE_SECURITY_HOOK_LIST if (s_all_pages_collected) return; #endif for (i = 0; i < s_unprotected_pages_count; i++) if (address == s_unprotected_pages[i].address) return; page = (struct page *) lookup_address(address, &level); if (!page) return; unprotected_idx = s_unprotected_pages_count++; s_unprotected_pages[unprotected_idx].page = page; s_unprotected_pages[unprotected_idx].address = address; if (!test_bit(_PAGE_BIT_RW, &page->flags)) { set_bit(_PAGE_BIT_RW, &page->flags); s_unprotected_pages[unprotected_idx].changed = true; } else { s_unprotected_pages[unprotected_idx].changed = false; } } static void do_unprotect_struct(void* ptr, size_t sz) { unprotect(ptr); unprotect((char*) ptr + sz); } #define UNPROTECT_STRUCT(ptr) do_unprotect_struct(&(ptr), sizeof(ptr)) static void unprotect_all(void) { int i; for (i = 0; i < s_unprotected_pages_count; i++) { if (s_unprotected_pages[i].changed) set_bit(_PAGE_BIT_RW, &s_unprotected_pages[i].page->flags); } } static void reprotect_all(void) { int i; for (i = 0; i < s_unprotected_pages_count; i++) { if (s_unprotected_pages[i].changed) clear_bit(_PAGE_BIT_RW, &s_unprotected_pages[i].page->flags); } #ifndef HAVE_SECURITY_HOOK_LIST s_all_pages_collected = 1; #else s_unprotected_pages_count = 0; #endif } #endif #ifdef HAVE_SECURITY_HOOK_LIST #include <linux/lsm_hooks.h> #include <linux/rcupdate.h> typedef struct { struct security_hook_list file_open; struct security_hook_list mmap_file; struct security_hook_list settime; struct security_hook_list path_rename; struct security_hook_list path_unlink; struct security_hook_list path_rmdir; struct security_hook_list path_link; struct security_hook_list path_chmod; struct security_hook_list path_chown; struct security_hook_list path_mkdir; struct security_hook_list path_truncate; #ifdef HAVE_FILE_TRUNCATE struct security_hook_list file_truncate; #endif #ifdef HAVE_FSNOTIFY_PUBLIC_API struct security_hook_list inode_create; #endif } lsm_hook_list_t; static lsm_hook_list_t lsm_hook_list = { .file_open = { .hook.file_open = lsm_file_open, }, .mmap_file = { .hook.mmap_file = lsm_mmap_file, }, .settime = { .hook.settime = lsm_settime, }, #ifdef CONFIG_SECURITY_PATH .path_rename = { .hook.path_rename = lsm_path_rename, }, .path_unlink = { .hook.path_unlink = lsm_path_unlink, }, .path_rmdir = { .hook.path_rmdir = lsm_path_rmdir, }, .path_link = { .hook.path_link = lsm_path_link, }, .path_chmod = { .hook.path_chmod = lsm_path_chmod, }, .path_chown = { .hook.path_chown = lsm_path_chown, }, .path_mkdir = { .hook.path_mkdir = lsm_path_mkdir, }, .path_truncate = { .hook.path_truncate = lsm_path_truncate, }, #endif #ifdef HAVE_FILE_TRUNCATE .file_truncate = { .hook.file_truncate = lsm_file_truncate, }, #endif #ifdef HAVE_FSNOTIFY_PUBLIC_API .inode_create = { .hook.inode_create = lsm_inode_create, }, #endif }; #endif #if defined(HAVE_SECURITY_HOOK_LIST) && !defined(HAVE_LSM_STATIC_CALL) /* The original func to add lsm hooks, the lsm name and lsm_append() is not mandatory void __init security_add_hooks(struct security_hook_list *hooks, int count, const char *lsm) { int i; for (i = 0; i < count; i++) { hooks[i].lsm = lsm; hlist_add_tail_rcu(&hooks[i].list, hooks[i].head); } if (slab_is_available()) { if (lsm_append(lsm, &lsm_names) < 0) panic("%s - Cannot get early memory.\n", __func__); } } */ #ifdef HOOK_LIST_USE_HLIST static void unprotect_hlist_node(struct hlist_node* node) { UNPROTECT_STRUCT(*node); if (node->pprev) unprotect(node->pprev); } #define add_hook_to_list_head(hook_list, security_hook_heads_addr, hook_name) \ UNPROTECT_STRUCT(security_hook_heads_addr->hook_name); \ if (security_hook_heads_addr->hook_name.first) \ unprotect_hlist_node(security_hook_heads_addr->hook_name.first); \ hlist_add_head_rcu(&hook_list.hook_name.list, &security_hook_heads_addr->hook_name); \ print_hooks(security_hook_heads_addr, hook_name); #define remove_hook(hook_list, hook_name) \ if (hook_list.hook_name.list.pprev) \ unprotect(hook_list.hook_name.list.pprev); \ if (hook_list.hook_name.list.next) \ unprotect_hlist_node(hook_list.hook_name.list.next); \ hlist_del_rcu(&hook_list.hook_name.list) #else #define add_hook_to_list_head(hook_list, security_hook_heads_addr, hook_name) \ UNPROTECT_STRUCT(security_hook_heads_addr->hook_name); \ if (security_hook_heads_addr->hook_name.next) \ UNPROTECT_STRUCT(*security_hook_heads_addr->hook_name.next); \ if (security_hook_heads_addr->hook_name.prev) \ UNPROTECT_STRUCT(*security_hook_heads_addr->hook_name.prev); \ list_add_rcu(&hook_list.hook_name.list, &security_hook_heads_addr->hook_name); \ print_hooks(security_hook_heads_addr, hook_name); #define remove_hook(hook_list, hook_name) \ if (hook_list.hook_name.list.next) \ UNPROTECT_STRUCT(*hook_list.hook_name.list.next); \ if (hook_list.hook_name.list.prev) \ UNPROTECT_STRUCT(*hook_list.hook_name.list.prev); \ list_del_rcu(&hook_list.hook_name.list) #endif static int _register_lsm_pre_events(void) { struct security_hook_heads *security_hook_heads_addr = (struct security_hook_heads *)compat_kallsyms_lookup_name("security_hook_heads"); if (security_hook_heads_addr == NULL) { return -1; } print_address_symbol(security_hook_heads_addr); { unsigned long flags; cr0_write_protect_t wp; local_irq_save(flags); wp = disable_write_protect(); { unprotect_all(); if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, file_open); IPRINTF("LSM %s was planted!", "open"); } add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, mmap_file); IPRINTF("LSM %s was planted!", "mmap"); add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, settime); IPRINTF("LSM %s was planted!", "settime"); #ifdef CONFIG_SECURITY_PATH if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_rename); IPRINTF("LSM %s was planted!", "rename"); add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_unlink); IPRINTF("LSM %s was planted!", "unlink"); add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_rmdir); IPRINTF("LSM %s was planted!", "rmdir"); } add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_link); IPRINTF("LSM %s was planted!", "link"); add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_chmod); IPRINTF("LSM %s was planted!", "chmod"); add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_chown); IPRINTF("LSM %s was planted!", "chown"); add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_mkdir); IPRINTF("LSM %s was planted!", "mkdir"); add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, path_truncate); IPRINTF("LSM %s was planted!", "truncate"); #endif #ifdef HAVE_FILE_TRUNCATE add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, file_truncate); IPRINTF("LSM %s was planted!", "file_truncate"); #endif #ifdef HAVE_FSNOTIFY_PUBLIC_API add_hook_to_list_head(lsm_hook_list, security_hook_heads_addr, inode_create); IPRINTF("LSM %s was planted!", "inode_create"); #endif reprotect_all(); } restore_write_protect(wp); local_irq_restore(flags); } return 0; } static void _unregister_lsm_pre_events(void) { unsigned long flags; cr0_write_protect_t wp; local_irq_save(flags); wp = disable_write_protect(); { unprotect_all(); if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { remove_hook(lsm_hook_list, file_open); IPRINTF("LSM %s was removed!", "open"); } remove_hook(lsm_hook_list, mmap_file); IPRINTF("LSM %s was removed!", "mmap"); remove_hook(lsm_hook_list, settime); IPRINTF("LSM %s was removed!", "settime"); #ifdef CONFIG_SECURITY_PATH if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { remove_hook(lsm_hook_list, path_rename); IPRINTF("LSM %s was removed!", "rename"); remove_hook(lsm_hook_list, path_unlink); IPRINTF("LSM %s was removed!", "unlink"); remove_hook(lsm_hook_list, path_rmdir); IPRINTF("LSM %s was removed!", "rmdir"); } remove_hook(lsm_hook_list, path_link); IPRINTF("LSM %s was removed!", "link"); remove_hook(lsm_hook_list, path_chmod); IPRINTF("LSM %s was removed!", "chmod"); remove_hook(lsm_hook_list, path_chown); IPRINTF("LSM %s was removed!", "chown"); remove_hook(lsm_hook_list, path_mkdir); IPRINTF("LSM %s was removed!", "mkdir"); remove_hook(lsm_hook_list, path_truncate); IPRINTF("LSM %s was removed!", "truncate"); #endif #ifdef HAVE_FILE_TRUNCATE remove_hook(lsm_hook_list, file_truncate); IPRINTF("LSM %s was removed!", "file_truncate"); #endif #ifdef HAVE_FSNOTIFY_PUBLIC_API remove_hook(lsm_hook_list, inode_create); IPRINTF("LSM %s was removed!", "inode_create"); #endif reprotect_all(); } restore_write_protect(wp); local_irq_restore(flags); synchronize_rcu(); } #else #define MAP0(m,...) #define MAP1(m,t,a,...) m(t,a) #define MAP2(m,t,a,...) m(t,a), MAP1(m,__VA_ARGS__) #define MAP3(m,t,a,...) m(t,a), MAP2(m,__VA_ARGS__) #define MAP4(m,t,a,...) m(t,a), MAP3(m,__VA_ARGS__) #define MAP5(m,t,a,...) m(t,a), MAP4(m,__VA_ARGS__) #define MAP6(m,t,a,...) m(t,a), MAP5(m,__VA_ARGS__) #define MAP7(m,t,a,...) m(t,a), MAP6(m,__VA_ARGS__) #define MAP8(m,t,a,...) m(t,a), MAP7(m,__VA_ARGS__) #define MAP9(m,t,a,...) m(t,a), MAP8(m,__VA_ARGS__) #define MAP10(m,t,a,...) m(t,a), MAP9(m,__VA_ARGS__) #define MAP11(m,t,a,...) m(t,a), MAP10(m,__VA_ARGS__) #define MAP(n,...) MAP##n(__VA_ARGS__) #define SC_DECL(t, a) t a #define SC_ARGS(t, a) a #ifndef HAVE_LSM_STATIC_CALL #include <linux/security.h> struct security_operations **security_ops_addr; static struct security_operations lsm_security_ops; static struct security_operations *ori_security_ops; #define LSM_HOOK_ENTER(name, args_cnt, ...) \ static inline void* ori_get_##name(void) { return (void*) ori_security_ops->name; } #include "lsm_defs_x.h" #else #include <linux/lsm_hooks.h> struct lsm_static_calls_table* lsm_static_calls; #define LSM_HOOK_ENTER(name, args_cnt, ...) \ static void* lsm_ori_##name; \ static inline void* ori_get_##name(void) { return lsm_ori_##name; } \ struct security_hook_list* lsm_ori_hl_##name; \ static int lsm_planted_slot_##name; #include "lsm_defs_x.h" #endif #include "hook_trampoline_common.h" #define LSM_HOOK_TRAMPOLINE(name) lsm_##name##_trampoline #define LSM_HOOK_NAME(name) lsm_##name##_enter #define HOOK_TRAMPOLINE(name, ...) HOOK_TRAMPOLINE_ASM(LSM_HOOK_TRAMPOLINE(name), LSM_HOOK_NAME(name)) \ int LSM_HOOK_TRAMPOLINE(name) (__VA_ARGS__); #define HOOK_TRAMPOLINE_NO_RET(name, ...) HOOK_TRAMPOLINE_ASM(LSM_HOOK_TRAMPOLINE(name), LSM_HOOK_NAME(name)) \ void LSM_HOOK_TRAMPOLINE(name) (__VA_ARGS__); #define LSM_HOOK_ENTER(name, args_cnt, ...) \ hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)); \ hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)) \ { \ hook_ret_t ret; \ ret.ret = lsm_##name(MAP(args_cnt, SC_ARGS, __VA_ARGS__)); \ if (0 == ret.ret) \ ret.fn = (long) ori_get_##name(); \ else \ ret.fn = 0; \ return ret; \ } \ HOOK_TRAMPOLINE(name, MAP(args_cnt, SC_DECL, __VA_ARGS__)) #define LSM_HOOK_ENTER_NO_RET(name, args_cnt, ...) \ hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)); \ hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)) \ { \ hook_ret_t ret; \ lsm_##name(MAP(args_cnt, SC_ARGS, __VA_ARGS__)); \ ret.ret = 0; \ ret.fn = (long) ori_get_##name(); \ return ret; \ } \ HOOK_TRAMPOLINE_NO_RET(name, MAP(args_cnt, SC_DECL, __VA_ARGS__)) // Ignore the ENOSYS result and just jump to the original function #define LSM_HOOK_ENTER_ENOSYS(name, args_cnt, ...) \ hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)); \ hook_ret_t lsm_##name##_enter(MAP(args_cnt, SC_DECL, __VA_ARGS__)) \ { \ hook_ret_t ret; \ (void) lsm_##name(MAP(args_cnt, SC_ARGS, __VA_ARGS__)); \ ret.ret = 0; \ ret.fn = (long) ori_get_##name(); \ return ret; \ } \ HOOK_TRAMPOLINE(name, MAP(args_cnt, SC_DECL, __VA_ARGS__)) #include "lsm_defs_x.h" #ifndef HAVE_LSM_STATIC_CALL static void fixup_lsm_ops(struct security_operations *security_ops) { lsm_security_ops = *security_ops; // TODO: If we use LSM inotify_create, fsnotify restriction can be lifted if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { #ifdef HAVE_FILE_OPEN lsm_security_ops.file_open = lsm_file_open_trampoline; #else lsm_security_ops.dentry_open = lsm_dentry_open_trampoline; #endif IPRINTF("LSM %s was planted!", "open"); } #ifdef HAVE_MMAP_FILE lsm_security_ops.mmap_file = lsm_mmap_file_trampoline; IPRINTF("LSM %s was planted!", "mmap"); #endif lsm_security_ops.settime = lsm_settime_trampoline; IPRINTF("LSM %s was planted!", "settime"); #ifdef CONFIG_SECURITY_PATH if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { lsm_security_ops.path_rename = lsm_path_rename_trampoline; IPRINTF("LSM %s was planted!", "rename"); lsm_security_ops.path_unlink = lsm_path_unlink_trampoline; IPRINTF("LSM %s was planted!", "unlink"); lsm_security_ops.path_rmdir = lsm_path_rmdir_trampoline; IPRINTF("LSM %s was planted!", "rmdir"); } lsm_security_ops.path_truncate = lsm_path_truncate_trampoline; IPRINTF("LSM %s was planted!", "truncate"); lsm_security_ops.path_link = lsm_path_link_trampoline; IPRINTF("LSM %s was planted!", "link"); lsm_security_ops.path_chmod = lsm_path_chmod_trampoline; IPRINTF("LSM %s was planted!", "chmod"); lsm_security_ops.path_chown = lsm_path_chown_trampoline; IPRINTF("LSM %s was planted!", "chown"); lsm_security_ops.path_mkdir = lsm_path_mkdir_trampoline; IPRINTF("LSM %s was planted!", "mkdir"); #endif #ifdef HAVE_FSNOTIFY_PUBLIC_API lsm_security_ops.inode_create = lsm_inode_create_trampoline; IPRINTF("LSM %s was planted!", "inode_create"); #endif } static int _register_lsm_pre_events(void) { security_ops_addr = (struct security_operations **)compat_kallsyms_lookup_name("security_ops"); ori_security_ops = *security_ops_addr; if (security_ops_addr == NULL || ori_security_ops == NULL) { EPRINTF("%p %p", security_ops_addr, ori_security_ops); return -1; } print_address_symbol(security_ops_addr); print_address_symbol(ori_security_ops); fixup_lsm_ops(*security_ops_addr); { unsigned long flags; local_irq_save(flags); { cr0_write_protect_t wp = disable_write_protect(); *security_ops_addr = &lsm_security_ops; restore_write_protect(wp); } local_irq_restore(flags); } return 0; } static void _unregister_lsm_pre_events(void) { if (security_ops_addr == NULL || ori_security_ops == NULL) { EPRINTF("%p %p", security_ops_addr, ori_security_ops); return; } { unsigned long flags; local_irq_save(flags); { cr0_write_protect_t wp = disable_write_protect(); *security_ops_addr = ori_security_ops; restore_write_protect(wp); } local_irq_restore(flags); } IPRINTF("LSM was unregistered"); } #else // This function assumes the following features of static calls: // 1) There is 'func' in the 'key' that can be pulled out safely // 2) '__static_call_update' exists to allow safely installing the hook // Search for an empty slot and place our hook in there. // If slot is not found, just place the hook at the first entry with trampoline. // Note that for some reason there is both static_calls AND original hook lists are used. // Most functions use static_call as normal but 'prctl' in particular uses an old method. // I assume eventually 'lsm_hook_list' usage will be dropped but for now, setup both hooks. #define LSM_BUSY_SLOT (-1) #define add_slot(name) \ do { \ int i; \ bool found_empty_slot = false; \ void* fn; \ for (i = 0; i < MAX_LSM_COUNT - 1; i++) \ if (!static_key_enabled(&lsm_static_calls->name[i].active->key)) { \ found_empty_slot = true; break; \ } \ if (!found_empty_slot) i = 0; \ lsm_planted_slot_##name = found_empty_slot ? i : LSM_BUSY_SLOT; \ lsm_ori_##name = (void*) lsm_static_calls->name[i].key->func; \ lsm_ori_hl_##name = lsm_static_calls->name[i].hl; \ fn = found_empty_slot ? (void*) lsm_##name : (void*) lsm_##name##_trampoline; \ lsm_hook_list.name.hook.name = fn; \ unprotect(&lsm_static_calls->name[i].hl); \ lsm_static_calls->name[i].hl = &lsm_hook_list.name; \ UNPROTECT_STRUCT(lsm_static_calls->name[i].key); \ restore_write_protect(wp); \ local_irq_restore(flags); \ __static_call_update(lsm_static_calls->name[i].key, lsm_static_calls->name[i].trampoline, fn); \ local_irq_save(flags); \ redisable_write_protect(wp); \ if (found_empty_slot) \ static_key_enable(&lsm_static_calls->name[i].active->key); \ IPRINTF("LSM %s was planted at slot %d!", #name, lsm_planted_slot_##name); \ } while (0) #define remove_slot(name) \ do { \ int i = lsm_planted_slot_##name == LSM_BUSY_SLOT ? 0 : lsm_planted_slot_##name; \ if (lsm_planted_slot_##name != LSM_BUSY_SLOT) \ static_key_disable(&lsm_static_calls->name[i].active->key); \ unprotect(&lsm_static_calls->name[i].hl); \ lsm_static_calls->name[i].hl = lsm_ori_hl_##name; \ UNPROTECT_STRUCT(lsm_static_calls->name[i].key); \ restore_write_protect(wp); \ local_irq_restore(flags); \ __static_call_update(lsm_static_calls->name[i].key, lsm_static_calls->name[i].trampoline, lsm_ori_##name); \ local_irq_save(flags); \ redisable_write_protect(wp); \ IPRINTF("LSM %s was removed!", #name); \ } while (0) static int _register_lsm_pre_events(void) { lsm_static_calls = (struct lsm_static_calls_table*) compat_kallsyms_lookup_name("static_calls_table"); if (lsm_static_calls == NULL) { return -1; } print_address_symbol(lsm_static_calls); { cr0_write_protect_t wp; unsigned long flags; local_irq_save(flags); wp = disable_write_protect(); { unprotect_all(); if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { add_slot(file_open); } add_slot(mmap_file); add_slot(settime); #ifdef CONFIG_SECURITY_PATH if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { add_slot(path_rename); add_slot(path_unlink); add_slot(path_rmdir); } add_slot(path_link); add_slot(path_chmod); add_slot(path_chown); add_slot(path_mkdir); add_slot(path_truncate); #endif #ifdef HAVE_FILE_TRUNCATE add_slot(file_truncate); #endif #ifdef HAVE_FSNOTIFY_PUBLIC_API add_slot(inode_create); #endif reprotect_all(); } restore_write_protect(wp); local_irq_restore(flags); } return 0; } static void _unregister_lsm_pre_events(void) { cr0_write_protect_t wp; unsigned long flags; if (!lsm_static_calls) return; local_irq_save(flags); wp = disable_write_protect(); { unprotect_all(); if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { remove_slot(file_open); } remove_slot(mmap_file); remove_slot(settime); #ifdef CONFIG_SECURITY_PATH if (ftrace_post_event_have(FTRACE_POST_EVENT_FSNOTIFY)) { remove_slot(path_rename); remove_slot(path_unlink); remove_slot(path_rmdir); } remove_slot(path_link); remove_slot(path_chmod); remove_slot(path_chown); remove_slot(path_mkdir); remove_slot(path_truncate); #endif #ifdef HAVE_FILE_TRUNCATE remove_slot(file_truncate); #endif #ifdef HAVE_FSNOTIFY_PUBLIC_API remove_slot(inode_create); #endif reprotect_all(); } restore_write_protect(wp); local_irq_restore(flags); synchronize_rcu(); lsm_static_calls = NULL; } #endif #endif int register_lsm_pre_events(void) { if (_register_lsm_pre_events() == 0) { return 0; } return -1; } void unregister_lsm_pre_events(void) { _unregister_lsm_pre_events(); } #else int register_lsm_pre_events(void) { return -1; } void unregister_lsm_pre_events(void) { } #endif