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>
242 lines
6.5 KiB
C
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);
|
|
} |