diff --git a/kernel/Makefile b/kernel/Makefile index d70c52e5..c25ca643 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -1,18 +1,22 @@ kernelsu-objs := ksu.o kernelsu-objs += allowlist.o +kernelsu-objs += app_profile.o kernelsu-objs += dynamic_manager.o kernelsu-objs += apk_sign.o kernelsu-objs += sucompat.o +kernelsu-objs += syscall_hook_manager.o +kernelsu-objs += throne_tracker.o kernelsu-objs += pkg_observer.o -kernelsu-objs += core_hook.o +kernelsu-objs += umount_manager.o +kernelsu-objs += kprobe_hook_manager.o +kernelsu-objs += setuid_hook.o +kernelsu-objs += kernel_umount.o kernelsu-objs += supercalls.o kernelsu-objs += feature.o kernelsu-objs += ksud.o kernelsu-objs += embed_ksud.o kernelsu-objs += seccomp_cache.o -kernelsu-objs += kernel_compat.o kernelsu-objs += file_wrapper.o -kernelsu-objs += throne_tracker.o kernelsu-objs += throne_comm.o kernelsu-objs += sulog.o diff --git a/kernel/allowlist.c b/kernel/allowlist.c index 947048f2..52538ee0 100644 --- a/kernel/allowlist.c +++ b/kernel/allowlist.c @@ -14,7 +14,6 @@ #include #endif -#include "ksu.h" #include "klog.h" // IWYU pragma: keep #include "selinux/selinux.h" #include "allowlist.h" diff --git a/kernel/allowlist.h b/kernel/allowlist.h index 3494d920..bb099b25 100644 --- a/kernel/allowlist.h +++ b/kernel/allowlist.h @@ -2,7 +2,7 @@ #define __KSU_H_ALLOWLIST #include -#include "ksu.h" +#include "app_profile.h" void ksu_allowlist_init(void); diff --git a/kernel/app_profile.c b/kernel/app_profile.c new file mode 100644 index 00000000..e8f040ed --- /dev/null +++ b/kernel/app_profile.c @@ -0,0 +1,277 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "app_profile.h" +#include "klog.h" // IWYU pragma: keep +#include "selinux/selinux.h" +#include "syscall_hook_manager.h" +#include "sucompat.h" + +#include "sulog.h" +#include "kprobe_hook_manager.h" + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0) + static struct group_info root_groups = { .usage = REFCOUNT_INIT(2), }; +#else + static struct group_info root_groups = { .usage = ATOMIC_INIT(2) }; +#endif + +static void setup_groups(struct root_profile *profile, struct cred *cred) +{ + if (profile->groups_count > KSU_MAX_GROUPS) { + pr_warn("Failed to setgroups, too large group: %d!\n", + profile->uid); + return; + } + + if (profile->groups_count == 1 && profile->groups[0] == 0) { + // setgroup to root and return early. + if (cred->group_info) + put_group_info(cred->group_info); + cred->group_info = get_group_info(&root_groups); + return; + } + + u32 ngroups = profile->groups_count; + struct group_info *group_info = groups_alloc(ngroups); + if (!group_info) { + pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid); + return; + } + + int i; + for (i = 0; i < ngroups; i++) { + gid_t gid = profile->groups[i]; + kgid_t kgid = make_kgid(current_user_ns(), gid); + if (!gid_valid(kgid)) { + pr_warn("Failed to setgroups, invalid gid: %d\n", gid); + put_group_info(group_info); + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) + group_info->gid[i] = kgid; +#else + GROUP_AT(group_info, i) = kgid; +#endif + } + + groups_sort(group_info); + set_groups(cred, group_info); + put_group_info(group_info); +} + +static void disable_seccomp() +{ + assert_spin_locked(¤t->sighand->siglock); + // disable seccomp +#if defined(CONFIG_GENERIC_ENTRY) && \ + LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) + clear_syscall_work(SECCOMP); +#else + clear_thread_flag(TIF_SECCOMP); +#endif + +#ifdef CONFIG_SECCOMP + current->seccomp.mode = 0; + current->seccomp.filter = NULL; +#else +#endif +} + +void escape_with_root_profile(void) +{ + struct cred *cred; + struct task_struct *p = current; + struct task_struct *t; + + cred = prepare_creds(); + if (!cred) { + pr_warn("prepare_creds failed!\n"); + return; + } + + if (cred->euid.val == 0) { + pr_warn("Already root, don't escape!\n"); +#if __SULOG_GATE + ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root_failed"); +#endif + abort_creds(cred); + return; + } + + struct root_profile *profile = ksu_get_root_profile(cred->uid.val); + + cred->uid.val = profile->uid; + cred->suid.val = profile->uid; + cred->euid.val = profile->uid; + cred->fsuid.val = profile->uid; + + cred->gid.val = profile->gid; + cred->fsgid.val = profile->gid; + cred->sgid.val = profile->gid; + cred->egid.val = profile->gid; + cred->securebits = 0; + + BUILD_BUG_ON(sizeof(profile->capabilities.effective) != + sizeof(kernel_cap_t)); + + // setup capabilities + // we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process + // we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec! + u64 cap_for_ksud = + profile->capabilities.effective | CAP_DAC_READ_SEARCH; + memcpy(&cred->cap_effective, &cap_for_ksud, + sizeof(cred->cap_effective)); + memcpy(&cred->cap_permitted, &profile->capabilities.effective, + sizeof(cred->cap_permitted)); + memcpy(&cred->cap_bset, &profile->capabilities.effective, + sizeof(cred->cap_bset)); + + setup_groups(profile, cred); + + commit_creds(cred); + + // Refer to kernel/seccomp.c: seccomp_set_mode_strict + // When disabling Seccomp, ensure that current->sighand->siglock is held during the operation. + spin_lock_irq(¤t->sighand->siglock); + disable_seccomp(); + spin_unlock_irq(¤t->sighand->siglock); + + setup_selinux(profile->selinux_domain); +#if __SULOG_GATE + ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root"); +#endif + + for_each_thread (p, t) { + ksu_set_task_tracepoint_flag(t); + } +} + +#ifdef CONFIG_KSU_MANUAL_SU + +static void disable_seccomp_for_task(struct task_struct *tsk) +{ + if (!tsk->seccomp.filter && tsk->seccomp.mode == SECCOMP_MODE_DISABLED) + return; + + if (WARN_ON(!spin_is_locked(&tsk->sighand->siglock))) + return; + +#ifdef CONFIG_SECCOMP + tsk->seccomp.mode = 0; + if (tsk->seccomp.filter) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) + seccomp_filter_release(tsk); + atomic_set(&tsk->seccomp.filter_count, 0); +#else + // for 6.11+ kernel support? +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + put_seccomp_filter(tsk); +#endif + tsk->seccomp.filter = NULL; +#endif + } +#endif +} + +void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid) +{ + struct cred *newcreds; + struct task_struct *target_task; + struct task_struct *p = current; + struct task_struct *t; + + pr_info("cmd_su: escape_to_root_for_cmd_su called for UID: %d, PID: %d\n", target_uid, target_pid); + + // Find target task by PID + rcu_read_lock(); + target_task = pid_task(find_vpid(target_pid), PIDTYPE_PID); + if (!target_task) { + rcu_read_unlock(); + pr_err("cmd_su: target task not found for PID: %d\n", target_pid); +#if __SULOG_GATE + ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found"); +#endif + return; + } + get_task_struct(target_task); + rcu_read_unlock(); + + if (task_uid(target_task).val == 0) { + pr_warn("cmd_su: target task is already root, PID: %d\n", target_pid); + put_task_struct(target_task); + return; + } + + newcreds = prepare_kernel_cred(target_task); + if (newcreds == NULL) { + pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid); +#if __SULOG_GATE + ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed"); +#endif + put_task_struct(target_task); + return; + } + + struct root_profile *profile = ksu_get_root_profile(target_uid); + + newcreds->uid.val = profile->uid; + newcreds->suid.val = profile->uid; + newcreds->euid.val = profile->uid; + newcreds->fsuid.val = profile->uid; + + newcreds->gid.val = profile->gid; + newcreds->fsgid.val = profile->gid; + newcreds->sgid.val = profile->gid; + newcreds->egid.val = profile->gid; + newcreds->securebits = 0; + + u64 cap_for_cmd_su = profile->capabilities.effective | CAP_DAC_READ_SEARCH | CAP_SETUID | CAP_SETGID; + memcpy(&newcreds->cap_effective, &cap_for_cmd_su, sizeof(newcreds->cap_effective)); + memcpy(&newcreds->cap_permitted, &profile->capabilities.effective, sizeof(newcreds->cap_permitted)); + memcpy(&newcreds->cap_bset, &profile->capabilities.effective, sizeof(newcreds->cap_bset)); + + setup_groups(profile, newcreds); + task_lock(target_task); + + const struct cred *old_creds = get_task_cred(target_task); + + rcu_assign_pointer(target_task->real_cred, newcreds); + rcu_assign_pointer(target_task->cred, get_cred(newcreds)); + task_unlock(target_task); + + if (target_task->sighand) { + spin_lock_irq(&target_task->sighand->siglock); + disable_seccomp_for_task(target_task); + spin_unlock_irq(&target_task->sighand->siglock); + } + + setup_selinux(profile->selinux_domain); + put_cred(old_creds); + wake_up_process(target_task); + + if (target_task->signal->tty) { + struct inode *inode = target_task->signal->tty->driver_data; + if (inode && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) { + __ksu_handle_devpts(inode); + } + } + + put_task_struct(target_task); +#if __SULOG_GATE + ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation"); +#endif + for_each_thread (p, t) { + ksu_set_task_tracepoint_flag(t); + } + pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid); +} +#endif diff --git a/kernel/app_profile.h b/kernel/app_profile.h new file mode 100644 index 00000000..5390ecc4 --- /dev/null +++ b/kernel/app_profile.h @@ -0,0 +1,68 @@ +#ifndef __KSU_H_APP_PROFILE +#define __KSU_H_APP_PROFILE + +#include + +// Forward declarations +struct cred; + +#define KSU_APP_PROFILE_VER 2 +#define KSU_MAX_PACKAGE_NAME 256 +// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. +#define KSU_MAX_GROUPS 32 +#define KSU_SELINUX_DOMAIN 64 + +struct root_profile { + int32_t uid; + int32_t gid; + + int32_t groups_count; + int32_t groups[KSU_MAX_GROUPS]; + + // kernel_cap_t is u32[2] for capabilities v3 + struct { + u64 effective; + u64 permitted; + u64 inheritable; + } capabilities; + + char selinux_domain[KSU_SELINUX_DOMAIN]; + + int32_t namespaces; +}; + +struct non_root_profile { + bool umount_modules; +}; + +struct app_profile { + // It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this. + u32 version; + + // this is usually the package of the app, but can be other value for special apps + char key[KSU_MAX_PACKAGE_NAME]; + int32_t current_uid; + bool allow_su; + + union { + struct { + bool use_default; + char template_name[KSU_MAX_PACKAGE_NAME]; + + struct root_profile profile; + } rp_config; + + struct { + bool use_default; + + struct non_root_profile profile; + } nrp_config; + }; +}; + +// Escalate current process to root with the appropriate profile +void escape_with_root_profile(void); + +void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid); + +#endif diff --git a/kernel/arch.h b/kernel/arch.h index 66d8d59a..905bd432 100644 --- a/kernel/arch.h +++ b/kernel/arch.h @@ -22,9 +22,6 @@ #define PRCTL_SYMBOL "__arm64_sys_prctl" #define REBOOT_SYMBOL "__arm64_sys_reboot" #define SYS_READ_SYMBOL "__arm64_sys_read" -#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat" -#define SYS_FSTATAT64_SYMBOL "__arm64_sys_fstatat64" -#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat" #define SYS_EXECVE_SYMBOL "__arm64_sys_execve" #define SYS_EXECVE_COMPAT_SYMBOL "__arm64_compat_sys_execve" #else @@ -62,9 +59,6 @@ #define PRCTL_SYMBOL "__x64_sys_prctl" #define REBOOT_SYMBOL "__x64_sys_reboot" #define SYS_READ_SYMBOL "__x64_sys_read" -#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat" -#define SYS_FSTATAT64_SYMBOL "__x64_sys_fstatat64" -#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat" #define SYS_EXECVE_SYMBOL "__x64_sys_execve" #define SYS_EXECVE_COMPAT_SYMBOL "__x64_compat_sys_execve" #else diff --git a/kernel/core_hook.h b/kernel/core_hook.h deleted file mode 100644 index 47405fa9..00000000 --- a/kernel/core_hook.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef __KSU_H_KSU_CORE -#define __KSU_H_KSU_CORE - -#include -#include "apk_sign.h" - -void __init ksu_core_init(void); -void ksu_core_exit(void); - -void escape_to_root(void); - -void nuke_ext4_sysfs(void); - -extern bool ksu_module_mounted; - -#endif diff --git a/kernel/file_wrapper.c b/kernel/file_wrapper.c index 24ae979c..c5c105f9 100644 --- a/kernel/file_wrapper.c +++ b/kernel/file_wrapper.c @@ -10,14 +10,8 @@ #include #include -#include "allowlist.h" #include "klog.h" // IWYU pragma: keep -#include "ksu.h" -#include "ksud.h" -#include "manager.h" #include "selinux/selinux.h" -#include "core_hook.h" -#include "objsec.h" #include "file_wrapper.h" diff --git a/kernel/kernel_umount.c b/kernel/kernel_umount.c new file mode 100644 index 00000000..b9e5c1f4 --- /dev/null +++ b/kernel/kernel_umount.c @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kernel_umount.h" +#include "klog.h" // IWYU pragma: keep +#include "allowlist.h" +#include "selinux/selinux.h" +#include "feature.h" +#include "ksud.h" + +static bool ksu_kernel_umount_enabled = true; + +static int kernel_umount_feature_get(u64 *value) +{ + *value = ksu_kernel_umount_enabled ? 1 : 0; + return 0; +} + +static int kernel_umount_feature_set(u64 value) +{ + bool enable = value != 0; + ksu_kernel_umount_enabled = enable; + pr_info("kernel_umount: set to %d\n", enable); + return 0; +} + +static const struct ksu_feature_handler kernel_umount_handler = { + .feature_id = KSU_FEATURE_KERNEL_UMOUNT, + .name = "kernel_umount", + .get_handler = kernel_umount_feature_get, + .set_handler = kernel_umount_feature_set, +}; + +static bool should_umount(struct path *path) +{ + if (!path) { + return false; + } + + if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) { + pr_info("ignore global mnt namespace process: %d\n", current_uid().val); + return false; + } + + if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) { + const char *fstype = path->mnt->mnt_sb->s_type->name; + return strcmp(fstype, "overlay") == 0; + } + return false; +} + +extern int path_umount(struct path *path, int flags); + +static void ksu_umount_mnt(struct path *path, int flags) +{ + int err = path_umount(path, flags); + if (err) { + pr_info("umount %s failed: %d\n", path->dentry->d_iname, err); + } +} + +static void try_umount(const char *mnt, bool check_mnt, int flags) +{ + struct path path; + int err = kern_path(mnt, 0, &path); + if (err) { + return; + } + + if (path.dentry != path.mnt->mnt_root) { + // it is not root mountpoint, maybe umounted by others already. + path_put(&path); + return; + } + + // we are only interest in some specific mounts + if (check_mnt && !should_umount(&path)) { + path_put(&path); + return; + } + + ksu_umount_mnt(&path, flags); +} + +struct umount_tw { + struct callback_head cb; + const struct cred *old_cred; +}; + +#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT +static void umount_tw_func(struct callback_head *cb) +{ + struct umount_tw *tw = container_of(cb, struct umount_tw, cb); + const struct cred *saved = NULL; + if (tw->old_cred) { + saved = override_creds(tw->old_cred); + } + + uid_t uid = current_uid().val; + + // fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and + // filter the mountpoint whose target is `/data/adb` + try_umount("/odm", true, 0, uid); + try_umount("/system", true, 0, uid); + try_umount("/vendor", true, 0, uid); + try_umount("/product", true, 0, uid); + try_umount("/system_ext", true, 0, uid); + try_umount("/data/adb/modules", false, MNT_DETACH, uid); + try_umount("/data/adb/kpm", false, MNT_DETACH, uid); + + // try umount ksu temp path + try_umount("/debug_ramdisk", false, MNT_DETACH, uid); + try_umount("/sbin", false, MNT_DETACH, uid); + + // try umount lsposed dex2oat bins + try_umount("/system/etc/hosts", false, MNT_DETACH, uid); + + // try umount lsposed dex2oat bins + try_umount("/apex/com.android.art/bin/dex2oat64", false, MNT_DETACH, uid); + try_umount("/apex/com.android.art/bin/dex2oat32", false, MNT_DETACH, uid); + + if (saved) + revert_creds(saved); + + if (tw->old_cred) + put_cred(tw->old_cred); + + kfree(tw); +} +#else +static void umount_tw_func(struct callback_head *cb) +{ + struct umount_tw *tw = container_of(cb, struct umount_tw, cb); + const struct cred *saved = NULL; + if (tw->old_cred) { + saved = override_creds(tw->old_cred); + } + + // fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and + // filter the mountpoint whose target is `/data/adb` + try_umount("/odm", true, 0); + try_umount("/system", true, 0); + try_umount("/vendor", true, 0); + try_umount("/product", true, 0); + try_umount("/system_ext", true, 0); + try_umount("/data/adb/modules", false, MNT_DETACH); + try_umount("/data/adb/kpm", false, MNT_DETACH); + // try umount ksu temp path + try_umount("/debug_ramdisk", false, MNT_DETACH); + try_umount("/sbin", false, MNT_DETACH); + + try_umount("/system/etc/hosts", false, MNT_DETACH); + // try umount lsposed dex2oat bins + try_umount("/apex/com.android.art/bin/dex2oat64", false, MNT_DETACH); + try_umount("/apex/com.android.art/bin/dex2oat32", false, MNT_DETACH); + + if (saved) + revert_creds(saved); + + if (tw->old_cred) + put_cred(tw->old_cred); + + kfree(tw); +} + +static inline bool is_appuid(uid_t uid) +{ +#define PER_USER_RANGE 100000 +#define FIRST_APPLICATION_UID 10000 +#define LAST_APPLICATION_UID 19999 + + uid_t appid = uid % PER_USER_RANGE; + return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID; +} + +static inline bool is_unsupported_uid(uid_t uid) +{ +#define LAST_APPLICATION_UID 19999 + uid_t appid = uid % 100000; + return appid > LAST_APPLICATION_UID; +} + +int ksu_handle_umount(uid_t old_uid, uid_t new_uid) +{ + struct umount_tw *tw; + + // this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it! + if (!ksu_module_mounted) { + return 0; + } + + if (!ksu_kernel_umount_enabled) { + return 0; + } + + if (!is_appuid(new_uid) || is_unsupported_uid(new_uid)) { + pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid); + return 0; + } + + if (!ksu_uid_should_umount(new_uid)) { + return 0; + } else { + pr_info("uid: %d should not umount!\n", current_uid().val); + } + + // check old process's selinux context, if it is not zygote, ignore it! + // because some su apps may setuid to untrusted_app but they are in global mount namespace + // when we umount for such process, that is a disaster! + bool is_zygote_child = is_zygote(get_current_cred()); + if (!is_zygote_child) { + pr_info("handle umount ignore non zygote child: %d\n", current->pid); + return 0; + } + // umount the target mnt + pr_info("handle umount for uid: %d, pid: %d\n", new_uid, current->pid); + + tw = kmalloc(sizeof(*tw), GFP_ATOMIC); + if (!tw) + return 0; + + tw->old_cred = get_current_cred(); + tw->cb.func = umount_tw_func; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) + int err = task_work_add(current, &tw->cb, TWA_RESUME); +#else + int err = task_work_add(current, &tw->cb, true); +#endif + if (err) { + if (tw->old_cred) { + put_cred(tw->old_cred); + } + kfree(tw); + pr_warn("unmount add task_work failed\n"); + } + + return 0; +} + +void ksu_kernel_umount_init(void) +{ + int rc = 0; + rc = ksu_umount_manager_init(); + if (rc) { + pr_err("Failed to initialize umount manager: %d\n", rc); + } + if (ksu_register_feature_handler(&kernel_umount_handler)) { + pr_err("Failed to register kernel_umount feature handler\n"); + } +} + +void ksu_kernel_umount_exit(void) +{ + ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT); +} \ No newline at end of file diff --git a/kernel/kernel_umount.h b/kernel/kernel_umount.h new file mode 100644 index 00000000..4c7a158c --- /dev/null +++ b/kernel/kernel_umount.h @@ -0,0 +1,12 @@ +#ifndef __KSU_H_KERNEL_UMOUNT +#define __KSU_H_KERNEL_UMOUNT + +#include + +void ksu_kernel_umount_init(void); +void ksu_kernel_umount_exit(void); + +// Handler function to be called from setresuid hook +int ksu_handle_umount(uid_t old_uid, uid_t new_uid); + +#endif \ No newline at end of file diff --git a/kernel/kprobe_hook_manager.c b/kernel/kprobe_hook_manager.c new file mode 100644 index 00000000..7326aa5d --- /dev/null +++ b/kernel/kprobe_hook_manager.c @@ -0,0 +1,163 @@ +#include "kprobe_hook_manager.h" +#include +#include +#include +#include +#include +#include +#include + +#include "arch.h" +#include "klog.h" +#include "ksud.h" + +#ifdef CONFIG_KSU_MANUAL_SU +#include "manual_su.h" +#endif + +#ifdef CONFIG_COMPAT +bool ksu_is_compat __read_mostly = false; +#endif + +#ifdef CONFIG_KSU_MANUAL_SU +static void ksu_try_escalate_for_uid(uid_t uid) +{ + if (!is_pending_root(uid)) + return; + + pr_info("pending_root: UID=%d temporarily allowed\n", uid); + remove_pending_root(uid); +} +#endif + +// inode_permission hook for handling devpts +static int ksu_inode_permission_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct inode *inode = (struct inode *)PT_REGS_PARM1(regs); + + if (inode && inode->i_sb && unlikely(inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) { + // pr_info("%s: handling devpts for: %s \n", __func__, current->comm); + __ksu_handle_devpts(inode); + } + + return 0; +} + +static struct kprobe ksu_inode_permission_kp = { + .symbol_name = "security_inode_permission", + .pre_handler = ksu_inode_permission_handler_pre, +}; + + +// bprm_check_security hook for handling ksud compatibility +static int ksu_bprm_check_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct linux_binprm *bprm = (struct linux_binprm *)PT_REGS_PARM1(regs); + char *filename = (char *)bprm->filename; + + if (likely(!ksu_execveat_hook)) + return 0; + +#ifdef CONFIG_COMPAT + static bool compat_check_done __read_mostly = false; + if (unlikely(!compat_check_done) && unlikely(!strcmp(filename, "/data/adb/ksud")) + && !memcmp(bprm->buf, "\x7f\x45\x4c\x46", 4)) { + if (bprm->buf[4] == 0x01) + ksu_is_compat = true; + + pr_info("%s: %s ELF magic found! ksu_is_compat: %d \n", __func__, filename, ksu_is_compat); + compat_check_done = true; + } +#endif + + ksu_handle_pre_ksud(filename); + +#ifdef CONFIG_KSU_MANUAL_SU + ksu_try_escalate_for_uid(current_uid().val); +#endif + + return 0; +} + +static struct kprobe ksu_bprm_check_kp = { + .symbol_name = "security_bprm_check", + .pre_handler = ksu_bprm_check_handler_pre, +}; + +#ifdef CONFIG_KSU_MANUAL_SU +// task_alloc hook for handling manual su escalation +static int ksu_task_alloc_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct task_struct *task = (struct task_struct *)PT_REGS_PARM1(regs); + + ksu_try_escalate_for_uid(task_uid(task).val); + return 0; +} + +static struct kprobe ksu_task_alloc_kp = { + .symbol_name = "security_task_alloc", + .pre_handler = ksu_task_alloc_handler_pre, +}; +#endif + +__maybe_unused int ksu_kprobe_init(void) +{ + int rc = 0; + + // Register inode_permission kprobe + rc = register_kprobe(&ksu_inode_permission_kp); + if (rc) { + pr_err("inode_permission kprobe failed: %d\n", rc); + } else { + pr_info("inode_permission kprobe registered successfully\n"); + } + + // Register bprm_check_security kprobe + rc = register_kprobe(&ksu_bprm_check_kp); + if (rc) { + pr_err("bprm_check_security kprobe failed: %d\n", rc); + } else { + pr_info("bprm_check_security kprobe registered successfully\n"); + } + +#ifdef CONFIG_KSU_MANUAL_SU + // Register task_alloc kprobe + rc = register_kprobe(&ksu_task_alloc_kp); + if (rc) { + pr_err("task_alloc kprobe failed: %d\n", rc); + } else { + pr_info("task_alloc kprobe registered successfully\n"); + } +#endif + + return 0; +} + +__maybe_unused int ksu_kprobe_exit(void) +{ + unregister_kprobe(&ksu_inode_permission_kp); + unregister_kprobe(&ksu_bprm_check_kp); +#ifdef CONFIG_KSU_MANUAL_SU + unregister_kprobe(&ksu_task_alloc_kp); +#endif + return 0; +} + +void ksu_kprobe_hook_init(void) +{ + int rc = 0; +#ifdef CONFIG_KPROBES + rc = ksu_kprobe_init(); + if (rc) { + pr_err("ksu_kprobe_init failed: %d\n", rc); + } +#endif +} + +void ksu_kprobe_hook_exit(void) +{ +#ifdef CONFIG_KPROBES + pr_info("ksu_core_exit\n"); + ksu_kprobe_exit(); +#endif +} \ No newline at end of file diff --git a/kernel/kprobe_hook_manager.h b/kernel/kprobe_hook_manager.h new file mode 100644 index 00000000..2a610cc1 --- /dev/null +++ b/kernel/kprobe_hook_manager.h @@ -0,0 +1,23 @@ +#ifndef __KSU_H_KSU_KBROBES_HOOK_MANAGER +#define __KSU_H_KSU_KBROBES_HOOK_MANAGER + +#include +#include +#include +#include +#include +#include "selinux/selinux.h" +#include "objsec.h" + +#ifndef DEVPTS_SUPER_MAGIC +#define DEVPTS_SUPER_MAGIC 0x1cd1 +#endif + +extern bool ksu_is_compat __read_mostly; + +extern int __ksu_handle_devpts(struct inode *inode); // sucompat.c + +void ksu_kprobe_hook_init(void); +void ksu_kprobe_hook_exit(void); + +#endif \ No newline at end of file diff --git a/kernel/ksu.c b/kernel/ksu.c index 28802959..74fb2f1a 100644 --- a/kernel/ksu.c +++ b/kernel/ksu.c @@ -6,16 +6,18 @@ #include #include "allowlist.h" -#include "arch.h" -#include "core_hook.h" #include "feature.h" #include "klog.h" // IWYU pragma: keep -#include "ksu.h" #include "throne_tracker.h" -#include "sucompat.h" +#include "syscall_hook_manager.h" #include "ksud.h" #include "supercalls.h" +#include "sulog.h" +#include "throne_comm.h" +#include "dynamic_manager.h" +#include "kprobe_hook_manager.h" + #ifdef CONFIG_KSU_SUSFS #include #endif @@ -27,68 +29,90 @@ bool ksu_queue_work(struct work_struct *work) return queue_work(ksu_workqueue, work); } +void sukisu_custom_config_init(void) +{ + ksu_kprobe_hook_init(); +} + +void sukisu_custom_config_exit(void) +{ + ksu_kprobe_hook_exit(); + ksu_uid_exit(); + ksu_throne_comm_exit(); + ksu_dynamic_manager_exit(); +#if __SULOG_GATE + ksu_sulog_exit(); +#endif +} + int __init kernelsu_init(void) { #ifdef CONFIG_KSU_DEBUG - pr_alert("*************************************************************"); - pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); - pr_alert("** **"); - pr_alert("** You are running KernelSU in DEBUG mode **"); - pr_alert("** **"); - pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); - pr_alert("*************************************************************"); + pr_alert("*************************************************************"); + pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); + pr_alert("** **"); + pr_alert("** You are running KernelSU in DEBUG mode **"); + pr_alert("** **"); + pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); + pr_alert("*************************************************************"); #endif #ifdef CONFIG_KSU_SUSFS susfs_init(); #endif - ksu_feature_init(); + ksu_feature_init(); - ksu_supercalls_init(); + ksu_supercalls_init(); - ksu_core_init(); + sukisu_custom_config_init(); - ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0); + ksu_syscall_hook_manager_init(); - ksu_allowlist_init(); + ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0); - ksu_throne_tracker_init(); + ksu_allowlist_init(); - ksu_sucompat_init(); + ksu_throne_tracker_init(); #ifdef KSU_KPROBES_HOOK ksu_ksud_init(); #else - pr_debug("init ksu driver\n"); + pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html"); #endif #ifdef MODULE #ifndef CONFIG_KSU_DEBUG - kobject_del(&THIS_MODULE->mkobj.kobj); + kobject_del(&THIS_MODULE->mkobj.kobj); #endif #endif - return 0; + return 0; } +extern void ksu_observer_exit(void); void kernelsu_exit(void) { - ksu_allowlist_exit(); + ksu_allowlist_exit(); - ksu_throne_tracker_exit(); + ksu_observer_exit(); - ksu_observer_exit(); + ksu_throne_tracker_exit(); + + destroy_workqueue(ksu_workqueue); - destroy_workqueue(ksu_workqueue); - ksu_sucompat_exit(); #ifdef KSU_KPROBES_HOOK ksu_ksud_exit(); #endif - ksu_core_exit(); - ksu_feature_exit(); + ksu_syscall_hook_manager_exit(); + + sukisu_custom_config_exit(); + + ksu_supercalls_exit(); + + ksu_feature_exit(); } module_init(kernelsu_init); diff --git a/kernel/ksu.h b/kernel/ksu.h index f3693e1a..2ff1bd32 100644 --- a/kernel/ksu.h +++ b/kernel/ksu.h @@ -13,12 +13,6 @@ extern bool ksu_uid_scanner_enabled; #define EVENT_BOOT_COMPLETED 2 #define EVENT_MODULE_MOUNTED 3 -#define KSU_APP_PROFILE_VER 2 -#define KSU_MAX_PACKAGE_NAME 256 -// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. -#define KSU_MAX_GROUPS 32 -#define KSU_SELINUX_DOMAIN 64 - // SukiSU Ultra kernel su version full strings #ifndef KSU_VERSION_FULL #define KSU_VERSION_FULL "v3.x-00000000@unknown" @@ -47,54 +41,6 @@ struct manager_list_info { } managers[2]; }; -struct root_profile { - int32_t uid; - int32_t gid; - - int32_t groups_count; - int32_t groups[KSU_MAX_GROUPS]; - - // kernel_cap_t is u32[2] for capabilities v3 - struct { - u64 effective; - u64 permitted; - u64 inheritable; - } capabilities; - - char selinux_domain[KSU_SELINUX_DOMAIN]; - - int32_t namespaces; -}; - -struct non_root_profile { - bool umount_modules; -}; - -struct app_profile { - // It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this. - u32 version; - - // this is usually the package of the app, but can be other value for special apps - char key[KSU_MAX_PACKAGE_NAME]; - int32_t current_uid; - bool allow_su; - - union { - struct { - bool use_default; - char template_name[KSU_MAX_PACKAGE_NAME]; - - struct root_profile profile; - } rp_config; - - struct { - bool use_default; - - struct non_root_profile profile; - } nrp_config; - }; -}; - bool ksu_queue_work(struct work_struct *work); static inline int startswith(char *s, char *prefix) diff --git a/kernel/ksud.c b/kernel/ksud.c index fefad007..0b3efa68 100644 --- a/kernel/ksud.c +++ b/kernel/ksud.c @@ -2,7 +2,6 @@ #include #include #include -#include "manager.h" #include #include #include @@ -23,8 +22,10 @@ #include #include #include +#include #include +#include "manager.h" #include "allowlist.h" #include "arch.h" #include "klog.h" // IWYU pragma: keep @@ -32,7 +33,10 @@ #include "kernel_compat.h" #include "selinux/selinux.h" -#include "sucompat.h" +#include "syscall_hook_manager.h" + +bool ksu_module_mounted __read_mostly = false; +bool ksu_boot_completed __read_mostly = false; static const char KERNEL_SU_RC[] = "\n" @@ -103,17 +107,44 @@ void on_post_fs_data(void) pr_info("ksu_file sid: %d\n", ksu_file_sid); } -struct user_arg_ptr { -#ifdef CONFIG_COMPAT - bool is_compat; +extern void ext4_unregister_sysfs(struct super_block *sb); +static void nuke_ext4_sysfs(void) +{ +#ifdef CONFIG_EXT4_FS + struct path path; + int err = kern_path("/data/adb/modules", 0, &path); + if (err) { + pr_err("nuke path err: %d\n", err); + return; + } + + struct super_block *sb = path.dentry->d_inode->i_sb; + const char *name = sb->s_type->name; + if (strcmp(name, "ext4") != 0) { + pr_info("nuke but module aren't mounted\n"); + path_put(&path); + return; + } + + ext4_unregister_sysfs(sb); + path_put(&path); #endif - union { - const char __user *const __user *native; -#ifdef CONFIG_COMPAT - const compat_uptr_t __user *compat; -#endif - } ptr; -}; +} + +void on_module_mounted(void){ + pr_info("on_module_mounted!\n"); + ksu_module_mounted = true; + nuke_ext4_sysfs(); +} + +void on_boot_completed(void){ + ksu_boot_completed = true; + pr_info("on_boot_completed!\n"); + // remark process, we don't want to mark other init + // forked process excepte zygote and adbd + ksu_unmark_all_process(); + ksu_mark_running_process(); +} static void on_post_fs_data_cbfun(struct callback_head *cb) { diff --git a/kernel/ksud.h b/kernel/ksud.h index 9a184114..4c9f6bc8 100644 --- a/kernel/ksud.h +++ b/kernel/ksud.h @@ -9,10 +9,14 @@ void ksu_ksud_init(); void ksu_ksud_exit(); void on_post_fs_data(void); +void on_module_mounted(void); +void on_boot_completed(void); bool ksu_is_safe_mode(void); extern u32 ksu_file_sid; +extern bool ksu_module_mounted; +extern bool ksu_boot_completed; extern bool ksu_execveat_hook __read_mostly; extern int ksu_handle_pre_ksud(const char *filename); diff --git a/kernel/manual_su.c b/kernel/manual_su.c index 856b9491..c721e430 100644 --- a/kernel/manual_su.c +++ b/kernel/manual_su.c @@ -13,8 +13,8 @@ #include "allowlist.h" #include "manager.h" #include "allowlist.h" +#include "app_profile.h" -extern void escape_to_root_for_cmd_su(uid_t, pid_t); static bool current_verified = false; static void ksu_cleanup_expired_tokens(void); static bool is_current_verified(void); diff --git a/kernel/selinux/rules.c b/kernel/selinux/rules.c index 659a9f97..018a1681 100644 --- a/kernel/selinux/rules.c +++ b/kernel/selinux/rules.c @@ -6,7 +6,7 @@ #include "selinux.h" #include "sepolicy.h" #include "ss/services.h" -#include "linux/lsm_audit.h" +#include "linux/lsm_audit.h" // IWYU pragma: keep #include "xfrm.h" #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) diff --git a/kernel/core_hook.c b/kernel/setuid_hook.c similarity index 51% rename from kernel/core_hook.c rename to kernel/setuid_hook.c index 32c3e1b2..5b322c33 100644 --- a/kernel/core_hook.c +++ b/kernel/setuid_hook.c @@ -51,21 +51,41 @@ #endif // #ifdef CONFIG_KSU_SUSFS #include "allowlist.h" -#include "arch.h" -#include "core_hook.h" +#include "setuid_hook.h" #include "feature.h" #include "klog.h" // IWYU pragma: keep #include "ksu.h" -#include "ksud.h" #include "manager.h" #include "selinux/selinux.h" -#include "kernel_compat.h" -#include "supercalls.h" -#include "sucompat.h" -#include "sulog.h" #include "seccomp_cache.h" +#include "supercalls.h" +#include "syscall_hook_manager.h" +#include "kernel_umount.h" -#include "throne_comm.h" +#include "sulog.h" + +static bool ksu_enhanced_security_enabled = false; + +static int enhanced_security_feature_get(u64 *value) +{ + *value = ksu_enhanced_security_enabled ? 1 : 0; + return 0; +} + +static int enhanced_security_feature_set(u64 value) +{ + bool enable = value != 0; + ksu_enhanced_security_enabled = enable; + pr_info("enhanced_security: set to %d\n", enable); + return 0; +} + +static const struct ksu_feature_handler enhanced_security_handler = { + .feature_id = KSU_FEATURE_ENHANCED_SECURITY, + .name = "enhanced_security", + .get_handler = enhanced_security_feature_get, + .set_handler = enhanced_security_feature_set, +}; #ifdef CONFIG_KSU_SUSFS bool susfs_is_boot_completed_triggered = false; @@ -148,74 +168,6 @@ static inline bool is_zygote_normal_app_uid(uid_t uid) #endif // #ifdef CONFIG_KSU_SUSFS -bool ksu_module_mounted __read_mostly = false; - -#ifdef CONFIG_COMPAT -bool ksu_is_compat __read_mostly = false; -#endif - -#ifndef DEVPTS_SUPER_MAGIC -#define DEVPTS_SUPER_MAGIC 0x1cd1 -#endif - -extern int __ksu_handle_devpts(struct inode *inode); // sucompat.c - -#ifdef CONFIG_KSU_MANUAL_SU -static void ksu_try_escalate_for_uid(uid_t uid) -{ - if (!is_pending_root(uid)) - return; - - pr_info("pending_root: UID=%d temporarily allowed\n", uid); - remove_pending_root(uid); -} -#endif - -static bool ksu_kernel_umount_enabled = true; -static bool ksu_enhanced_security_enabled = false; - -static int kernel_umount_feature_get(u64 *value) -{ - *value = ksu_kernel_umount_enabled ? 1 : 0; - return 0; -} - -static int kernel_umount_feature_set(u64 value) -{ - bool enable = value != 0; - ksu_kernel_umount_enabled = enable; - pr_info("kernel_umount: set to %d\n", enable); - return 0; -} - -static const struct ksu_feature_handler kernel_umount_handler = { - .feature_id = KSU_FEATURE_KERNEL_UMOUNT, - .name = "kernel_umount", - .get_handler = kernel_umount_feature_get, - .set_handler = kernel_umount_feature_set, -}; - -static int enhanced_security_feature_get(u64 *value) -{ - *value = ksu_enhanced_security_enabled ? 1 : 0; - return 0; -} - -static int enhanced_security_feature_set(u64 value) -{ - bool enable = value != 0; - ksu_enhanced_security_enabled = enable; - pr_info("enhanced_security: set to %d\n", enable); - return 0; -} - -static const struct ksu_feature_handler enhanced_security_handler = { - .feature_id = KSU_FEATURE_ENHANCED_SECURITY, - .name = "enhanced_security", - .get_handler = enhanced_security_feature_get, - .set_handler = enhanced_security_feature_set, -}; - static inline bool is_allow_su(void) { if (is_manager()) { @@ -232,309 +184,6 @@ static inline bool is_unsupported_uid(uid_t uid) return appid > LAST_APPLICATION_UID; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0) - static struct group_info root_groups = { .usage = REFCOUNT_INIT(2), }; -#else - static struct group_info root_groups = { .usage = ATOMIC_INIT(2) }; -#endif - -static void setup_groups(struct root_profile *profile, struct cred *cred) -{ - if (profile->groups_count > KSU_MAX_GROUPS) { - pr_warn("Failed to setgroups, too large group: %d!\n", - profile->uid); - return; - } - - if (profile->groups_count == 1 && profile->groups[0] == 0) { - // setgroup to root and return early. - if (cred->group_info) - put_group_info(cred->group_info); - cred->group_info = get_group_info(&root_groups); - return; - } - - u32 ngroups = profile->groups_count; - struct group_info *group_info = groups_alloc(ngroups); - if (!group_info) { - pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid); - return; - } - - int i; - for (i = 0; i < ngroups; i++) { - gid_t gid = profile->groups[i]; - kgid_t kgid = make_kgid(current_user_ns(), gid); - if (!gid_valid(kgid)) { - pr_warn("Failed to setgroups, invalid gid: %d\n", gid); - put_group_info(group_info); - return; - } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) - group_info->gid[i] = kgid; -#else - GROUP_AT(group_info, i) = kgid; -#endif - } - - groups_sort(group_info); - set_groups(cred, group_info); - put_group_info(group_info); -} - -static void disable_seccomp() -{ - assert_spin_locked(¤t->sighand->siglock); - // disable seccomp -#if defined(CONFIG_GENERIC_ENTRY) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) - clear_syscall_work(SECCOMP); -#else - clear_thread_flag(TIF_SECCOMP); -#endif - -#ifdef CONFIG_SECCOMP - current->seccomp.mode = 0; - current->seccomp.filter = NULL; -#else -#endif -} - -void escape_to_root(void) -{ - struct cred *cred; - struct task_struct *p = current; - struct task_struct *t; - - cred = prepare_creds(); - if (!cred) { - pr_warn("prepare_creds failed!\n"); - return; - } - - if (cred->euid.val == 0) { - pr_warn("Already root, don't escape!\n"); -#if __SULOG_GATE - ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root_failed"); -#endif - abort_creds(cred); - return; - } - - struct root_profile *profile = ksu_get_root_profile(cred->uid.val); - - cred->uid.val = profile->uid; - cred->suid.val = profile->uid; - cred->euid.val = profile->uid; - cred->fsuid.val = profile->uid; - - cred->gid.val = profile->gid; - cred->fsgid.val = profile->gid; - cred->sgid.val = profile->gid; - cred->egid.val = profile->gid; - cred->securebits = 0; - - BUILD_BUG_ON(sizeof(profile->capabilities.effective) != - sizeof(kernel_cap_t)); - - // setup capabilities - // we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process - // we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec! - u64 cap_for_ksud = - profile->capabilities.effective | CAP_DAC_READ_SEARCH; - memcpy(&cred->cap_effective, &cap_for_ksud, - sizeof(cred->cap_effective)); - memcpy(&cred->cap_permitted, &profile->capabilities.effective, - sizeof(cred->cap_permitted)); - memcpy(&cred->cap_bset, &profile->capabilities.effective, - sizeof(cred->cap_bset)); - - setup_groups(profile, cred); - - commit_creds(cred); - - // Refer to kernel/seccomp.c: seccomp_set_mode_strict - // When disabling Seccomp, ensure that current->sighand->siglock is held during the operation. - spin_lock_irq(¤t->sighand->siglock); - disable_seccomp(); - spin_unlock_irq(¤t->sighand->siglock); - - setup_selinux(profile->selinux_domain); -#if __SULOG_GATE - ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root"); -#endif - - for_each_thread (p, t) { - ksu_set_task_tracepoint_flag(t); - } -} - -#ifdef CONFIG_KSU_MANUAL_SU - -static void disable_seccomp_for_task(struct task_struct *tsk) -{ - if (!tsk->seccomp.filter && tsk->seccomp.mode == SECCOMP_MODE_DISABLED) - return; - - if (WARN_ON(!spin_is_locked(&tsk->sighand->siglock))) - return; - -#ifdef CONFIG_SECCOMP - tsk->seccomp.mode = 0; - if (tsk->seccomp.filter) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) - seccomp_filter_release(tsk); - atomic_set(&tsk->seccomp.filter_count, 0); -#else - // for 6.11+ kernel support? -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) - put_seccomp_filter(tsk); -#endif - tsk->seccomp.filter = NULL; -#endif - } -#endif -} - -void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid) -{ - struct cred *newcreds; - struct task_struct *target_task; - struct task_struct *p = current; - struct task_struct *t; - - pr_info("cmd_su: escape_to_root_for_cmd_su called for UID: %d, PID: %d\n", target_uid, target_pid); - - // Find target task by PID - rcu_read_lock(); - target_task = pid_task(find_vpid(target_pid), PIDTYPE_PID); - if (!target_task) { - rcu_read_unlock(); - pr_err("cmd_su: target task not found for PID: %d\n", target_pid); -#if __SULOG_GATE - ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found"); -#endif - return; - } - get_task_struct(target_task); - rcu_read_unlock(); - - if (task_uid(target_task).val == 0) { - pr_warn("cmd_su: target task is already root, PID: %d\n", target_pid); - put_task_struct(target_task); - return; - } - - newcreds = prepare_kernel_cred(target_task); - if (newcreds == NULL) { - pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid); -#if __SULOG_GATE - ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed"); -#endif - put_task_struct(target_task); - return; - } - - struct root_profile *profile = ksu_get_root_profile(target_uid); - - newcreds->uid.val = profile->uid; - newcreds->suid.val = profile->uid; - newcreds->euid.val = profile->uid; - newcreds->fsuid.val = profile->uid; - - newcreds->gid.val = profile->gid; - newcreds->fsgid.val = profile->gid; - newcreds->sgid.val = profile->gid; - newcreds->egid.val = profile->gid; - newcreds->securebits = 0; - - u64 cap_for_cmd_su = profile->capabilities.effective | CAP_DAC_READ_SEARCH | CAP_SETUID | CAP_SETGID; - memcpy(&newcreds->cap_effective, &cap_for_cmd_su, sizeof(newcreds->cap_effective)); - memcpy(&newcreds->cap_permitted, &profile->capabilities.effective, sizeof(newcreds->cap_permitted)); - memcpy(&newcreds->cap_bset, &profile->capabilities.effective, sizeof(newcreds->cap_bset)); - - setup_groups(profile, newcreds); - task_lock(target_task); - - const struct cred *old_creds = get_task_cred(target_task); - - rcu_assign_pointer(target_task->real_cred, newcreds); - rcu_assign_pointer(target_task->cred, get_cred(newcreds)); - task_unlock(target_task); - - if (target_task->sighand) { - spin_lock_irq(&target_task->sighand->siglock); - disable_seccomp_for_task(target_task); - spin_unlock_irq(&target_task->sighand->siglock); - } - - setup_selinux(profile->selinux_domain); - put_cred(old_creds); - wake_up_process(target_task); - - if (target_task->signal->tty) { - struct inode *inode = target_task->signal->tty->driver_data; - if (inode && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) { - __ksu_handle_devpts(inode); - } - } - - put_task_struct(target_task); -#if __SULOG_GATE - ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation"); -#endif - for_each_thread (p, t) { - ksu_set_task_tracepoint_flag(t); - } - pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid); -} -#endif - -void nuke_ext4_sysfs(void) -{ -#ifdef CONFIG_EXT4_FS - struct path path; - int err = kern_path("/data/adb/modules", 0, &path); - if (err) { - pr_err("nuke path err: %d\n", err); - return; - } - - struct super_block* sb = path.dentry->d_inode->i_sb; - const char* name = sb->s_type->name; - if (strcmp(name, "ext4") != 0) { - pr_info("nuke but module aren't mounted\n"); - path_put(&path); - return; - } - - ext4_unregister_sysfs(sb); - path_put(&path); -#endif -} - -static bool is_system_bin_su() -{ - if (!current->mm || current->in_execve) { - return 0; - } - - // quick af check - return (current->mm->exe_file && !strcmp(current->mm->exe_file->f_path.dentry->d_name.name, "su")); -} - -#ifdef CONFIG_KSU_MANUAL_SU -static bool is_system_uid(void) -{ - if (!current->mm || current->in_execve) { - return 0; - } - - uid_t caller_uid = current_uid().val; - return caller_uid <= 2000; -} -#endif - #if __SULOG_GATE static void sulog_prctl_cmd(uid_t uid, unsigned long cmd) { @@ -829,13 +478,13 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, return 0; } -static bool is_appuid(kuid_t uid) +static bool is_appuid(uid_t uid) { #define PER_USER_RANGE 100000 #define FIRST_APPLICATION_UID 10000 #define LAST_APPLICATION_UID 19999 - uid_t appid = uid.val % PER_USER_RANGE; + uid_t appid = uid % PER_USER_RANGE; return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID; } @@ -851,52 +500,20 @@ static bool should_umount(struct path *path) return false; } -#ifdef CONFIG_KSU_SUSFS - return susfs_is_mnt_devname_ksu(path); -#else if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) { const char *fstype = path->mnt->mnt_sb->s_type->name; return strcmp(fstype, "overlay") == 0; } return false; -#endif } - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || defined(KSU_HAS_PATH_UMOUNT) -static int ksu_path_umount(struct path *path, int flags) +extern int path_umount(struct path *path, int flags); +static void ksu_umount_mnt(struct path *path, int flags) { - return path_umount(path, flags); + int err = path_umount(path, flags); + if (err) { + pr_info("umount %s failed: %d\n", path->dentry->d_iname, err); + } } -#define ksu_umount_mnt(__unused, path, flags) (ksu_path_umount(path, flags)) -#else -// TODO: Search a way to make this works without set_fs functions -static int ksu_sys_umount(const char *mnt, int flags) -{ - char __user *usermnt = (char __user *)mnt; - mm_segment_t old_fs; - int ret; // although asmlinkage long - - old_fs = get_fs(); - set_fs(KERNEL_DS); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) - ret = ksys_umount(usermnt, flags); -#else - ret = sys_umount(usermnt, flags); // cuz asmlinkage long sys##name -#endif - set_fs(old_fs); - pr_info("%s was called, ret: %d\n", __func__, ret); - return ret; -} - -#define ksu_umount_mnt(mnt, __unused, flags) \ - ({ \ - int ret; \ - path_put(__unused); \ - ret = ksu_sys_umount(mnt, flags); \ - ret; \ - }) - -#endif #ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT void try_umount(const char *mnt, bool check_mnt, int flags, uid_t uid) @@ -963,108 +580,21 @@ void susfs_try_umount_all(uid_t uid) { } #endif -struct umount_tw { - struct callback_head cb; - const struct cred *old_cred; -}; - -#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT -static void umount_tw_func(struct callback_head *cb) -{ - struct umount_tw *tw = container_of(cb, struct umount_tw, cb); - const struct cred *saved = NULL; - if (tw->old_cred) { - saved = override_creds(tw->old_cred); - } - - uid_t uid = current_uid().val; - - - // fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and - // filter the mountpoint whose target is `/data/adb` - try_umount("/odm", true, 0, uid); - try_umount("/system", true, 0, uid); - try_umount("/vendor", true, 0, uid); - try_umount("/product", true, 0, uid); - try_umount("/system_ext", true, 0, uid); - try_umount("/data/adb/modules", false, MNT_DETACH, uid); - try_umount("/data/adb/kpm", false, MNT_DETACH, uid); - - // try umount ksu temp path - try_umount("/debug_ramdisk", false, MNT_DETACH, uid); - try_umount("/sbin", false, MNT_DETACH, uid); - - // try umount lsposed dex2oat bins - try_umount("/system/etc/hosts", false, MNT_DETACH, uid); - - // try umount lsposed dex2oat bins - try_umount("/apex/com.android.art/bin/dex2oat64", false, MNT_DETACH, uid); - try_umount("/apex/com.android.art/bin/dex2oat32", false, MNT_DETACH, uid); - - if (saved) - revert_creds(saved); - - if (tw->old_cred) - put_cred(tw->old_cred); - - kfree(tw); -} -#else -static void umount_tw_func(struct callback_head *cb) -{ - struct umount_tw *tw = container_of(cb, struct umount_tw, cb); - const struct cred *saved = NULL; - if (tw->old_cred) { - saved = override_creds(tw->old_cred); - } - - // fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and - // filter the mountpoint whose target is `/data/adb` - try_umount("/odm", true, 0); - try_umount("/system", true, 0); - try_umount("/vendor", true, 0); - try_umount("/product", true, 0); - try_umount("/system_ext", true, 0); - try_umount("/data/adb/modules", false, MNT_DETACH); - try_umount("/data/adb/kpm", false, MNT_DETACH); - // try umount ksu temp path - try_umount("/debug_ramdisk", false, MNT_DETACH); - try_umount("/sbin", false, MNT_DETACH); - - try_umount("/system/etc/hosts", false, MNT_DETACH); - // try umount lsposed dex2oat bins - try_umount("/apex/com.android.art/bin/dex2oat64", false, MNT_DETACH); - try_umount("/apex/com.android.art/bin/dex2oat32", false, MNT_DETACH); - - if (saved) - revert_creds(saved); - - if (tw->old_cred) - put_cred(tw->old_cred); - - kfree(tw); -} -#endif - #ifdef CONFIG_KSU_SUSFS -int ksu_handle_setuid(struct cred *new, const struct cred *old) +int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid) { - __maybe_unused struct umount_tw *tw; - if (!new || !old) { - return 0; - } + uid_t new_uid = ruid; + uid_t old_uid = current_uid().val; + pr_info("handle_setuid from %d to %d\n", old_uid, new_uid); - kuid_t new_uid = new->uid; - kuid_t old_uid = old->uid; - - if (0 != old_uid.val) { + if (0 != old_uid) { // old process is not root, ignore it. if (ksu_enhanced_security_enabled) { // disallow any non-ksu domain escalation from non-root to root! - if (unlikely(new_uid.val) == 0) { + if (unlikely(new_uid) == 0) { if (!is_ksu_domain()) { pr_warn("find suspicious EoP: %d %s, from %d to %d\n", - current->pid, current->comm, old_uid.val, new_uid.val); + current->pid, current->comm, old_uid, new_uid); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) force_sig(SIGKILL); #else @@ -1075,9 +605,9 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) } // disallow appuid decrease to any other uid if it is allowed to su if (is_appuid(old_uid)) { - if (new_uid.val < old_uid.val && !ksu_is_allow_uid_for_current(old_uid.val)) { + if (new_uid < old_uid && !ksu_is_allow_uid_for_current(old_uid)) { pr_warn("find suspicious EoP: %d %s, from %d to %d\n", - current->pid, current->comm, old_uid.val, new_uid.val); + current->pid, current->comm, old_uid, new_uid); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) force_sig(SIGKILL); #else @@ -1090,20 +620,23 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) return 0; } - if (new_uid.val == 2000) { - if (ksu_su_compat_enabled) { - ksu_set_task_tracepoint_flag(current); - } + if (new_uid == 2000) { + ksu_set_task_tracepoint_flag(current); + } + + if (!is_appuid(new_uid) || is_unsupported_uid(new_uid)) { + pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid); + ksu_clear_task_tracepoint_flag(current); + return 0; } // if on private space, see if its possibly the manager - if (unlikely(new_uid.val > 100000 && new_uid.val % 100000 == ksu_get_manager_uid())) { - ksu_set_manager_uid(new_uid.val); + if (unlikely(new_uid > 100000 && new_uid % 100000 == ksu_get_manager_uid())) { + ksu_set_manager_uid(new_uid); } - if (unlikely(ksu_get_manager_uid() == new_uid.val)) { - pr_info("install fd for: %d\n", new_uid.val); - + if (unlikely(ksu_get_manager_uid() == new_uid)) { + pr_info("install fd for manager: %d\n", new_uid); ksu_install_fd(); spin_lock_irq(¤t->sighand->siglock); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2 @@ -1113,14 +646,12 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) // lets just do original thing where we disable seccomp disable_seccomp(); #endif - if (ksu_su_compat_enabled) { - ksu_set_task_tracepoint_flag(current); - } + ksu_set_task_tracepoint_flag(current); spin_unlock_irq(¤t->sighand->siglock); return 0; } - if (unlikely(ksu_is_allow_uid_for_current(new_uid.val))) { + if (unlikely(ksu_is_allow_uid_for_current(new_uid))) { if (current->seccomp.mode == SECCOMP_MODE_FILTER && current->seccomp.filter) { spin_lock_irq(¤t->sighand->siglock); @@ -1133,32 +664,20 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) #endif spin_unlock_irq(¤t->sighand->siglock); } - if (ksu_su_compat_enabled) { - ksu_set_task_tracepoint_flag(current); - } + ksu_set_task_tracepoint_flag(current); } else { - // Disable syscall tracepoint sucompat for non-allowed processes - if (ksu_su_compat_enabled) { - ksu_clear_task_tracepoint_flag(current); - } - } - - // this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it! - if (!ksu_module_mounted) { - return 0; - } - - if (!ksu_kernel_umount_enabled) { - return 0; + ksu_clear_task_tracepoint_flag(current); } + // Handle kernel umount + // We only interest in process spwaned by zygote if (!susfs_is_sid_equal(old->security, susfs_zygote_sid)) { return 0; } // Check if spawned process is isolated service first, and force to do umount if so - if (is_zygote_isolated_service_uid(new_uid.val) && susfs_is_umount_for_zygote_iso_service_enabled) { + if (is_zygote_isolated_service_uid(new_uid) && susfs_is_umount_for_zygote_iso_service_enabled) { goto do_umount; } @@ -1166,22 +685,22 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) // will always return true, that's why we need to explicitly check if new_uid.val belongs to // ksu manager if (ksu_is_manager_uid_valid() && - (new_uid.val % 1000000 == ksu_get_manager_uid())) // % 1000000 in case it is private space uid + (new_uid % 1000000 == ksu_get_manager_uid())) // % 1000000 in case it is private space uid { return 0; } // Check if spawned process is normal user app and needs to be umounted - if (likely(is_zygote_normal_app_uid(new_uid.val) && ksu_uid_should_umount(new_uid.val))) { + if (likely(is_zygote_normal_app_uid(new_uid) && ksu_uid_should_umount(new_uid))) { goto do_umount; } // Lastly, Check if spawned process is some system process and needs to be umounted - if (unlikely(is_some_system_uid(new_uid.val) && susfs_is_umount_for_zygote_system_process_enabled)) { + if (unlikely(is_some_system_uid(new_uid) && susfs_is_umount_for_zygote_system_process_enabled)) { goto do_umount; } #if __SULOG_GATE - ksu_sulog_report_syscall(new_uid.val, NULL, "setuid", NULL); + ksu_sulog_report_syscall(new_uid, NULL, "setuid", NULL); #endif return 0; @@ -1189,27 +708,9 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) do_umount: #ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT // susfs come first, and lastly umount by ksu, make sure umount in reversed order - susfs_try_umount_all(new_uid.val); + susfs_try_umount_all(new_uid); #else - tw = kmalloc(sizeof(*tw), GFP_ATOMIC); - if (!tw) - return 0; - - tw->old_cred = get_current_cred(); - tw->cb.func = umount_tw_func; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) - int err = task_work_add(current, &tw->cb, TWA_RESUME); -#else - int err = task_work_add(current, &tw->cb, true); -#endif - if (err) { - if (tw->old_cred) { - put_cred(tw->old_cred); - } - kfree(tw); - pr_warn("unmount add task_work failed\n"); - } + ksu_handle_umount(old_uid, new_uid); #endif // #ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT @@ -1229,44 +730,31 @@ do_umount: #endif // #ifdef CONFIG_KSU_SUSFS_SUS_PATH return 0; } - #else -int ksu_handle_setuid(struct cred *new, const struct cred *old) +int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid) { - __maybe_unused struct umount_tw *tw; - if (!new || !old) { - return 0; - } + uid_t new_uid = ruid; + uid_t old_uid = current_uid().val; + pr_info("handle_setuid from %d to %d\n", old_uid, new_uid); - kuid_t new_uid = new->uid; - kuid_t old_uid = old->uid; - - if (0 != old_uid.val) { + if (0 != old_uid) { // old process is not root, ignore it. if (ksu_enhanced_security_enabled) { // disallow any non-ksu domain escalation from non-root to root! - if (unlikely(new_uid.val) == 0) { + if (unlikely(new_uid) == 0) { if (!is_ksu_domain()) { pr_warn("find suspicious EoP: %d %s, from %d to %d\n", - current->pid, current->comm, old_uid.val, new_uid.val); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) + current->pid, current->comm, old_uid, new_uid); force_sig(SIGKILL); -#else - force_sig(SIGKILL, current); -#endif return 0; } } // disallow appuid decrease to any other uid if it is allowed to su if (is_appuid(old_uid)) { - if (new_uid.val < old_uid.val && !ksu_is_allow_uid_for_current(old_uid.val)) { + if (new_uid < old_uid && !ksu_is_allow_uid_for_current(old_uid)) { pr_warn("find suspicious EoP: %d %s, from %d to %d\n", - current->pid, current->comm, old_uid.val, new_uid.val); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) + current->pid, current->comm, old_uid, new_uid); force_sig(SIGKILL); -#else - force_sig(SIGKILL, current); -#endif return 0; } } @@ -1274,25 +762,23 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) return 0; } - if (new_uid.val == 2000) { - if (ksu_su_compat_enabled) { - ksu_set_task_tracepoint_flag(current); - } + if (new_uid == 2000) { + ksu_set_task_tracepoint_flag(current); } - if (!is_appuid(new_uid) || is_unsupported_uid(new_uid.val)) { - // pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid.val); + if (!is_appuid(new_uid) || is_unsupported_uid(new_uid)) { + pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid); + ksu_clear_task_tracepoint_flag(current); return 0; } // if on private space, see if its possibly the manager - if (unlikely(new_uid.val > 100000 && new_uid.val % 100000 == ksu_get_manager_uid())) { - ksu_set_manager_uid(new_uid.val); + if (unlikely(new_uid > 100000 && new_uid % 100000 == ksu_get_manager_uid())) { + ksu_set_manager_uid(new_uid); } - if (unlikely(ksu_get_manager_uid() == new_uid.val)) { - pr_info("install fd for: %d\n", new_uid.val); - + if (unlikely(ksu_get_manager_uid() == new_uid)) { + pr_info("install fd for manager: %d\n", new_uid); ksu_install_fd(); spin_lock_irq(¤t->sighand->siglock); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2 @@ -1302,14 +788,12 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) // lets just do original thing where we disable seccomp disable_seccomp(); #endif - if (ksu_su_compat_enabled) { - ksu_set_task_tracepoint_flag(current); - } + ksu_set_task_tracepoint_flag(current); spin_unlock_irq(¤t->sighand->siglock); return 0; } - if (unlikely(ksu_is_allow_uid_for_current(new_uid.val))) { + if (unlikely(ksu_is_allow_uid_for_current(new_uid))) { if (current->seccomp.mode == SECCOMP_MODE_FILTER && current->seccomp.filter) { spin_lock_irq(¤t->sighand->siglock); @@ -1322,123 +806,22 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) #endif spin_unlock_irq(¤t->sighand->siglock); } - if (ksu_su_compat_enabled) { - ksu_set_task_tracepoint_flag(current); - } + ksu_set_task_tracepoint_flag(current); } else { - // Disable syscall tracepoint sucompat for non-allowed processes - if (ksu_su_compat_enabled) { - ksu_clear_task_tracepoint_flag(current); - } + ksu_clear_task_tracepoint_flag(current); } - // this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it! - if (!ksu_module_mounted) { - return 0; - } - - if (!ksu_kernel_umount_enabled) { - return 0; - } - - if (!ksu_uid_should_umount(new_uid.val)) { - return 0; - } else { -#ifdef CONFIG_KSU_DEBUG - pr_info("uid: %d should not umount!\n", current_uid().val); -#endif - } - - // check old process's selinux context, if it is not zygote, ignore it! - // because some su apps may setuid to untrusted_app but they are in global mount namespace - // when we umount for such process, that is a disaster! - bool is_zygote_child = is_zygote(old); - if (!is_zygote_child) { - pr_info("handle umount ignore non zygote child: %d\n", - current->pid); - return 0; - } + // Handle kernel umount + ksu_handle_umount(old_uid, new_uid); + #if __SULOG_GATE - ksu_sulog_report_syscall(new_uid.val, NULL, "setuid", NULL); + ksu_sulog_report_syscall(new_uid, NULL, "setuid", NULL); #endif -#ifdef CONFIG_KSU_DEBUG - // umount the target mnt - pr_info("handle umount for uid: %d, pid: %d\n", new_uid.val, - current->pid); -#endif - - tw = kmalloc(sizeof(*tw), GFP_ATOMIC); - if (!tw) - return 0; - - tw->old_cred = get_current_cred(); - tw->cb.func = umount_tw_func; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) - int err = task_work_add(current, &tw->cb, TWA_RESUME); -#else - int err = task_work_add(current, &tw->cb, true); -#endif - if (err) { - if (tw->old_cred) { - put_cred(tw->old_cred); - } - kfree(tw); - pr_warn("unmount add task_work failed\n"); - } return 0; } - #endif // #ifdef CONFIG_KSU_SUSFS -// downstream: make sure to pass arg as reference, this can allow us to extend things. -int ksu_handle_sys_reboot(int magic1, int magic2, unsigned int cmd, void __user **arg) -{ - - if (magic1 != KSU_INSTALL_MAGIC1) - return 0; - -#ifdef CONFIG_KSU_DEBUG - pr_info("sys_reboot: intercepted call! magic: 0x%x id: %d\n", magic1, magic2); -#endif - - // Check if this is a request to install KSU fd - if (magic2 == KSU_INSTALL_MAGIC2) { - int fd = ksu_install_fd(); - pr_info("[%d] install ksu fd: %d\n", current->pid, fd); - - // downstream: dereference all arg usage! - if (copy_to_user((void __user *)*arg, &fd, sizeof(fd))) { - pr_err("install ksu fd reply err\n"); - } - - return 0; - } - - // extensions - - return 0; -} - -// Init functons - kprobe hooks - -// 1. Reboot hook for installing fd -static int reboot_handler_pre(struct kprobe *p, struct pt_regs *regs) -{ - struct pt_regs *real_regs = PT_REAL_REGS(regs); - int magic1 = (int)PT_REGS_PARM1(real_regs); - int magic2 = (int)PT_REGS_PARM2(real_regs); - int cmd = (int)PT_REGS_PARM3(real_regs); - void __user **arg = (void __user **)&PT_REGS_SYSCALL_PARM4(real_regs); - - return ksu_handle_sys_reboot(magic1, magic2, cmd, arg); -} - -static struct kprobe reboot_kp = { - .symbol_name = REBOOT_SYMBOL, - .pre_handler = reboot_handler_pre, -}; static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) @@ -1446,6 +829,7 @@ static int ksu_task_prctl(int option, unsigned long arg2, unsigned long arg3, ksu_handle_prctl(option, arg2, arg3, arg4, arg5); return -ENOSYS; } + // kernel 4.4 and 4.9 #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || \ defined(CONFIG_IS_HW_HISI) || \ @@ -1466,71 +850,9 @@ static int ksu_key_permission(key_ref_t key_ref, const struct cred *cred, } #endif -int ksu_inode_permission(struct inode *inode, int mask) -{ - if (inode && inode->i_sb - && unlikely(inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) { - //pr_info("%s: handling devpts for: %s \n", __func__, current->comm); - __ksu_handle_devpts(inode); - } - return 0; -} - -int ksu_bprm_check(struct linux_binprm *bprm) -{ - char *filename = (char *)bprm->filename; - - if (likely(!ksu_execveat_hook)) - return 0; - -#ifdef CONFIG_COMPAT - static bool compat_check_done __read_mostly = false; - if ( unlikely(!compat_check_done) && unlikely(!strcmp(filename, "/data/adb/ksud")) - && !memcmp(bprm->buf, "\x7f\x45\x4c\x46", 4) ) { - if (bprm->buf[4] == 0x01 ) - ksu_is_compat = true; - - pr_info("%s: %s ELF magic found! ksu_is_compat: %d \n", __func__, filename, ksu_is_compat); - compat_check_done = true; - } -#endif - - ksu_handle_pre_ksud(filename); - -#ifdef CONFIG_KSU_MANUAL_SU - ksu_try_escalate_for_uid(current_uid().val); -#endif - - return 0; - -} - -#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 10, 0) && defined(CONFIG_KSU_MANUAL_SU) -static int ksu_task_alloc(struct task_struct *task, - unsigned long clone_flags) -{ - ksu_try_escalate_for_uid(task_uid(task).val); - return 0; -} -#endif - -static int ksu_task_fix_setuid(struct cred *new, const struct cred *old, - int flags) -{ - return ksu_handle_setuid(new, old); -} - #ifndef MODULE static struct security_hook_list ksu_hooks[] = { LSM_HOOK_INIT(task_prctl, ksu_task_prctl), - LSM_HOOK_INIT(task_fix_setuid, ksu_task_fix_setuid), - LSM_HOOK_INIT(inode_permission, ksu_inode_permission), -#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 10, 0) && defined(CONFIG_KSU_MANUAL_SU) - LSM_HOOK_INIT(task_alloc, ksu_task_alloc), -#endif -#ifndef KSU_KPROBES_HOOK - LSM_HOOK_INIT(bprm_check_security, ksu_bprm_check), -#endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) || \ defined(CONFIG_IS_HW_HISI) || defined(CONFIG_KSU_ALLOWLIST_WORKAROUND) LSM_HOOK_INIT(key_permission, ksu_key_permission) @@ -1556,206 +878,20 @@ void __init ksu_lsm_hook_init(void) security_add_hooks(ksu_hooks, ARRAY_SIZE(ksu_hooks)); #endif } - -#else - -static int override_security_head(void *head, const void *new_head, size_t len) -{ - unsigned long base = (unsigned long)head & PAGE_MASK; - unsigned long offset = offset_in_page(head); - - // this is impossible for our case because the page alignment - // but be careful for other cases! - BUG_ON(offset + len > PAGE_SIZE); - struct page *page = phys_to_page(__pa(base)); - if (!page) { - return -EFAULT; - } - - void *addr = vmap(&page, 1, VM_MAP, PAGE_KERNEL); - if (!addr) { - return -ENOMEM; - } - local_irq_disable(); - memcpy(addr + offset, new_head, len); - local_irq_enable(); - vunmap(addr); - return 0; -} - -static void free_security_hook_list(struct hlist_head *head) -{ - struct hlist_node *temp; - struct security_hook_list *entry; - - if (!head) - return; - - hlist_for_each_entry_safe (entry, temp, head, list) { - hlist_del(&entry->list); - kfree(entry); - } - - kfree(head); -} - -struct hlist_head *copy_security_hlist(struct hlist_head *orig) -{ - struct hlist_head *new_head = kmalloc(sizeof(*new_head), GFP_KERNEL); - if (!new_head) - return NULL; - - INIT_HLIST_HEAD(new_head); - - struct security_hook_list *entry; - struct security_hook_list *new_entry; - - hlist_for_each_entry (entry, orig, list) { - new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL); - if (!new_entry) { - free_security_hook_list(new_head); - return NULL; - } - - *new_entry = *entry; - - hlist_add_tail_rcu(&new_entry->list, new_head); - } - - return new_head; -} - -#define LSM_SEARCH_MAX 180 // This should be enough to iterate -static void *find_head_addr(void *security_ptr, int *index) -{ - if (!security_ptr) { - return NULL; - } - struct hlist_head *head_start = - (struct hlist_head *)&security_hook_heads; - - for (int i = 0; i < LSM_SEARCH_MAX; i++) { - struct hlist_head *head = head_start + i; - struct security_hook_list *pos; - hlist_for_each_entry (pos, head, list) { - if (pos->hook.capget == security_ptr) { - if (index) { - *index = i; - } - return head; - } - } - } - - return NULL; -} - -#define GET_SYMBOL_ADDR(sym) \ - ({ \ - void *addr = kallsyms_lookup_name(#sym ".cfi_jt"); \ - if (!addr) { \ - addr = kallsyms_lookup_name(#sym); \ - } \ - addr; \ - }) - -#define KSU_LSM_HOOK_HACK_INIT(head_ptr, name, func) \ - do { \ - static struct security_hook_list hook = { \ - .hook = { .name = func } \ - }; \ - hook.head = head_ptr; \ - hook.lsm = "ksu"; \ - struct hlist_head *new_head = copy_security_hlist(hook.head); \ - if (!new_head) { \ - pr_err("Failed to copy security list: %s\n", #name); \ - break; \ - } \ - hlist_add_tail_rcu(&hook.list, new_head); \ - if (override_security_head(hook.head, new_head, \ - sizeof(*new_head))) { \ - free_security_hook_list(new_head); \ - pr_err("Failed to hack lsm for: %s\n", #name); \ - } \ - } while (0) - -void __init ksu_lsm_hook_init(void) -{ - void *cap_prctl = GET_SYMBOL_ADDR(cap_task_prctl); - void *prctl_head = find_head_addr(cap_prctl, NULL); - if (prctl_head) { - if (prctl_head != &security_hook_heads.task_prctl) { - pr_warn("prctl's address has shifted!\n"); - } - KSU_LSM_HOOK_HACK_INIT(prctl_head, task_prctl, ksu_task_prctl); - } else { - pr_warn("Failed to find task_prctl!\n"); - } - void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid); - void *setuid_head = find_head_addr(cap_setuid, NULL); - if (setuid_head) { - if (setuid_head != &security_hook_heads.task_fix_setuid) { - pr_warn("setuid's address has shifted!\n"); - } - KSU_LSM_HOOK_HACK_INIT(setuid_head, task_fix_setuid, - ksu_task_fix_setuid); - } else { - pr_warn("Failed to find task_fix_setuid!\n"); - } - smp_mb(); -} #endif -__maybe_unused int ksu_kprobe_init(void) -{ - int rc = 0; - - // Register reboot kprobe - rc = register_kprobe(&reboot_kp); - if (rc) { - pr_err("reboot kprobe failed: %d\n", rc); - } else { - pr_info("reboot kprobe registered successfully\n"); - } - - return 0; -} - -__maybe_unused int ksu_kprobe_exit(void) -{ - unregister_kprobe(&reboot_kp); - return 0; -} - -void __init ksu_core_init(void) +void ksu_setuid_hook_init(void) { ksu_lsm_hook_init(); -#ifdef KSU_KPROBES_HOOK - int rc = ksu_kprobe_init(); - if (rc) { - pr_err("ksu_kprobe_init failed: %d\n", rc); - } -#endif - if (ksu_register_feature_handler(&kernel_umount_handler)) { - pr_err("Failed to register umount feature handler\n"); - } + ksu_kernel_umount_init(); if (ksu_register_feature_handler(&enhanced_security_handler)) { pr_err("Failed to register enhanced security feature handler\n"); } } -void ksu_core_exit(void) +void ksu_setuid_hook_exit(void) { - ksu_uid_exit(); - ksu_throne_comm_exit(); -#if __SULOG_GATE - ksu_sulog_exit(); -#endif - -#ifdef KSU_KPROBES_HOOK - pr_info("ksu_core_kprobe_exit\n"); - ksu_kprobe_exit(); -#endif - ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT); + pr_info("ksu_core_exit\n"); + ksu_kernel_umount_exit(); ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY); } diff --git a/kernel/setuid_hook.h b/kernel/setuid_hook.h new file mode 100644 index 00000000..fc5b93a0 --- /dev/null +++ b/kernel/setuid_hook.h @@ -0,0 +1,14 @@ +#ifndef __KSU_H_KSU_CORE +#define __KSU_H_KSU_CORE + +#include +#include +#include "apk_sign.h" +#include + +void ksu_setuid_hook_init(void); +void ksu_setuid_hook_exit(void); + +int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid); + +#endif diff --git a/kernel/sucompat.c b/kernel/sucompat.c index e75f652e..7d360331 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -1,14 +1,8 @@ #include "linux/compiler.h" #include "linux/printk.h" -#include "selinux/selinux.h" -#include -#include #include #include -#include #include -#include -#include #include #include #include @@ -18,74 +12,23 @@ #else #include #endif -#include -#include -#include #ifdef CONFIG_KSU_SUSFS_SUS_SU #include #endif -#include "objsec.h" #include "allowlist.h" -#include "arch.h" #include "feature.h" #include "klog.h" // IWYU pragma: keep #include "ksud.h" #include "sucompat.h" -#include "core_hook.h" +#include "app_profile.h" +#include "syscall_hook_manager.h" + #include "sulog.h" #include "kernel_compat.h" #define SU_PATH "/system/bin/su" #define SH_PATH "/system/bin/sh" -extern void escape_to_root(); -void ksu_sucompat_enable(); -void ksu_sucompat_disable(); - -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; - bool ksu_root_process = - uid == 0 && is_task_ksu_domain(get_task_cred(t)); - if (ksu_root_process || ksu_is_allow_uid(uid)) { - ksu_set_task_tracepoint_flag(t); - pr_info("sucompat: mark process: pid:%d, uid: %d, comm:%s\n", - t->pid, uid, t->comm); - } - } - read_unlock(&tasklist_lock); -} - -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); -} - -static void mark_all_process(void) -{ - handle_process_mark(true); - pr_info("sucompat: mark all user process done!\n"); -} - -static void unmark_all_process(void) -{ - handle_process_mark(false); - pr_info("sucompat: unmark all user process done!\n"); -} bool ksu_su_compat_enabled __read_mostly = true; @@ -99,17 +42,6 @@ static int su_compat_feature_set(u64 value) { bool enable = value != 0; - if (enable == ksu_su_compat_enabled) { - pr_info("su_compat: no need to change\n"); - return 0; - } - - if (enable) { - ksu_sucompat_enable(); - } else { - ksu_sucompat_disable(); - } - ksu_su_compat_enabled = enable; pr_info("su_compat: set to %d\n", enable); @@ -153,7 +85,7 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, { #ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - if (!ksu_sucompat_hook_state) { + if (!ksu_su_compat_enabled) { return 0; } #endif @@ -209,7 +141,7 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) { #ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - if (!ksu_sucompat_hook_state) { + if (!ksu_su_compat_enabled) { return 0; } #endif @@ -266,7 +198,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, struct filename *filename; #ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - if (!ksu_sucompat_hook_state) { + if (!ksu_su_compat_enabled) { return 0; } #endif @@ -306,7 +238,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_with_root_profile(); return 0; } @@ -329,10 +261,9 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, #endif #ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - if (!ksu_sucompat_hook_state) { + if (!ksu_su_compat_enabled) { return 0; } -#endif if (unlikely(!filename_user)) return 0; @@ -371,7 +302,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_with_root_profile(); return 0; } @@ -380,7 +311,7 @@ int __ksu_handle_devpts(struct inode *inode) { #ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - if (!ksu_sucompat_hook_state) + if (!ksu_su_compat_enabled) return 0; #endif @@ -409,186 +340,16 @@ int __ksu_handle_devpts(struct inode *inode) return 0; } -#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - -// Tracepoint probe for sys_enter -static void sucompat_sys_enter_handler(void *data, struct pt_regs *regs, - long id) -{ - // Handle newfstatat - if (unlikely(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 (unlikely(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 (unlikely(id == __NR_execve)) { - const char __user **filename_user = - (const char __user **)&PT_REGS_PARM1(regs); - ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL, NULL); - return; - } -} - -#endif // KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - -#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("sucompat: 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; -} - -#endif - -#ifdef CONFIG_KRETPROBES - -static int tracepoint_reg_count = 0; -static DEFINE_SPINLOCK(tracepoint_reg_lock); - -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 - unmark_all_process(); - ksu_mark_running_process(); - } else { - // while installing other tracepoint, mark all processes - 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 - unmark_all_process(); - } else { - // while uninstalling other tracepoint, mark our processes - 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 - -void ksu_sucompat_enable() -{ - int ret; - pr_info("sucompat: ksu_sucompat_enable 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 KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - ret = register_trace_sys_enter(sucompat_sys_enter_handler, NULL); -#ifndef CONFIG_KRETPROBES - unmark_all_process(); - ksu_mark_running_process(); -#endif - if (ret) { - pr_err("sucompat: failed to register sys_enter tracepoint: %d\n", ret); - } else { - pr_info("sucompat: sys_enter tracepoint registered\n"); - } -#else - ksu_sucompat_hook_state = true; - pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n"); -#endif -} - -void ksu_sucompat_disable() -{ - pr_info("sucompat: ksu_sucompat_disable called\n"); -#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK - unregister_trace_sys_enter(sucompat_sys_enter_handler, NULL); - tracepoint_synchronize_unregister(); - pr_info("sucompat: sys_enter tracepoint unregistered\n"); -#else - ksu_sucompat_hook_state = false; - pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n"); -#endif - -#ifdef CONFIG_KRETPROBES - destroy_kretprobe(&syscall_regfunc_rp); - destroy_kretprobe(&syscall_unregfunc_rp); -#endif - -} - // sucompat: permited process can execute 'su' to gain root access. void ksu_sucompat_init() { if (ksu_register_feature_handler(&su_compat_handler)) { pr_err("Failed to register su_compat feature handler\n"); } - if (ksu_su_compat_enabled) { - ksu_sucompat_enable(); - } } void ksu_sucompat_exit() { - if (ksu_su_compat_enabled) { - ksu_sucompat_disable(); - } ksu_unregister_feature_handler(KSU_FEATURE_SU_COMPAT); } diff --git a/kernel/sucompat.h b/kernel/sucompat.h index b76fc54a..d78d0c57 100644 --- a/kernel/sucompat.h +++ b/kernel/sucompat.h @@ -1,34 +1,18 @@ #ifndef __KSU_H_SUCOMPAT #define __KSU_H_SUCOMPAT -#include -#include -#include +#include extern bool ksu_su_compat_enabled; void ksu_sucompat_init(void); void ksu_sucompat_exit(void); -void ksu_sucompat_enable(void); -void ksu_sucompat_disable(void); +// Handler functions exported for hook_manager +int ksu_handle_faccessat(int *dfd, const char __user **filename_user, + int *mode, int *__unused_flags); +int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); +int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, + void *__never_use_argv, void *__never_use_envp, + int *__never_use_flags); -void ksu_mark_running_process(void); - -static inline void ksu_set_task_tracepoint_flag(struct task_struct *t) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) - set_task_syscall_work(t, SYSCALL_TRACEPOINT); -#else - set_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT); -#endif -} - -static inline void ksu_clear_task_tracepoint_flag(struct task_struct *t) -{ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) - clear_task_syscall_work(t, SYSCALL_TRACEPOINT); -#else - clear_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT); -#endif -} #endif \ No newline at end of file diff --git a/kernel/supercalls.c b/kernel/supercalls.c index 897c7634..c1e2f73b 100644 --- a/kernel/supercalls.c +++ b/kernel/supercalls.c @@ -10,6 +10,7 @@ #include #include +#include "arch.h" #include "allowlist.h" #include "feature.h" #include "klog.h" // IWYU pragma: keep @@ -17,9 +18,9 @@ #include "manager.h" #include "sulog.h" #include "selinux/selinux.h" -#include "core_hook.h" #include "objsec.h" #include "file_wrapper.h" +#include "syscall_hook_manager.h" #include "throne_comm.h" #include "dynamic_manager.h" @@ -77,7 +78,7 @@ static int do_grant_root(void __user *arg) // we already check uid above on allowed_for_su() pr_info("allow root for: %d\n", current_uid().val); - escape_to_root(); + escape_with_root_profile(); return 0; } @@ -130,13 +131,13 @@ static int do_report_event(void __user *arg) if (!boot_complete_lock) { boot_complete_lock = true; pr_info("boot_complete triggered\n"); + on_boot_completed(); } break; } case EVENT_MODULE_MOUNTED: { - ksu_module_mounted = true; pr_info("module mounted!\n"); - nuke_ext4_sysfs(); + on_module_mounted(); break; } default: @@ -408,6 +409,71 @@ put_orig_file: return ret; } +static int do_manage_mark(void __user *arg) +{ + struct ksu_manage_mark_cmd cmd; + int ret = 0; + + if (copy_from_user(&cmd, arg, sizeof(cmd))) { + pr_err("manage_mark: copy_from_user failed\n"); + return -EFAULT; + } + + switch (cmd.operation) { + case KSU_MARK_GET: { + // Get task mark status + ret = ksu_get_task_mark(cmd.pid); + if (ret < 0) { + pr_err("manage_mark: get failed for pid %d: %d\n", cmd.pid, ret); + return ret; + } + cmd.result = (u32)ret; + break; + } + case KSU_MARK_MARK: { + if (cmd.pid == 0) { + ksu_mark_all_process(); + } else { + ret = ksu_set_task_mark(cmd.pid, true); + if (ret < 0) { + pr_err("manage_mark: set_mark failed for pid %d: %d\n", cmd.pid, + ret); + return ret; + } + } + break; + } + case KSU_MARK_UNMARK: { + if (cmd.pid == 0) { + ksu_unmark_all_process(); + } else { + ret = ksu_set_task_mark(cmd.pid, false); + if (ret < 0) { + pr_err("manage_mark: set_unmark failed for pid %d: %d\n", + cmd.pid, ret); + return ret; + } + } + break; + } + case KSU_MARK_REFRESH: { + ksu_mark_running_process(); + pr_info("manage_mark: refreshed running processes\n"); + break; + } + default: { + pr_err("manage_mark: invalid operation %u\n", cmd.operation); + return -EINVAL; + } + } + if (copy_to_user(arg, &cmd, sizeof(cmd))) { + pr_err("manage_mark: copy_to_user failed\n"); + return -EFAULT; + } + + return 0; +} + // 100. GET_FULL_VERSION - Get full version string static int do_get_full_version(void __user *arg) { @@ -626,6 +692,7 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = { { .cmd = KSU_IOCTL_GET_FEATURE, .name = "GET_FEATURE", .handler = do_get_feature, .perm_check = manager_or_root }, { .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_feature, .perm_check = manager_or_root }, { .cmd = KSU_IOCTL_GET_WRAPPER_FD, .name = "GET_WRAPPER_FD", .handler = do_get_wrapper_fd, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_MANAGE_MARK, .name = "MANAGE_MARK", .handler = do_manage_mark, .perm_check = manager_or_root }, { .cmd = KSU_IOCTL_GET_FULL_VERSION,.name = "GET_FULL_VERSION", .handler = do_get_full_version, .perm_check = always_allow}, { .cmd = KSU_IOCTL_HOOK_TYPE,.name = "GET_HOOK_TYPE", .handler = do_get_hook_type, .perm_check = manager_or_root}, { .cmd = KSU_IOCTL_ENABLE_KPM, .name = "GET_ENABLE_KPM", .handler = do_enable_kpm, .perm_check = manager_or_root}, @@ -641,6 +708,52 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = { { .cmd = 0, .name = NULL, .handler = NULL, .perm_check = NULL} // Sentine }; +// downstream: make sure to pass arg as reference, this can allow us to extend things. +int ksu_handle_sys_reboot(int magic1, int magic2, unsigned int cmd, void __user **arg) +{ + + if (magic1 != KSU_INSTALL_MAGIC1) + return 0; + +#ifdef CONFIG_KSU_DEBUG + pr_info("sys_reboot: intercepted call! magic: 0x%x id: %d\n", magic1, magic2); +#endif + + // Check if this is a request to install KSU fd + if (magic2 == KSU_INSTALL_MAGIC2) { + int fd = ksu_install_fd(); + pr_info("[%d] install ksu fd: %d\n", current->pid, fd); + + // downstream: dereference all arg usage! + if (copy_to_user((void __user *)*arg, &fd, sizeof(fd))) { + pr_err("install ksu fd reply err\n"); + } + + return 0; + } + + // extensions + + return 0; +} + +// Reboot hook for installing fd +static int reboot_handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + struct pt_regs *real_regs = PT_REAL_REGS(regs); + int magic1 = (int)PT_REGS_PARM1(real_regs); + int magic2 = (int)PT_REGS_PARM2(real_regs); + int cmd = (int)PT_REGS_PARM3(real_regs); + void __user **arg = (void __user **)&PT_REGS_SYSCALL_PARM4(real_regs); + + return ksu_handle_sys_reboot(magic1, magic2, cmd, arg); +} + +static struct kprobe reboot_kp = { + .symbol_name = REBOOT_SYMBOL, + .pre_handler = reboot_handler_pre, +}; + void ksu_supercalls_init(void) { int i; @@ -649,6 +762,17 @@ void ksu_supercalls_init(void) for (i = 0; ksu_ioctl_handlers[i].handler; i++) { pr_info(" %-18s = 0x%08x\n", ksu_ioctl_handlers[i].name, ksu_ioctl_handlers[i].cmd); } + + int rc = register_kprobe(&reboot_kp); + if (rc) { + pr_err("reboot kprobe failed: %d\n", rc); + } else { + pr_info("reboot kprobe registered successfully\n"); + } +} + +void ksu_supercalls_exit(void){ + unregister_kprobe(&reboot_kp); } static inline void ksu_ioctl_audit(unsigned int cmd, const char *cmd_name, uid_t uid, int ret) diff --git a/kernel/supercalls.h b/kernel/supercalls.h index 29b5f7ee..568d05b5 100644 --- a/kernel/supercalls.h +++ b/kernel/supercalls.h @@ -4,6 +4,7 @@ #include #include #include "ksu.h" +#include "app_profile.h" #ifdef CONFIG_KPM #include "kpm/kpm.h" @@ -82,6 +83,17 @@ struct ksu_get_wrapper_fd_cmd { __u32 flags; // Input: flags of userspace fd }; +struct ksu_manage_mark_cmd { + __u32 operation; // Input: KSU_MARK_* + __s32 pid; // Input: target pid (0 for all processes) + __u32 result; // Output: for get operation - mark status or reg_count +}; + +#define KSU_MARK_GET 1 +#define KSU_MARK_MARK 2 +#define KSU_MARK_UNMARK 3 +#define KSU_MARK_REFRESH 4 + // Other command structures struct ksu_get_full_version_cmd { char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string @@ -134,6 +146,7 @@ struct ksu_manual_su_cmd { #define KSU_IOCTL_GET_FEATURE _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0) #define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0) #define KSU_IOCTL_GET_WRAPPER_FD _IOC(_IOC_WRITE, 'K', 15, 0) +#define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0) // Other IOCTL command definitions #define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0) #define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0) @@ -160,6 +173,7 @@ struct ksu_ioctl_cmd_map { // Install KSU fd to current process int ksu_install_fd(void); -void ksu_supercalls_init(); +void ksu_supercalls_init(void); +void ksu_supercalls_exit(void); #endif // __KSU_H_SUPERCALLS \ No newline at end of file diff --git a/kernel/syscall_hook_manager.c b/kernel/syscall_hook_manager.c new file mode 100644 index 00000000..de0822b6 --- /dev/null +++ b/kernel/syscall_hook_manager.c @@ -0,0 +1,333 @@ +#include "linux/compiler.h" +#include "linux/cred.h" +#include "linux/printk.h" +#include "selinux/selinux.h" +#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: + 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; +} + +#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK +// 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))) { + 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; + } + } + + // 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; + } + } +} +#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 KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK + ret = register_trace_sys_enter(ksu_sys_enter_handler, NULL); +#ifndef CONFIG_KRETPROBES + 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 KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK + 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(); +} diff --git a/kernel/syscall_hook_manager.h b/kernel/syscall_hook_manager.h new file mode 100644 index 00000000..d8703345 --- /dev/null +++ b/kernel/syscall_hook_manager.h @@ -0,0 +1,40 @@ +#ifndef __KSU_H_HOOK_MANAGER +#define __KSU_H_HOOK_MANAGER + +#include +#include +#include + +// Hook manager initialization and cleanup +void ksu_syscall_hook_manager_init(void); +void ksu_syscall_hook_manager_exit(void); + +// Process marking for tracepoint +void ksu_mark_all_process(void); +void ksu_unmark_all_process(void); +void ksu_mark_running_process(void); + +// Per-task mark operations +int ksu_get_task_mark(pid_t pid); +int ksu_set_task_mark(pid_t pid, bool mark); + + +static inline void ksu_set_task_tracepoint_flag(struct task_struct *t) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) + set_task_syscall_work(t, SYSCALL_TRACEPOINT); +#else + set_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT); +#endif +} + +static inline void ksu_clear_task_tracepoint_flag(struct task_struct *t) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) + clear_task_syscall_work(t, SYSCALL_TRACEPOINT); +#else + clear_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT); +#endif +} + +#endif diff --git a/kernel/throne_tracker.c b/kernel/throne_tracker.c index b1e15bbe..2c2dac0d 100644 --- a/kernel/throne_tracker.c +++ b/kernel/throne_tracker.c @@ -10,7 +10,6 @@ #include "allowlist.h" #include "klog.h" // IWYU pragma: keep -#include "ksu.h" #include "manager.h" #include "throne_tracker.h" #include "apk_sign.h"