Files
SukiSU-Ultra/kernel/kernel_umount.c
backslashxx 02c3f70ab5 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>
2025-11-18 15:36:51 +08:00

242 lines
6.5 KiB
C

#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/task_work.h>
#include <linux/version.h>
#include <linux/cred.h>
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/nsproxy.h>
#include <linux/path.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include "manager.h"
#include "kernel_umount.h"
#include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h"
#include "allowlist.h"
#include "selinux/selinux.h"
#include "feature.h"
#include "ksud.h"
#include "sulog.h"
#ifndef CONFIG_KSU_SUSFS
static bool ksu_kernel_umount_enabled = true;
#else
bool ksu_kernel_umount_enabled = true;
#endif
static int kernel_umount_feature_get(u64 *value)
{
*value = ksu_kernel_umount_enabled ? 1 : 0;
return 0;
}
static int kernel_umount_feature_set(u64 value)
{
bool enable = value != 0;
ksu_kernel_umount_enabled = enable;
pr_info("kernel_umount: set to %d\n", enable);
return 0;
}
static const struct ksu_feature_handler kernel_umount_handler = {
.feature_id = KSU_FEATURE_KERNEL_UMOUNT,
.name = "kernel_umount",
.get_handler = kernel_umount_feature_get,
.set_handler = kernel_umount_feature_set,
};
#ifdef CONFIG_KSU_SUSFS
extern bool susfs_is_mnt_devname_ksu(struct path *path);
#if defined(CONFIG_KSU_SUSFS_TRY_UMOUNT) && defined(CONFIG_KSU_SUSFS_ENABLE_LOG)
extern bool susfs_is_log_enabled;
#endif // #if defined(CONFIG_KSU_SUSFS_TRY_UMOUNT) && defined(CONFIG_KSU_SUSFS_ENABLE_LOG)
#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT
extern void susfs_try_umount(void);
#endif // #ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT
#endif // #ifdef CONFIG_KSU_SUSFS
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || defined(KSU_HAS_PATH_UMOUNT)
static int ksu_path_umount(struct path *path, int flags)
{
return path_umount(path, flags);
}
#define ksu_umount_mnt(__unused, path, flags) (ksu_path_umount(path, flags))
#else
// TODO: Search a way to make this works without set_fs functions
static int ksu_sys_umount(const char *mnt, int flags)
{
char __user *usermnt = (char __user *)mnt;
mm_segment_t old_fs;
int ret; // although asmlinkage long
old_fs = get_fs();
set_fs(KERNEL_DS);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)
ret = ksys_umount(usermnt, flags);
#else
ret = sys_umount(usermnt, flags); // cuz asmlinkage long sys##name
#endif
set_fs(old_fs);
pr_info("%s was called, ret: %d\n", __func__, ret);
return ret;
}
#define ksu_umount_mnt(mnt, __unused, flags) \
({ \
int ret; \
path_put(__unused); \
ret = ksu_sys_umount(mnt, flags); \
ret; \
})
#endif
#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT
void try_umount(const char *mnt, int flags)
#else
static void try_umount(const char *mnt, int flags)
#endif // #ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT
{
struct path path;
int ret;
int err = kern_path(mnt, 0, &path);
if (err) {
return;
}
if (path.dentry != path.mnt->mnt_root) {
// it is not root mountpoint, maybe umounted by others already.
path_put(&path);
return;
}
#if defined(CONFIG_KSU_SUSFS_TRY_UMOUNT) && defined(CONFIG_KSU_SUSFS_ENABLE_LOG)
if (susfs_is_log_enabled) {
pr_info("susfs: umounting '%s'\n", mnt);
}
#endif // #if defined(CONFIG_KSU_SUSFS_TRY_UMOUNT) && defined(CONFIG_KSU_SUSFS_ENABLE_LOG)
ret = ksu_umount_mnt(mnt, &path, flags);
if (ret) {
#ifdef CONFIG_KSU_DEBUG
pr_info("%s: path: %s, ret: %d\n", __func__, mnt, ret);
#endif
}
}
#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT
void susfs_try_umount_all(void) {
susfs_try_umount();
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("/debug_ramdisk", true, MNT_DETACH);
}
#endif // #ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT
#ifndef CONFIG_KSU_SUSFS
struct umount_tw {
struct callback_head cb;
const struct cred *old_cred;
};
static void umount_tw_func(struct callback_head *cb)
{
struct umount_tw *tw = container_of(cb, struct umount_tw, cb);
const struct cred *saved = NULL;
if (tw->old_cred) {
saved = override_creds(tw->old_cred);
}
struct mount_entry *entry;
down_read(&mount_list_lock);
list_for_each_entry(entry, &mount_list, list) {
pr_info("%s: unmounting: %s flags 0x%x\n", __func__, entry->umountable, entry->flags);
try_umount(entry->umountable, entry->flags);
}
up_read(&mount_list_lock);
if (saved)
revert_creds(saved);
if (tw->old_cred)
put_cred(tw->old_cred);
kfree(tw);
}
int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
{
struct umount_tw *tw;
// this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it!
if (!ksu_module_mounted) {
return 0;
}
if (!ksu_kernel_umount_enabled) {
return 0;
}
// FIXME: isolated process which directly forks from zygote is not handled
if (!is_appuid(new_uid)) {
return 0;
}
if (!ksu_uid_should_umount(new_uid)) {
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
// when we umount for such process, that is a disaster!
bool is_zygote_child = is_zygote(get_current_cred());
if (!is_zygote_child) {
pr_info("handle umount ignore non zygote child: %d\n", current->pid);
return 0;
}
#if __SULOG_GATE
ksu_sulog_report_syscall(new_uid, NULL, "setuid", NULL);
#endif
// umount the target mnt
pr_info("handle umount for uid: %d, pid: %d\n", new_uid, current->pid);
tw = kzalloc(sizeof(*tw), GFP_ATOMIC);
if (!tw)
return 0;
tw->old_cred = get_current_cred();
tw->cb.func = umount_tw_func;
int err = task_work_add(current, &tw->cb, TWA_RESUME);
if (err) {
if (tw->old_cred) {
put_cred(tw->old_cred);
}
kfree(tw);
pr_warn("unmount add task_work failed\n");
}
return 0;
}
#endif // #ifndef CONFIG_KSU_SUSFS
void ksu_kernel_umount_init(void)
{
if (ksu_register_feature_handler(&kernel_umount_handler)) {
pr_err("Failed to register kernel_umount feature handler\n");
}
}
void ksu_kernel_umount_exit(void)
{
ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT);
}