diff --git a/kernel/.clangd b/kernel/.clangd index 0e11bf68..83860fae 100644 --- a/kernel/.clangd +++ b/kernel/.clangd @@ -1,3 +1,4 @@ Diagnostics: + UnusedIncludes: Strict ClangTidy: Remove: bugprone-sizeof-expression \ No newline at end of file diff --git a/kernel/Makefile b/kernel/Makefile index c2026feb..cf98838d 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -5,8 +5,9 @@ obj-y += kernelsu.o obj-y += module_api.o obj-y += sucompat.o obj-y += uid_observer.o -obj-y += lsm_hook.o -obj-y += kprobe_hook.o +obj-y += manager.o +obj-y += core_hook.o +obj-y += ksud.o obj-y += selinux/ @@ -21,4 +22,4 @@ endif ccflags-y += -DEXPECTED_SIZE=$(EXPECTED_SIZE) ccflags-y += -DEXPECTED_HASH=$(EXPECTED_HASH) ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat -ccflags-y += -Wno-macro-redefined -Wno-declaration-after-statement \ No newline at end of file +ccflags-y += -Wno-macro-redefined -Wno-declaration-after-statement diff --git a/kernel/allowlist.c b/kernel/allowlist.c index e462880d..fcf8c062 100644 --- a/kernel/allowlist.c +++ b/kernel/allowlist.c @@ -1,26 +1,10 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "linux/delay.h" +#include "linux/fs.h" +#include "linux/kernel.h" +#include "linux/list.h" +#include "linux/printk.h" +#include "linux/slab.h" -#include -#include -#include -#include -#include - -#include // msleep - -#include "klog.h" #include "selinux/selinux.h" #define FILE_MAGIC 0x7f4b5355 // ' KSU', u32 @@ -256,39 +240,37 @@ void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data) } } -static int init_work(void) -{ - INIT_WORK(&ksu_save_work, do_persistent_allow_list); - INIT_WORK(&ksu_load_work, do_load_allow_list); - return 0; -} - // make sure allow list works cross boot bool persistent_allow_list(void) { - ksu_queue_work(&ksu_save_work); - return true; + return ksu_queue_work(&ksu_save_work); } bool ksu_load_allow_list(void) { - ksu_queue_work(&ksu_load_work); - return true; + return ksu_queue_work(&ksu_load_work); } -bool ksu_allowlist_init(void) +void ksu_allowlist_init(void) { INIT_LIST_HEAD(&allow_list); - init_work(); - - // start load allow list, we load it before app_process exec now, refer: sucompat#execve_handler_pre - // ksu_load_allow_list(); - - return true; + INIT_WORK(&ksu_save_work, do_persistent_allow_list); + INIT_WORK(&ksu_load_work, do_load_allow_list); } -bool ksu_allowlist_exit(void) +void ksu_allowlist_exit(void) { - return true; + struct perm_data *np = NULL; + struct perm_data *n = NULL; + + do_persistent_allow_list(NULL); + + // free allowlist + mutex_lock(&allowlist_mutex); + list_for_each_entry_safe (np, n, &allow_list, list) { + list_del(&np->list); + kfree(np); + } + mutex_unlock(&allowlist_mutex); } \ No newline at end of file diff --git a/kernel/allowlist.h b/kernel/allowlist.h index bd6de7b6..feb6e218 100644 --- a/kernel/allowlist.h +++ b/kernel/allowlist.h @@ -1,11 +1,13 @@ #ifndef __KSU_H_ALLOWLIST #define __KSU_H_ALLOWLIST -#include +#include "linux/types.h" -bool ksu_allowlist_init(); +void ksu_allowlist_init(void); -bool ksu_allowlist_exit(); +void ksu_allowlist_exit(void); + +bool ksu_load_allow_list(void); bool ksu_is_allow_uid(uid_t uid); @@ -13,8 +15,6 @@ bool ksu_allow_uid(uid_t uid, bool allow); bool ksu_get_allow_list(int *array, int *length, bool allow); -bool ksu_load_allow_list(void); - -void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void* data); +void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data); #endif \ No newline at end of file diff --git a/kernel/apk_sign.c b/kernel/apk_sign.c index e78609ae..c32ece76 100644 --- a/kernel/apk_sign.c +++ b/kernel/apk_sign.c @@ -1,11 +1,10 @@ -#include -#include +#include "linux/fs.h" +#include "linux/moduleparam.h" #include "apk_sign.h" -#include "klog.h" -static __always_inline int check_v2_signature(char *path, unsigned expected_size, - unsigned expected_hash) +static __always_inline int +check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash) { unsigned char buffer[0x11] = { 0 }; u32 size4; @@ -67,23 +66,23 @@ static __always_inline int check_v2_signature(char *path, unsigned expected_size offset = 4; pr_info("id: 0x%08x\n", id); if ((id ^ 0xdeadbeefu) == 0xafa439f5u || - (id ^ 0xdeadbeefu) == 0x2efed62f) { + (id ^ 0xdeadbeefu) == 0x2efed62f) { kernel_read(fp, &size4, 0x4, - &pos); // signer-sequence length + &pos); // signer-sequence length kernel_read(fp, &size4, 0x4, &pos); // signer length kernel_read(fp, &size4, 0x4, - &pos); // signed data length + &pos); // signed data length offset += 0x4 * 3; kernel_read(fp, &size4, 0x4, - &pos); // digests-sequence length + &pos); // digests-sequence length pos += size4; offset += 0x4 + size4; kernel_read(fp, &size4, 0x4, - &pos); // certificates length + &pos); // certificates length kernel_read(fp, &size4, 0x4, - &pos); // certificate length + &pos); // certificate length offset += 0x4 * 2; #if 0 int hash = 1; @@ -104,7 +103,7 @@ static __always_inline int check_v2_signature(char *path, unsigned expected_size } offset += size4; if ((((unsigned)hash) ^ 0x14131211u) == - expected_hash) { + expected_hash) { sign = 0; break; } @@ -127,8 +126,38 @@ clean: unsigned ksu_expected_size = EXPECTED_SIZE; unsigned ksu_expected_hash = EXPECTED_HASH; -module_param(ksu_expected_size, uint, S_IRUSR | S_IWUSR); -module_param(ksu_expected_hash, uint, S_IRUSR | S_IWUSR); +#include "manager.h" + +static int set_expected_size(const char *val, const struct kernel_param *kp) +{ + int rv = param_set_uint(val, kp); + ksu_invalidate_manager_uid(); + pr_info("ksu_expected_size set to %x", ksu_expected_size); + return rv; +} + +static int set_expected_hash(const char *val, const struct kernel_param *kp) +{ + int rv = param_set_uint(val, kp); + ksu_invalidate_manager_uid(); + pr_info("ksu_expected_hash set to %x", ksu_expected_hash); + return rv; +} + +static struct kernel_param_ops expected_size_ops = { + .set = set_expected_size, + .get = param_get_uint, +}; + +static struct kernel_param_ops expected_hash_ops = { + .set = set_expected_hash, + .get = param_get_uint, +}; + +module_param_cb(ksu_expected_size, &expected_size_ops, &ksu_expected_size, + S_IRUSR | S_IWUSR); +module_param_cb(ksu_expected_hash, &expected_hash_ops, &ksu_expected_hash, + S_IRUSR | S_IWUSR); int is_manager_apk(char *path) { @@ -137,7 +166,6 @@ int is_manager_apk(char *path) #else - int is_manager_apk(char *path) { return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH); diff --git a/kernel/apk_sign.h b/kernel/apk_sign.h index d4ba8beb..033971f9 100644 --- a/kernel/apk_sign.h +++ b/kernel/apk_sign.h @@ -2,6 +2,6 @@ #define __KSU_H_APK_V2_SIGN // return 0 if signature match -int is_manager_apk(char* path); +int is_manager_apk(char *path); #endif \ No newline at end of file diff --git a/kernel/arch.h b/kernel/arch.h index b8d84947..739421d0 100644 --- a/kernel/arch.h +++ b/kernel/arch.h @@ -1,7 +1,7 @@ #ifndef __KSU_H_ARCH #define __KSU_H_ARCH -#include +#include "linux/version.h" #if defined(__aarch64__) diff --git a/kernel/core_hook.c b/kernel/core_hook.c new file mode 100644 index 00000000..dbddc180 --- /dev/null +++ b/kernel/core_hook.c @@ -0,0 +1,355 @@ +#include "linux/cred.h" +#include "linux/err.h" +#include "linux/init.h" +#include "linux/kernel.h" +#include "linux/kprobes.h" +#include "linux/lsm_hooks.h" +#include "linux/printk.h" +#include "linux/uaccess.h" +#include "linux/uidgid.h" +#include "linux/version.h" + +#include "linux/fs.h" +#include "linux/namei.h" +#include "linux/rcupdate.h" + +#include "allowlist.h" +#include "arch.h" +#include "core_hook.h" +#include "ksu.h" +#include "manager.h" +#include "selinux/selinux.h" +#include "uid_observer.h" + +static inline bool is_allow_su() +{ + if (is_manager()) { + // we are manager, allow! + return true; + } + return ksu_is_allow_uid(current_uid().val); +} + +static struct group_info root_groups = { .usage = ATOMIC_INIT(2) }; + +void escape_to_root(void) +{ + struct cred *cred; + + cred = (struct cred *)__task_cred(current); + + memset(&cred->uid, 0, sizeof(cred->uid)); + memset(&cred->gid, 0, sizeof(cred->gid)); + memset(&cred->suid, 0, sizeof(cred->suid)); + memset(&cred->euid, 0, sizeof(cred->euid)); + memset(&cred->egid, 0, sizeof(cred->egid)); + memset(&cred->fsuid, 0, sizeof(cred->fsuid)); + memset(&cred->fsgid, 0, sizeof(cred->fsgid)); + memset(&cred->cap_inheritable, 0xff, sizeof(cred->cap_inheritable)); + memset(&cred->cap_permitted, 0xff, sizeof(cred->cap_permitted)); + memset(&cred->cap_effective, 0xff, sizeof(cred->cap_effective)); + memset(&cred->cap_bset, 0xff, sizeof(cred->cap_bset)); + memset(&cred->cap_ambient, 0xff, sizeof(cred->cap_ambient)); + + // disable seccomp +#if defined(CONFIG_GENERIC_ENTRY) && \ + LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) + current_thread_info()->syscall_work &= ~SYSCALL_WORK_SECCOMP; +#else + current_thread_info()->flags &= ~(TIF_SECCOMP | _TIF_SECCOMP); +#endif + current->seccomp.mode = 0; + current->seccomp.filter = NULL; + + // setgroup to root + if (cred->group_info) + put_group_info(cred->group_info); + cred->group_info = get_group_info(&root_groups); + + setup_selinux(); +} + +int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry) +{ + if (!current->mm) { + // skip kernel threads + return 0; + } + + if (current_uid().val != 1000) { + // skip non system uid + return 0; + } + + if (!old_dentry || !new_dentry) { + return 0; + } + + // /data/system/packages.list.tmp -> /data/system/packages.list + if (strcmp(new_dentry->d_iname, "packages.list")) { + return 0; + } + + char path[128]; + char *buf = dentry_path_raw(new_dentry, path, sizeof(path)); + if (IS_ERR(buf)) { + pr_err("dentry_path_raw failed.\n"); + return 0; + } + + if (strcmp(buf, "/system/packages.list")) { + return 0; + } + pr_info("renameat: %s -> %s\n, new path: %s", old_dentry->d_iname, + new_dentry->d_iname, buf); + + update_uid(); + + return 0; +} + +int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + // if success, we modify the arg5 as result! + u32 *result = (u32 *)arg5; + u32 reply_ok = KERNEL_SU_OPTION; + + if (KERNEL_SU_OPTION != option) { + return 0; + } + + pr_info("option: 0x%x, cmd: %ld\n", option, arg2); + + if (arg2 == CMD_BECOME_MANAGER) { + // quick check + if (is_manager()) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("become_manager: prctl reply error\n"); + } + return 0; + } + if (ksu_is_manager_uid_valid()) { + pr_info("manager already exist: %d\n", + ksu_get_manager_uid()); + return 0; + } + + // someone wants to be root manager, just check it! + // arg3 should be `/data/data/` + char param[128]; + const char *prefix = "/data/data/"; + if (copy_from_user(param, arg3, sizeof(param))) { + pr_err("become_manager: copy param err\n"); + return 0; + } + + if (startswith(param, (char *)prefix) != 0) { + pr_info("become_manager: invalid param: %s\n", param); + return 0; + } + + // stat the param, app must have permission to do this + // otherwise it may fake the path! + struct path path; + if (kern_path(param, LOOKUP_DIRECTORY, &path)) { + pr_err("become_manager: kern_path err\n"); + return 0; + } + if (path.dentry->d_inode->i_uid.val != current_uid().val) { + pr_err("become_manager: path uid != current uid\n"); + path_put(&path); + return 0; + } + char *pkg = param + strlen(prefix); + pr_info("become_manager: param pkg: %s\n", pkg); + + bool success = become_manager(pkg); + if (success) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("become_manager: prctl reply error\n"); + } + } + path_put(&path); + return 0; + } + + if (arg2 == CMD_GRANT_ROOT) { + if (is_allow_su()) { + pr_info("allow root for: %d\n", current_uid()); + escape_to_root(); + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("grant_root: prctl reply error\n"); + } + } else { + pr_info("deny root for: %d\n", current_uid()); + // add it to deny list! + ksu_allow_uid(current_uid().val, false); + } + return 0; + } + + // Both root manager and root processes should be allowed to get version + if (arg2 == CMD_GET_VERSION) { + if (is_manager() || 0 == current_uid().val) { + u32 version = KERNEL_SU_VERSION; + if (copy_to_user(arg3, &version, sizeof(version))) { + pr_err("prctl reply error, cmd: %d\n", arg2); + return 0; + } + } + } + + // all other cmds are for 'root manager' + if (!is_manager()) { + pr_info("Only manager can do cmd: %d\n", arg2); + return 0; + } + + // we are already manager + if (arg2 == CMD_ALLOW_SU || arg2 == CMD_DENY_SU) { + bool allow = arg2 == CMD_ALLOW_SU; + bool success = false; + uid_t uid = (uid_t)arg3; + success = ksu_allow_uid(uid, allow); + if (success) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %d\n", arg2); + } + } + } else if (arg2 == CMD_GET_ALLOW_LIST || arg2 == CMD_GET_DENY_LIST) { + u32 array[128]; + u32 array_length; + bool success = ksu_get_allow_list(array, &array_length, + arg2 == CMD_GET_ALLOW_LIST); + if (success) { + if (!copy_to_user(arg4, &array_length, + sizeof(array_length)) && + !copy_to_user(arg3, array, + sizeof(u32) * array_length)) { + if (!copy_to_user(result, &reply_ok, + sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %d\n", + arg2); + } + } else { + pr_err("prctl copy allowlist error\n"); + } + } + } + + return 0; +} + +// Init functons + +static int handler_pre(struct kprobe *p, struct pt_regs *regs) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) + struct pt_regs *real_regs = (struct pt_regs *)PT_REGS_PARM1(regs); +#else + struct pt_regs *real_regs = regs; +#endif + int option = (int)PT_REGS_PARM1(real_regs); + unsigned long arg2 = (unsigned long)PT_REGS_PARM2(real_regs); + unsigned long arg3 = (unsigned long)PT_REGS_PARM3(real_regs); + unsigned long arg4 = (unsigned long)PT_REGS_PARM4(real_regs); + unsigned long arg5 = (unsigned long)PT_REGS_PARM5(real_regs); + + return ksu_handle_prctl(option, arg2, arg3, arg4, arg5); +} + +static struct kprobe prctl_kp = { + .symbol_name = PRCTL_SYMBOL, + .pre_handler = handler_pre, +}; + +static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + // https://elixir.bootlin.com/linux/v5.12-rc1/source/include/linux/fs.h + struct renamedata *rd = PT_REGS_PARM1(regs); + struct dentry *old_entry = rd->old_dentry; + struct dentry *new_entry = rd->new_dentry; +#else + struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs); + struct dentry *new_entry = (struct dentry *)PT_REGS_PARM4(regs); +#endif + + return ksu_handle_rename(old_entry, new_entry); +} + +static struct kprobe renameat_kp = { + .symbol_name = "vfs_rename", + .pre_handler = renameat_handler_pre, +}; + +__maybe_unused int ksu_kprobe_init(void) +{ + int rc = 0; + rc = register_kprobe(&prctl_kp); + + if (rc) { + pr_info("prctl kprobe failed: %d.\n", rc); + return rc; + } + + rc = register_kprobe(&renameat_kp); + pr_info("renameat kp: %d\n", rc); + + return rc; +} + +__maybe_unused int ksu_kprobe_exit(void) +{ + unregister_kprobe(&prctl_kp); + unregister_kprobe(&renameat_kp); + return 0; +} + +static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + ksu_handle_prctl(option, arg2, arg3, arg4, arg5); + return -ENOSYS; +} + +static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return ksu_handle_rename(old_dentry, new_dentry); +} + +static struct security_hook_list ksu_hooks[] = { + LSM_HOOK_INIT(task_prctl, ksu_task_prctl), + LSM_HOOK_INIT(inode_rename, ksu_inode_rename), +}; + +void __init ksu_lsm_hook_init(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks), "ksu"); +#else + // https://elixir.bootlin.com/linux/v4.10.17/source/include/linux/lsm_hooks.h#L1892 + security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks)); +#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(); +#endif +} + +void ksu_core_exit(void) +{ +#ifndef MODULE + pr_info("ksu_kprobe_exit\n"); + ksu_kprobe_exit(); +#endif +} \ No newline at end of file diff --git a/kernel/core_hook.h b/kernel/core_hook.h new file mode 100644 index 00000000..8e8bfc2c --- /dev/null +++ b/kernel/core_hook.h @@ -0,0 +1,9 @@ +#ifndef __KSU_H_KSU_CORE +#define __KSU_H_KSU_CORE + +#include "linux/init.h" + +void __init ksu_core_init(void); +void ksu_core_exit(void); + +#endif diff --git a/kernel/include/ksu_hook.h b/kernel/include/ksu_hook.h index 899dfda8..a3520c6b 100644 --- a/kernel/include/ksu_hook.h +++ b/kernel/include/ksu_hook.h @@ -1,17 +1,24 @@ #ifndef __KSU_H_KSHOOK #define __KSU_H_KSHOOK -#include -#include +#include "linux/fs.h" +#include "linux/types.h" + +// For sucompat int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, int *flags); int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); -int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, - void *envp, int *flags); +// For ksud int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, size_t *count_ptr, loff_t **pos); + +// For ksud and sucompat + +int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags); + #endif \ No newline at end of file diff --git a/kernel/kprobe_hook.c b/kernel/kprobe_hook.c deleted file mode 100644 index 779d2e8f..00000000 --- a/kernel/kprobe_hook.c +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include - -#include "arch.h" -#include "ksu.h" -#include "klog.h" - -static int handler_pre(struct kprobe *p, struct pt_regs *regs) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) - struct pt_regs *real_regs = (struct pt_regs *)PT_REGS_PARM1(regs); -#else - struct pt_regs *real_regs = regs; -#endif - int option = (int)PT_REGS_PARM1(real_regs); - unsigned long arg2 = (unsigned long)PT_REGS_PARM2(real_regs); - unsigned long arg3 = (unsigned long)PT_REGS_PARM3(real_regs); - unsigned long arg4 = (unsigned long)PT_REGS_PARM4(real_regs); - unsigned long arg5 = (unsigned long)PT_REGS_PARM5(real_regs); - - return ksu_handle_prctl(option, arg2, arg3, arg4, arg5); -} - -static struct kprobe prctl_kp = { - .symbol_name = PRCTL_SYMBOL, - .pre_handler = handler_pre, -}; - -static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) - // https://elixir.bootlin.com/linux/v5.12-rc1/source/include/linux/fs.h - struct renamedata *rd = PT_REGS_PARM1(regs); - struct dentry *old_entry = rd->old_dentry; - struct dentry *new_entry = rd->new_dentry; -#else - struct dentry *old_entry = PT_REGS_PARM2(regs); - struct dentry *new_entry = PT_REGS_PARM4(regs); -#endif - - return ksu_handle_rename(old_entry, new_entry); -} - -static struct kprobe renameat_kp = { - .symbol_name = "vfs_rename", - .pre_handler = renameat_handler_pre, -}; - -__maybe_unused int ksu_kprobe_init() -{ - int rc = 0; - rc = register_kprobe(&prctl_kp); - - if (rc) { - pr_info("prctl kprobe failed: %d.\n", rc); - return rc; - } - - rc = register_kprobe(&renameat_kp); - pr_info("renameat kp: %d\n", rc); - - return rc; -} - -__maybe_unused int ksu_kprobe_exit() -{ - unregister_kprobe(&prctl_kp); - unregister_kprobe(&renameat_kp); - return 0; -} \ No newline at end of file diff --git a/kernel/ksu.c b/kernel/ksu.c index 56ea85f8..f94d9555 100644 --- a/kernel/ksu.c +++ b/kernel/ksu.c @@ -1,324 +1,51 @@ -#include "linux/export.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include // msleep +#include "linux/module.h" +#include "linux/workqueue.h" +#include "linux/fs.h" #include "allowlist.h" -#include "apk_sign.h" #include "arch.h" -#include "klog.h" +#include "core_hook.h" #include "ksu.h" -#include "selinux/selinux.h" #include "uid_observer.h" -static struct group_info root_groups = { .usage = ATOMIC_INIT(2) }; - static struct workqueue_struct *ksu_workqueue; -uid_t ksu_manager_uid = INVALID_UID; - -void ksu_queue_work(struct work_struct *work) +bool ksu_queue_work(struct work_struct *work) { - queue_work(ksu_workqueue, work); + return queue_work(ksu_workqueue, work); } -void escape_to_root() +extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); + +extern int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); + +int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags) { - struct cred *cred; - - cred = (struct cred *)__task_cred(current); - - memset(&cred->uid, 0, sizeof(cred->uid)); - memset(&cred->gid, 0, sizeof(cred->gid)); - memset(&cred->suid, 0, sizeof(cred->suid)); - memset(&cred->euid, 0, sizeof(cred->euid)); - memset(&cred->egid, 0, sizeof(cred->egid)); - memset(&cred->fsuid, 0, sizeof(cred->fsuid)); - memset(&cred->fsgid, 0, sizeof(cred->fsgid)); - memset(&cred->cap_inheritable, 0xff, sizeof(cred->cap_inheritable)); - memset(&cred->cap_permitted, 0xff, sizeof(cred->cap_permitted)); - memset(&cred->cap_effective, 0xff, sizeof(cred->cap_effective)); - memset(&cred->cap_bset, 0xff, sizeof(cred->cap_bset)); - memset(&cred->cap_ambient, 0xff, sizeof(cred->cap_ambient)); - - // disable seccomp -#if defined(CONFIG_GENERIC_ENTRY) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) - current_thread_info()->syscall_work &= ~SYSCALL_WORK_SECCOMP; -#else - current_thread_info()->flags &= ~(TIF_SECCOMP | _TIF_SECCOMP); -#endif - current->seccomp.mode = 0; - current->seccomp.filter = NULL; - - // setgroup to root - if (cred->group_info) - put_group_info(cred->group_info); - cred->group_info = get_group_info(&root_groups); - - setup_selinux(); + ksu_handle_execveat_ksud(fd, filename_ptr, argv, envp, flags); + return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, + flags); } -int startswith(char *s, char *prefix) -{ - return strncmp(s, prefix, strlen(prefix)); -} - -int endswith(const char *s, const char *t) -{ - size_t slen = strlen(s); - size_t tlen = strlen(t); - if (tlen > slen) - return 1; - return strcmp(s + slen - tlen, t); -} - -static bool is_manager() -{ - return ksu_manager_uid == current_uid().val; -} - -static bool become_manager(char *pkg) -{ - struct fdtable *files_table; - int i = 0; - struct path files_path; - char *cwd; - char *buf; - bool result = false; - - // must be zygote's direct child, otherwise any app can fork a new process and - // open manager's apk - if (task_uid(current->real_parent).val != 0) { - pr_info("parent is not zygote!\n"); - return false; - } - - buf = (char *)kmalloc(PATH_MAX, GFP_ATOMIC); - if (!buf) { - pr_err("kalloc path failed.\n"); - return false; - } - - files_table = files_fdtable(current->files); - - // todo: use iterate_fd - while (files_table->fd[i] != NULL) { - files_path = files_table->fd[i]->f_path; - if (!d_is_reg(files_path.dentry)) { - i++; - continue; - } - cwd = d_path(&files_path, buf, PATH_MAX); - if (startswith(cwd, "/data/app/") == 0 && - endswith(cwd, "/base.apk") == 0) { - // we have found the apk! - pr_info("found apk: %s", cwd); - if (!strstr(cwd, pkg)) { - pr_info("apk path not match package name!\n"); - i++; - continue; - } - if (is_manager_apk(cwd) == 0) { - // check passed - uid_t uid = current_uid().val; - pr_info("manager uid: %d\n", uid); - - ksu_set_manager_uid(uid); - - result = true; - goto clean; - } else { - pr_info("manager signature invalid!"); - } - - break; - } - i++; - } - -clean: - kfree(buf); - return result; -} - -static bool is_allow_su() -{ - uid_t uid = current_uid().val; - if (uid == ksu_manager_uid) { - // we are manager, allow! - return true; - } - - return ksu_is_allow_uid(uid); -} - -extern void enable_sucompat(); - -int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, - unsigned long arg4, unsigned long arg5) -{ - // if success, we modify the arg5 as result! - u32 *result = (u32 *)arg5; - u32 reply_ok = KERNEL_SU_OPTION; - - if (KERNEL_SU_OPTION != option) { - return 0; - } - - pr_info("option: 0x%x, cmd: %ld\n", option, arg2); - - if (arg2 == CMD_BECOME_MANAGER) { - // quick check - if (is_manager()) { - if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { - pr_err("become_manager: prctl reply error\n"); - } - return 0; - } - if (ksu_is_manager_uid_valid()) { - pr_info("manager already exist: %d\n", ksu_manager_uid); - return 0; - } - - // someone wants to be root manager, just check it! - // arg3 should be `/data/data/` - char param[128]; - const char *prefix = "/data/data/"; - if (copy_from_user(param, arg3, sizeof(param))) { - pr_err("become_manager: copy param err\n"); - return 0; - } - - if (startswith(param, (char *)prefix) != 0) { - pr_info("become_manager: invalid param: %s\n", param); - return 0; - } - - // stat the param, app must have permission to do this - // otherwise it may fake the path! - struct path path; - if (kern_path(param, LOOKUP_DIRECTORY, &path)) { - pr_err("become_manager: kern_path err\n"); - return 0; - } - if (path.dentry->d_inode->i_uid.val != current_uid().val) { - pr_err("become_manager: path uid != current uid\n"); - path_put(&path); - return 0; - } - char *pkg = param + strlen(prefix); - pr_info("become_manager: param pkg: %s\n", pkg); - - bool success = become_manager(pkg); - if (success) { - if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { - pr_err("become_manager: prctl reply error\n"); - } - } - path_put(&path); - return 0; - } - - if (arg2 == CMD_GRANT_ROOT) { - if (is_allow_su()) { - pr_info("allow root for: %d\n", current_uid()); - escape_to_root(); - if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { - pr_err("grant_root: prctl reply error\n"); - } - } else { - pr_info("deny root for: %d\n", current_uid()); - // add it to deny list! - ksu_allow_uid(current_uid().val, false); - } - return 0; - } - - // Both root manager and root processes should be allowed to get version - if (arg2 == CMD_GET_VERSION) { - if (is_manager() || 0 == current_uid().val) { - u32 version = KERNEL_SU_VERSION; - if (copy_to_user(arg3, &version, sizeof(version))) { - pr_err("prctl reply error, cmd: %d\n", arg2); - return 0; - } - } - } - - // all other cmds are for 'root manager' - if (!is_manager()) { - pr_info("Only manager can do cmd: %d\n", arg2); - return 0; - } - - // we are already manager - if (arg2 == CMD_ALLOW_SU || arg2 == CMD_DENY_SU) { - bool allow = arg2 == CMD_ALLOW_SU; - bool success = false; - uid_t uid = (uid_t)arg3; - success = ksu_allow_uid(uid, allow); - if (success) { - if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { - pr_err("prctl reply error, cmd: %d\n", arg2); - } - } - } else if (arg2 == CMD_GET_ALLOW_LIST || arg2 == CMD_GET_DENY_LIST) { - u32 array[128]; - u32 array_length; - bool success = ksu_get_allow_list(array, &array_length, - arg2 == CMD_GET_ALLOW_LIST); - if (success) { - if (!copy_to_user(arg4, &array_length, - sizeof(array_length)) && - !copy_to_user(arg3, array, - sizeof(u32) * array_length)) { - if (!copy_to_user(result, &reply_ok, - sizeof(reply_ok))) { - pr_err("prctl reply error, cmd: %d\n", - arg2); - } - } else { - pr_err("prctl copy allowlist error\n"); - } - } - } - - return 0; -} +extern void ksu_enable_sucompat(); +extern void ksu_enable_ksud(); int __init kernelsu_init(void) { #ifdef CONFIG_KSU_DEBUG - pr_alert("You are running DEBUG version of KernelSU"); + pr_alert( + "*************************************************************\n" + "** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n" + "** **\n" + "** You are running DEBUG version of KernelSU **\n" + "** **\n" + "** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n" + "*************************************************************\n"); #endif -#ifndef MODULE - ksu_lsm_hook_init(); -#else - ksu_kprobe_init(); -#endif + ksu_core_init(); ksu_workqueue = alloc_workqueue("kernelsu_work_queue", 0, 0); @@ -327,7 +54,8 @@ int __init kernelsu_init(void) ksu_uid_observer_init(); #ifdef CONFIG_KPROBES - enable_sucompat(); + ksu_enable_sucompat(); + ksu_enable_ksud(); #else #warning("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html") #endif @@ -339,7 +67,11 @@ void kernelsu_exit(void) { ksu_allowlist_exit(); + ksu_uid_observer_exit(); + destroy_workqueue(ksu_workqueue); + + ksu_core_exit(); } module_init(kernelsu_init); @@ -350,6 +82,5 @@ MODULE_AUTHOR("weishu"); MODULE_DESCRIPTION("Android KernelSU"); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) -MODULE_IMPORT_NS( - VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); // 5+才需要导出命名空间 +MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); #endif diff --git a/kernel/ksu.h b/kernel/ksu.h index 269c5533..129bd649 100644 --- a/kernel/ksu.h +++ b/kernel/ksu.h @@ -1,9 +1,7 @@ #ifndef __KSU_H_KSU #define __KSU_H_KSU -#include -#include -#include +#include "linux/workqueue.h" #define KERNEL_SU_VERSION 10 @@ -18,45 +16,20 @@ #define CMD_GET_ALLOW_LIST 5 #define CMD_GET_DENY_LIST 6 -#define INVALID_UID -1 +bool ksu_queue_work(struct work_struct *work); -extern uid_t ksu_manager_uid; - -static inline bool ksu_is_manager_uid_valid() +static inline int startswith(char *s, char *prefix) { -#ifndef CONFIG_KSU_DEBUG - return ksu_manager_uid != INVALID_UID; -#else - return false; // always allow in debug mode -#endif + return strncmp(s, prefix, strlen(prefix)); } -static inline uid_t ksu_get_manager_uid() +static inline int endswith(const char *s, const char *t) { - return ksu_manager_uid; + size_t slen = strlen(s); + size_t tlen = strlen(t); + if (tlen > slen) + return 1; + return strcmp(s + slen - tlen, t); } -static inline void ksu_set_manager_uid(uid_t uid) -{ - ksu_manager_uid = uid; -} - -static inline void ksu_invalidate_manager_uid() -{ - ksu_manager_uid = INVALID_UID; -} - -void ksu_queue_work(struct work_struct *work); - -void ksu_lsm_hook_init(void); - -int ksu_kprobe_init(void); -int ksu_kprobe_exit(void); - -/// KernelSU hooks -int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, - unsigned long arg4, unsigned long arg5); - -int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry); - #endif diff --git a/kernel/ksud.c b/kernel/ksud.c new file mode 100644 index 00000000..54a524e6 --- /dev/null +++ b/kernel/ksud.c @@ -0,0 +1,262 @@ +#include "asm/current.h" +#include "linux/cred.h" +#include "linux/dcache.h" +#include "linux/err.h" +#include "linux/fs.h" +#include "linux/kprobes.h" +#include "linux/printk.h" +#include "linux/types.h" +#include "linux/uaccess.h" +#include "linux/version.h" +#include "linux/workqueue.h" + +#include "allowlist.h" +#include "arch.h" +#include "selinux/selinux.h" + +static const char KERNEL_SU_RC[] = + "\n" + + "on post-fs-data\n" + // We should wait for the post-fs-data finish + " exec u:r:su:s0 root -- /data/adb/ksud post-fs-data\n" + "\n" + + "on nonencrypted\n" + " exec u:r:su:s0 root -- /data/adb/ksud services\n" + "\n" + + "on property:vold.decrypt=trigger_restart_framework\n" + " exec u:r:su:s0 root -- /data/adb/ksud services\n" + "\n" + + "on property:sys.boot_completed=1\n" + " exec u:r:su:s0 root -- /data/adb/ksud boot-completed\n" + "\n" + + "\n"; + +static void stop_vfs_read_hook(); +static void stop_execve_hook(); + +#ifdef CONFIG_KPROBES +static struct work_struct stop_vfs_read_work; +static struct work_struct stop_execve_hook_work; +#else +static bool vfs_read_hook = true; +static bool execveat_hook = true; +#endif + +int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags) +{ +#ifndef CONFIG_KPROBES + if (!execveat_hook) { + return 0; + } +#endif + struct filename *filename; + + static const char app_process[] = "/system/bin/app_process"; + static bool first_app_process = true; + static const char system_bin_init[] = "/system/bin/init"; + static int init_count = 0; + + if (!filename_ptr) + return 0; + + filename = *filename_ptr; + if (IS_ERR(filename)) { + return 0; + } + + if (!memcmp(filename->name, system_bin_init, + sizeof(system_bin_init) - 1)) { + // /system/bin/init executed + if (++init_count == 2) { + // 1: /system/bin/init selinux_setup + // 2: /system/bin/init second_stage + pr_info("/system/bin/init second_stage executed\n"); + apply_kernelsu_rules(); + } + } + + if (first_app_process && + !memcmp(filename->name, app_process, sizeof(app_process) - 1)) { + first_app_process = false; + pr_info("exec app_process, /data prepared!\n"); + ksu_load_allow_list(); + stop_execve_hook(); + } + + return 0; +} + +int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, + size_t *count_ptr, loff_t **pos) +{ +#ifndef CONFIG_KPROBES + if (!vfs_read_hook) { + return 0; + } +#endif + struct file *file; + char __user *buf; + size_t count; + + if (strcmp(current->comm, "init")) { + // we are only interest in `init` process + return 0; + } + + file = *file_ptr; + if (IS_ERR(file)) { + return 0; + } + + if (!d_is_reg(file->f_path.dentry)) { + return 0; + } + + const char *short_name = file->f_path.dentry->d_name.name; + if (strcmp(short_name, "atrace.rc")) { + // we are only interest `atrace.rc` file name file + return 0; + } + char path[256]; + char *dpath = d_path(&file->f_path, path, sizeof(path)); + + if (IS_ERR(dpath)) { + return 0; + } + + if (strcmp(dpath, "/system/etc/init/atrace.rc")) { + return 0; + } + + // we only process the first read + static bool rc_inserted = false; + if (rc_inserted) { + // we don't need this kprobe, unregister it! + stop_vfs_read_hook(); + return 0; + } + rc_inserted = true; + + // now we can sure that the init process is reading + // `/system/etc/init/atrace.rc` + buf = *buf_ptr; + count = *count_ptr; + + size_t rc_count = strlen(KERNEL_SU_RC); + + pr_info("vfs_read: %s, comm: %s, count: %d, rc_count: %d\n", dpath, + current->comm, count, rc_count); + + if (count < rc_count) { + pr_err("count: %d < rc_count: %d", count, rc_count); + return 0; + } + + size_t ret = copy_to_user(buf, KERNEL_SU_RC, rc_count); + if (ret) { + pr_err("copy ksud.rc failed: %d\n", ret); + return 0; + } + + *buf_ptr = buf + rc_count; + *count_ptr = count - rc_count; + + return 0; +} + +#ifdef CONFIG_KPROBES + +// https://elixir.bootlin.com/linux/v5.10.158/source/fs/exec.c#L1864 +static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + int *fd = (int *)&PT_REGS_PARM1(regs); + struct filename **filename_ptr = + (struct filename **)&PT_REGS_PARM2(regs); + void *argv = (void *)&PT_REGS_PARM3(regs); + void *envp = (void *)&PT_REGS_PARM4(regs); + int *flags = (int *)&PT_REGS_PARM5(regs); + + return ksu_handle_execveat_ksud(fd, filename_ptr, argv, envp, flags); +} + +static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct file **file_ptr = (struct file **)&PT_REGS_PARM1(regs); + char __user **buf_ptr = (char **)&PT_REGS_PARM2(regs); + size_t *count_ptr = (size_t *)&PT_REGS_PARM3(regs); + loff_t **pos_ptr = (loff_t **)&PT_REGS_PARM4(regs); + + return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr); +} + +static struct kprobe execve_kp = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + .symbol_name = "do_execveat_common", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) && \ + LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + .symbol_name = "__do_execve_file", +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) && \ + LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0) + .symbol_name = "do_execveat_common", +#endif + .pre_handler = execve_handler_pre, +}; + +static struct kprobe vfs_read_kp = { + .symbol_name = "vfs_read", + .pre_handler = read_handler_pre, +}; + +static void do_stop_vfs_read_hook(struct work_struct *work) +{ + unregister_kprobe(&vfs_read_kp); +} + +static void do_stop_execve_hook(struct work_struct *work) +{ + unregister_kprobe(&execve_kp); +} +#endif + +static void stop_vfs_read_hook() +{ +#ifdef CONFIG_KPROBES + bool ret = schedule_work(&stop_vfs_read_work); + pr_info("unregister vfs_read kprobe: %d!\n", ret); +#else + vfs_read_hook = false; +#endif +} + +static void stop_execve_hook() +{ +#ifdef CONFIG_KPROBES + bool ret = schedule_work(&stop_execve_hook_work); + pr_info("unregister execve kprobe: %d!\n", ret); +#else + execveat_hook = false; +#endif +} + +// ksud: module support +void ksu_enable_ksud() +{ +#ifdef CONFIG_KPROBES + int ret; + + ret = register_kprobe(&execve_kp); + pr_info("ksud: execve_kp: %d\n", ret); + + ret = register_kprobe(&vfs_read_kp); + pr_info("ksud: vfs_read_kp: %d\n", ret); + + INIT_WORK(&stop_vfs_read_work, do_stop_vfs_read_hook); + INIT_WORK(&stop_execve_hook_work, do_stop_execve_hook); +#endif +} diff --git a/kernel/lsm_hook.c b/kernel/lsm_hook.c deleted file mode 100644 index d57c84ba..00000000 --- a/kernel/lsm_hook.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "klog.h" -#include "ksu.h" - -static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3, - unsigned long arg4, unsigned long arg5) -{ - ksu_handle_prctl(option, arg2, arg3, arg4, arg5); - return -ENOSYS; -} - -static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry, - struct inode *new_inode, struct dentry *new_dentry) -{ - return ksu_handle_rename(old_dentry, new_dentry); -} - -static struct security_hook_list ksu_hooks[] = { - LSM_HOOK_INIT(task_prctl, ksu_task_prctl), - LSM_HOOK_INIT(inode_rename, ksu_inode_rename), -}; - -void __init ksu_lsm_hook_init(void) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) - security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks), "ksu"); -#else - // https://elixir.bootlin.com/linux/v4.10.17/source/include/linux/lsm_hooks.h#L1892 - security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks)); -#endif - - pr_info("security_add_hooks\n"); -} \ No newline at end of file diff --git a/kernel/manager.c b/kernel/manager.c new file mode 100644 index 00000000..627d2c71 --- /dev/null +++ b/kernel/manager.c @@ -0,0 +1,80 @@ +#include "linux/cred.h" +#include "linux/gfp.h" +#include "linux/printk.h" +#include "linux/slab.h" +#include "linux/uidgid.h" +#include "linux/version.h" + +#include "linux/fdtable.h" +#include "linux/fs.h" +#include "linux/rcupdate.h" + +#include "apk_sign.h" +#include "ksu.h" +#include "manager.h" + +uid_t ksu_manager_uid = INVALID_UID; + +bool become_manager(char *pkg) +{ + struct fdtable *files_table; + int i = 0; + struct path files_path; + char *cwd; + char *buf; + bool result = false; + + // must be zygote's direct child, otherwise any app can fork a new process and + // open manager's apk + if (task_uid(current->real_parent).val != 0) { + pr_info("parent is not zygote!\n"); + return false; + } + + buf = (char *)kmalloc(PATH_MAX, GFP_ATOMIC); + if (!buf) { + pr_err("kalloc path failed.\n"); + return false; + } + + files_table = files_fdtable(current->files); + + // todo: use iterate_fd + while (files_table->fd[i] != NULL) { + files_path = files_table->fd[i]->f_path; + if (!d_is_reg(files_path.dentry)) { + i++; + continue; + } + cwd = d_path(&files_path, buf, PATH_MAX); + if (startswith(cwd, "/data/app/") == 0 && + endswith(cwd, "/base.apk") == 0) { + // we have found the apk! + pr_info("found apk: %s", cwd); + if (!strstr(cwd, pkg)) { + pr_info("apk path not match package name!\n"); + i++; + continue; + } + if (is_manager_apk(cwd) == 0) { + // check passed + uid_t uid = current_uid().val; + pr_info("manager uid: %d\n", uid); + + ksu_set_manager_uid(uid); + + result = true; + goto clean; + } else { + pr_info("manager signature invalid!"); + } + + break; + } + i++; + } + +clean: + kfree(buf); + return result; +} diff --git a/kernel/manager.h b/kernel/manager.h new file mode 100644 index 00000000..fcb54f57 --- /dev/null +++ b/kernel/manager.h @@ -0,0 +1,38 @@ +#ifndef __KSU_H_KSU_MANAGER +#define __KSU_H_KSU_MANAGER + +#include "linux/cred.h" +#include "linux/types.h" + +#define INVALID_UID -1 + +extern uid_t ksu_manager_uid; // DO NOT DIRECT USE + +static inline bool ksu_is_manager_uid_valid() +{ + return ksu_manager_uid != INVALID_UID; +} + +static inline bool is_manager() +{ + return ksu_manager_uid == current_uid().val; +} + +static inline uid_t ksu_get_manager_uid() +{ + return ksu_manager_uid; +} + +static inline void ksu_set_manager_uid(uid_t uid) +{ + ksu_manager_uid = uid; +} + +static inline void ksu_invalidate_manager_uid() +{ + ksu_manager_uid = INVALID_UID; +} + +bool become_manager(char *pkg); + +#endif diff --git a/kernel/module_api.c b/kernel/module_api.c index 4a838f95..6a81351d 100644 --- a/kernel/module_api.c +++ b/kernel/module_api.c @@ -1,20 +1,20 @@ -#include -#include +#include "linux/kallsyms.h" -#define RE_EXPORT_SYMBOL1(ret, func, t1, v1) \ - ret ksu_##func(t1 v1) { \ - return func(v1); \ - } \ - EXPORT_SYMBOL(ksu_##func); \ +#define RE_EXPORT_SYMBOL1(ret, func, t1, v1) \ + ret ksu_##func(t1 v1) \ + { \ + return func(v1); \ + } \ + EXPORT_SYMBOL(ksu_##func); -#define RE_EXPORT_SYMBOL2(ret, func, t1, v1, t2, v2) \ - ret ksu_##func(t1 v1, t2 v2) { \ - return func(v1, v2); \ - } \ - EXPORT_SYMBOL(ksu_##func); \ +#define RE_EXPORT_SYMBOL2(ret, func, t1, v1, t2, v2) \ + ret ksu_##func(t1 v1, t2 v2) \ + { \ + return func(v1, v2); \ + } \ + EXPORT_SYMBOL(ksu_##func); - -RE_EXPORT_SYMBOL1(unsigned long, kallsyms_lookup_name, const char*, name) +RE_EXPORT_SYMBOL1(unsigned long, kallsyms_lookup_name, const char *, name) // RE_EXPORT_SYMBOL2(int, register_kprobe, struct kprobe *, p) // RE_EXPORT_SYMBOL2(void, unregister_kprobe, struct kprobe *, p) diff --git a/kernel/selinux/rules.c b/kernel/selinux/rules.c index 9455caff..df46ca21 100644 --- a/kernel/selinux/rules.c +++ b/kernel/selinux/rules.c @@ -1,15 +1,13 @@ -#include -#include "sepolicy.h" +#include "linux/version.h" + #include "selinux.h" +#include "sepolicy.h" +#include "ss/services.h" #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) #define SELINUX_POLICY_INSTEAD_SELINUX_SS #endif -#ifndef SELINUX_POLICY_INSTEAD_SELINUX_SS -#include -#endif - #define KERNEL_SU_DOMAIN "su" #define KERNEL_SU_FILE "ksu_file" #define ALL NULL @@ -37,10 +35,10 @@ void apply_kernelsu_rules() ksu_typeattribute(db, KERNEL_SU_DOMAIN, "netdomain"); ksu_typeattribute(db, KERNEL_SU_DOMAIN, "bluetoothdomain"); - // Create unconstrained file type - ksu_type(db, KERNEL_SU_FILE, "file_type"); - ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject"); - ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL); + // Create unconstrained file type + ksu_type(db, KERNEL_SU_FILE, "file_type"); + ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject"); + ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL); // allow all! ksu_allow(db, KERNEL_SU_DOMAIN, ALL, ALL, ALL); @@ -59,9 +57,11 @@ void apply_kernelsu_rules() ksu_allow(db, "kernel", "shell_data_file", "file", ALL); // we need to read /data/system/packages.list ksu_allow(db, "kernel", "kernel", "capability", "dac_override"); - // Android 10+: http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512 + // Android 10+: + // http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512 ksu_allow(db, "kernel", "packages_list_file", "file", ALL); - // Android 9-: http://aospxref.com/android-9.0.0_r61/xref/system/sepolicy/private/file_contexts#360 + // Android 9-: + // http://aospxref.com/android-9.0.0_r61/xref/system/sepolicy/private/file_contexts#360 ksu_allow(db, "kernel", "system_data_file", "file", ALL); // our ksud triggered by init @@ -94,14 +94,17 @@ void apply_kernelsu_rules() ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "dir", "search"); ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "read"); ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "open"); - ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process", "getattr"); + ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process", + "getattr"); // Allow all binder transactions ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL); // Allow system server devpts - ksu_allow(db, "system_server", "untrusted_app_all_devpts", "chr_file", "read"); - ksu_allow(db, "system_server", "untrusted_app_all_devpts", "chr_file", "write"); + ksu_allow(db, "system_server", "untrusted_app_all_devpts", "chr_file", + "read"); + ksu_allow(db, "system_server", "untrusted_app_all_devpts", "chr_file", + "write"); rcu_read_unlock(); } \ No newline at end of file diff --git a/kernel/selinux/selinux.c b/kernel/selinux/selinux.c index 5ff1d56c..196f4d05 100644 --- a/kernel/selinux/selinux.c +++ b/kernel/selinux/selinux.c @@ -1,20 +1,7 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include +#include "linux/printk.h" +#include "objsec.h" #include "selinux.h" -#include "../klog.h" #define KERNEL_SU_DOMAIN "u:r:su:s0" @@ -57,11 +44,11 @@ void setup_selinux() } /* we didn't need this now, we have change selinux rules when boot! - if (!is_domain_permissive) { - if (set_domain_permissive() == 0) { - is_domain_permissive = true; - } - }*/ +if (!is_domain_permissive) { + if (set_domain_permissive() == 0) { + is_domain_permissive = true; + } +}*/ } void setenforce(bool enforce) diff --git a/kernel/selinux/selinux.h b/kernel/selinux/selinux.h index 388ed1b4..2ecdd09c 100644 --- a/kernel/selinux/selinux.h +++ b/kernel/selinux/selinux.h @@ -1,7 +1,7 @@ #ifndef __KSU_H_SELINUX #define __KSU_H_SELINUX -#include +#include "linux/types.h" void setup_selinux(); diff --git a/kernel/selinux/sepolicy.c b/kernel/selinux/sepolicy.c index b63e6ab6..de26caa3 100644 --- a/kernel/selinux/sepolicy.c +++ b/kernel/selinux/sepolicy.c @@ -1,9 +1,8 @@ -#include -#include -#include -#include #include "sepolicy.h" -#include "../klog.h" +#include "linux/gfp.h" +#include "linux/printk.h" +#include "linux/slab.h" +#include "linux/version.h" #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) // TODO: backport to lower kernel @@ -57,7 +56,8 @@ static bool add_typeattribute(struct policydb *db, const char *type, // Implementation ////////////////////////////////////////////////////// -// Invert is adding rules for auditdeny; in other cases, invert is removing rules +// Invert is adding rules for auditdeny; in other cases, invert is removing +// rules #define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert) #define hash_for_each(node_ptr, n_slot, cur) \ @@ -65,7 +65,8 @@ static bool add_typeattribute(struct policydb *db, const char *type, for (i = 0; i < n_slot; ++i) \ for (cur = node_ptr[i]; cur; cur = cur->next) -// htable is a struct instead of pointer above 5.8.0: https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h +// htable is a struct instead of pointer above 5.8.0: +// https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) #define hashtab_for_each(htab, cur) hash_for_each (htab.htable, htab.size, cur) #else @@ -73,7 +74,8 @@ static bool add_typeattribute(struct policydb *db, const char *type, hash_for_each (htab->htable, htab->size, cur) #endif -// symtab_search is introduced on 5.9.0: https://elixir.bootlin.com/linux/v5.9-rc1/source/security/selinux/ss/symtab.h +// symtab_search is introduced on 5.9.0: +// https://elixir.bootlin.com/linux/v5.9-rc1/source/security/selinux/ss/symtab.h #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) #define symtab_search(s, name) hashtab_search((s)->table, name) #endif @@ -110,9 +112,9 @@ static struct avtab_node *get_avtab_node(struct policydb *db, if (!node) { struct avtab_datum avdatum = {}; /* - * AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for - * others. Initialize the data accordingly. - */ + * AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for + * others. Initialize the data accordingly. + */ if (key->specified & AVTAB_XPERMS) { avdatum.u.xperms = xperms; } else { diff --git a/kernel/selinux/sepolicy.h b/kernel/selinux/sepolicy.h index 909147c8..a5071236 100644 --- a/kernel/selinux/sepolicy.h +++ b/kernel/selinux/sepolicy.h @@ -1,37 +1,46 @@ #ifndef __KSU_H_SEPOLICY #define __KSU_H_SEPOLICY -#include -#include -#include -#include +#include "linux/types.h" +#include "ss/policydb.h" // Operation on types -bool ksu_type(struct policydb* db, const char* name, const char* attr); -bool ksu_attribute(struct policydb* db, const char* name); -bool ksu_permissive(struct policydb* db, const char* type); -bool ksu_enforce(struct policydb* db, const char* type); -bool ksu_typeattribute(struct policydb* db, const char* type, const char* attr); -bool ksu_exists(struct policydb* db, const char* type); +bool ksu_type(struct policydb *db, const char *name, const char *attr); +bool ksu_attribute(struct policydb *db, const char *name); +bool ksu_permissive(struct policydb *db, const char *type); +bool ksu_enforce(struct policydb *db, const char *type); +bool ksu_typeattribute(struct policydb *db, const char *type, const char *attr); +bool ksu_exists(struct policydb *db, const char *type); // Access vector rules -bool ksu_allow(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* perm); -bool ksu_deny(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* perm); -bool ksu_auditallow(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* perm); -bool ksu_dontaudit(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* perm); +bool ksu_allow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_deny(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); +bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *perm); // Extended permissions access vector rules -bool ksu_allowxperm(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* range); -bool ksu_auditallowxperm(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* range); -bool ksu_dontauditxperm(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* range); +bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); +bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); +bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *range); // Type rules -bool ksu_type_transition(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* def, const char* obj); -bool ksu_type_change(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* def); -bool ksu_type_member(struct policydb* db, const char* src, const char* tgt, const char* cls, const char* def); +bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def, const char *obj); +bool ksu_type_change(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def); +bool ksu_type_member(struct policydb *db, const char *src, const char *tgt, + const char *cls, const char *def); // File system labeling -bool ksu_genfscon(struct policydb* db, const char* fs_name, const char* path, const char* ctx); +bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path, + const char *ctx); #endif diff --git a/kernel/sucompat.c b/kernel/sucompat.c index 2eaa49ab..3cd82a9e 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -1,39 +1,20 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "asm/current.h" +#include "linux/cred.h" +#include "linux/err.h" +#include "linux/fs.h" +#include "linux/kprobes.h" +#include "linux/printk.h" +#include "linux/types.h" +#include "linux/uaccess.h" +#include "linux/version.h" #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) -#include +#include "linux/sched/task_stack.h" #else -#include +#include "linux/sched.h" #endif -#include -#include -#include -#include -#include -#include - -#include "klog.h" -#include "arch.h" #include "allowlist.h" -#include "selinux/selinux.h" +#include "arch.h" #define SU_PATH "/system/bin/su" #define SH_PATH "/system/bin/sh" @@ -42,7 +23,8 @@ extern void escape_to_root(); static void __user *userspace_stack_buffer(const void *d, size_t len) { - /* To avoid having to mmap a page in userspace, just write below the stack pointer. */ + /* To avoid having to mmap a page in userspace, just write below the stack + * pointer. */ char __user *p = (void __user *)current_user_stack_pointer() - len; return copy_to_user(p, d, len) ? NULL : p; @@ -109,18 +91,13 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) return 0; } -int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, - void *envp, int *flags) +int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags) { struct filename *filename; const char sh[] = SH_PATH; const char su[] = SU_PATH; - static const char app_process[] = "/system/bin/app_process"; - static bool first_app_process = true; - static const char system_bin_init[] = "/system/bin/init"; - static int init_count = 0; - if (!filename_ptr) return 0; @@ -129,24 +106,6 @@ int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, return 0; } - if (!memcmp(filename->name, system_bin_init, - sizeof(system_bin_init) - 1)) { - // /system/bin/init executed - if (++init_count == 2) { - // 1: /system/bin/init selinux_setup - // 2: /system/bin/init second_stage - pr_info("/system/bin/init second_stage executed\n"); - apply_kernelsu_rules(); - } - } - - if (first_app_process && - !memcmp(filename->name, app_process, sizeof(app_process) - 1)) { - first_app_process = false; - pr_info("exec app_process, /data prepared!\n"); - ksu_load_allow_list(); - } - if (!ksu_is_allow_uid(current_uid().val)) { return 0; } @@ -161,102 +120,7 @@ int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, return 0; } -static const char KERNEL_SU_RC[] = - "\n" - - "on post-fs-data\n" - // We should wait for the post-fs-data finish - " exec u:r:su:s0 root -- /data/adb/ksud post-fs-data\n" - "\n" - - "on nonencrypted\n" - " exec u:r:su:s0 root -- /data/adb/ksud services\n" - "\n" - - "on property:vold.decrypt=trigger_restart_framework\n" - " exec u:r:su:s0 root -- /data/adb/ksud services\n" - "\n" - - "on property:sys.boot_completed=1\n" - " exec u:r:su:s0 root -- /data/adb/ksud boot-completed\n" - "\n" - - "\n"; - -static void unregister_vfs_read_kp(); -static struct work_struct unregister_vfs_read_work; - -int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, - size_t *count_ptr, loff_t **pos) -{ - struct file *file; - char __user *buf; - size_t count; - - if (strcmp(current->comm, "init")) { - // we are only interest in `init` process - return 0; - } - - file = *file_ptr; - if (IS_ERR(file)) { - return 0; - } - - if (!d_is_reg(file->f_path.dentry)) { - return 0; - } - - const char *short_name = file->f_path.dentry->d_name.name; - if (strcmp(short_name, "atrace.rc")) { - // we are only interest `atrace.rc` file name file - return 0; - } - char path[256]; - char *dpath = d_path(&file->f_path, path, sizeof(path)); - - if (IS_ERR(dpath)) { - return 0; - } - - if (strcmp(dpath, "/system/etc/init/atrace.rc")) { - return 0; - } - - // we only process the first read - static bool rc_inserted = false; - if (rc_inserted) { - // we don't need this kprobe, unregister it! - unregister_vfs_read_kp(); - return 0; - } - rc_inserted = true; - - // now we can sure that the init process is reading `/system/etc/init/atrace.rc` - buf = *buf_ptr; - count = *count_ptr; - - size_t rc_count = strlen(KERNEL_SU_RC); - - pr_info("vfs_read: %s, comm: %s, count: %d, rc_count: %d\n", dpath, - current->comm, count, rc_count); - - if (count < rc_count) { - pr_err("count: %d < rc_count: %d", count, rc_count); - return 0; - } - - size_t ret = copy_to_user(buf, KERNEL_SU_RC, rc_count); - if (ret) { - pr_err("copy ksud.rc failed: %d\n", ret); - return 0; - } - - *buf_ptr = buf + rc_count; - *count_ptr = count - rc_count; - - return 0; -} +#ifdef CONFIG_KPROBES static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) { @@ -287,17 +151,8 @@ static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) void *envp = (void *)&PT_REGS_PARM4(regs); int *flags = (int *)&PT_REGS_PARM5(regs); - return ksu_handle_execveat(fd, filename_ptr, argv, envp, flags); -} - -static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) -{ - struct file **file_ptr = (struct file **)&PT_REGS_PARM1(regs); - char __user **buf_ptr = (char **)&PT_REGS_PARM2(regs); - size_t *count_ptr = (size_t *)&PT_REGS_PARM3(regs); - loff_t **pos_ptr = (loff_t **)&PT_REGS_PARM4(regs); - - return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr); + return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, + flags); } static struct kprobe faccessat_kp = { @@ -327,36 +182,18 @@ static struct kprobe execve_kp = { .pre_handler = execve_handler_pre, }; -static struct kprobe vfs_read_kp = { - .symbol_name = "vfs_read", - .pre_handler = read_handler_pre, -}; - -static void do_unregister_vfs_read_kp(struct work_struct *work) -{ - unregister_kprobe(&vfs_read_kp); -} - -static void unregister_vfs_read_kp() -{ - bool ret = schedule_work(&unregister_vfs_read_work); - pr_info("unregister vfs_read kprobe: %d!\n", ret); -} +#endif // sucompat: permited process can execute 'su' to gain root access. -void enable_sucompat() +void ksu_enable_sucompat() { +#ifdef CONFIG_KPROBES int ret; - ret = register_kprobe(&execve_kp); - pr_info("execve_kp: %d\n", ret); + pr_info("sucompat: execve_kp: %d\n", ret); ret = register_kprobe(&newfstatat_kp); - pr_info("newfstatat_kp: %d\n", ret); + pr_info("sucompat: newfstatat_kp: %d\n", ret); ret = register_kprobe(&faccessat_kp); - pr_info("faccessat_kp: %d\n", ret); - - ret = register_kprobe(&vfs_read_kp); - pr_info("vfs_read_kp: %d\n", ret); - - INIT_WORK(&unregister_vfs_read_work, do_unregister_vfs_read_kp); + pr_info("sucompat: faccessat_kp: %d\n", ret); +#endif } diff --git a/kernel/uid_observer.c b/kernel/uid_observer.c index 7f482a9c..ab88f3b6 100644 --- a/kernel/uid_observer.c +++ b/kernel/uid_observer.c @@ -1,21 +1,16 @@ -#include "linux/kprobes.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "linux/err.h" +#include "linux/fs.h" +#include "linux/list.h" +#include "linux/slab.h" +#include "linux/string.h" +#include "linux/types.h" +#include "linux/version.h" +#include "linux/workqueue.h" -#include "uid_observer.h" #include "allowlist.h" -#include "arch.h" -#include "klog.h" #include "ksu.h" +#include "manager.h" +#include "uid_observer.h" #define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list" static struct work_struct ksu_update_uid_work; @@ -121,50 +116,11 @@ out: filp_close(fp, 0); } -static void update_uid() +void update_uid() { ksu_queue_work(&ksu_update_uid_work); } -int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry) -{ - if (!current->mm) { - // skip kernel threads - return 0; - } - - if (current_uid().val != 1000) { - // skip non system uid - return 0; - } - - if (!old_dentry || !new_dentry) { - return 0; - } - - // /data/system/packages.list.tmp -> /data/system/packages.list - if (strcmp(new_dentry->d_iname, "packages.list")) { - return 0; - } - - char path[128]; - char *buf = dentry_path_raw(new_dentry, path, sizeof(path)); - if (IS_ERR(buf)) { - pr_err("dentry_path_raw failed.\n"); - return 0; - } - - if (strcmp(buf, "/system/packages.list")) { - return 0; - } - pr_info("renameat: %s -> %s\n, new path: %s", old_dentry->d_iname, - new_dentry->d_iname, buf); - - update_uid(); - - return 0; -} - int ksu_uid_observer_init() { INIT_WORK(&ksu_update_uid_work, do_update_uid); diff --git a/kernel/uid_observer.h b/kernel/uid_observer.h index 74ba4bbd..5604b219 100644 --- a/kernel/uid_observer.h +++ b/kernel/uid_observer.h @@ -5,4 +5,6 @@ int ksu_uid_observer_init(); int ksu_uid_observer_exit(); +void update_uid(); + #endif \ No newline at end of file