From deac6163d610c940573b3558e3d4cf5626e50131 Mon Sep 17 00:00:00 2001 From: weishu Date: Thu, 19 Jan 2023 13:31:55 +0700 Subject: [PATCH] kernel: 1. use prctl lsm hook; 2. refine sucompat hook --- kernel/Makefile | 2 + kernel/include/ksu_hook.h | 17 +++++ kernel/kprobe_hook.c | 46 +++++++++++++ kernel/ksu.c | 38 +++------- kernel/ksu.h | 29 +++++--- kernel/lsm_hook.c | 33 +++++++++ kernel/sucompat.c | 141 ++++++++++++++++++++++++++------------ 7 files changed, 224 insertions(+), 82 deletions(-) create mode 100644 kernel/include/ksu_hook.h create mode 100644 kernel/kprobe_hook.c create mode 100644 kernel/lsm_hook.c diff --git a/kernel/Makefile b/kernel/Makefile index 09e6ffe2..c2026feb 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -5,6 +5,8 @@ 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 += selinux/ diff --git a/kernel/include/ksu_hook.h b/kernel/include/ksu_hook.h new file mode 100644 index 00000000..899dfda8 --- /dev/null +++ b/kernel/include/ksu_hook.h @@ -0,0 +1,17 @@ +#ifndef __KSU_H_KSHOOK +#define __KSU_H_KSHOOK + +#include +#include + +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); + +int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, + size_t *count_ptr, loff_t **pos); +#endif \ No newline at end of file diff --git a/kernel/kprobe_hook.c b/kernel/kprobe_hook.c new file mode 100644 index 00000000..47c08870 --- /dev/null +++ b/kernel/kprobe_hook.c @@ -0,0 +1,46 @@ +#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 kp = { + .symbol_name = PRCTL_SYMBOL, + .pre_handler = handler_pre, +}; + +__maybe_unused int ksu_kprobe_init() +{ + int rc = 0; + rc = register_kprobe(&kp); + + if (rc) { + pr_info("prctl kprobe failed: %d, please check your kernel config.\n", + rc); + return rc; + } + + return rc; +} + +__maybe_unused int ksu_kprobe_exit() { + unregister_kprobe(&kp); + return 0; +} \ No newline at end of file diff --git a/kernel/ksu.c b/kernel/ksu.c index 84f63f3b..452ecb15 100644 --- a/kernel/ksu.c +++ b/kernel/ksu.c @@ -37,8 +37,9 @@ static struct workqueue_struct *ksu_workqueue; uid_t ksu_manager_uid = INVALID_UID; -void ksu_queue_work(struct work_struct *work) { - queue_work(ksu_workqueue, work); +void ksu_queue_work(struct work_struct *work) +{ + queue_work(ksu_workqueue, work); } void escape_to_root() @@ -179,19 +180,9 @@ static bool is_allow_su() extern void enable_sucompat(); -static int handler_pre(struct kprobe *p, struct pt_regs *regs) +int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) { -#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); - // if success, we modify the arg5 as result! u32 *result = (u32 *)arg5; u32 reply_ok = KERNEL_SU_OPTION; @@ -309,11 +300,6 @@ static int handler_pre(struct kprobe *p, struct pt_regs *regs) return 0; } -static struct kprobe kp = { - .symbol_name = PRCTL_SYMBOL, - .pre_handler = handler_pre, -}; - int kernelsu_init(void) { int rc = 0; @@ -322,17 +308,12 @@ int kernelsu_init(void) pr_alert("You are running DEBUG version of KernelSU"); #endif + ksu_lsm_hook_init(); // use ksu_kprobe_init if compiled as module + ksu_workqueue = alloc_workqueue("kernelsu_work_queue", 0, 0); ksu_allowlist_init(); - rc = register_kprobe(&kp); - if (rc) { - pr_info("prctl kprobe failed: %d, please check your kernel config.\n", - rc); - return rc; - } - ksu_uid_observer_init(); enable_sucompat(); @@ -342,12 +323,9 @@ int kernelsu_init(void) void kernelsu_exit(void) { - // should never happen... - unregister_kprobe(&kp); - ksu_allowlist_exit(); - destroy_workqueue(ksu_workqueue); + destroy_workqueue(ksu_workqueue); } module_init(kernelsu_init); diff --git a/kernel/ksu.h b/kernel/ksu.h index 44751da5..e1fb5099 100644 --- a/kernel/ksu.h +++ b/kernel/ksu.h @@ -21,22 +21,35 @@ extern uid_t ksu_manager_uid; -static inline bool ksu_is_manager_uid_valid() { - return ksu_manager_uid != INVALID_UID; +static inline bool ksu_is_manager_uid_valid() +{ + return ksu_manager_uid != INVALID_UID; } -static inline uid_t ksu_get_manager_uid() { - return ksu_manager_uid; +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_set_manager_uid(uid_t uid) +{ + ksu_manager_uid = uid; } -static inline void ksu_invalidate_manager_uid() { - ksu_manager_uid = INVALID_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); + #endif \ No newline at end of file diff --git a/kernel/lsm_hook.c b/kernel/lsm_hook.c new file mode 100644 index 00000000..d1f60698 --- /dev/null +++ b/kernel/lsm_hook.c @@ -0,0 +1,33 @@ +#include "asm-generic/errno.h" +#include "linux/kernel.h" +#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 struct security_hook_list ksu_hooks[] = { + LSM_HOOK_INIT(task_prctl, ksu_task_prctl), +}; + +void 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/sucompat.c b/kernel/sucompat.c index 56c3d002..2b64bc09 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -1,4 +1,4 @@ - +#include #include #include #include @@ -51,7 +51,8 @@ static char __user *sh_user_path(void) return userspace_stack_buffer(sh_path, sizeof(sh_path)); } -static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) +int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, + int *flags) { struct filename *filename; const char su[] = SU_PATH; @@ -60,14 +61,14 @@ static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) return 0; } - filename = getname(PT_REGS_PARM2(regs)); + filename = getname(*filename_user); if (IS_ERR(filename)) { return 0; } if (!memcmp(filename->name, su, sizeof(su))) { pr_info("faccessat su->sh!\n"); - PT_REGS_PARM2(regs) = sh_user_path(); + *filename_user = sh_user_path(); } putname(filename); @@ -75,7 +76,7 @@ static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) return 0; } -static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs) +int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) { // const char sh[] = SH_PATH; struct filename *filename; @@ -85,14 +86,18 @@ static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs) return 0; } - filename = getname(PT_REGS_PARM2(regs)); + if (!filename_user) { + return 0; + } + + filename = getname(*filename_user); if (IS_ERR(filename)) { return 0; } if (!memcmp(filename->name, su, sizeof(su))) { pr_info("newfstatat su->sh!\n"); - PT_REGS_PARM2(regs) = sh_user_path(); + *filename_user = sh_user_path(); } putname(filename); @@ -100,8 +105,8 @@ static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs) return 0; } -// 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 ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, + void *envp, int *flags) { struct filename *filename; const char sh[] = SH_PATH; @@ -112,12 +117,16 @@ static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) static const char system_bin_init[] = "/system/bin/init"; static int init_count = 0; - filename = PT_REGS_PARM2(regs); + 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)) { + 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 @@ -128,7 +137,7 @@ static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) } if (first_app_process && - !memcmp(filename->name, app_process, sizeof(app_process) - 1)) { + !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(); @@ -149,32 +158,32 @@ static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) } static const char KERNEL_SU_RC[] = -"\n" + "\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 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 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: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" + "on property:sys.boot_completed=1\n" + " exec u:r:su:s0 root -- /data/adb/ksud boot-completed\n" + "\n" -"\n" -; + "\n"; static void unregister_vfs_read_kp(); static struct work_struct unregister_vfs_read_work; -static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) +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; @@ -185,7 +194,7 @@ static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) return 0; } - file = PT_REGS_PARM1(regs); + file = *file_ptr; if (IS_ERR(file)) { return 0; } @@ -199,11 +208,8 @@ static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) // we are only interest `atrace.rc` file name file return 0; } - #define RC_PATH_MAX 256 - char path[RC_PATH_MAX]; - char* dpath = d_path(&file->f_path, path, RC_PATH_MAX); - - #undef RC_PATH_MAX + char path[256]; + char *dpath = d_path(&file->f_path, path, sizeof(path)); if (IS_ERR(dpath)) { return 0; @@ -223,12 +229,13 @@ static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) rc_inserted = true; // now we can sure that the init process is reading `/system/etc/init/atrace.rc` - buf = PT_REGS_PARM2(regs); - count = PT_REGS_PARM3(regs); + 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); + 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); @@ -241,12 +248,54 @@ static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) return 0; } - PT_REGS_PARM2(regs) = buf + rc_count; - PT_REGS_PARM3(regs) = count - rc_count; + *buf_ptr = buf + rc_count; + *count_ptr = count - rc_count; return 0; } +static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + int *dfd = (int *)PT_REGS_PARM1(regs); + const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs); + int *mode = (int *)&PT_REGS_PARM3(regs); + int *flags = (int *)&PT_REGS_PARM4(regs); + + return ksu_handle_faccessat(dfd, filename_user, mode, flags); +} + +static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + int *dfd = (int *)PT_REGS_PARM1(regs); + const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs); + int *flags = (int *)&PT_REGS_PARM3(regs); + + return ksu_handle_stat(dfd, filename_user, flags); +} + +// 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(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 faccessat_kp = { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) .symbol_name = "do_faccessat", @@ -264,9 +313,11 @@ static struct kprobe newfstatat_kp = { 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) +#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) +#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, @@ -277,11 +328,13 @@ static struct kprobe vfs_read_kp = { .pre_handler = read_handler_pre, }; -static void do_unregister_vfs_read_kp(struct work_struct *work) { +static void do_unregister_vfs_read_kp(struct work_struct *work) +{ unregister_kprobe(&vfs_read_kp); } -static void unregister_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); }