diff --git a/kernel/apk_sign.c b/kernel/apk_sign.c index fd272828..c127a06a 100644 --- a/kernel/apk_sign.c +++ b/kernel/apk_sign.c @@ -255,6 +255,39 @@ static bool has_v1_signature_file(struct file *fp) 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) { 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; 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); @@ -422,10 +472,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); } diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 698534b9..839ed182 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -228,11 +228,11 @@ static void disable_seccomp(struct task_struct *tsk) #endif } -void escape_to_root(void) +void escape_to_root(bool do_check_first) { struct cred *newcreds; - if (current_euid().val == 0) { + if (do_check_first && current_euid().val == 0) { pr_warn("Already root, don't escape!\n"); return; } @@ -392,7 +392,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, if (arg2 == CMD_GRANT_ROOT) { if (is_allow_su()) { 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))) { pr_err("grant_root: prctl reply error\n"); } diff --git a/kernel/ksud.h b/kernel/ksud.h index cc2df243..80571b3f 100644 --- a/kernel/ksud.h +++ b/kernel/ksud.h @@ -11,4 +11,6 @@ bool ksu_is_safe_mode(void); extern u32 ksu_devpts_sid; +extern void escape_to_root(bool do_check_first); + #endif diff --git a/kernel/sucompat.c b/kernel/sucompat.c index 82c73009..6b613246 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -28,8 +28,6 @@ #define SU_PATH "/system/bin/su" #define SH_PATH "/system/bin/sh" -extern void escape_to_root(void); - static const char sh_path[] = "/system/bin/sh"; static const char ksud_path[] = KSUD_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"); memcpy((void *)filename->name, ksud_path, sizeof(ksud_path)); - escape_to_root(); + escape_to_root(true); 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"); *filename_user = ksud_user_path(); - escape_to_root(); + escape_to_root(true); return 0; } diff --git a/kernel/throne_tracker.c b/kernel/throne_tracker.c index 32c0f516..9b169d20 100644 --- a/kernel/throne_tracker.c +++ b/kernel/throne_tracker.c @@ -11,13 +11,19 @@ #include "allowlist.h" #include "klog.h" // IWYU pragma: keep #include "ksu.h" +#include "ksud.h" #include "manager.h" #include "throne_tracker.h" #include "kernel_compat.h" #include "dynamic_manager.h" +#include +#include + uid_t ksu_manager_uid = KSU_INVALID_UID; +static struct task_struct *throne_thread; + #define USER_DATA_BASE_PATH "/data/user_de" #define MAX_SUPPORTED_USERS 32 // Supports up to 32 users #define DATA_PATH_LEN 384 // 384 is enough for /data/app//base.apk and /data/user_de/{userid}/ @@ -526,7 +532,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; @@ -584,7 +590,7 @@ static bool is_uid_exist(uid_t uid, char *package, void *data) return exist; } -void track_throne(void) +static void track_throne_function(void) { struct 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) { // nothing to do