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`
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-24 01:50:49 +08:00
parent 23dde3f863
commit 86ccca18eb
4 changed files with 130 additions and 2 deletions

View File

@@ -59,6 +59,15 @@ config KSU_LSM_SECURITY_HOOKS
Disabling this is mostly only useful for kernel 4.1 and older.
Make sure to implement manual hooks on security/security.c.
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

View File

@@ -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;
@@ -265,6 +266,23 @@ check_v2_signature(char *path, bool check_multi_manager, int *signature_index)
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);
@@ -420,10 +438,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);
}

View File

@@ -17,8 +17,13 @@
#include "dynamic_manager.h"
#include "throne_comm.h"
#include <linux/kthread.h>
#include <linux/sched.h>
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
@@ -547,7 +552,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
if (!stop) {
file = ksu_filp_open_compat(
pos->dirpath, O_RDONLY | O_NOFOLLOW, 0);
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));
@@ -613,7 +618,7 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
return exist;
}
void track_throne(void)
static void track_throne_function()
{
struct list_head uid_list;
INIT_LIST_HEAD(&uid_list);
@@ -695,6 +700,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(void)
{
#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(void)
{
// nothing to do

View File

@@ -7,4 +7,37 @@ void ksu_throne_tracker_exit(void);
void track_throne(void);
/*
* 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