From 9b209765c46c4aafe913d84f0de87cde0d2f63a8 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Fri, 7 Nov 2025 12:16:42 +0800 Subject: [PATCH] kernel: Implementing editable, removable mount points --- kernel/Makefile | 1 + kernel/core_hook.c | 22 ++- kernel/supercalls.c | 31 ++++ kernel/supercalls.h | 1 + kernel/umount_manager.c | 314 ++++++++++++++++++++++++++++++++++++++++ kernel/umount_manager.h | 67 +++++++++ 6 files changed, 423 insertions(+), 13 deletions(-) create mode 100644 kernel/umount_manager.c create mode 100644 kernel/umount_manager.h diff --git a/kernel/Makefile b/kernel/Makefile index 522e8538..d6477d16 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -5,6 +5,7 @@ kernelsu-objs += apk_sign.o kernelsu-objs += sucompat.o kernelsu-objs += pkg_observer.o kernelsu-objs += throne_tracker.o +kernelsu-objs += umount_manager.o kernelsu-objs += core_hook.o kernelsu-objs += supercalls.o kernelsu-objs += feature.o diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 22d24b37..31347846 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -46,6 +46,7 @@ #include "sulog.h" #include "throne_tracker.h" #include "throne_comm.h" +#include "umount_manager.h" #ifdef CONFIG_KSU_MANUAL_SU #include "manual_su.h" @@ -501,18 +502,7 @@ static void umount_tw_func(struct callback_head *cb) 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); + ksu_umount_manager_execute_all(tw->old_cred); if (saved) revert_creds(saved); @@ -857,12 +847,17 @@ __maybe_unused int ksu_kprobe_exit(void) void __init ksu_core_init(void) { + int rc = 0; #ifdef CONFIG_KPROBES - int rc = ksu_kprobe_init(); + rc = ksu_kprobe_init(); if (rc) { pr_err("ksu_kprobe_init failed: %d\n", rc); } #endif + 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 umount feature handler\n"); } @@ -884,4 +879,5 @@ void ksu_core_exit(void) #endif ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT); ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY); + } diff --git a/kernel/supercalls.c b/kernel/supercalls.c index 91762be2..337362a6 100644 --- a/kernel/supercalls.c +++ b/kernel/supercalls.c @@ -23,6 +23,7 @@ #include "kernel_compat.h" #include "throne_comm.h" #include "dynamic_manager.h" +#include "umount_manager.h" #ifdef CONFIG_KSU_MANUAL_SU #include "manual_su.h" @@ -616,6 +617,35 @@ 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.check_mnt, 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 }, @@ -645,6 +675,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 5e1bbeaf..5e3e3cdb 100644 --- a/kernel/supercalls.h +++ b/kernel/supercalls.h @@ -144,6 +144,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..a3c3ac74 --- /dev/null +++ b/kernel/umount_manager.c @@ -0,0 +1,314 @@ +#include "umount_manager.h" +#include +#include +#include +#include +#include +#include +#include +#include "klog.h" + +static struct umount_manager g_umount_mgr = { + .entry_count = 0, + .max_entries = 64, +}; + +extern int path_umount(struct path *path, int flags); + +static bool check_path_busy(const char *path) +{ + struct path kpath; + int err; + + err = kern_path(path, 0, &kpath); + if (err) { + return false; + } + + bool busy = (kpath.mnt->mnt_root != kpath.dentry); + path_put(&kpath); + + return busy; +} + +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; +} + +static int init_default_entries(void) +{ + int ret; + + const struct { + const char *path; + bool check_mnt; + int flags; + } defaults[] = { + { "/odm", true, 0 }, + { "/system", true, 0 }, + { "/vendor", true, 0 }, + { "/product", true, 0 }, + { "/system_ext", true, 0 }, + { "/data/adb/modules", false, MNT_DETACH }, + }; + + for (int i = 0; i < ARRAY_SIZE(defaults); i++) { + ret = ksu_umount_manager_add(defaults[i].path, + defaults[i].check_mnt, + defaults[i].flags, + true); // is_default = true + if (ret) { + pr_err("Failed to add default entry: %s, ret=%d\n", + defaults[i].path, ret); + return ret; + } + } + + pr_info("Initialized %zu default umount entries\n", ARRAY_SIZE(defaults)); + return 0; +} + +int ksu_umount_manager_init(void) +{ + INIT_LIST_HEAD(&g_umount_mgr.entry_list); + spin_lock_init(&g_umount_mgr.lock); + + return init_default_entries(); +} + +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, bool check_mnt, int flags, bool is_default) +{ + struct umount_entry *entry; + unsigned long irqflags; + int ret = 0; + + 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->check_mnt = check_mnt; + 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; +} + +bool ksu_umount_path_is_busy(const char *path) +{ + return check_path_busy(path); +} + +static void execute_umount_entry(struct umount_entry *entry, const struct cred *cred) +{ + struct path kpath; + int err; + + entry->ref_count++; + entry->state = UMOUNT_STATE_ACTIVE; + + err = kern_path(entry->path, 0, &kpath); + if (err) { + goto done; + } + + if (kpath.dentry != kpath.mnt->mnt_root) { + path_put(&kpath); + goto done; + } + + if (entry->check_mnt) { + if (kpath.mnt && kpath.mnt->mnt_sb && kpath.mnt->mnt_sb->s_type) { + const char *fstype = kpath.mnt->mnt_sb->s_type->name; + if (strcmp(fstype, "overlay") != 0) { + path_put(&kpath); + goto done; + } + } + } + + err = path_umount(&kpath, entry->flags); + if (err) { + pr_info("umount %s failed: %d\n", entry->path, err); + } + + path_put(&kpath); + +done: + entry->state = UMOUNT_STATE_IDLE; + entry->ref_count--; +} + +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) { + execute_umount_entry(entry, cred); + } + } + + 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.check_mnt = entry->check_mnt; + 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; +} diff --git a/kernel/umount_manager.h b/kernel/umount_manager.h new file mode 100644 index 00000000..75c550e1 --- /dev/null +++ b/kernel/umount_manager.h @@ -0,0 +1,67 @@ +#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]; + bool check_mnt; + 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]; + __u8 check_mnt; + __s32 flags; + __u32 count; + __aligned_u64 entries_ptr; +}; + +struct ksu_umount_entry_info { + char path[256]; + __u8 check_mnt; + __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, bool check_mnt, int flags, bool is_default); +int ksu_umount_manager_remove(const char *path); +bool ksu_umount_path_is_busy(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