From a821b7d29996d38df3e541dc63f907c7da3255e5 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:33:09 +0800 Subject: [PATCH] kernel: Implementing editable, removable mount points --- kernel/Makefile | 1 + kernel/kernel_umount.c | 33 ++++-- kernel/kernel_umount.h | 2 + kernel/supercalls.c | 43 ++++++- kernel/supercalls.h | 1 + kernel/umount_manager.c | 242 ++++++++++++++++++++++++++++++++++++++++ kernel/umount_manager.h | 63 +++++++++++ 7 files changed, 368 insertions(+), 17 deletions(-) create mode 100644 kernel/umount_manager.c create mode 100644 kernel/umount_manager.h diff --git a/kernel/Makefile b/kernel/Makefile index a7534c5f..f6824941 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -20,6 +20,7 @@ kernelsu-objs += seccomp_cache.o kernelsu-objs += file_wrapper.o kernelsu-objs += throne_comm.o kernelsu-objs += sulog.o +kernelsu-objs += umount_manager.o ifeq ($(CONFIG_KSU_MANUAL_SU), y) ccflags-y += -DCONFIG_KSU_MANUAL_SU diff --git a/kernel/kernel_umount.c b/kernel/kernel_umount.c index 793dc2e3..4ab3c444 100644 --- a/kernel/kernel_umount.c +++ b/kernel/kernel_umount.c @@ -26,6 +26,7 @@ #include "ksud.h" #include "sulog.h" +#include "umount_manager.h" static bool ksu_kernel_umount_enabled = true; @@ -54,7 +55,7 @@ static const struct ksu_feature_handler kernel_umount_handler = { extern bool susfs_is_log_enabled; #endif // #ifdef CONFIG_KSU_SUSFS -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || \ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || \ defined(KSU_HAS_PATH_UMOUNT) extern int path_umount(struct path *path, int flags); static void ksu_umount_mnt(const char *__never_use_mnt, struct path *path, @@ -89,7 +90,7 @@ static void ksu_sys_umount(const char *mnt, int flags) #endif -static void try_umount(const char *mnt, int flags) +void try_umount(const char *mnt, int flags) { struct path path; int err = kern_path(mnt, 0, &path); @@ -128,6 +129,8 @@ static void umount_tw_func(struct callback_head *cb) } up_read(&mount_list_lock); + ksu_umount_manager_execute_all(tw->old_cred); + if (saved) revert_creds(saved); @@ -152,18 +155,18 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid) #ifndef CONFIG_KSU_SUSFS // There are 5 scenarios: - // 1. Normal app: zygote -> appuid - // 2. Isolated process forked from zygote: zygote -> isolated_process - // 3. App zygote forked from zygote: zygote -> appuid - // 4. Isolated process froked from app zygote: appuid -> isolated_process (already handled by 3) - // 5. Isolated process froked from webview zygote (no need to handle, app cannot run custom code) - if (!is_appuid(new_uid) && !is_isolated_process(new_uid)) { - return 0; - } + // 1. Normal app: zygote -> appuid + // 2. Isolated process forked from zygote: zygote -> isolated_process + // 3. App zygote forked from zygote: zygote -> appuid + // 4. Isolated process froked from app zygote: appuid -> isolated_process (already handled by 3) + // 5. Isolated process froked from webview zygote (no need to handle, app cannot run custom code) + if (!is_appuid(new_uid) && !is_isolated_process(new_uid)) { + return 0; + } if (!ksu_uid_should_umount(new_uid) && !is_isolated_process(new_uid)) { - return 0; - } + return 0; + } // 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 @@ -204,6 +207,11 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid) 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"); } @@ -211,5 +219,6 @@ void ksu_kernel_umount_init(void) void ksu_kernel_umount_exit(void) { + ksu_umount_manager_exit(); 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 index 46486f8e..6ada3583 100644 --- a/kernel/kernel_umount.h +++ b/kernel/kernel_umount.h @@ -20,4 +20,6 @@ struct mount_entry { extern struct list_head mount_list; extern struct rw_semaphore mount_list_lock; +void try_umount(const char *mnt, int flags); + #endif diff --git a/kernel/supercalls.c b/kernel/supercalls.c index edecc3b3..1cdaec82 100644 --- a/kernel/supercalls.c +++ b/kernel/supercalls.c @@ -40,6 +40,8 @@ #include "manual_su.h" #endif +#include "umount_manager.h" + #ifdef CONFIG_KSU_SUSFS bool susfs_is_boot_completed_triggered = false; #endif // #ifdef CONFIG_KSU_SUSFS @@ -471,7 +473,7 @@ static int do_manage_mark(void __user *arg) break; #else cmd.result = 0; - break; + break; #endif } case KSU_MARK_MARK: { @@ -488,8 +490,8 @@ static int do_manage_mark(void __user *arg) } #else if (cmd.pid != 0) { - return 0; - } + return 0; + } #endif break; } @@ -507,8 +509,8 @@ static int do_manage_mark(void __user *arg) } #else if (cmd.pid != 0) { - return 0; - } + return 0; + } #endif break; } @@ -871,6 +873,36 @@ static int do_manual_su(void __user *arg) } #endif +static int do_umount_manager(void __user *arg) +{ + struct ksu_umount_manager_cmd cmd; + + if (copy_from_user(&cmd, arg, sizeof(cmd))) { + pr_err("umount_manager: copy_from_user failed\n"); + return -EFAULT; + } + + switch (cmd.operation) { + case UMOUNT_OP_ADD: { + return ksu_umount_manager_add(cmd.path, cmd.flags, false); + } + case UMOUNT_OP_REMOVE: { + return ksu_umount_manager_remove(cmd.path); + } + case UMOUNT_OP_LIST: { + struct ksu_umount_entry_info __user *entries = + (struct ksu_umount_entry_info __user *)cmd.entries_ptr; + return ksu_umount_manager_get_entries(entries, &cmd.count); + } + case UMOUNT_OP_CLEAR_CUSTOM: { + return ksu_umount_manager_clear_custom(); + } + default: + return -EINVAL; + } +} + + // IOCTL handlers mapping table static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = { { .cmd = KSU_IOCTL_GRANT_ROOT, .name = "GRANT_ROOT", .handler = do_grant_root, .perm_check = allowed_for_su }, @@ -903,6 +935,7 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = { #ifdef CONFIG_KPM { .cmd = KSU_IOCTL_KPM, .name = "KPM_OPERATION", .handler = do_kpm, .perm_check = manager_or_root}, #endif + { .cmd = KSU_IOCTL_UMOUNT_MANAGER, .name = "UMOUNT_MANAGER", .handler = do_umount_manager, .perm_check = manager_or_root}, { .cmd = 0, .name = NULL, .handler = NULL, .perm_check = NULL} // Sentine }; diff --git a/kernel/supercalls.h b/kernel/supercalls.h index aa300285..a293ea59 100644 --- a/kernel/supercalls.h +++ b/kernel/supercalls.h @@ -174,6 +174,7 @@ struct ksu_manual_su_cmd { #ifdef CONFIG_KSU_MANUAL_SU #define KSU_IOCTL_MANUAL_SU _IOC(_IOC_READ|_IOC_WRITE, 'K', 106, 0) #endif +#define KSU_IOCTL_UMOUNT_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 107, 0) // IOCTL handler types typedef int (*ksu_ioctl_handler_t)(void __user *arg); diff --git a/kernel/umount_manager.c b/kernel/umount_manager.c new file mode 100644 index 00000000..1702ee34 --- /dev/null +++ b/kernel/umount_manager.c @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "klog.h" +#include "kernel_umount.h" +#include "umount_manager.h" + +static struct umount_manager g_umount_mgr = { + .entry_count = 0, + .max_entries = 64, +}; + +static void try_umount_path(struct umount_entry *entry) +{ + try_umount(entry->path, entry->flags); +} + +static struct umount_entry *find_entry_locked(const char *path) +{ + struct umount_entry *entry; + + list_for_each_entry(entry, &g_umount_mgr.entry_list, list) { + if (strcmp(entry->path, path) == 0) { + return entry; + } + } + + return NULL; +} + +int ksu_umount_manager_init(void) +{ + INIT_LIST_HEAD(&g_umount_mgr.entry_list); + spin_lock_init(&g_umount_mgr.lock); + + return 0; +} + +void ksu_umount_manager_exit(void) +{ + struct umount_entry *entry, *tmp; + unsigned long flags; + + spin_lock_irqsave(&g_umount_mgr.lock, flags); + + list_for_each_entry_safe(entry, tmp, &g_umount_mgr.entry_list, list) { + list_del(&entry->list); + kfree(entry); + g_umount_mgr.entry_count--; + } + + spin_unlock_irqrestore(&g_umount_mgr.lock, flags); + + pr_info("Umount manager cleaned up\n"); +} + +int ksu_umount_manager_add(const char *path, int flags, bool is_default) +{ + struct umount_entry *entry; + unsigned long irqflags; + int ret = 0; + + if (flags == -1) + flags = MNT_DETACH; + + if (!path || strlen(path) == 0 || strlen(path) >= 256) { + return -EINVAL; + } + + spin_lock_irqsave(&g_umount_mgr.lock, irqflags); + + if (g_umount_mgr.entry_count >= g_umount_mgr.max_entries) { + pr_err("Umount manager: max entries reached\n"); + ret = -ENOMEM; + goto out; + } + + if (find_entry_locked(path)) { + pr_warn("Umount manager: path already exists: %s\n", path); + ret = -EEXIST; + goto out; + } + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + ret = -ENOMEM; + goto out; + } + + strncpy(entry->path, path, sizeof(entry->path) - 1); + entry->flags = flags; + entry->state = UMOUNT_STATE_IDLE; + entry->is_default = is_default; + entry->ref_count = 0; + + list_add_tail(&entry->list, &g_umount_mgr.entry_list); + g_umount_mgr.entry_count++; + + pr_info("Umount manager: added %s entry: %s\n", + is_default ? "default" : "custom", path); + +out: + spin_unlock_irqrestore(&g_umount_mgr.lock, irqflags); + return ret; +} + +int ksu_umount_manager_remove(const char *path) +{ + struct umount_entry *entry; + unsigned long flags; + int ret = 0; + + if (!path) { + return -EINVAL; + } + + spin_lock_irqsave(&g_umount_mgr.lock, flags); + + entry = find_entry_locked(path); + if (!entry) { + ret = -ENOENT; + goto out; + } + + if (entry->is_default) { + pr_err("Umount manager: cannot remove default entry: %s\n", path); + ret = -EPERM; + goto out; + } + + if (entry->state == UMOUNT_STATE_BUSY || entry->ref_count > 0) { + pr_err("Umount manager: entry is busy: %s\n", path); + ret = -EBUSY; + goto out; + } + + list_del(&entry->list); + g_umount_mgr.entry_count--; + kfree(entry); + + pr_info("Umount manager: removed entry: %s\n", path); + +out: + spin_unlock_irqrestore(&g_umount_mgr.lock, flags); + return ret; +} + +void ksu_umount_manager_execute_all(const struct cred *cred) +{ + struct umount_entry *entry; + unsigned long flags; + + spin_lock_irqsave(&g_umount_mgr.lock, flags); + + list_for_each_entry(entry, &g_umount_mgr.entry_list, list) { + if (entry->state == UMOUNT_STATE_IDLE) { + entry->ref_count++; + } + } + + spin_unlock_irqrestore(&g_umount_mgr.lock, flags); + + list_for_each_entry(entry, &g_umount_mgr.entry_list, list) { + if (entry->ref_count > 0 && entry->state == UMOUNT_STATE_IDLE) { + try_umount_path(entry); + } + } + + spin_lock_irqsave(&g_umount_mgr.lock, flags); + + list_for_each_entry(entry, &g_umount_mgr.entry_list, list) { + if (entry->ref_count > 0) { + entry->ref_count--; + } + } + + spin_unlock_irqrestore(&g_umount_mgr.lock, flags); +} + +int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count) +{ + struct umount_entry *entry; + struct ksu_umount_entry_info info; + unsigned long flags; + u32 idx = 0; + u32 max_count = *count; + + spin_lock_irqsave(&g_umount_mgr.lock, flags); + + list_for_each_entry(entry, &g_umount_mgr.entry_list, list) { + if (idx >= max_count) { + break; + } + + memset(&info, 0, sizeof(info)); + strncpy(info.path, entry->path, sizeof(info.path) - 1); + info.flags = entry->flags; + info.is_default = entry->is_default; + info.state = entry->state; + info.ref_count = entry->ref_count; + + if (copy_to_user(&entries[idx], &info, sizeof(info))) { + spin_unlock_irqrestore(&g_umount_mgr.lock, flags); + return -EFAULT; + } + + idx++; + } + + *count = idx; + + spin_unlock_irqrestore(&g_umount_mgr.lock, flags); + return 0; +} + +int ksu_umount_manager_clear_custom(void) +{ + struct umount_entry *entry, *tmp; + unsigned long flags; + u32 cleared = 0; + + spin_lock_irqsave(&g_umount_mgr.lock, flags); + + list_for_each_entry_safe(entry, tmp, &g_umount_mgr.entry_list, list) { + if (!entry->is_default && entry->state == UMOUNT_STATE_IDLE && entry->ref_count == 0) { + list_del(&entry->list); + kfree(entry); + g_umount_mgr.entry_count--; + cleared++; + } + } + + spin_unlock_irqrestore(&g_umount_mgr.lock, flags); + + pr_info("Umount manager: cleared %u custom entries\n", cleared); + return 0; +} \ No newline at end of file diff --git a/kernel/umount_manager.h b/kernel/umount_manager.h new file mode 100644 index 00000000..cd1e5454 --- /dev/null +++ b/kernel/umount_manager.h @@ -0,0 +1,63 @@ +#ifndef __KSU_H_UMOUNT_MANAGER +#define __KSU_H_UMOUNT_MANAGER + +#include +#include +#include + +struct cred; + +enum umount_entry_state { + UMOUNT_STATE_IDLE = 0, + UMOUNT_STATE_ACTIVE = 1, + UMOUNT_STATE_BUSY = 2, +}; + +struct umount_entry { + struct list_head list; + char path[256]; + int flags; + enum umount_entry_state state; + bool is_default; + u32 ref_count; +}; + +struct umount_manager { + struct list_head entry_list; + spinlock_t lock; + u32 entry_count; + u32 max_entries; +}; + +enum umount_manager_op { + UMOUNT_OP_ADD = 0, + UMOUNT_OP_REMOVE = 1, + UMOUNT_OP_LIST = 2, + UMOUNT_OP_CLEAR_CUSTOM = 3, +}; + +struct ksu_umount_manager_cmd { + __u32 operation; + char path[256]; + __s32 flags; + __u32 count; + __aligned_u64 entries_ptr; +}; + +struct ksu_umount_entry_info { + char path[256]; + __s32 flags; + __u8 is_default; + __u32 state; + __u32 ref_count; +}; + +int ksu_umount_manager_init(void); +void ksu_umount_manager_exit(void); +int ksu_umount_manager_add(const char *path, int flags, bool is_default); +int ksu_umount_manager_remove(const char *path); +void ksu_umount_manager_execute_all(const struct cred *cred); +int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count); +int ksu_umount_manager_clear_custom(void); + +#endif // __KSU_H_UMOUNT_MANAGER \ No newline at end of file