#include "linux/compiler.h" #include "linux/cred.h" #include "linux/printk.h" #include "selinux/selinux.h" #include #include #include #include #include #include #include #include #include "allowlist.h" #include "arch.h" #include "klog.h" // IWYU pragma: keep #include "syscall_hook_manager.h" #include "sucompat.h" #include "setuid_hook.h" #include "selinux/selinux.h" // Tracepoint registration count management static int tracepoint_reg_count = 0; static DEFINE_SPINLOCK(tracepoint_reg_lock); // Process marking management static void handle_process_mark(bool mark) { struct task_struct *p, *t; read_lock(&tasklist_lock); for_each_process_thread(p, t) { if (mark) ksu_set_task_tracepoint_flag(t); else ksu_clear_task_tracepoint_flag(t); } read_unlock(&tasklist_lock); } void ksu_mark_all_process(void) { handle_process_mark(true); pr_info("hook_manager: mark all user process done!\n"); } void ksu_unmark_all_process(void) { handle_process_mark(false); pr_info("hook_manager: unmark all user process done!\n"); } void ksu_mark_running_process(void) { struct task_struct *p, *t; read_lock(&tasklist_lock); for_each_process_thread (p, t) { if (!t->mm) { // only user processes continue; } int uid = task_uid(t).val; const struct cred *cred = get_task_cred(t); bool ksu_root_process = uid == 0 && is_task_ksu_domain(cred); bool is_zygote_process = is_zygote(cred); bool is_shell = uid == 2000; // before boot completed, we shall mark init for marking zygote bool is_init = t->pid == 1; if (ksu_root_process || is_zygote_process || is_shell || is_init || ksu_is_allow_uid(uid)) { ksu_set_task_tracepoint_flag(t); pr_info("hook_manager: mark process: pid:%d, uid: %d, comm:%s\n", t->pid, uid, t->comm); } put_cred(cred); } read_unlock(&tasklist_lock); } // Get task mark status // Returns: 1 if marked, 0 if not marked, -ESRCH if task not found int ksu_get_task_mark(pid_t pid) { struct task_struct *task; int marked = -ESRCH; rcu_read_lock(); task = find_task_by_vpid(pid); if (task) { get_task_struct(task); rcu_read_unlock(); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) marked = test_task_syscall_work(task, SYSCALL_TRACEPOINT) ? 1 : 0; #else marked = test_tsk_thread_flag(task, TIF_SYSCALL_TRACEPOINT) ? 1 : 0; #endif put_task_struct(task); } else { rcu_read_unlock(); } return marked; } // Set task mark status // Returns: 0 on success, -ESRCH if task not found int ksu_set_task_mark(pid_t pid, bool mark) { struct task_struct *task; int ret = -ESRCH; rcu_read_lock(); task = find_task_by_vpid(pid); if (task) { get_task_struct(task); rcu_read_unlock(); if (mark) { ksu_set_task_tracepoint_flag(task); pr_info("hook_manager: marked task pid=%d comm=%s\n", pid, task->comm); } else { ksu_clear_task_tracepoint_flag(task); pr_info("hook_manager: unmarked task pid=%d comm=%s\n", pid, task->comm); } put_task_struct(task); ret = 0; } else { rcu_read_unlock(); } return ret; } #ifdef CONFIG_KRETPROBES static struct kretprobe *init_kretprobe(const char *name, kretprobe_handler_t handler) { struct kretprobe *rp = kzalloc(sizeof(struct kretprobe), GFP_KERNEL); if (!rp) return NULL; rp->kp.symbol_name = name; rp->handler = handler; rp->data_size = 0; rp->maxactive = 0; int ret = register_kretprobe(rp); pr_info("hook_manager: register_%s kretprobe: %d\n", name, ret); if (ret) { kfree(rp); return NULL; } return rp; } static void destroy_kretprobe(struct kretprobe **rp_ptr) { struct kretprobe *rp = *rp_ptr; if (!rp) return; unregister_kretprobe(rp); synchronize_rcu(); kfree(rp); *rp_ptr = NULL; } static int syscall_regfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { unsigned long flags; spin_lock_irqsave(&tracepoint_reg_lock, flags); if (tracepoint_reg_count < 1) { // while install our tracepoint, mark our processes ksu_unmark_all_process(); ksu_mark_running_process(); } else { // while installing other tracepoint, mark all processes ksu_mark_all_process(); } tracepoint_reg_count++; spin_unlock_irqrestore(&tracepoint_reg_lock, flags); return 0; } static int syscall_unregfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { unsigned long flags; spin_lock_irqsave(&tracepoint_reg_lock, flags); if (tracepoint_reg_count <= 1) { // while uninstall our tracepoint, unmark all processes ksu_unmark_all_process(); } else { // while uninstalling other tracepoint, mark our processes ksu_unmark_all_process(); ksu_mark_running_process(); } tracepoint_reg_count--; spin_unlock_irqrestore(&tracepoint_reg_lock, flags); return 0; } static struct kretprobe *syscall_regfunc_rp = NULL; static struct kretprobe *syscall_unregfunc_rp = NULL; #endif static inline bool check_syscall_fastpath(int nr) { switch (nr) { case __NR_newfstatat: case __NR_faccessat: case __NR_execve: case __NR_setresuid: case __NR_faccessat2: case __NR_execveat: case __NR_clone: case __NR_clone3: return true; default: return false; } } int ksu_handle_init_mark_tracker(int *fd, const char __user **filename_user, void *__never_use_argv, void *__never_use_envp, int *__never_use_flags) { char path[64]; if (unlikely(!filename_user)) return 0; memset(path, 0, sizeof(path)); strncpy_from_user_nofault(path, *filename_user, sizeof(path)); if (likely(strstr(path, "adbd") == NULL)){ ksu_clear_task_tracepoint_flag(current); } return 0; } #include "ksud.h" #ifdef CONFIG_KSU_MANUAL_SU #include "manual_su.h" #endif #ifdef CONFIG_COMPAT bool ksu_is_compat __read_mostly = false; #endif #ifndef LOOKUP_FOLLOW #define LOOKUP_FOLLOW 0x0001 #endif static inline void ksu_handle_inode_permission(struct pt_regs *regs) { struct inode *inode = NULL; struct path path; int dfd = (int)PT_REGS_PARM1(regs); const char __user *filename = (const char __user *)PT_REGS_PARM2(regs); if (!user_path_at(dfd, filename, LOOKUP_FOLLOW, &path)) { inode = path.dentry->d_inode; if (inode && inode->i_sb && unlikely(inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) __ksu_handle_devpts(inode); path_put(&path); } } static inline void ksu_handle_bprm_check_security(struct pt_regs *regs, long id) { const char __user *filename; char path_buf[256]; if (id == __NR_execve) filename = (const char __user *)PT_REGS_PARM1(regs); else /* __NR_execveat */ filename = (const char __user *)PT_REGS_PARM2(regs); if (!ksu_execveat_hook) return; memset(path_buf, 0, sizeof(path_buf)); strncpy_from_user_nofault(path_buf, filename, sizeof(path_buf)); #ifdef CONFIG_COMPAT static bool compat_check_done __read_mostly = false; if (unlikely(!compat_check_done) && unlikely(!strcmp(path_buf, "/data/adb/ksud"))) { char buf[4]; struct file *file = filp_open(path_buf, O_RDONLY, 0); if (!IS_ERR(file)) { loff_t pos = 0; kernel_read(file, buf, 4, &pos); if (!memcmp(buf, "\x7f\x45\x4c\x46", 4)) { char elf_class; pos = 4; kernel_read(file, &elf_class, 1, &pos); if (elf_class == 0x01) ksu_is_compat = true; pr_info("%s: %s ELF magic found! ksu_is_compat: %d\n", __func__, path_buf, ksu_is_compat); compat_check_done = true; } filp_close(file, NULL); } } #endif ksu_handle_pre_ksud(path_buf); } static inline void ksu_handle_task_alloc(struct pt_regs *regs) { #ifdef CONFIG_KSU_MANUAL_SU ksu_try_escalate_for_uid(current_uid().val); #endif } #ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS // Generic sys_enter handler that dispatches to specific handlers static void ksu_sys_enter_handler(void *data, struct pt_regs *regs, long id) { if (unlikely(check_syscall_fastpath(id))) { #ifdef KSU_TP_HOOK if (ksu_su_compat_enabled) { // Handle newfstatat if (id == __NR_newfstatat) { int *dfd = (int *)&PT_REGS_PARM1(regs); const char __user **filename_user = (const char __user **)&PT_REGS_PARM2(regs); int *flags = (int *)&PT_REGS_SYSCALL_PARM4(regs); ksu_handle_stat(dfd, filename_user, flags); return; } // Handle faccessat if (id == __NR_faccessat) { int *dfd = (int *)&PT_REGS_PARM1(regs); const char __user **filename_user = (const char __user **)&PT_REGS_PARM2(regs); int *mode = (int *)&PT_REGS_PARM3(regs); ksu_handle_faccessat(dfd, filename_user, mode, NULL); return; } // Handle execve if (id == __NR_execve) { const char __user **filename_user = (const char __user **)&PT_REGS_PARM1(regs); if (current->pid == 1) { ksu_handle_init_mark_tracker(AT_FDCWD, filename_user, NULL, NULL, NULL); } else { ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL, NULL); } return; } } #endif // Handle setresuid if (id == __NR_setresuid) { uid_t ruid = (uid_t)PT_REGS_PARM1(regs); uid_t euid = (uid_t)PT_REGS_PARM2(regs); uid_t suid = (uid_t)PT_REGS_PARM3(regs); ksu_handle_setresuid(ruid, euid, suid); return; } // Handle inode_permission via faccessat if (id == __NR_faccessat || id == __NR_faccessat2) return ksu_handle_inode_permission(regs); // Handle bprm_check_security via execve/execveat if (id == __NR_execve || id == __NR_execveat) return ksu_handle_bprm_check_security(regs, id); #ifdef CONFIG_KSU_MANUAL_SU // Handle task_alloc via clone/fork if (id == __NR_clone || id == __NR_clone3) return ksu_handle_task_alloc(regs); #endif } } #endif void ksu_syscall_hook_manager_init(void) { int ret; pr_info("hook_manager: ksu_hook_manager_init called\n"); #ifdef CONFIG_KRETPROBES // Register kretprobe for syscall_regfunc syscall_regfunc_rp = init_kretprobe("syscall_regfunc", syscall_regfunc_handler); // Register kretprobe for syscall_unregfunc syscall_unregfunc_rp = init_kretprobe("syscall_unregfunc", syscall_unregfunc_handler); #endif #ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS ret = register_trace_sys_enter(ksu_sys_enter_handler, NULL); #ifndef CONFIG_KRETPROBES ksu_unmark_all_process(); ksu_mark_running_process(); #endif if (ret) { pr_err("hook_manager: failed to register sys_enter tracepoint: %d\n", ret); } else { pr_info("hook_manager: sys_enter tracepoint registered\n"); } #endif ksu_setuid_hook_init(); ksu_sucompat_init(); } void ksu_syscall_hook_manager_exit(void) { pr_info("hook_manager: ksu_hook_manager_exit called\n"); #ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS unregister_trace_sys_enter(ksu_sys_enter_handler, NULL); tracepoint_synchronize_unregister(); pr_info("hook_manager: sys_enter tracepoint unregistered\n"); #endif #ifdef CONFIG_KRETPROBES destroy_kretprobe(&syscall_regfunc_rp); destroy_kretprobe(&syscall_unregfunc_rp); #endif ksu_sucompat_exit(); ksu_setuid_hook_exit(); }