kernel: expose umount list to ioctl interface (#2950)

This idea is borrowed from simonpunk's susfs4ksu.
What we see here is that, yeah well, lets just have userspace send us
what it
wants unmounted, this is better than hardcoding everything.

This also solves that issue where MNT_DETACH fails, as long as we send
unmountables in proper order.

A small anti-duplicate mechanism is also added.

While in-kernel umount is a bit worse than zygisk-provider-based ones,
this can still
serve as a healthy alternative.

---------

Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
Co-authored-by: weishu <twsxtd@gmail.com>
Signed-off-by: fc5b87cf <rissu.ntk@gmail.com>
This commit is contained in:
backslashxx
2025-11-18 11:10:44 +08:00
committed by ShirkNeko
parent 2aa0034695
commit 3510203fa6
4 changed files with 156 additions and 40 deletions

View File

@@ -44,28 +44,11 @@ static const struct ksu_feature_handler kernel_umount_handler = {
.set_handler = kernel_umount_feature_set, .set_handler = kernel_umount_feature_set,
}; };
static bool should_umount(struct path *path) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || \
{ defined(KSU_HAS_PATH_UMOUNT)
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;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || defined(KSU_HAS_PATH_UMOUNT)
extern int path_umount(struct path *path, int flags); extern int path_umount(struct path *path, int flags);
static void ksu_umount_mnt(const char *__never_use_mnt, struct path *path, int flags) static void ksu_umount_mnt(const char *__never_use_mnt, struct path *path,
int flags)
{ {
int err = path_umount(path, flags); int err = path_umount(path, flags);
if (err) { if (err) {
@@ -91,12 +74,12 @@ static void ksu_sys_umount(const char *mnt, int flags)
#define ksu_umount_mnt(mnt, __unused, flags) \ #define ksu_umount_mnt(mnt, __unused, flags) \
({ \ ({ \
path_put(__unused); \ path_put(__unused); \
ksu_sys_umount(mnt, flags); \ ksu_sys_umount(mnt, flags); \
}) })
#endif #endif
static void try_umount(const char *mnt, bool check_mnt, int flags) static void try_umount(const char *mnt, int flags)
{ {
struct path path; struct path path;
int err = kern_path(mnt, 0, &path); int err = kern_path(mnt, 0, &path);
@@ -110,25 +93,17 @@ static void try_umount(const char *mnt, bool check_mnt, int flags)
return; return;
} }
// we are only interest in some specific mounts
if (check_mnt && !should_umount(&path)) {
path_put(&path);
return;
}
ksu_umount_mnt(mnt, &path, flags); ksu_umount_mnt(mnt, &path, flags);
} }
static inline void do_ksu_umount_lists(void) static inline void do_umount_work(void)
{ {
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and struct mount_entry *entry;
// filter the mountpoint whose target is `/data/adb` list_for_each_entry (entry, &mount_list, list) {
try_umount("/odm", true, 0); pr_info("%s: unmounting: %s flags 0x%x\n", __func__,
try_umount("/system", true, 0); entry->umountable, entry->flags);
try_umount("/vendor", true, 0); try_umount(entry->umountable, entry->flags);
try_umount("/product", true, 0); }
try_umount("/system_ext", true, 0);
try_umount("/data/adb/modules", false, MNT_DETACH);
} }
#ifdef KSU_SHOULD_USE_NEW_TP #ifdef KSU_SHOULD_USE_NEW_TP
@@ -145,7 +120,9 @@ static void umount_tw_func(struct callback_head *cb)
saved = override_creds(tw->old_cred); saved = override_creds(tw->old_cred);
} }
do_ksu_umount_lists(); down_read(&mount_list_lock);
do_umount_work();
up_read(&mount_list_lock);
if (saved) if (saved)
revert_creds(saved); revert_creds(saved);
@@ -208,7 +185,9 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
} }
#else #else
// Using task work for non-kp context is expansive? // Using task work for non-kp context is expansive?
do_ksu_umount_lists(); down_read(&mount_list_lock);
do_umount_work();
up_read(&mount_list_lock);
#endif #endif
return 0; return 0;

View File

@@ -2,6 +2,8 @@
#define __KSU_H_KERNEL_UMOUNT #define __KSU_H_KERNEL_UMOUNT
#include <linux/types.h> #include <linux/types.h>
#include <linux/list.h>
#include <linux/rwsem.h>
void ksu_kernel_umount_init(void); void ksu_kernel_umount_init(void);
void ksu_kernel_umount_exit(void); void ksu_kernel_umount_exit(void);
@@ -9,4 +11,13 @@ void ksu_kernel_umount_exit(void);
// Handler function to be called from setresuid hook // Handler function to be called from setresuid hook
int ksu_handle_umount(uid_t old_uid, uid_t new_uid); int ksu_handle_umount(uid_t old_uid, uid_t new_uid);
// for the umount list
struct mount_entry {
char *umountable;
unsigned int flags;
struct list_head list;
};
extern struct list_head mount_list;
extern struct rw_semaphore mount_list_lock;
#endif #endif

View File

@@ -19,6 +19,8 @@
#include "feature.h" #include "feature.h"
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "ksud.h" #include "ksud.h"
#include "kernel_compat.h"
#include "kernel_umount.h"
#include "manager.h" #include "manager.h"
#include "selinux/selinux.h" #include "selinux/selinux.h"
#include "objsec.h" #include "objsec.h"
@@ -475,6 +477,116 @@ static int do_manage_mark(void __user *arg)
return 0; return 0;
} }
struct list_head mount_list = LIST_HEAD_INIT(mount_list);
DECLARE_RWSEM(mount_list_lock);
static int add_try_umount(void __user *arg)
{
struct mount_entry *new_entry, *entry, *tmp;
struct ksu_add_try_umount_cmd cmd;
char buf[256] = { 0 };
if (copy_from_user(&cmd, arg, sizeof cmd))
return -EFAULT;
switch (cmd.mode) {
case KSU_UMOUNT_WIPE: {
struct mount_entry *entry, *tmp;
down_write(&mount_list_lock);
list_for_each_entry_safe (entry, tmp, &mount_list, list) {
pr_info("wipe_umount_list: removing entry: %s\n",
entry->umountable);
list_del(&entry->list);
kfree(entry->umountable);
kfree(entry);
}
up_write(&mount_list_lock);
return 0;
}
case KSU_UMOUNT_ADD: {
long len = strncpy_from_user(buf, (const char __user *)cmd.arg,
256);
if (len <= 0)
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
if (!new_entry)
return -ENOMEM;
new_entry->umountable = kstrdup(buf, GFP_KERNEL);
if (!new_entry->umountable) {
kfree(new_entry);
return -1;
}
down_write(&mount_list_lock);
// disallow dupes
// if this gets too many, we can consider moving this whole task to a kthread
list_for_each_entry (entry, &mount_list, list) {
if (!strcmp(entry->umountable, buf)) {
pr_info("cmd_add_try_umount: %s is already here!\n",
buf);
up_write(&mount_list_lock);
kfree(new_entry->umountable);
kfree(new_entry);
return -1;
}
}
// now check flags and add
// this also serves as a null check
if (cmd.flags)
new_entry->flags = cmd.flags;
else
new_entry->flags = 0;
// debug
list_add(&new_entry->list, &mount_list);
up_write(&mount_list_lock);
pr_info("cmd_add_try_umount: %s added!\n", buf);
return 0;
}
// this is just strcmp'd wipe anyway
case KSU_UMOUNT_DEL: {
long len = strncpy_from_user(buf, (const char __user *)cmd.arg,
sizeof(buf) - 1);
if (len <= 0)
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
down_write(&mount_list_lock);
list_for_each_entry_safe (entry, tmp, &mount_list, list) {
if (!strcmp(entry->umountable, buf)) {
pr_info("cmd_add_try_umount: entry removed: %s\n",
entry->umountable);
list_del(&entry->list);
kfree(entry->umountable);
kfree(entry);
}
}
up_write(&mount_list_lock);
return 0;
}
default: {
pr_err("cmd_add_try_umount: invalid operation %u\n", cmd.mode);
return -EINVAL;
}
} // switch(cmd.mode)
return 0;
}
// 100. GET_FULL_VERSION - Get full version string // 100. GET_FULL_VERSION - Get full version string
static int do_get_full_version(void __user *arg) static int do_get_full_version(void __user *arg)
{ {
@@ -651,6 +763,8 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
{ .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_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_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_MANAGE_MARK, .name = "MANAGE_MARK", .handler = do_manage_mark, .perm_check = manager_or_root },
//{ .cmd = KSU_IOCTL_NUKE_EXT4_SYSFS, .name = "NUKE_EXT4_SYSFS", .handler = do_nuke_ext4_sysfs, .perm_check = manager_or_root },
{ .cmd = KSU_IOCTL_ADD_TRY_UMOUNT, .name = "ADD_TRY_UMOUNT", .handler = add_try_umount, .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_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_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}, { .cmd = KSU_IOCTL_ENABLE_KPM, .name = "GET_ENABLE_KPM", .handler = do_enable_kpm, .perm_check = manager_or_root},

View File

@@ -121,6 +121,16 @@ struct ksu_manage_mark_cmd {
#define KSU_MARK_UNMARK 3 #define KSU_MARK_UNMARK 3
#define KSU_MARK_REFRESH 4 #define KSU_MARK_REFRESH 4
struct ksu_add_try_umount_cmd {
__aligned_u64 arg; // char ptr, this is the mountpoint
__u32 flags; // this is the flag we use for it
__u8 mode; // denotes what to do with it 0:wipe_list 1:add_to_list 2:delete_entry
};
#define KSU_UMOUNT_WIPE 0 // ignore everything and wipe list
#define KSU_UMOUNT_ADD 1 // add entry (path + flags)
#define KSU_UMOUNT_DEL 2 // delete entry, strcmp
// IOCTL command definitions // IOCTL command definitions
#define KSU_IOCTL_GRANT_ROOT _IOC(_IOC_NONE, 'K', 1, 0) #define KSU_IOCTL_GRANT_ROOT _IOC(_IOC_NONE, 'K', 1, 0)
#define KSU_IOCTL_GET_INFO _IOC(_IOC_READ, 'K', 2, 0) #define KSU_IOCTL_GET_INFO _IOC(_IOC_READ, 'K', 2, 0)
@@ -138,6 +148,8 @@ struct ksu_manage_mark_cmd {
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 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_GET_WRAPPER_FD _IOC(_IOC_WRITE, 'K', 15, 0)
#define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ | _IOC_WRITE, 'K', 16, 0) #define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ | _IOC_WRITE, 'K', 16, 0)
// #define KSU_IOCTL_NUKE_EXT4_SYSFS _IOC(_IOC_WRITE, 'K', 17, 0)
#define KSU_IOCTL_ADD_TRY_UMOUNT _IOC(_IOC_WRITE, 'K', 18, 0)
// Other IOCTL command definitions // Other IOCTL command definitions
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0) #define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0) #define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)