From 826661dffb6a74dceff0396d57bdf4534afbb4d3 Mon Sep 17 00:00:00 2001 From: 5ec1cff <56485584+5ec1cff@users.noreply.github.com> Date: Thu, 6 Nov 2025 23:29:58 +0800 Subject: [PATCH] feature: add devpts fd wrapper (#21) This feature is intended to resolve devpts problem. --- .github/workflows/build-lkm.yml | 1 + kernel/Makefile | 1 + kernel/file_wrapper.c | 347 ++++++++++++++++++++++++++++++++ kernel/file_wrapper.h | 10 + kernel/ksu.h | 18 ++ kernel/ksud.c | 8 +- kernel/ksud.h | 2 +- kernel/selinux/selinux.c | 14 +- kernel/selinux/selinux.h | 2 +- kernel/sucompat.c | 4 +- kernel/supercalls.c | 61 ++++++ kernel/supercalls.h | 6 + userspace/ksud/src/defs.rs | 2 + userspace/ksud/src/ksucalls.rs | 15 ++ userspace/ksud/src/su.rs | 57 +++++- 15 files changed, 526 insertions(+), 22 deletions(-) create mode 100644 kernel/file_wrapper.c create mode 100644 kernel/file_wrapper.h diff --git a/.github/workflows/build-lkm.yml b/.github/workflows/build-lkm.yml index 2da532cc..e5ed7eac 100644 --- a/.github/workflows/build-lkm.yml +++ b/.github/workflows/build-lkm.yml @@ -5,6 +5,7 @@ on: jobs: build-lkm: strategy: + fail-fast: false matrix: kmi: - android12-5.10 diff --git a/kernel/Makefile b/kernel/Makefile index 8adc403b..e6d6cd9f 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -11,6 +11,7 @@ kernelsu-objs += feature.o kernelsu-objs += ksud.o kernelsu-objs += embed_ksud.o kernelsu-objs += kernel_compat.o +kernelsu-objs += file_wrapper.o kernelsu-objs += throne_comm.o kernelsu-objs += sulog.o diff --git a/kernel/file_wrapper.c b/kernel/file_wrapper.c new file mode 100644 index 00000000..dbea97bd --- /dev/null +++ b/kernel/file_wrapper.c @@ -0,0 +1,347 @@ +#include "linux/export.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "allowlist.h" +#include "klog.h" // IWYU pragma: keep +#include "ksu.h" +#include "ksud.h" +#include "manager.h" +#include "selinux/selinux.h" +#include "core_hook.h" +#include "objsec.h" + +#include "file_wrapper.h" + +static loff_t mksu_wrapper_llseek(struct file *fp, loff_t off, int flags) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->llseek(data->orig, off, flags); +} + +static ssize_t mksu_wrapper_read(struct file *fp, char __user *ptr, size_t sz, loff_t *off) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->read(orig, ptr, sz, off); +} + +static ssize_t mksu_wrapper_write(struct file *fp, const char __user *ptr, size_t sz, loff_t *off) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->write(orig, ptr, sz, off); +} + +static ssize_t mksu_wrapper_read_iter(struct kiocb *iocb, struct iov_iter *iovi) { + struct ksu_file_wrapper* data = iocb->ki_filp->private_data; + struct file* orig = data->orig; + iocb->ki_filp = orig; + return orig->f_op->read_iter(iocb, iovi); +} + +static ssize_t mksu_wrapper_write_iter (struct kiocb *iocb, struct iov_iter *iovi) { + struct ksu_file_wrapper* data = iocb->ki_filp->private_data; + struct file* orig = data->orig; + iocb->ki_filp = orig; + return orig->f_op->write_iter(iocb, iovi); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static int mksu_wrapper_iopoll(struct kiocb *kiocb, struct io_comp_batch* icb, unsigned int v) { + struct ksu_file_wrapper* data = kiocb->ki_filp->private_data; + struct file* orig = data->orig; + kiocb->ki_filp = orig; + return orig->f_op->iopoll(kiocb, icb, v); +} +#else +static int mksu_wrapper_iopoll(struct kiocb *kiocb, bool spin) { + struct ksu_file_wrapper* data = kiocb->ki_filp->private_data; + struct file* orig = data->orig; + kiocb->ki_filp = orig; + return orig->f_op->iopoll(kiocb, spin); +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) +static int mksu_wrapper_iterate (struct file *fp, struct dir_context *dc) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->iterate(orig, dc); +} +#endif + +static int mksu_wrapper_iterate_shared (struct file *fp, struct dir_context *dc) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->iterate_shared(orig, dc); +} + +static __poll_t mksu_wrapper_poll (struct file *fp, struct poll_table_struct *pts) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->poll(orig, pts); +} + +static long mksu_wrapper_unlocked_ioctl (struct file *fp, unsigned int cmd, unsigned long arg) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->unlocked_ioctl(orig, cmd, arg); +} + +static long mksu_wrapper_compat_ioctl (struct file *fp, unsigned int cmd, unsigned long arg) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->compat_ioctl(orig, cmd, arg); +} + +static int mksu_wrapper_mmap (struct file *fp, struct vm_area_struct * vma) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->mmap(orig, vma); +} + +// static unsigned long mmap_supported_flags {} + +static int mksu_wrapper_open(struct inode *ino, struct file *fp) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + struct inode *orig_ino = file_inode(orig); + return orig->f_op->open(orig_ino, orig); +} + +static int mksu_wrapper_flush (struct file *fp, fl_owner_t id) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->flush(orig, id); +} + + +static int mksu_wrapper_fsync(struct file *fp, loff_t off1, loff_t off2, int datasync) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->fsync(orig, off1, off2, datasync); +} + +static int mksu_wrapper_fasync(int arg, struct file *fp, int arg2) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->fasync(arg, orig, arg2); +} + +static int mksu_wrapper_lock(struct file *fp, int arg1, struct file_lock *fl) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + return orig->f_op->lock(orig, arg1, fl); +} + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) +static ssize_t mksu_wrapper_sendpage (struct file *fp, struct page *pg, int arg1, size_t sz, loff_t *off, int arg2) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->sendpage) { + return orig->f_op->sendpage(orig, pg, arg1, sz, off, arg2); + } + return -EINVAL; +} +#endif + +static unsigned long mksu_wrapper_get_unmapped_area(struct file *fp, unsigned long arg1, unsigned long arg2, unsigned long arg3, unsigned long arg4) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->get_unmapped_area) { + return orig->f_op->get_unmapped_area(orig, arg1, arg2, arg3, arg4); + } + return -EINVAL; +} + +// static int mksu_wrapper_check_flags(int arg) {} + +static int mksu_wrapper_flock(struct file *fp, int arg1, struct file_lock *fl) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->flock) { + return orig->f_op->flock(orig, arg1, fl); + } + return -EINVAL; +} + +static ssize_t mksu_wrapper_splice_write(struct pipe_inode_info * pii, struct file *fp, loff_t *off, size_t sz, unsigned int arg1) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->splice_write) { + return orig->f_op->splice_write(pii, orig, off, sz, arg1); + } + return -EINVAL; +} + +static ssize_t mksu_wrapper_splice_read(struct file *fp, loff_t *off, struct pipe_inode_info *pii, size_t sz, unsigned int arg1) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->splice_read) { + return orig->f_op->splice_read(orig, off, pii, sz, arg1); + } + return -EINVAL; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +void mksu_wrapper_splice_eof(struct file *fp) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->splice_eof) { + return orig->f_op->splice_eof(orig); + } +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +static int mksu_wrapper_setlease(struct file *fp, int arg1, struct file_lease **fl, void **p) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->setlease) { + return orig->f_op->setlease(orig, arg1, fl, p); + } + return -EINVAL; +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +static int mksu_wrapper_setlease(struct file *fp, int arg1, struct file_lock **fl, void **p) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->setlease) { + return orig->f_op->setlease(orig, arg1, fl, p); + } + return -EINVAL; +} +#else +static int mksu_wrapper_setlease(struct file *fp, long arg1, struct file_lock **fl, void **p) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->setlease) { + return orig->f_op->setlease(orig, arg1, fl, p); + } + return -EINVAL; +} +#endif + +static long mksu_wrapper_fallocate(struct file *fp, int mode, loff_t offset, loff_t len) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->fallocate) { + return orig->f_op->fallocate(orig, mode, offset, len); + } + return -EINVAL; +} + +static void mksu_wrapper_show_fdinfo(struct seq_file *m, struct file *f) { + struct ksu_file_wrapper* data = m->file->private_data; + struct file* orig = data->orig; + if (orig->f_op->show_fdinfo) { + orig->f_op->show_fdinfo(m, orig); + } +} + +static ssize_t mksu_wrapper_copy_file_range(struct file *f1, loff_t off1, struct file *f2, + loff_t off2, size_t sz, unsigned int flags) { + // TODO: determine which file to use + struct ksu_file_wrapper* data = f1->private_data; + struct file* orig = data->orig; + if (orig->f_op->copy_file_range) { + return orig->f_op->copy_file_range(orig, off1, f2, off2, sz, flags); + } + return -EINVAL; +} + +static loff_t mksu_wrapper_remap_file_range(struct file *file_in, loff_t pos_in, + struct file *file_out, loff_t pos_out, + loff_t len, unsigned int remap_flags) { + // TODO: determine which file to use + struct ksu_file_wrapper* data = file_in->private_data; + struct file* orig = data->orig; + if (orig->f_op->remap_file_range) { + return orig->f_op->remap_file_range(orig, pos_in, file_out, pos_out, len, remap_flags); + } + return -EINVAL; +} + +static int mksu_wrapper_fadvise(struct file *fp, loff_t off1, loff_t off2, int flags) { + struct ksu_file_wrapper* data = fp->private_data; + struct file* orig = data->orig; + if (orig->f_op->fadvise) { + return orig->f_op->fadvise(orig, off1, off2, flags); + } + return -EINVAL; +} + +static int mksu_wrapper_release(struct inode *inode, struct file *filp) { + mksu_delete_file_wrapper(filp->private_data); + return 0; +} + +struct ksu_file_wrapper* mksu_create_file_wrapper(struct file* fp) { + struct ksu_file_wrapper* p = kcalloc(sizeof(struct ksu_file_wrapper), 1, GFP_KERNEL); + if (!p) { + return NULL; + } + + get_file(fp); + + p->orig = fp; + p->ops.owner = THIS_MODULE; + p->ops.llseek = fp->f_op->llseek ? mksu_wrapper_llseek : NULL; + p->ops.read = fp->f_op->read ? mksu_wrapper_read : NULL; + p->ops.write = fp->f_op->write ? mksu_wrapper_write : NULL; + p->ops.read_iter = fp->f_op->read_iter ? mksu_wrapper_read_iter : NULL; + p->ops.write_iter = fp->f_op->write_iter ? mksu_wrapper_write_iter : NULL; + p->ops.iopoll = fp->f_op->iopoll ? mksu_wrapper_iopoll : NULL; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) + p->ops.iterate = fp->f_op->iterate ? mksu_wrapper_iterate : NULL; +#endif + p->ops.iterate_shared = fp->f_op->iterate_shared ? mksu_wrapper_iterate_shared : NULL; + p->ops.poll = fp->f_op->poll ? mksu_wrapper_poll : NULL; + p->ops.unlocked_ioctl = fp->f_op->unlocked_ioctl ? mksu_wrapper_unlocked_ioctl : NULL; + p->ops.compat_ioctl = fp->f_op->compat_ioctl ? mksu_wrapper_compat_ioctl : NULL; + p->ops.mmap = fp->f_op->mmap ? mksu_wrapper_mmap : NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + p->ops.fop_flags = fp->f_op->fop_flags; +#else + p->ops.mmap_supported_flags = fp->f_op->mmap_supported_flags; +#endif + p->ops.open = fp->f_op->open ? mksu_wrapper_open : NULL; + p->ops.flush = fp->f_op->flush ? mksu_wrapper_flush : NULL; + p->ops.release = mksu_wrapper_release; + p->ops.fsync = fp->f_op->fsync ? mksu_wrapper_fsync : NULL; + p->ops.fasync = fp->f_op->fasync ? mksu_wrapper_fasync : NULL; + p->ops.lock = fp->f_op->lock ? mksu_wrapper_lock : NULL; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) + p->ops.sendpage = fp->f_op->sendpage ? mksu_wrapper_sendpage : NULL; +#endif + p->ops.get_unmapped_area = fp->f_op->get_unmapped_area ? mksu_wrapper_get_unmapped_area : NULL; + p->ops.check_flags = fp->f_op->check_flags; + p->ops.flock = fp->f_op->flock ? mksu_wrapper_flock : NULL; + p->ops.splice_write = fp->f_op->splice_write ? mksu_wrapper_splice_write : NULL; + p->ops.splice_read = fp->f_op->splice_read ? mksu_wrapper_splice_read : NULL; + p->ops.setlease = fp->f_op->setlease ? mksu_wrapper_setlease : NULL; + p->ops.fallocate = fp->f_op->fallocate ? mksu_wrapper_fallocate : NULL; + p->ops.show_fdinfo = fp->f_op->show_fdinfo ? mksu_wrapper_show_fdinfo : NULL; + p->ops.copy_file_range = fp->f_op->copy_file_range ? mksu_wrapper_copy_file_range : NULL; + p->ops.remap_file_range = fp->f_op->remap_file_range ? mksu_wrapper_remap_file_range : NULL; + p->ops.fadvise = fp->f_op->fadvise ? mksu_wrapper_fadvise : NULL; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + p->ops.splice_eof = fp->f_op->splice_eof ? mksu_wrapper_splice_eof : NULL; +#endif + + return p; +} + +void mksu_delete_file_wrapper(struct ksu_file_wrapper* data) { + fput((struct file*) data->orig); + kfree(data); +} + diff --git a/kernel/file_wrapper.h b/kernel/file_wrapper.h new file mode 100644 index 00000000..8a7fb90f --- /dev/null +++ b/kernel/file_wrapper.h @@ -0,0 +1,10 @@ +#include +#include + +struct ksu_file_wrapper { + struct file* orig; + struct file_operations ops; +}; + +struct ksu_file_wrapper* mksu_create_file_wrapper(struct file* fp); +void mksu_delete_file_wrapper(struct ksu_file_wrapper* data); diff --git a/kernel/ksu.h b/kernel/ksu.h index 302fb935..b4766851 100644 --- a/kernel/ksu.h +++ b/kernel/ksu.h @@ -8,6 +8,24 @@ #define KERNEL_SU_OPTION 0xDEADBEEF extern bool ksu_uid_scanner_enabled; +#define CMD_GRANT_ROOT 0 +#define CMD_BECOME_MANAGER 1 +#define CMD_GET_VERSION 2 +#define CMD_ALLOW_SU 3 +#define CMD_DENY_SU 4 +#define CMD_GET_ALLOW_LIST 5 +#define CMD_GET_DENY_LIST 6 +#define CMD_REPORT_EVENT 7 +#define CMD_SET_SEPOLICY 8 +#define CMD_CHECK_SAFEMODE 9 +#define CMD_GET_APP_PROFILE 10 +#define CMD_SET_APP_PROFILE 11 +#define CMD_UID_GRANTED_ROOT 12 +#define CMD_UID_SHOULD_UMOUNT 13 +#define CMD_IS_SU_ENABLED 14 +#define CMD_ENABLE_SU 15 +#define CMD_GET_MANAGER_UID 16 +#define CMD_GET_WRAPPER_FD 10000 #define EVENT_POST_FS_DATA 1 #define EVENT_BOOT_COMPLETED 2 diff --git a/kernel/ksud.c b/kernel/ksud.c index a8bdd1b3..affdffc4 100644 --- a/kernel/ksud.c +++ b/kernel/ksud.c @@ -60,7 +60,7 @@ bool ksu_input_hook __read_mostly = true; #endif bool ksu_execveat_hook __read_mostly = true; -u32 ksu_devpts_sid; +u32 ksu_file_sid; // Detect whether it is on or not static bool is_boot_phase = true; @@ -81,11 +81,11 @@ void on_post_fs_data(void) // sanity check, this may influence the performance stop_input_hook(); - ksu_devpts_sid = ksu_get_devpts_sid(); - pr_info("devpts sid: %d\n", ksu_devpts_sid); - // End of boot state is_boot_phase = false; + + ksu_file_sid = ksu_get_ksu_file_sid(); + pr_info("ksu_file sid: %d\n", ksu_file_sid); } // since _ksud handler only uses argv and envp for comparisons diff --git a/kernel/ksud.h b/kernel/ksud.h index 62ff1030..9bc98384 100644 --- a/kernel/ksud.h +++ b/kernel/ksud.h @@ -10,7 +10,7 @@ void on_post_fs_data(void); bool ksu_is_safe_mode(void); -extern u32 ksu_devpts_sid; +extern u32 ksu_file_sid; extern bool ksu_execveat_hook __read_mostly; extern int ksu_handle_pre_ksud(const char *filename); diff --git a/kernel/selinux/selinux.c b/kernel/selinux/selinux.c index 7b8e4cd9..e3a3a653 100644 --- a/kernel/selinux/selinux.c +++ b/kernel/selinux/selinux.c @@ -164,15 +164,15 @@ bool is_zygote(const struct cred* cred) return result; } -#define DEVPTS_DOMAIN "u:object_r:ksu_file:s0" +#define KSU_FILE_DOMAIN "u:object_r:ksu_file:s0" -u32 ksu_get_devpts_sid() +u32 ksu_get_ksu_file_sid() { - u32 devpts_sid = 0; - int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN), - &devpts_sid); + u32 ksu_file_sid = 0; + int err = security_secctx_to_secid(KSU_FILE_DOMAIN, strlen(KSU_FILE_DOMAIN), + &ksu_file_sid); if (err) { - pr_info("get devpts sid err %d\n", err); + pr_info("get ksufile sid err %d\n", err); } - return devpts_sid; + return ksu_file_sid; } diff --git a/kernel/selinux/selinux.h b/kernel/selinux/selinux.h index e458dd73..d0aceb9c 100644 --- a/kernel/selinux/selinux.h +++ b/kernel/selinux/selinux.h @@ -19,7 +19,7 @@ bool is_zygote(const struct cred* cred); void apply_kernelsu_rules(); -u32 ksu_get_devpts_sid(); +u32 ksu_get_ksu_file_sid(); int handle_sepolicy(unsigned long arg3, void __user *arg4); diff --git a/kernel/sucompat.c b/kernel/sucompat.c index a2eb0f59..4052b898 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -346,8 +346,8 @@ int __ksu_handle_devpts(struct inode *inode) struct inode_security_struct *sec = selinux_inode(inode); - if (ksu_devpts_sid && sec) - sec->sid = ksu_devpts_sid; + if (ksu_file_sid && sec) + sec->sid = ksu_file_sid; return 0; } diff --git a/kernel/supercalls.c b/kernel/supercalls.c index f22b149e..91762be2 100644 --- a/kernel/supercalls.c +++ b/kernel/supercalls.c @@ -18,6 +18,8 @@ #include "sulog.h" #include "selinux/selinux.h" #include "core_hook.h" +#include "objsec.h" +#include "file_wrapper.h" #include "kernel_compat.h" #include "throne_comm.h" #include "dynamic_manager.h" @@ -355,6 +357,64 @@ static int do_set_feature(void __user *arg) return 0; } +static int do_get_wrapper_fd(void __user *arg) { + if (!ksu_file_sid) { + return -1; + } + + struct ksu_get_wrapper_fd_cmd cmd; + int ret; + + if (copy_from_user(&cmd, arg, sizeof(cmd))) { + pr_err("get_wrapper_fd: copy_from_user failed\n"); + return -EFAULT; + } + + struct file* f = fget(cmd.fd); + if (!f) { + return -EBADF; + } + + struct ksu_file_wrapper *data = mksu_create_file_wrapper(f); + if (data == NULL) { + ret = -ENOMEM; + goto put_orig_file; + } + + struct file* pf = anon_inode_getfile("[mksu_fdwrapper]", &data->ops, data, f->f_flags); + if (IS_ERR(pf)) { + ret = PTR_ERR(pf); + pr_err("mksu_fdwrapper: anon_inode_getfile failed: %ld\n", PTR_ERR(pf)); + goto put_wrapper_data; + } + + struct inode* wrapper_inode = file_inode(pf); + struct inode_security_struct *sec = selinux_inode(wrapper_inode); + if (sec) { + sec->sid = ksu_file_sid; + } + + ret = get_unused_fd_flags(cmd.flags); + if (ret < 0) { + pr_err("mksu_fdwrapper: get unused fd failed: %d\n", ret); + goto put_wrapper_file; + } + + // pr_info("mksu_fdwrapper: installed wrapper fd for %p %d (flags=%d, mode=%d) to %p %d (flags=%d, mode=%d)", f, cmd.fd, f->f_flags, f->f_mode, pf, ret, pf->f_flags, pf->f_mode); + // pf->f_mode |= FMODE_READ | FMODE_CAN_READ | FMODE_WRITE | FMODE_CAN_WRITE; + fd_install(ret, pf); + goto put_orig_file; + +put_wrapper_file: + fput(pf); +put_wrapper_data: + mksu_delete_file_wrapper(data); +put_orig_file: + fput(f); + + return ret; +} + // 100. GET_FULL_VERSION - Get full version string static int do_get_full_version(void __user *arg) { @@ -572,6 +632,7 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = { { .cmd = KSU_IOCTL_SET_APP_PROFILE, .name = "SET_APP_PROFILE", .handler = do_set_app_profile, .perm_check = only_manager }, { .cmd = KSU_IOCTL_GET_FEATURE, .name = "GET_FEATURE", .handler = do_get_feature, .perm_check = manager_or_root }, { .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_feature, .perm_check = manager_or_root }, + { .cmd = KSU_IOCTL_GET_WRAPPER_FD, .name = "GET_WRAPPER_FD", .handler = do_get_wrapper_fd, .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}, diff --git a/kernel/supercalls.h b/kernel/supercalls.h index 29a8d8cc..5e1bbeaf 100644 --- a/kernel/supercalls.h +++ b/kernel/supercalls.h @@ -77,6 +77,11 @@ struct ksu_set_feature_cmd { __u64 value; // Input: feature value/state to set }; +struct ksu_get_wrapper_fd_cmd { + __u32 fd; + __u32 flags; // CLOEXEC +}; + // Other command structures struct ksu_get_full_version_cmd { char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string @@ -128,6 +133,7 @@ struct ksu_manual_su_cmd { #define KSU_IOCTL_SET_APP_PROFILE _IOC(_IOC_WRITE, 'K', 12, 0) #define KSU_IOCTL_GET_FEATURE _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0) #define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0) +#define KSU_IOCTL_GET_WRAPPER_FD _IOC(_IOC_NONE, 'K', 10000, 0) // Other IOCTL command definitions #define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0) #define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0) diff --git a/userspace/ksud/src/defs.rs b/userspace/ksud/src/defs.rs index 8033ee26..4aa08876 100644 --- a/userspace/ksud/src/defs.rs +++ b/userspace/ksud/src/defs.rs @@ -40,3 +40,5 @@ pub const BACKUP_FILENAME: &str = "stock_image.sha1"; pub const NO_TMPFS_PATH: &str = concatcp!(WORKING_DIR, ".notmpfs"); pub const NO_MOUNT_PATH: &str = concatcp!(WORKING_DIR, ".nomount"); + +pub const NO_FD_WRAPPER_PATH: &str = concatcp!(WORKING_DIR, ".nofdwrapper"); diff --git a/userspace/ksud/src/ksucalls.rs b/userspace/ksud/src/ksucalls.rs index 8948352e..da4a1e67 100644 --- a/userspace/ksud/src/ksucalls.rs +++ b/userspace/ksud/src/ksucalls.rs @@ -1,4 +1,5 @@ use std::fs; +use std::os::fd::OwnedFd; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::fd::RawFd; use std::sync::OnceLock; @@ -15,6 +16,7 @@ const KSU_IOCTL_SET_SEPOLICY: u32 = 0xc0004b04; // _IOC(_IOC_READ|_IOC_WRITE, 'K const KSU_IOCTL_CHECK_SAFEMODE: u32 = 0x80004b05; // _IOC(_IOC_READ, 'K', 5, 0) const KSU_IOCTL_GET_FEATURE: u32 = 0xc0004b0d; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0) const KSU_IOCTL_SET_FEATURE: u32 = 0x40004b0e; // _IOC(_IOC_WRITE, 'K', 14, 0) +const KSU_IOCTL_GET_WRAPPER_FD: u32 = 0x00006f10; // _IOC(_IOC_NONE, 'K', 10000, 0) #[allow(dead_code)] const KSU_IOCTL_KPM: u32 = 0xc0004bc8; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 200, 0) @@ -58,6 +60,13 @@ struct SetFeatureCmd { value: u64, } +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct GetWrapperFdCmd { + fd: i32, + flags: u32, +} + // Global driver fd cache #[cfg(any(target_os = "linux", target_os = "android"))] static DRIVER_FD: OnceLock = OnceLock::new(); @@ -223,6 +232,12 @@ pub fn set_feature(feature_id: u32, value: u64) -> std::io::Result<()> { Ok(()) } +pub fn get_wrapped_fd(fd: RawFd) -> std::io::Result { + let mut cmd = GetWrapperFdCmd { fd, flags: 0 }; + let result = ksuctl(KSU_IOCTL_GET_WRAPPER_FD, &mut cmd as *mut _)?; + Ok(result) +} + #[repr(C)] #[derive(Clone, Copy, Default)] #[allow(dead_code)] diff --git a/userspace/ksud/src/su.rs b/userspace/ksud/src/su.rs index 44f37b99..6c3095b8 100644 --- a/userspace/ksud/src/su.rs +++ b/userspace/ksud/src/su.rs @@ -1,20 +1,26 @@ +use crate::{ + defs, + utils::{self, umask}, +}; +use anyhow::{Context, Ok, Result, bail}; +use getopts::Options; +use libc::c_int; +use log::{debug, error, info}; +use procfs::process::FDTarget::Path; +use std::fs::File; #[cfg(unix)] use std::os::unix::process::CommandExt; use std::{env, ffi::CStr, path::PathBuf, process::Command}; -use anyhow::{Ok, Result}; -use getopts::Options; +use crate::defs::NO_FD_WRAPPER_PATH; +use crate::ksucalls::get_wrapped_fd; + #[cfg(any(target_os = "linux", target_os = "android"))] use rustix::{ process::getuid, thread::{Gid, Uid, set_thread_res_gid, set_thread_res_uid}, }; -use crate::{ - defs, - utils::{self, umask}, -}; - #[cfg(any(target_os = "linux", target_os = "android"))] pub fn grant_root(global_mnt: bool) -> Result<()> { crate::ksucalls::grant_root()?; @@ -62,6 +68,29 @@ fn set_identity(uid: u32, gid: u32, groups: &[u32]) { } } +#[cfg(any(target_os = "android"))] +fn wrap_tty(fd: c_int) { + let inner_fn = move || -> Result<()> { + if unsafe { libc::isatty(fd) != 1 } { + debug!("not a tty: {fd}"); + return Ok(()); + } + let new_fd = get_wrapped_fd(fd).context("get_wrapped_fd")?; + if unsafe { libc::dup2(new_fd, fd) } == -1 { + bail!("dup {new_fd} -> {fd} errno: {}", unsafe { + *libc::__errno() + }); + } else { + unsafe { libc::close(new_fd) }; + Ok(()) + } + }; + + if let Err(e) = inner_fn() { + error!("wrap tty {fd}: {e:?}"); + } +} + #[cfg(not(any(target_os = "linux", target_os = "android")))] pub fn root_shell() -> Result<()> { unimplemented!() @@ -122,6 +151,8 @@ pub fn root_shell() -> Result<()> { "Specify a supplementary group. The first specified supplementary group is also used as a primary group if the option -g is not specified.", "GROUP", ); + opts.optflag("w", "wrapper", "Use mksu fd wrapper"); + opts.optflag("W", "no-wrapper", "Don't use mksu fd wrapper"); // Replace -cn with -z, -mm with -M for supporting getopt_long let args = args @@ -165,6 +196,11 @@ pub fn root_shell() -> Result<()> { let mut is_login = matches.opt_present("l"); let preserve_env = matches.opt_present("p"); let mount_master = matches.opt_present("M"); + let use_fd_wrapper = (!std::path::Path::new(NO_FD_WRAPPER_PATH).exists() + || matches.opt_present("w")) + && !matches.opt_present("W"); + + info!("use_fd_wrapper={use_fd_wrapper}"); let groups = matches .opt_strs("G") @@ -263,6 +299,13 @@ pub fn root_shell() -> Result<()> { let _ = utils::switch_mnt_ns(1); } + #[cfg(any(target_os = "android"))] + if use_fd_wrapper { + wrap_tty(0); + wrap_tty(1); + wrap_tty(2); + } + set_identity(uid, gid, &groups); Result::Ok(())