From d5d2bb35d2fe9c76eb454ec7b63f8bee796263cc Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Wed, 24 Sep 2025 01:51:28 +0800 Subject: [PATCH] kernel: throne_tracker: offload to kthread tiann #2632 Run throne_tracker() in kthread instead of blocking the caller. Prevents full lockup during installation and removing the manager. By default, first run remains synchronous for compatibility purposes (FDE, FBEv1, FBEv2) Features: - looks and waits for manager UID in /data/system/packages.list - run track_throne() in a kthread after the first synchronous run - prevent duplicate thread creation with a single-instance check - spinlock-on-d_lock based polling adressing possible race conditions. Race conditions adressed - single instance kthread lock, smp_mb() - track_throne_function, packages.list, spinlock-on-d_lock based polling - is_manager_apk, apk, spinlock-on-d_lock based polling This is a squash of: https://github.com/tiann/KernelSU/pull/2632 Original skeleton based on: `kernelsu: move throne_tracker() to kthread` `kernelsu: check locking before accessing files and dirs during searching manager` `kernelsu: look for manager UID in /data/system/packages.list, not /data/system/packages.list.tmp` https://github.com/acroreiser/android_kernel_lge_hammerhead/compare/0b05e927...8783badd Co-Authored-By: backslashxx <118538522+backslashxx@users.noreply.github.com> Co-Authored-By: Yaroslav Zviezda <10716792+acroreiser@users.noreply.github.com> Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com> --- kernel/Kconfig | 9 ++++++++ kernel/apk_sign.c | 50 +++++++++++++++++++++++++++++++++++++++++ kernel/throne_tracker.c | 40 +++++++++++++++++++++++++++++++-- kernel/throne_tracker.h | 33 +++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/kernel/Kconfig b/kernel/Kconfig index a24a5b90..a5564354 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -27,6 +27,15 @@ config KPM select KALLSYMS select KALLSYMS_ALL +config KSU_THRONE_TRACKER_ALWAYS_THREADED + bool "Always run throne_tracker in a kthread" + default n + help + Runs throne_tracker in a separate kthread, including the first run. + Significantly decreases boot time, but can cause crowning failure + on some FDE or FBEv1 setups. + If unsure, say n. + choice prompt "KernelSU hook type" depends on KSU diff --git a/kernel/apk_sign.c b/kernel/apk_sign.c index 3c22ca0c..61f9ff22 100644 --- a/kernel/apk_sign.c +++ b/kernel/apk_sign.c @@ -19,6 +19,7 @@ #include "klog.h" // IWYU pragma: keep #include "kernel_compat.h" #include "manager_sign.h" +#include "throne_tracker.h" struct sdesc { struct shash_desc shash; @@ -260,6 +261,23 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana bool v3_1_signing_exist = false; int matched_index = -1; int i; + struct path kpath; + if (kern_path(path, 0, &kpath)) + return false; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) + if (inode_is_locked(kpath.dentry->d_inode)) +#else + if (mutex_is_locked(&kpath.dentry->d_inode->i_mutex)) +#endif + { + pr_info("%s: inode is locked for %s\n", __func__, path); + path_put(&kpath); + return false; + } + + path_put(&kpath); + struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0); if (IS_ERR(fp)) { pr_err("open %s error.\n", path); @@ -413,10 +431,42 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops, bool is_manager_apk(char *path) { + int tries = 0; + + while (tries++ < 10) { + if (!is_lock_held(path)) + break; + + pr_info("%s: waiting for %s\n", __func__, path); + msleep(100); + } + + // let it go, if retry fails, check_v2_signature will fail to open it anyway + if (tries == 10) { + pr_info("%s: timeout for %s\n", __func__, path); + return false; + } + return check_v2_signature(path, false, NULL); } bool is_dynamic_manager_apk(char *path, int *signature_index) { + int tries = 0; + + while (tries++ < 10) { + if (!is_lock_held(path)) + break; + + pr_info("%s: waiting for %s\n", __func__, path); + msleep(100); + } + + // let it go, if retry fails, check_v2_signature will fail to open it anyway + if (tries == 10) { + pr_info("%s: timeout for %s\n", __func__, path); + return false; + } + return check_v2_signature(path, true, signature_index); } \ No newline at end of file diff --git a/kernel/throne_tracker.c b/kernel/throne_tracker.c index 45e07ad5..8a3ccc96 100644 --- a/kernel/throne_tracker.c +++ b/kernel/throne_tracker.c @@ -17,8 +17,13 @@ #include "dynamic_manager.h" #include "throne_comm.h" +#include +#include + uid_t ksu_manager_uid = KSU_INVALID_UID; +static struct task_struct *throne_thread; + #define KSU_UID_LIST_PATH "/data/misc/user_uid/uid_list" #define USER_DATA_PATH "/data/user_de/0" #define USER_DATA_PATH_LEN 256 @@ -510,7 +515,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data) struct file *file; if (!stop) { - file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0); + file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW | O_DIRECTORY, 0); if (IS_ERR(file)) { pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file)); goto skip_iterate; @@ -569,7 +574,7 @@ static bool is_uid_exist(uid_t uid, char *package, void *data) return exist; } -void track_throne() +static void track_throne_function() { struct list_head uid_list; INIT_LIST_HEAD(&uid_list); @@ -650,6 +655,37 @@ out: } } +static int throne_tracker_thread(void *data) +{ + pr_info("%s: pid: %d started\n", __func__, current->pid); + track_throne_function(); + throne_thread = NULL; + smp_mb(); + pr_info("%s: pid: %d exit!\n", __func__, current->pid); + return 0; +} + +void track_throne() +{ +#ifndef CONFIG_KSU_THRONE_TRACKER_ALWAYS_THREADED + static bool throne_tracker_first_run __read_mostly = true; + if (unlikely(throne_tracker_first_run)) { + track_throne_function(); + throne_tracker_first_run = false; + return; + } +#endif + smp_mb(); + if (throne_thread != NULL) // single instance lock + return; + + throne_thread = kthread_run(throne_tracker_thread, NULL, "throne_tracker"); + if (IS_ERR(throne_thread)) { + throne_thread = NULL; + return; + } +} + void ksu_throne_tracker_init() { // nothing to do diff --git a/kernel/throne_tracker.h b/kernel/throne_tracker.h index 5d7f4770..873731be 100644 --- a/kernel/throne_tracker.h +++ b/kernel/throne_tracker.h @@ -7,4 +7,37 @@ void ksu_throne_tracker_exit(); void track_throne(); +/* + * small helper to check if lock is held + * false - file is stable + * true - file is being deleted/renamed + * possibly optional + * + */ +static bool is_lock_held(const char *path) +{ + struct path kpath; + + // kern_path returns 0 on success + if (kern_path(path, 0, &kpath)) + return true; + + // just being defensive + if (!kpath.dentry) { + path_put(&kpath); + return true; + } + + if (!spin_trylock(&kpath.dentry->d_lock)) { + pr_info("%s: lock held, bail out!\n", __func__); + path_put(&kpath); + return true; + } + // we hold it ourselves here! + + spin_unlock(&kpath.dentry->d_lock); + path_put(&kpath); + return false; +} + #endif