kernel: Implementing editable, removable mount points
This commit is contained in:
@@ -20,6 +20,7 @@ kernelsu-objs += seccomp_cache.o
|
||||
kernelsu-objs += file_wrapper.o
|
||||
kernelsu-objs += throne_comm.o
|
||||
kernelsu-objs += sulog.o
|
||||
kernelsu-objs += umount_manager.o
|
||||
|
||||
ifeq ($(CONFIG_KSU_MANUAL_SU), y)
|
||||
ccflags-y += -DCONFIG_KSU_MANUAL_SU
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ksud.h"
|
||||
|
||||
#include "sulog.h"
|
||||
#include "umount_manager.h"
|
||||
|
||||
static bool ksu_kernel_umount_enabled = true;
|
||||
|
||||
@@ -54,7 +55,7 @@ static const struct ksu_feature_handler kernel_umount_handler = {
|
||||
extern bool susfs_is_log_enabled;
|
||||
#endif // #ifdef CONFIG_KSU_SUSFS
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || \
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) || \
|
||||
defined(KSU_HAS_PATH_UMOUNT)
|
||||
extern int path_umount(struct path *path, int flags);
|
||||
static void ksu_umount_mnt(const char *__never_use_mnt, struct path *path,
|
||||
@@ -89,7 +90,7 @@ static void ksu_sys_umount(const char *mnt, int flags)
|
||||
|
||||
#endif
|
||||
|
||||
static void try_umount(const char *mnt, int flags)
|
||||
void try_umount(const char *mnt, int flags)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
@@ -128,6 +129,8 @@ static void umount_tw_func(struct callback_head *cb)
|
||||
}
|
||||
up_read(&mount_list_lock);
|
||||
|
||||
ksu_umount_manager_execute_all(tw->old_cred);
|
||||
|
||||
if (saved)
|
||||
revert_creds(saved);
|
||||
|
||||
@@ -152,18 +155,18 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
||||
|
||||
#ifndef CONFIG_KSU_SUSFS
|
||||
// There are 5 scenarios:
|
||||
// 1. Normal app: zygote -> appuid
|
||||
// 2. Isolated process forked from zygote: zygote -> isolated_process
|
||||
// 3. App zygote forked from zygote: zygote -> appuid
|
||||
// 4. Isolated process froked from app zygote: appuid -> isolated_process (already handled by 3)
|
||||
// 5. Isolated process froked from webview zygote (no need to handle, app cannot run custom code)
|
||||
if (!is_appuid(new_uid) && !is_isolated_process(new_uid)) {
|
||||
return 0;
|
||||
}
|
||||
// 1. Normal app: zygote -> appuid
|
||||
// 2. Isolated process forked from zygote: zygote -> isolated_process
|
||||
// 3. App zygote forked from zygote: zygote -> appuid
|
||||
// 4. Isolated process froked from app zygote: appuid -> isolated_process (already handled by 3)
|
||||
// 5. Isolated process froked from webview zygote (no need to handle, app cannot run custom code)
|
||||
if (!is_appuid(new_uid) && !is_isolated_process(new_uid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_uid_should_umount(new_uid) && !is_isolated_process(new_uid)) {
|
||||
return 0;
|
||||
}
|
||||
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
|
||||
@@ -204,6 +207,11 @@ int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
||||
|
||||
void ksu_kernel_umount_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
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)) {
|
||||
pr_err("Failed to register kernel_umount feature handler\n");
|
||||
}
|
||||
@@ -211,5 +219,6 @@ void ksu_kernel_umount_init(void)
|
||||
|
||||
void ksu_kernel_umount_exit(void)
|
||||
{
|
||||
ksu_umount_manager_exit();
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT);
|
||||
}
|
||||
@@ -20,4 +20,6 @@ struct mount_entry {
|
||||
extern struct list_head mount_list;
|
||||
extern struct rw_semaphore mount_list_lock;
|
||||
|
||||
void try_umount(const char *mnt, int flags);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include "manual_su.h"
|
||||
#endif
|
||||
|
||||
#include "umount_manager.h"
|
||||
|
||||
#ifdef CONFIG_KSU_SUSFS
|
||||
bool susfs_is_boot_completed_triggered = false;
|
||||
#endif // #ifdef CONFIG_KSU_SUSFS
|
||||
@@ -471,7 +473,7 @@ static int do_manage_mark(void __user *arg)
|
||||
break;
|
||||
#else
|
||||
cmd.result = 0;
|
||||
break;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
case KSU_MARK_MARK: {
|
||||
@@ -488,8 +490,8 @@ static int do_manage_mark(void __user *arg)
|
||||
}
|
||||
#else
|
||||
if (cmd.pid != 0) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -507,8 +509,8 @@ static int do_manage_mark(void __user *arg)
|
||||
}
|
||||
#else
|
||||
if (cmd.pid != 0) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -871,6 +873,36 @@ static int do_manual_su(void __user *arg)
|
||||
}
|
||||
#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.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
|
||||
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 },
|
||||
@@ -903,6 +935,7 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
|
||||
#ifdef CONFIG_KPM
|
||||
{ .cmd = KSU_IOCTL_KPM, .name = "KPM_OPERATION", .handler = do_kpm, .perm_check = manager_or_root},
|
||||
#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
|
||||
};
|
||||
|
||||
|
||||
@@ -174,6 +174,7 @@ struct ksu_manual_su_cmd {
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#define KSU_IOCTL_MANUAL_SU _IOC(_IOC_READ|_IOC_WRITE, 'K', 106, 0)
|
||||
#endif
|
||||
#define KSU_IOCTL_UMOUNT_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 107, 0)
|
||||
|
||||
// IOCTL handler types
|
||||
typedef int (*ksu_ioctl_handler_t)(void __user *arg);
|
||||
|
||||
242
kernel/umount_manager.c
Normal file
242
kernel/umount_manager.c
Normal file
@@ -0,0 +1,242 @@
|
||||
#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"
|
||||
#include "kernel_umount.h"
|
||||
#include "umount_manager.h"
|
||||
|
||||
static struct umount_manager g_umount_mgr = {
|
||||
.entry_count = 0,
|
||||
.max_entries = 64,
|
||||
};
|
||||
|
||||
static void try_umount_path(struct umount_entry *entry)
|
||||
{
|
||||
try_umount(entry->path, entry->flags);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int ksu_umount_manager_init(void)
|
||||
{
|
||||
INIT_LIST_HEAD(&g_umount_mgr.entry_list);
|
||||
spin_lock_init(&g_umount_mgr.lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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, int flags, bool is_default)
|
||||
{
|
||||
struct umount_entry *entry;
|
||||
unsigned long irqflags;
|
||||
int ret = 0;
|
||||
|
||||
if (flags == -1)
|
||||
flags = MNT_DETACH;
|
||||
|
||||
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->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;
|
||||
}
|
||||
|
||||
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) {
|
||||
entry->ref_count++;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (entry->ref_count > 0 && entry->state == UMOUNT_STATE_IDLE) {
|
||||
try_umount_path(entry);
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
||||
if (entry->ref_count > 0) {
|
||||
entry->ref_count--;
|
||||
}
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
63
kernel/umount_manager.h
Normal file
63
kernel/umount_manager.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#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];
|
||||
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];
|
||||
__s32 flags;
|
||||
__u32 count;
|
||||
__aligned_u64 entries_ptr;
|
||||
};
|
||||
|
||||
struct ksu_umount_entry_info {
|
||||
char path[256];
|
||||
__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, int flags, bool is_default);
|
||||
int ksu_umount_manager_remove(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
|
||||
Reference in New Issue
Block a user