Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kernel/Kbuild
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
kernelsu-objs := core/init.o
kernelsu-objs += core/unload.o

kernelsu-objs += feature/kernel_umount.o
kernelsu-objs += feature/sulog.o
Expand Down
16 changes: 15 additions & 1 deletion kernel/core/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ bool allow_shell = false;
#endif
module_param(allow_shell, bool, 0);

#ifndef CONFIG_KSU_DEBUG
bool ksu_unloadable = false;
module_param_named(unloadable, ksu_unloadable, bool, 0);
#endif

void __init remove_my_kobj(void);

int __init kernelsu_init(void)
{
#if defined(__x86_64__)
Expand Down Expand Up @@ -176,7 +183,14 @@ int __init kernelsu_init(void)

#ifdef MODULE
#ifndef CONFIG_KSU_DEBUG
kobject_del(&THIS_MODULE->mkobj.kobj);
if (ksu_unloadable) {
pr_info("KernelSU unloadable is enabled\n");
remove_my_kobj();
} else {
kobject_del(&THIS_MODULE->mkobj.kobj);
// add a reference to myself to prevent from unloading
try_module_get(THIS_MODULE);
}
#endif
#endif
return 0;
Expand Down
144 changes: 144 additions & 0 deletions kernel/core/unload.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/kobject.h>
#include "klog.h" // IWYU pragma: keep

// we should have no usages
// module_mutex become static since 5.15, so disable this
/*
static void del_usage_links(struct module *mod)
{
struct module_use *use;

mutex_lock(&module_mutex);
list_for_each_entry(use, &mod->target_list, target_list)
sysfs_remove_link(use->target->holders_dir, mod->name);
mutex_unlock(&module_mutex);
}*/

static void __init module_remove_modinfo_attrs(struct module *mod, int end)
{
struct module_attribute *attr;
int i;

for (i = 0; (attr = &mod->modinfo_attrs[i]); i++) {
if (end >= 0 && i > end)
break;
/* pick a field to test for end of list */
if (!attr->attr.name)
break;
sysfs_remove_file(&mod->mkobj.kobj, &attr->attr);
if (attr->free)
attr->free(mod);
}
kfree(mod->modinfo_attrs);
// ADDED:
mod->modinfo_attrs = kzalloc(sizeof(struct module_attribute), GFP_KERNEL);
}

struct module_notes_attrs {
struct kobject *dir;
unsigned int notes;
struct bin_attribute attrs[];
};

static void __init free_notes_attrs(struct module_notes_attrs *notes_attrs, unsigned int i)
{
if (notes_attrs->dir) {
while (i-- > 0)
sysfs_remove_bin_file(notes_attrs->dir, &notes_attrs->attrs[i]);
kobject_put(notes_attrs->dir);
}
kfree(notes_attrs);
}

static void __init remove_notes_attrs(struct module *mod)
{
if (mod->notes_attrs)
free_notes_attrs(mod->notes_attrs, mod->notes_attrs->notes);
// ADDED
mod->notes_attrs = NULL;
}

struct module_sect_attr {
struct bin_attribute battr;
unsigned long address;
};

struct module_sect_attrs {
struct attribute_group grp;
unsigned int nsections;
struct module_sect_attr attrs[];
};

static void __init free_sect_attrs(struct module_sect_attrs *sect_attrs)
{
unsigned int section;

for (section = 0; section < sect_attrs->nsections; section++)
kfree(sect_attrs->attrs[section].battr.attr.name);
kfree(sect_attrs);
}

static void __init remove_sect_attrs(struct module *mod)
{
if (mod->sect_attrs) {
sysfs_remove_group(&mod->mkobj.kobj, &mod->sect_attrs->grp);
/* We are positive that no one is using any sect attrs
* at this point. Deallocate immediately. */
free_sect_attrs(mod->sect_attrs);
mod->sect_attrs = NULL;
}
}

static void mod_kobject_put(struct module *mod)
{
DECLARE_COMPLETION_ONSTACK(c);
mod->mkobj.kobj_completion = &c;
kobject_put(&mod->mkobj.kobj);
wait_for_completion(&c);
}

static void __init mod_sysfs_fini(struct module *mod)
{
remove_notes_attrs(mod);
remove_sect_attrs(mod);
mod_kobject_put(mod);
}

void my_release(struct kobject *kobj)
{
struct module_kobject *mk = container_of(kobj, struct module_kobject, kobj);

if (mk->kobj_completion)
complete(mk->kobj_completion);
}

struct kobj_type my_type = { .release = my_release };

// copy from mod_sysfs_teardown of module.c(module/main.c)
// unstable, may panic, so it's an optional feature
void __init remove_my_kobj(void)
{
struct module *mod = THIS_MODULE;
// del_usage_links(mod);
module_remove_modinfo_attrs(mod, -1);
module_param_sysfs_remove(mod);
kobject_put(mod->mkobj.drivers_dir);
kobject_put(mod->holders_dir);
mod_sysfs_fini(mod);

// ADDED
pr_info("refcnt: %d state_in_sysfs: %d state_initialized: %d parent: 0x%lx\n",
mod->mkobj.kobj.kref.refcount.refs.counter, mod->mkobj.kobj.state_in_sysfs,
mod->mkobj.kobj.state_initialized, (unsigned long)mod->mkobj.kobj.parent);
mod->mkobj.kobj.state_initialized = 1;
mod->mkobj.kobj.kref.refcount.refs.counter = 1;
mod->mkobj.kobj.ktype = &my_type;
mod->mkobj.kobj.name = NULL;
mod->mkobj.kobj.parent = NULL;
mod->holders_dir = NULL;
}
1 change: 1 addition & 0 deletions kernel/include/ksu.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
extern struct cred *ksu_cred;
extern bool ksu_late_loaded;
extern bool allow_shell;
extern bool ksu_unloadable;
extern struct selinux_policy *backup_sepolicy;

static inline int startswith(char *s, char *prefix)
Expand Down
24 changes: 24 additions & 0 deletions kernel/infra/file_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,23 @@ static struct file *ksu_anon_inode_create_getfile_compat(const char *name, const
}
#endif

static inline bool is_wrapper_file(struct file *f)
{
return f->f_op->release == ksu_wrapper_release;
}

int is_wrapper_fd(int fd)
{
int ret;
struct file *orig_file = fget(fd);
if (!orig_file) {
return -EBADF;
}
ret = is_wrapper_file(orig_file) ? 1 : 0;
fput(orig_file);
return ret;
}

int ksu_install_file_wrapper(int fd)
{
int out_fd, ret;
Expand All @@ -497,6 +514,13 @@ int ksu_install_file_wrapper(int fd)
return -EBADF;
}

if (is_wrapper_file(orig_file)) {
// DEBUG
pr_info("fd %d is already a wrapper\n", fd);
ret = fd;
goto done;
}

out_fd = get_unused_fd_flags(O_CLOEXEC);
if (out_fd < 0) {
ret = out_fd;
Expand Down
2 changes: 2 additions & 0 deletions kernel/infra/file_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
int ksu_install_file_wrapper(int fd);
void ksu_file_wrapper_init(void);

int is_wrapper_fd(int fd);

#endif // KSU_FILE_WRAPPER_H
20 changes: 20 additions & 0 deletions kernel/manager/pkg_observer.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
#include "linux/completion.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/namei.h>
Expand Down Expand Up @@ -35,8 +36,25 @@ static int ksu_handle_inode_event(struct fsnotify_mark *mark, u32 mask, struct i
return 0;
}

static void my_free_mark(struct fsnotify_mark *mark)
{
kfree(mark);
}

DECLARE_COMPLETION(free_group_completion);

static void __naked my_free_group_priv(struct fsnotify_group *group)
{
asm(".extern complete\n"
".extern free_group_completion\n"
"adr x0, free_group_completion\n"
"b complete\n");
}

static const struct fsnotify_ops ksu_ops = {
.handle_inode_event = ksu_handle_inode_event,
.free_mark = my_free_mark,
.free_group_priv = my_free_group_priv,
};

static int add_mark_on_inode(struct inode *inode, u32 mask, struct fsnotify_mark **out)
Expand Down Expand Up @@ -120,5 +138,7 @@ void __exit ksu_observer_exit(void)
{
unwatch_one_dir(&g_watch);
fsnotify_put_group(g);
pr_info("waiting for observer exit\n");
wait_for_completion(&free_group_completion);
pr_info("observer exit done\n");
}
21 changes: 21 additions & 0 deletions kernel/supercall/dispatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ static int do_get_info(void __user *arg)
if (ksu_late_loaded) {
cmd.flags |= KSU_GET_INFO_FLAG_LATE_LOAD;
}
if (ksu_unloadable) {
cmd.flags |= KSU_GET_INFO_FLAG_UNLOADABLE;
}
#ifdef EXPECTED_SIZE2
cmd.flags |= KSU_GET_INFO_FLAG_PR_BUILD;
#endif
Expand Down Expand Up @@ -654,6 +657,18 @@ static int do_get_sulog_fd(void __user *arg)
return ksu_install_sulog_fd();
}

static int do_check_wrapper_fd(void __user *arg)
{
struct ksu_check_wrapper_fd_cmd cmd;

if (copy_from_user(&cmd, arg, sizeof(cmd))) {
pr_err("get_sulog_fd: copy_from_user failed\n");
return -EFAULT;
}

return is_wrapper_fd(cmd.fd);
}

// IOCTL handlers mapping table
// clang-format off
static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
Expand Down Expand Up @@ -789,6 +804,12 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
.handler = do_get_sulog_fd,
.perm_check = only_root
},
{
.cmd = KSU_IOCTL_CHECK_WRAPPER_FD,
.name = "CHECK_WRAPPER_FD",
.handler = do_check_wrapper_fd,
.perm_check = manager_or_root
},
{
.cmd = 0,
.name = NULL,
Expand Down
7 changes: 7 additions & 0 deletions uapi/supercall.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ static const __u32 KSU_GET_INFO_FLAG_LKM = (1U << 0);
static const __u32 KSU_GET_INFO_FLAG_MANAGER = (1U << 1);
static const __u32 KSU_GET_INFO_FLAG_LATE_LOAD = (1U << 2);
static const __u32 KSU_GET_INFO_FLAG_PR_BUILD = (1U << 3);
static const __u32 KSU_GET_INFO_FLAG_UNLOADABLE = (1U << 4);

struct ksu_get_info_cmd {
__u32 version; /* Output: KERNEL_SU_VERSION */
Expand Down Expand Up @@ -134,6 +135,10 @@ struct ksu_get_sulog_fd_cmd {
__u32 flags; /* Input: reserved for future use, must be 0 */
};

struct ksu_check_wrapper_fd_cmd {
__u32 fd;
};

static const __u8 KSU_UMOUNT_WIPE = 0; /* ignore everything and wipe list */
static const __u8 KSU_UMOUNT_ADD = 1; /* add entry (path + flags) */
static const __u8 KSU_UMOUNT_DEL = 2; /* delete entry, strcmp */
Expand Down Expand Up @@ -163,5 +168,7 @@ static const __u32 KSU_IOCTL_NUKE_EXT4_SYSFS = _IOC(_IOC_WRITE, 'K', 17, 0);
static const __u32 KSU_IOCTL_ADD_TRY_UMOUNT = _IOC(_IOC_WRITE, 'K', 18, 0);
static const __u32 KSU_IOCTL_SET_INIT_PGRP = _IO('K', 19);
static const __u32 KSU_IOCTL_GET_SULOG_FD = _IOW('K', 20, struct ksu_get_sulog_fd_cmd);
static const __u32 KSU_IOCTL_CHECK_WRAPPER_FD = _IOW('K', 21, struct ksu_check_wrapper_fd_cmd);
static const __u32 KSU_IOCTL_KILL_ALL_WRAPPER_FD_HOLDERS = _IO('K', 22);

#endif
22 changes: 22 additions & 0 deletions userspace/ksud/src/boot_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ pub struct BootPatchArgs {
/// Do not (re-)install kernelsu, only modify configs (allow_shell, etc.)
#[arg(long, default_value = "false")]
no_install: bool,

/// Allow unload KernelSU LKM at runtime
#[arg(long, default_value = "false")]
unloadable: bool,
}

pub fn patch(args: BootPatchArgs) -> Result<()> {
Expand All @@ -544,6 +548,7 @@ pub fn patch(args: BootPatchArgs) -> Result<()> {
enable_adbd,
adb_debug_prop,
no_install,
unloadable,
#[cfg(target_os = "android")]
ota,
#[cfg(target_os = "android")]
Expand Down Expand Up @@ -773,6 +778,23 @@ pub fn patch(args: BootPatchArgs) -> Result<()> {
}
}

if unloadable {
println!("- Adding unloadable config");
{
let unloadable_file = workdir.join("ksu_unloadable");
File::create(unloadable_file)?;
}
do_cpio_cmd(
&magiskboot,
workdir,
ramdisk,
"add 0644 ksu_unloadable ksu_unloadable",
)?;
} else if do_cpio_cmd(&magiskboot, workdir, ramdisk, "exists ksu_unloadable").is_ok() {
println!("- Removing unloadable config");
do_cpio_cmd(&magiskboot, workdir, ramdisk, "rm ksu_unloadable").ok();
}

println!("- Repacking boot image");
// magiskboot repack boot.img
let status = Command::new(&magiskboot)
Expand Down
Loading