From d288b8f24f995bc973c5eb231ba3073a2b66df6d Mon Sep 17 00:00:00 2001 From: backslashxx <118538522+backslashxx@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:08:39 +0800 Subject: [PATCH] ksud: migrate ksud execution to security_bprm_check (tiann#2653) This migrates ksud execution decision-making to bprm_check_security. This requires passing proper argv and envp to a modified _ksud handler aptly named 'ksu_handle_bprm_ksud'. Introduces: int ksu_handle_bprm_ksud(const char *filename, const char *argv1, const char *envp, size_t envp_len) which is adapted from: int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, struct user_arg_ptr *argv, struct user_arg_ptr *envp, int *flags) ksu_handle_bprm_ksud handles all the decision making, it decides when it is time to apply_kernelsu_rules depending if it sees "second_stage". For LSM hook, turns out we can pull out argv and envp from mm_struct. The code in here explains itself on how to do it. whole blob exists on arg_start to arg_end, so we just pull it out and grab next array after the first null terminator. as for envp, we pass the pointer then hunt for it when needed My reasoning on adding a fallback on usercopy is that on some devices a fault happens, and it copies garbled data. On my creation of this, I actually had to lock that _nofault copy on a spinlock as a way to mimic preempt_disable/enable without actually doing it. As per user reports, no failed _nofault copies anyway but we have-to-have a fallback for resilience. References: - old version1 https://github.com/backslashxx/KernelSU/commit/6efcd8193e62d13a4e62cda0ce54d6770260c680 - old version2 https://github.com/backslashxx/KernelSU/commit/37d5938e66db2578acbd763072533245e50742e1 - bad usercopy #21 This now provides a small helper function, ksu_copy_from_user_retry, which explains itself. First we attempt a _nofault copy, if that fails, we try plain. With that, It also provides an inlined copy_from_user_nofault for < 5.8. While using strncpy_from_user_nofault was considered, this wont do, this will only copy up to the first \0. devlog: https://github.com/ximi-libra-test/android_kernel_xiaomi_libra/compare/16e5dce9e7e...16c1f5f521a https://github.com/backslashxx/mojito_krenol/compare/28642e60d7b...728de0c571c References: https://elixir.bootlin.com/linux/v4.14.1/source/include/linux/mm_types.h#L429 https://elixir.bootlin.com/linux/v4.14.1/source/include/linux/lsm_hooks.h Stale: https://github.com/tiann/KernelSU/pull/2653 Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com> --- kernel/core_hook.c | 16 ++ kernel/kernel_compat.c | 30 ++++ kernel/kernel_compat.h | 17 ++ kernel/ksud.c | 346 ++++++++++++++++++----------------------- kernel/ksud.h | 3 + 5 files changed, 217 insertions(+), 195 deletions(-) diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 955a71bc..957de49a 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -825,6 +826,19 @@ int ksu_key_permission(key_ref_t key_ref, const struct cred *cred, } #endif +int ksu_bprm_check(struct linux_binprm *bprm) +{ + char *filename = (char *)bprm->filename; + + if (likely(!ksu_execveat_hook)) + return 0; + + ksu_handle_pre_ksud(filename); + + return 0; + +} + #ifdef CONFIG_KSU_LSM_SECURITY_HOOKS static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) @@ -853,6 +867,8 @@ static struct security_hook_list ksu_hooks[] = { #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || \ defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND) LSM_HOOK_INIT(key_permission, ksu_key_permission) +#ifndef CONFIG_KSU_KPROBES_HOOK + LSM_HOOK_INIT(bprm_check_security, ksu_bprm_check), #endif }; diff --git a/kernel/kernel_compat.c b/kernel/kernel_compat.c index c00a369c..233981cb 100644 --- a/kernel/kernel_compat.c +++ b/kernel/kernel_compat.c @@ -69,8 +69,10 @@ void ksu_android_ns_fs_check(void) if (current->nsproxy && current->fs && current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) { android_context_saved_enabled = true; +#ifdef CONFIG_KSU_DEBUG pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n", current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns); +#endif ksu_save_ns_fs(&android_context_saved); } else { pr_info("android context saved disabled\n"); @@ -91,7 +93,9 @@ struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode) // switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns struct ksu_ns_fs_saved saved; if (android_context_saved_enabled) { +#ifdef CONFIG_KSU_DEBUG pr_info("start switch current nsproxy and fs to android context\n"); +#endif task_lock(current); ksu_save_ns_fs(&saved); ksu_load_ns_fs(&android_context_saved); @@ -102,7 +106,9 @@ struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode) task_lock(current); ksu_load_ns_fs(&saved); task_unlock(current); +#ifdef CONFIG_KSU_DEBUG pr_info("switch current nsproxy and fs back to saved successfully\n"); +#endif } return fp; } @@ -209,3 +215,27 @@ long ksu_strncpy_from_user_retry(char *dst, const void __user *unsafe_addr, return ret; } + +long ksu_copy_from_user_nofault(void *dst, const void __user *src, size_t size) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + return copy_from_user_nofault(dst, src, size); +#else + // https://elixir.bootlin.com/linux/v5.8/source/mm/maccess.c#L205 + long ret = -EFAULT; + mm_segment_t old_fs = get_fs(); + + set_fs(USER_DS); + // tweaked to use ksu_access_ok + if (ksu_access_ok(src, size)) { + pagefault_disable(); + ret = __copy_from_user_inatomic(dst, src, size); + pagefault_enable(); + } + set_fs(old_fs); + + if (ret) + return -EFAULT; + return 0; +#endif +} diff --git a/kernel/kernel_compat.h b/kernel/kernel_compat.h index fe75414e..779e2466 100644 --- a/kernel/kernel_compat.h +++ b/kernel/kernel_compat.h @@ -76,6 +76,23 @@ extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, loff_t *pos); extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, loff_t *pos); +extern long ksu_copy_from_user_nofault(void *dst, const void __user *src, size_t size); +/* + * ksu_copy_from_user_retry + * try nofault copy first, if it fails, try with plain + * paramters are the same as copy_from_user + * 0 = success + */ +static long ksu_copy_from_user_retry(void *to, + const void __user *from, unsigned long count) +{ + long ret = ksu_copy_from_user_nofault(to, from, count); + if (likely(!ret)) + return ret; + + // we faulted! fallback to slow path + return copy_from_user(to, from, count); +} #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) #define ksu_access_ok(addr, size) access_ok(addr, size) diff --git a/kernel/ksud.c b/kernel/ksud.c index 57dafd80..a50273b0 100644 --- a/kernel/ksud.c +++ b/kernel/ksud.c @@ -58,13 +58,13 @@ static void stop_input_hook(void); #ifdef CONFIG_KSU_KPROBES_HOOK static struct work_struct stop_vfs_read_work; -static struct work_struct stop_execve_hook_work; +static struct work_struct stop_bprm_check_work; static struct work_struct stop_input_hook_work; #else bool ksu_vfs_read_hook __read_mostly = true; -bool ksu_execveat_hook __read_mostly = true; bool ksu_input_hook __read_mostly = true; #endif +bool ksu_execveat_hook __read_mostly = true; u32 ksu_devpts_sid; @@ -91,7 +91,6 @@ void on_post_fs_data(void) is_boot_phase = false; } -#define MAX_ARG_STRINGS 0x7FFFFFFF struct user_arg_ptr { #ifdef CONFIG_COMPAT bool is_compat; @@ -104,75 +103,11 @@ struct user_arg_ptr { } ptr; }; -static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr) +// since _ksud handler only uses argv and envp for comparisons +// this can probably work +// adapted from ksu_handle_execveat_ksud +static int ksu_handle_bprm_ksud(const char *filename, const char *argv1, const char *envp, size_t envp_len) { - const char __user *native; - -#ifdef CONFIG_COMPAT - if (unlikely(argv.is_compat)) { - compat_uptr_t compat; - - if (get_user(compat, argv.ptr.compat + nr)) - return ERR_PTR(-EFAULT); - - ksu_is_compat = true; - return compat_ptr(compat); - } -#endif - - if (get_user(native, argv.ptr.native + nr)) - return ERR_PTR(-EFAULT); - - return native; -} - -/* - * count() counts the number of strings in array ARGV. - */ - -/* - * Make sure old GCC compiler can use __maybe_unused, - * Test passed in 4.4.x ~ 4.9.x when use GCC. - */ - -static int __maybe_unused count(struct user_arg_ptr argv, int max) -{ - int i = 0; - - if (argv.ptr.native != NULL) { - for (;;) { - const char __user *p = get_user_arg_ptr(argv, i); - - if (!p) - break; - - if (IS_ERR(p)) - return -EFAULT; - - if (i >= max) - return -E2BIG; - ++i; - - if (fatal_signal_pending(current)) - return -ERESTARTNOHAND; - cond_resched(); - } - } - return i; -} - -// IMPORTANT NOTE: the call from execve_handler_pre WON'T provided correct value for envp and flags in GKI version -int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, - struct user_arg_ptr *argv, - struct user_arg_ptr *envp, int *flags) -{ -#ifndef CONFIG_KSU_KPROBES_HOOK - if (!ksu_execveat_hook) { - return 0; - } -#endif - struct filename *filename; - static const char app_process[] = "/system/bin/app_process"; static bool first_app_process = true; @@ -182,113 +117,149 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, static const char old_system_init[] = "/init"; static bool init_second_stage_executed = false; - if (!filename_ptr) + // return early when disabled + if (!ksu_execveat_hook) return 0; - filename = *filename_ptr; - if (IS_ERR(filename)) { + if (!filename) return 0; - } - if (unlikely(!memcmp(filename->name, system_bin_init, - sizeof(system_bin_init) - 1) && - argv)) { - // /system/bin/init executed - int argc = count(*argv, MAX_ARG_STRINGS); - pr_info("/system/bin/init argc: %d\n", argc); - if (argc > 1 && !init_second_stage_executed) { - const char __user *p = get_user_arg_ptr(*argv, 1); - if (p && !IS_ERR(p)) { - char first_arg[16]; - ksu_strncpy_from_user_retry(first_arg, p, - sizeof(first_arg)); - pr_info("/system/bin/init first arg: %s\n", - first_arg); - if (!strcmp(first_arg, "second_stage")) { - pr_info("/system/bin/init second_stage executed\n"); - apply_kernelsu_rules(); - init_second_stage_executed = true; - ksu_android_ns_fs_check(); - } - } else { - pr_err("/system/bin/init parse args err!\n"); - } - } - } else if (unlikely(!memcmp(filename->name, old_system_init, - sizeof(old_system_init) - 1) && - argv)) { - // /init executed - int argc = count(*argv, MAX_ARG_STRINGS); - pr_info("/init argc: %d\n", argc); - if (argc > 1 && !init_second_stage_executed) { - /* This applies to versions between Android 6 ~ 7 */ - const char __user *p = get_user_arg_ptr(*argv, 1); - if (p && !IS_ERR(p)) { - char first_arg[16]; - ksu_strncpy_from_user_retry(first_arg, p, - sizeof(first_arg)); - pr_info("/init first arg: %s\n", first_arg); - if (!strcmp(first_arg, "--second-stage")) { - pr_info("/init second_stage executed\n"); - apply_kernelsu_rules(); - init_second_stage_executed = true; - ksu_android_ns_fs_check(); - } - } else { - pr_err("/init parse args err!\n"); - } - } else if (argc == 1 && !init_second_stage_executed && envp) { - /* This applies to versions between Android 8 ~ 9 */ - int envc = count(*envp, MAX_ARG_STRINGS); - if (envc > 0) { - int n; - for (n = 1; n <= envc; n++) { - const char __user *p = - get_user_arg_ptr(*envp, n); - if (!p || IS_ERR(p)) { - continue; - } - char env[256]; - // Reading environment variable strings from user space - if (ksu_strncpy_from_user_retry( - env, p, sizeof(env)) < 0) - continue; - // Parsing environment variable names and values - char *env_name = env; - char *env_value = strchr(env, '='); - if (env_value == NULL) - continue; - // Replace equal sign with string terminator - *env_value = '\0'; - env_value++; - // Check if the environment variable name and value are matching - if (!strcmp(env_name, - "INIT_SECOND_STAGE") && - (!strcmp(env_value, "1") || - !strcmp(env_value, "true"))) { - pr_info("/init second_stage executed\n"); - apply_kernelsu_rules(); - init_second_stage_executed = - true; - ksu_android_ns_fs_check(); - } - } - } + // debug! remove me! + pr_info("%s: filename: %s argv1: %s envp_len: %zu\n", __func__, filename, argv1, envp_len); + + + if (init_second_stage_executed) + goto first_app_process; + + // /system/bin/init with argv1 + if (!init_second_stage_executed + && (!memcmp(filename, system_bin_init, sizeof(system_bin_init) - 1))) { + if (argv1 && !strcmp(argv1, "second_stage")) { + pr_info("%s: /system/bin/init second_stage executed\n", __func__); + apply_kernelsu_rules(); + init_second_stage_executed = true; + ksu_android_ns_fs_check(); } } - if (unlikely(first_app_process && !memcmp(filename->name, app_process, - sizeof(app_process) - 1))) { + // /init with argv1 + if (!init_second_stage_executed + && (!memcmp(filename, old_system_init, sizeof(old_system_init) - 1))) { + if (argv1 && !strcmp(argv1, "--second-stage")) { + pr_info("%s: /init --second-stage executed\n", __func__); + apply_kernelsu_rules(); + init_second_stage_executed = true; + ksu_android_ns_fs_check(); + } + } + + // /init without argv1/useless-argv1 but usable envp + // untested! TODO: test and debug me! + if (!init_second_stage_executed && (!memcmp(filename, old_system_init, sizeof(old_system_init) - 1))) { + + // we hunt for "INIT_SECOND_STAGE" + const char *envp_n = envp; + unsigned int envc = 1; + do { + if (strstarts(envp_n, "INIT_SECOND_STAGE")) + break; + envp_n += strlen(envp_n) + 1; + envc++; + } while (envp_n < envp + envp_len); + pr_info("%s: envp[%d]: %s\n", __func__, envc, envp_n); + + if (!strcmp(envp_n, "INIT_SECOND_STAGE=1") + || !strcmp(envp_n, "INIT_SECOND_STAGE=true") ) { + pr_info("%s: /init +envp: INIT_SECOND_STAGE executed\n", __func__); + apply_kernelsu_rules(); + init_second_stage_executed = true; + ksu_android_ns_fs_check(); + } + } + +first_app_process: + if (first_app_process && !memcmp(filename, app_process, sizeof(app_process) - 1)) { first_app_process = false; - pr_info("exec app_process, /data prepared, second_stage: %d\n", - init_second_stage_executed); - on_post_fs_data(); // we keep this for old ksud + pr_info("%s: exec app_process, /data prepared, second_stage: %d\n", __func__, init_second_stage_executed); + on_post_fs_data(); stop_execve_hook(); } return 0; } +int ksu_handle_pre_ksud(const char *filename) +{ + if (likely(!ksu_execveat_hook)) + return 0; + + // not /system/bin/init, not /init, not /system/bin/app_process (64/32 thingy) + // return 0; + if (likely(strcmp(filename, "/system/bin/init") && strcmp(filename, "/init") + && !strstarts(filename, "/system/bin/app_process") )) + return 0; + + if (!current || !current->mm) + return 0; + + // https://elixir.bootlin.com/linux/v4.14.1/source/include/linux/mm_types.h#L429 + // unsigned long arg_start, arg_end, env_start, env_end; + unsigned long arg_start = current->mm->arg_start; + unsigned long arg_end = current->mm->arg_end; + unsigned long env_start = current->mm->env_start; + unsigned long env_end = current->mm->env_end; + + size_t arg_len = arg_end - arg_start; + size_t envp_len = env_end - env_start; + + if (arg_len <= 0 || envp_len <= 0) // this wont make sense, filter it + return 0; + + #define ARGV_MAX 32 // this is enough for argv1 + #define ENVP_MAX 256 // this is enough for INIT_SECOND_STAGE + char args[ARGV_MAX]; + size_t argv_copy_len = (arg_len > ARGV_MAX) ? ARGV_MAX : arg_len; + char envp[ENVP_MAX]; + size_t envp_copy_len = (envp_len > ENVP_MAX) ? ENVP_MAX : envp_len; + + // we cant use strncpy on here, else it will truncate once it sees \0 + if (ksu_copy_from_user_retry(args, (void __user *)arg_start, argv_copy_len)) + return 0; + + if (ksu_copy_from_user_retry(envp, (void __user *)env_start, envp_copy_len)) + return 0; + + args[ARGV_MAX - 1] = '\0'; + envp[ENVP_MAX - 1] = '\0'; + +#ifdef CONFIG_KSU_DEBUG + char *envp_n = envp; + unsigned int envc = 1; + do { + pr_info("%s: envp[%d]: %s\n", __func__, envc, envp_n); + envp_n += strlen(envp_n) + 1; + envc++; + } while (envp_n < envp + envp_copy_len); +#endif + + // we only need argv1 ! + // abuse strlen here since it only gets length up to \0 + char *argv1 = args + strlen(args) + 1; + if (argv1 >= args + argv_copy_len) // out of bounds! + argv1 = ""; + + return ksu_handle_bprm_ksud(filename, argv1, envp, envp_copy_len); +} + +int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr, + struct user_arg_ptr *argv, struct user_arg_ptr *envp, + int *flags) +{ + // this is now handled via security_bprm_check + // we only keep this for the sake of old hooks. + return 0; +} + static ssize_t (*orig_read)(struct file *, char __user *, size_t, loff_t *); static ssize_t (*orig_read_iter)(struct kiocb *, struct iov_iter *); static struct file_operations fops_proxy; @@ -500,28 +471,13 @@ static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) return ksu_handle_execveat_ksud(fd, filename_ptr, &argv, NULL, NULL); } -static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs) +extern int ksu_bprm_check(struct linux_binprm *bprm); +static int bprm_check_handler_pre(struct kprobe *p, struct pt_regs *regs) { - struct pt_regs *real_regs = PT_REAL_REGS(regs); - const char __user **filename_user = - (const char **)&PT_REGS_PARM1(real_regs); - const char __user *const __user *__argv = - (const char __user *const __user *)PT_REGS_PARM2(real_regs); - struct user_arg_ptr argv = { .ptr.native = __argv }; - struct filename filename_in, *filename_p; - char path[32]; + struct linux_binprm *bprm_local = (struct linux_binprm *)PT_REGS_PARM1(regs); - if (!filename_user) - return 0; - - memset(path, 0, sizeof(path)); - ksu_strncpy_from_user_nofault(path, *filename_user, 32); - filename_in.name = path; - - filename_p = &filename_in; - return ksu_handle_execveat_ksud(AT_FDCWD, &filename_p, &argv, NULL, - NULL); -} + return ksu_bprm_check(bprm_local); +}; static int sys_read_handler_pre(struct kprobe *p, struct pt_regs *regs) { @@ -542,9 +498,9 @@ static int input_handle_event_handler_pre(struct kprobe *p, return ksu_handle_input_handle_event(type, code, value); } -static struct kprobe execve_kp = { - .symbol_name = SYS_EXECVE_SYMBOL, - .pre_handler = sys_execve_handler_pre, +static struct kprobe bprm_check_kp = { + .symbol_name = "security_bprm_check", + .pre_handler = bprm_check_handler_pre, }; static struct kprobe vfs_read_kp = { @@ -562,9 +518,9 @@ 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) +static void do_stop_bprm_check_hook(struct work_struct *work) { - unregister_kprobe(&execve_kp); + unregister_kprobe(&bprm_check_kp); } static void do_stop_input_hook(struct work_struct *work) @@ -634,11 +590,11 @@ static void stop_vfs_read_hook(void) static void stop_execve_hook(void) { #ifdef CONFIG_KSU_KPROBES_HOOK - bool ret = schedule_work(&stop_execve_hook_work); + bool ret = schedule_work(&stop_bprm_check_work); pr_info("unregister execve kprobe: %d!\n", ret); #else - ksu_execveat_hook = false; pr_info("stop execve_hook\n"); + ksu_execveat_hook = false; #endif } @@ -667,8 +623,8 @@ void ksu_ksud_init(void) #ifdef CONFIG_KSU_KPROBES_HOOK int ret; - ret = register_kprobe(&execve_kp); - pr_info("ksud: execve_kp: %d\n", ret); + ret = register_kprobe(&bprm_check_kp); + pr_info("ksud: bprm_check_kp: %d\n", ret); ret = register_kprobe(&vfs_read_kp); pr_info("ksud: vfs_read_kp: %d\n", ret); @@ -677,7 +633,7 @@ void ksu_ksud_init(void) pr_info("ksud: input_event_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); + INIT_WORK(&stop_bprm_check_work, do_stop_bprm_check_hook); INIT_WORK(&stop_input_hook_work, do_stop_input_hook); #endif } @@ -685,7 +641,7 @@ void ksu_ksud_init(void) void ksu_ksud_exit(void) { #ifdef CONFIG_KSU_KPROBES_HOOK - unregister_kprobe(&execve_kp); + unregister_kprobe(&bprm_check_kp); // this should be done before unregister vfs_read_kp // unregister_kprobe(&vfs_read_kp); unregister_kprobe(&input_event_kp); diff --git a/kernel/ksud.h b/kernel/ksud.h index cc2df243..7fff4807 100644 --- a/kernel/ksud.h +++ b/kernel/ksud.h @@ -11,4 +11,7 @@ bool ksu_is_safe_mode(void); extern u32 ksu_devpts_sid; +extern bool ksu_execveat_hook __read_mostly; +extern int ksu_handle_pre_ksud(const char *filename); + #endif