kernel: Implementing editable, removable mount points

This commit is contained in:
ShirkNeko
2025-11-07 12:16:42 +08:00
parent d7c101e244
commit 9b209765c4
6 changed files with 423 additions and 13 deletions

View File

@@ -5,6 +5,7 @@ kernelsu-objs += apk_sign.o
kernelsu-objs += sucompat.o kernelsu-objs += sucompat.o
kernelsu-objs += pkg_observer.o kernelsu-objs += pkg_observer.o
kernelsu-objs += throne_tracker.o kernelsu-objs += throne_tracker.o
kernelsu-objs += umount_manager.o
kernelsu-objs += core_hook.o kernelsu-objs += core_hook.o
kernelsu-objs += supercalls.o kernelsu-objs += supercalls.o
kernelsu-objs += feature.o kernelsu-objs += feature.o

View File

@@ -46,6 +46,7 @@
#include "sulog.h" #include "sulog.h"
#include "throne_tracker.h" #include "throne_tracker.h"
#include "throne_comm.h" #include "throne_comm.h"
#include "umount_manager.h"
#ifdef CONFIG_KSU_MANUAL_SU #ifdef CONFIG_KSU_MANUAL_SU
#include "manual_su.h" #include "manual_su.h"
@@ -501,18 +502,7 @@ static void umount_tw_func(struct callback_head *cb)
saved = override_creds(tw->old_cred); saved = override_creds(tw->old_cred);
} }
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and ksu_umount_manager_execute_all(tw->old_cred);
// 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);
if (saved) if (saved)
revert_creds(saved); revert_creds(saved);
@@ -857,12 +847,17 @@ __maybe_unused int ksu_kprobe_exit(void)
void __init ksu_core_init(void) void __init ksu_core_init(void)
{ {
int rc = 0;
#ifdef CONFIG_KPROBES #ifdef CONFIG_KPROBES
int rc = ksu_kprobe_init(); rc = ksu_kprobe_init();
if (rc) { if (rc) {
pr_err("ksu_kprobe_init failed: %d\n", rc); pr_err("ksu_kprobe_init failed: %d\n", rc);
} }
#endif #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)) { if (ksu_register_feature_handler(&kernel_umount_handler)) {
pr_err("Failed to register umount feature handler\n"); pr_err("Failed to register umount feature handler\n");
} }
@@ -884,4 +879,5 @@ void ksu_core_exit(void)
#endif #endif
ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT); ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT);
ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY); ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY);
} }

View File

@@ -23,6 +23,7 @@
#include "kernel_compat.h" #include "kernel_compat.h"
#include "throne_comm.h" #include "throne_comm.h"
#include "dynamic_manager.h" #include "dynamic_manager.h"
#include "umount_manager.h"
#ifdef CONFIG_KSU_MANUAL_SU #ifdef CONFIG_KSU_MANUAL_SU
#include "manual_su.h" #include "manual_su.h"
@@ -616,6 +617,35 @@ static int do_manual_su(void __user *arg)
} }
#endif #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 // IOCTL handlers mapping table
static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = { 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 }, { .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 #ifdef CONFIG_KPM
{ .cmd = KSU_IOCTL_KPM, .name = "KPM_OPERATION", .handler = do_kpm, .perm_check = manager_or_root}, { .cmd = KSU_IOCTL_KPM, .name = "KPM_OPERATION", .handler = do_kpm, .perm_check = manager_or_root},
#endif #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 { .cmd = 0, .name = NULL, .handler = NULL, .perm_check = NULL} // Sentine
}; };

View File

@@ -144,6 +144,7 @@ struct ksu_manual_su_cmd {
#ifdef CONFIG_KSU_MANUAL_SU #ifdef CONFIG_KSU_MANUAL_SU
#define KSU_IOCTL_MANUAL_SU _IOC(_IOC_READ|_IOC_WRITE, 'K', 106, 0) #define KSU_IOCTL_MANUAL_SU _IOC(_IOC_READ|_IOC_WRITE, 'K', 106, 0)
#endif #endif
#define KSU_IOCTL_UMOUNT_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 107, 0)
// IOCTL handler types // IOCTL handler types
typedef int (*ksu_ioctl_handler_t)(void __user *arg); typedef int (*ksu_ioctl_handler_t)(void __user *arg);

314
kernel/umount_manager.c Normal file
View File

@@ -0,0 +1,314 @@
#include "umount_manager.h"
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/namei.h>
#include <linux/path.h>
#include <linux/mount.h>
#include <linux/cred.h>
#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;
}

67
kernel/umount_manager.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef __KSU_H_UMOUNT_MANAGER
#define __KSU_H_UMOUNT_MANAGER
#include <linux/types.h>
#include <linux/list.h>
#include <linux/spinlock.h>
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