From 4ca3db4e0178fb930fe456db3455de174b7a94d5 Mon Sep 17 00:00:00 2001 From: Ylarod Date: Fri, 15 Mar 2024 18:53:24 +0800 Subject: [PATCH] Build KernelSU as LKM (#1254) Co-authored-by: weishu --- drivers/kernelsu/Kconfig | 7 ++ drivers/kernelsu/Makefile | 50 +++++++--- drivers/kernelsu/core_hook.c | 182 ++++++++++++++++++++++++++++++++++- 3 files changed, 222 insertions(+), 17 deletions(-) diff --git a/drivers/kernelsu/Kconfig b/drivers/kernelsu/Kconfig index 641e7123fecc..ab9c5097847a 100644 --- a/drivers/kernelsu/Kconfig +++ b/drivers/kernelsu/Kconfig @@ -14,4 +14,11 @@ config KSU_DEBUG help Enable KernelSU debug mode +config KSU_MODULE + bool "Build KernelSU as a module" + depends on KSU + default n + help + Build KernelSU as a loadable kernel module + endmenu diff --git a/drivers/kernelsu/Makefile b/drivers/kernelsu/Makefile index 3b982e900c35..9cf898c9d3d5 100644 --- a/drivers/kernelsu/Makefile +++ b/drivers/kernelsu/Makefile @@ -1,17 +1,27 @@ -obj-y += ksu.o -obj-y += allowlist.o -kernelsu-objs := apk_sign.o +kernelsu-objs := ksu.o +kernelsu-objs += allowlist.o +kernelsu-objs += apk_sign.o +kernelsu-objs += module_api.o +kernelsu-objs += sucompat.o +kernelsu-objs += uid_observer.o +kernelsu-objs += manager.o +kernelsu-objs += core_hook.o +kernelsu-objs += ksud.o +kernelsu-objs += embed_ksud.o +kernelsu-objs += kernel_compat.o + +kernelsu-objs += selinux/selinux.o +kernelsu-objs += selinux/sepolicy.o +kernelsu-objs += selinux/rules.o +ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include +ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h + +ifndef KSU_MODULE obj-y += kernelsu.o -obj-y += module_api.o -obj-y += sucompat.o -obj-y += uid_observer.o -obj-y += manager.o -obj-y += core_hook.o -obj-y += ksud.o -obj-y += embed_ksud.o -obj-y += kernel_compat.o - -obj-y += selinux/ +else +obj-m += kernelsu.o +endif + # .git is a text file while the module is imported by 'git submodule add'. ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0) # We must use the absolute path to git, otherwise the build will fail if git is not in the PATH @@ -19,6 +29,14 @@ KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr ccflags-y += -DKSU_GIT_VERSION=$(KSU_GIT_VERSION) endif +ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID +endif + +ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0) +ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE +endif + ifndef KSU_EXPECTED_SIZE KSU_EXPECTED_SIZE := 0x033b endif @@ -32,7 +50,11 @@ ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\" $(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE)) endif +$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE)) +$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH)) + ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE) ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\" + ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat -ccflags-y += -Wno-declaration-after-statement +ccflags-y += -Wno-declaration-after-statement -Wno-unused-function diff --git a/drivers/kernelsu/core_hook.c b/drivers/kernelsu/core_hook.c index 08bf87de346e..81b4ea3210fd 100644 --- a/drivers/kernelsu/core_hook.c +++ b/drivers/kernelsu/core_hook.c @@ -4,13 +4,21 @@ #include "linux/err.h" #include "linux/init.h" #include "linux/init_task.h" +#include "linux/kallsyms.h" #include "linux/kernel.h" #include "linux/kprobes.h" +#include "linux/list.h" #include "linux/lsm_hooks.h" #include "linux/module.h" +#include "linux/mm.h" +#include "linux/mm_types.h" #include "linux/nsproxy.h" #include "linux/path.h" #include "linux/printk.h" +#include "linux/sched.h" +#include "linux/security.h" +#include "linux/stddef.h" +#include "linux/types.h" #include "linux/uaccess.h" #include "linux/uidgid.h" #include "linux/version.h" @@ -28,6 +36,7 @@ #include "klog.h" // IWYU pragma: keep #include "ksu.h" #include "ksud.h" +#include "linux/vmalloc.h" #include "manager.h" #include "selinux/selinux.h" #include "uid_observer.h" @@ -243,7 +252,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, #ifdef CONFIG_KSU_DEBUG pr_info("manager already exist: %d\n", ksu_get_manager_uid()); -#endif +#endif return 0; } @@ -735,14 +744,181 @@ void __init ksu_lsm_hook_init(void) #endif } +#ifdef MODULE +static int override_security_head(void *head, const void *new_head, size_t len) +{ + unsigned long base = (unsigned long)head & PAGE_MASK; + unsigned long offset = offset_in_page(head); + + // this is impossible for our case because the page alignment + // but be careful for other cases! + BUG_ON(offset + len > PAGE_SIZE); + struct page *page = phys_to_page(__pa(base)); + if (!page) { + return -EFAULT; + } + + void *addr = vmap(&page, 1, VM_MAP, PAGE_KERNEL); + if (!addr) { + return -ENOMEM; + } + memcpy(addr + offset, new_head, len); + vunmap(addr); + return 0; +} + +static void free_security_hook_list(struct hlist_head *head) +{ + struct hlist_node *temp; + struct security_hook_list *entry; + + if (!head) + return; + + hlist_for_each_entry_safe (entry, temp, head, list) { + hlist_del(&entry->list); + kfree(entry); + } + + kfree(head); +} + +struct hlist_head *copy_security_hlist(struct hlist_head *orig) +{ + struct hlist_head *new_head = kmalloc(sizeof(*new_head), GFP_KERNEL); + if (!new_head) + return NULL; + + INIT_HLIST_HEAD(new_head); + + struct security_hook_list *entry; + struct security_hook_list *new_entry; + + hlist_for_each_entry (entry, orig, list) { + new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) { + free_security_hook_list(new_head); + return NULL; + } + + *new_entry = *entry; + + hlist_add_tail_rcu(&new_entry->list, new_head); + } + + return new_head; +} + +#define LSM_SEARCH_MAX 180 // This should be enough to iterate +static void *find_head_addr(void *security_ptr, int *index) +{ + if (!security_ptr) { + return NULL; + } + struct hlist_head *head_start = + (struct hlist_head *)&security_hook_heads; + + for (int i = 0; i < LSM_SEARCH_MAX; i++) { + struct hlist_head *head = head_start + i; + struct security_hook_list *pos; + hlist_for_each_entry (pos, head, list) { + if (pos->hook.capget == security_ptr) { + if (index) { + *index = i; + } + return head; + } + } + } + + return NULL; +} + +#define GET_SYMBOL_ADDR(sym) \ + ({ \ + void *addr = kallsyms_lookup_name(#sym ".cfi_jt"); \ + if (!addr) { \ + addr = kallsyms_lookup_name(#sym); \ + } \ + addr; \ + }) + +#define KSU_LSM_HOOK_HACK_INIT(head_ptr, name, func) \ + do { \ + static struct security_hook_list hook = { \ + .hook = { .name = func } \ + }; \ + hook.head = head_ptr; \ + hook.lsm = "ksu"; \ + struct hlist_head *new_head = copy_security_hlist(hook.head); \ + if (!new_head) { \ + pr_err("Failed to copy security list: %s\n", #name); \ + break; \ + } \ + hlist_add_tail_rcu(&hook.list, new_head); \ + if (override_security_head(hook.head, new_head, \ + sizeof(*new_head))) { \ + free_security_hook_list(new_head); \ + pr_err("Failed to hack lsm for: %s\n", #name); \ + } \ + } while (0) + +void __init ksu_lsm_hook_init_hack(void) +{ + void *cap_prctl = GET_SYMBOL_ADDR(cap_task_prctl); + void *prctl_head = find_head_addr(cap_prctl, NULL); + if (prctl_head) { + if (prctl_head != &security_hook_heads.task_prctl) { + pr_warn("prctl's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(prctl_head, task_prctl, ksu_task_prctl); + } else { + pr_warn("Failed to find task_prctl!\n"); + } + + int inode_killpriv_index = -1; + void *cap_killpriv = GET_SYMBOL_ADDR(cap_inode_killpriv); + find_head_addr(cap_killpriv, &inode_killpriv_index); + if (inode_killpriv_index < 0) { + pr_warn("Failed to find inode_rename, use kprobe instead!\n"); + register_kprobe(&renameat_kp); + } else { + int inode_rename_index = inode_killpriv_index + + &security_hook_heads.inode_rename - + &security_hook_heads.inode_killpriv; + struct hlist_head *head_start = + (struct hlist_head *)&security_hook_heads; + void *inode_rename_head = head_start + inode_rename_index; + if (inode_rename_head != &security_hook_heads.inode_rename) { + pr_warn("inode_rename's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(inode_rename_head, inode_rename, + ksu_inode_rename); + } + void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid); + void *setuid_head = find_head_addr(cap_setuid, NULL); + if (setuid_head) { + if (setuid_head != &security_hook_heads.task_fix_setuid) { + pr_warn("setuid's address has shifted!\n"); + } + KSU_LSM_HOOK_HACK_INIT(setuid_head, task_fix_setuid, + ksu_task_fix_setuid); + } else { + pr_warn("Failed to find task_fix_setuid!\n"); + } + smp_mb(); +} +#endif + void __init ksu_core_init(void) { #ifndef MODULE pr_info("ksu_lsm_hook_init\n"); ksu_lsm_hook_init(); + #else - pr_info("ksu_kprobe_init\n"); - ksu_kprobe_init(); + pr_info("ksu_lsm_hook_init hack!!!!\n"); + ksu_lsm_hook_init_hack(); #endif }