kernel: throne_tracker: offload to kthread (tiann[#2632](https://github.com/SukiSU-Ultra/SukiSU-Ultra/issues/2632))

Run throne_tracker() in kthread instead of blocking the caller.
Prevents full lockup during installation and removing the manager.

First run remains synchronous for compatibility purposes (FDE, FBEv1, FBEv2)

Features:
- 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()
- is_manager_apk, apk, spinlock-on-d_lock based polling

This is a squash of:
https://github.com/tiann/KernelSU/pull/2632

Rebased on top of
https://github.com/tiann/KernelSU/pull/2757

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`
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>
This commit is contained in:
ShirkNeko
2025-09-14 11:53:33 +08:00
parent 4c3bdcd016
commit debd7d5a01
5 changed files with 132 additions and 9 deletions

View File

@@ -255,6 +255,39 @@ static bool has_v1_signature_file(struct file *fp)
return false; return false;
} }
/*
* 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;
}
static __always_inline bool check_v2_signature(char *path, bool check_multi_manager, int *signature_index) static __always_inline bool check_v2_signature(char *path, bool check_multi_manager, int *signature_index)
{ {
unsigned char buffer[0x11] = { 0 }; unsigned char buffer[0x11] = { 0 };
@@ -269,6 +302,23 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
bool v3_1_signing_exist = false; bool v3_1_signing_exist = false;
int matched_index = -1; int matched_index = -1;
int i; 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); struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("open %s error.\n", path); pr_err("open %s error.\n", path);
@@ -422,10 +472,42 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
bool is_manager_apk(char *path) 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); return check_v2_signature(path, false, NULL);
} }
bool is_dynamic_manager_apk(char *path, int *signature_index) 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); return check_v2_signature(path, true, signature_index);
} }

View File

@@ -228,11 +228,11 @@ static void disable_seccomp(struct task_struct *tsk)
#endif #endif
} }
void escape_to_root(void) void escape_to_root(bool do_check_first)
{ {
struct cred *newcreds; struct cred *newcreds;
if (current_euid().val == 0) { if (do_check_first && current_euid().val == 0) {
pr_warn("Already root, don't escape!\n"); pr_warn("Already root, don't escape!\n");
return; return;
} }
@@ -392,7 +392,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
if (arg2 == CMD_GRANT_ROOT) { if (arg2 == CMD_GRANT_ROOT) {
if (is_allow_su()) { if (is_allow_su()) {
pr_info("allow root for: %d\n", current_uid().val); pr_info("allow root for: %d\n", current_uid().val);
escape_to_root(); escape_to_root(true);
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("grant_root: prctl reply error\n"); pr_err("grant_root: prctl reply error\n");
} }

View File

@@ -11,4 +11,6 @@ bool ksu_is_safe_mode(void);
extern u32 ksu_devpts_sid; extern u32 ksu_devpts_sid;
extern void escape_to_root(bool do_check_first);
#endif #endif

View File

@@ -28,8 +28,6 @@
#define SU_PATH "/system/bin/su" #define SU_PATH "/system/bin/su"
#define SH_PATH "/system/bin/sh" #define SH_PATH "/system/bin/sh"
extern void escape_to_root(void);
static const char sh_path[] = "/system/bin/sh"; static const char sh_path[] = "/system/bin/sh";
static const char ksud_path[] = KSUD_PATH; static const char ksud_path[] = KSUD_PATH;
static const char su[] = SU_PATH; static const char su[] = SU_PATH;
@@ -188,7 +186,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
pr_info("do_execveat_common su found\n"); pr_info("do_execveat_common su found\n");
memcpy((void *)filename->name, ksud_path, sizeof(ksud_path)); memcpy((void *)filename->name, ksud_path, sizeof(ksud_path));
escape_to_root(); escape_to_root(true);
return 0; return 0;
} }
@@ -237,7 +235,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
pr_info("sys_execve su found\n"); pr_info("sys_execve su found\n");
*filename_user = ksud_user_path(); *filename_user = ksud_user_path();
escape_to_root(); escape_to_root(true);
return 0; return 0;
} }

View File

@@ -11,13 +11,19 @@
#include "allowlist.h" #include "allowlist.h"
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "ksu.h" #include "ksu.h"
#include "ksud.h"
#include "manager.h" #include "manager.h"
#include "throne_tracker.h" #include "throne_tracker.h"
#include "kernel_compat.h" #include "kernel_compat.h"
#include "dynamic_manager.h" #include "dynamic_manager.h"
#include <linux/kthread.h>
#include <linux/sched.h>
uid_t ksu_manager_uid = KSU_INVALID_UID; uid_t ksu_manager_uid = KSU_INVALID_UID;
static struct task_struct *throne_thread;
#define USER_DATA_BASE_PATH "/data/user_de" #define USER_DATA_BASE_PATH "/data/user_de"
#define MAX_SUPPORTED_USERS 32 // Supports up to 32 users #define MAX_SUPPORTED_USERS 32 // Supports up to 32 users
#define DATA_PATH_LEN 384 // 384 is enough for /data/app/<package>/base.apk and /data/user_de/{userid}/<package> #define DATA_PATH_LEN 384 // 384 is enough for /data/app/<package>/base.apk and /data/user_de/{userid}/<package>
@@ -526,7 +532,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
struct file *file; struct file *file;
if (!stop) { 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)) { if (IS_ERR(file)) {
pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file)); pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file));
goto skip_iterate; goto skip_iterate;
@@ -584,7 +590,7 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
return exist; return exist;
} }
void track_throne(void) static void track_throne_function(void)
{ {
struct list_head uid_list; struct list_head uid_list;
INIT_LIST_HEAD(&uid_list); INIT_LIST_HEAD(&uid_list);
@@ -652,6 +658,41 @@ out:
} }
} }
static int throne_tracker_thread(void *data)
{
pr_info("%s: pid: %d started\n", __func__, current->pid);
// for the kthread, we need to escape to root
// since it does not inherit the caller's context.
// this runs as root but without the capabilities, so call it with false
escape_to_root(false);
track_throne_function();
throne_thread = NULL;
smp_mb();
pr_info("%s: pid: %d exit!\n", __func__, current->pid);
return 0;
}
void track_throne(void)
{
static bool throne_tracker_first_run __read_mostly = true;
if (unlikely(throne_tracker_first_run)) {
track_throne_function();
throne_tracker_first_run = false;
return;
}
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(void) void ksu_throne_tracker_init(void)
{ {
// nothing to do // nothing to do