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.

---------

- Remove duplicate checks

Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
Co-authored-by: weishu <twsxtd@gmail.com>
Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
backslashxx
2025-11-18 11:10:44 +08:00
committed by ShirkNeko
parent 58c8289890
commit 029ae8d389
48 changed files with 252 additions and 260 deletions

View File

@@ -18,6 +18,7 @@
#include "feature.h"
#include "klog.h" // IWYU pragma: keep
#include "ksud.h"
#include "kernel_umount.h"
#include "manager.h"
#include "selinux/selinux.h"
#include "objsec.h"
@@ -510,6 +511,111 @@ static int do_nuke_ext4_sysfs(void __user *arg)
return nuke_ext4_sysfs(mnt);
}
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
static int do_get_full_version(void __user *arg)
{
@@ -721,7 +827,7 @@ static int do_umount_manager(void __user *arg)
switch (cmd.operation) {
case UMOUNT_OP_ADD: {
return ksu_umount_manager_add(cmd.path, cmd.check_mnt, cmd.flags, false);
return ksu_umount_manager_add(cmd.path, cmd.flags, false);
}
case UMOUNT_OP_REMOVE: {
return ksu_umount_manager_remove(cmd.path);
@@ -758,6 +864,7 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
{ .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_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_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},