Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
243 changes: 227 additions & 16 deletions kernel/file_wrapper.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <linux/gfp.h>
#include <linux/fdtable.h>
#include <linux/export.h>
#include <linux/anon_inodes.h>
#include <linux/capability.h>
Expand All @@ -9,12 +11,46 @@
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <linux/mount.h>

#include "objsec.h"

#include "klog.h" // IWYU pragma: keep
#include "selinux/selinux.h"
#include "ksud.h"

#include "file_wrapper.h"

struct ksu_file_wrapper {
struct file *orig;
struct file_operations ops;
};

static struct ksu_file_wrapper *ksu_create_file_wrapper(struct file *fp);

static int ksu_wrapper_open(struct inode *ino, struct file *fp)
{
struct path *orig_path = fp->f_path.dentry->d_fsdata;
struct file *orig_file =
dentry_open(orig_path, fp->f_flags, current_cred());
if (IS_ERR(orig_file)) {
return PTR_ERR(orig_file);
}
struct ksu_file_wrapper *wrapper = ksu_create_file_wrapper(orig_file);
if (IS_ERR(wrapper)) {
filp_close(orig_file, current->files);
return PTR_ERR(wrapper);
}
fp->private_data = wrapper;
const struct file_operations *new_fops = fops_get(&wrapper->ops);
replace_fops(fp, new_fops);
return 0;
}

struct file_operations ksu_file_wrapper_inode_fops = { .owner = THIS_MODULE,
.open =
ksu_wrapper_open };

static loff_t ksu_wrapper_llseek(struct file *fp, loff_t off, int flags)
{
struct ksu_file_wrapper *data = fp->private_data;
Expand Down Expand Up @@ -119,16 +155,6 @@ static int ksu_wrapper_mmap(struct file *fp, struct vm_area_struct *vma)
return orig->f_op->mmap(orig, vma);
}

// static unsigned long mmap_supported_flags {}

static int ksu_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 ksu_wrapper_flush(struct file *fp, fl_owner_t id)
{
struct ksu_file_wrapper *data = fp->private_data;
Expand Down Expand Up @@ -333,18 +359,25 @@ static int ksu_wrapper_fadvise(struct file *fp, loff_t off1, loff_t off2,
return -EINVAL;
}

static void ksu_release_file_wrapper(struct ksu_file_wrapper *data);

static int ksu_wrapper_release(struct inode *inode, struct file *filp)
{
ksu_delete_file_wrapper(filp->private_data);
// https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/fs/file_table.c;l=467-473;drc=3be0b283b562eabbc2b1f3bb534dc8903079bbaa
// f_op->release is called before fops_put(f_op), so manually put it.
// FIXME: is this safe?
fops_put(filp->f_op);
filp->f_op = NULL;
ksu_release_file_wrapper(filp->private_data);
return 0;
}

struct ksu_file_wrapper *ksu_create_file_wrapper(struct file *fp)
static struct ksu_file_wrapper *ksu_create_file_wrapper(struct file *fp)
{
struct ksu_file_wrapper *p =
kcalloc(sizeof(struct ksu_file_wrapper), 1, GFP_KERNEL);
kcalloc(1, sizeof(struct ksu_file_wrapper), GFP_KERNEL);
if (!p) {
return NULL;
return ERR_PTR(-ENOMEM);
}

get_file(fp);
Expand Down Expand Up @@ -373,7 +406,6 @@ struct ksu_file_wrapper *ksu_create_file_wrapper(struct file *fp)
#else
p->ops.mmap_supported_flags = fp->f_op->mmap_supported_flags;
#endif
p->ops.open = fp->f_op->open ? ksu_wrapper_open : NULL;
p->ops.flush = fp->f_op->flush ? ksu_wrapper_flush : NULL;
p->ops.release = ksu_wrapper_release;
p->ops.fsync = fp->f_op->fsync ? ksu_wrapper_fsync : NULL;
Expand Down Expand Up @@ -405,8 +437,187 @@ struct ksu_file_wrapper *ksu_create_file_wrapper(struct file *fp)
return p;
}

void ksu_delete_file_wrapper(struct ksu_file_wrapper *data)
static void ksu_release_file_wrapper(struct ksu_file_wrapper *data)
{
fput((struct file *)data->orig);
kfree(data);
}

static char *ksu_wrapper_d_dname(struct dentry *dentry, char *buffer,
int buflen)
{
struct path *orig_path = dentry->d_fsdata;
return d_path(orig_path, buffer, buflen);
}

static void ksu_wrapper_d_release(struct dentry *dentry)
{
struct path *orig_path = dentry->d_fsdata;
path_put(orig_path);
kfree(orig_path);
}

static const struct dentry_operations ksu_file_wrapper_d_ops = {
.d_dname = ksu_wrapper_d_dname,
.d_release = ksu_wrapper_d_release
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
#define ksu_anon_inode_create_getfile_compat anon_inode_create_getfile
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)
#define ksu_anon_inode_create_getfile_compat anon_inode_getfile_secure
#else
// There is no anon_inode_create_getfile before 5.16, but it's not difficult to implement it.
// https://cs.android.com/android/kernel/superproject/+/common-android12-5.10:common/fs/anon_inodes.c;l=58-125;drc=0d34ce8aa78e38affbb501690bcabec4df88620e

// Borrow kernel's anon_inode_mnt, so that we don't need to mount one by ourselves.
static struct vfsmount *anon_inode_mnt __read_mostly;

static struct inode *
ksu_anon_inode_make_secure_inode(const char *name,
const struct inode *context_inode)
{
struct inode *inode;
const struct qstr qname = QSTR_INIT(name, strlen(name));
int error;

if (unlikely(!anon_inode_mnt)) {
return ERR_PTR(-ENODEV);
}

inode = alloc_anon_inode(anon_inode_mnt->mnt_sb);
if (IS_ERR(inode))
return inode;
inode->i_flags &= ~S_PRIVATE;
error = security_inode_init_security_anon(inode, &qname, context_inode);
if (error) {
iput(inode);
return ERR_PTR(error);
}
return inode;
}

static struct file *ksu_anon_inode_create_getfile_compat(
const char *name, const struct file_operations *fops, void *priv, int flags,
const struct inode *context_inode)
{
struct inode *inode;
struct file *file;

if (fops->owner && !try_module_get(fops->owner))
return ERR_PTR(-ENOENT);

inode = ksu_anon_inode_make_secure_inode(name, context_inode);
if (IS_ERR(inode)) {
file = ERR_CAST(inode);
goto err;
}

file = alloc_file_pseudo(inode, anon_inode_mnt, name,
flags & (O_ACCMODE | O_NONBLOCK), fops);
if (IS_ERR(file))
goto err_iput;

file->f_mapping = inode->i_mapping;

file->private_data = priv;

return file;

err_iput:
iput(inode);
err:
module_put(fops->owner);
return file;
}
#endif

int ksu_install_file_wrapper(int fd)
{
int out_fd, ret;
struct file *orig_file = fget(fd);
if (!orig_file) {
return -EBADF;
}

out_fd = get_unused_fd_flags(O_CLOEXEC);
if (out_fd < 0) {
ret = out_fd;
goto done;
}

struct ksu_file_wrapper *data = ksu_create_file_wrapper(orig_file);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
goto out_put_fd;
}

struct file *wrapper_file = ksu_anon_inode_create_getfile_compat(
"[ksu_fdwrapper]", &data->ops, data, orig_file->f_flags, NULL);
if (IS_ERR(wrapper_file)) {
pr_err("ksu_fdwrapper: getfile failed: %ld\n", PTR_ERR(wrapper_file));
ret = PTR_ERR(wrapper_file);
goto out_release_wrapper;
}

// Now do magic on inode and dentry.
// It should be safe to modify them since the file hasn't been published.

struct inode *wrapper_inode = file_inode(wrapper_file);
// libc's stdio relies on the fstat() result of the fd to determine its buffer type.
wrapper_inode->i_mode = file_inode(orig_file)->i_mode;
struct inode_security_struct *wrapper_sec = selinux_inode(wrapper_inode);
// Use ksu_file_sid to bypass SELinux check.
// When we call `su` from terminal app, this is useful.
if (wrapper_sec) {
wrapper_sec->sid = ksu_file_sid;
}
// Install open file operation for inode.
wrapper_inode->i_fop = &ksu_file_wrapper_inode_fops;

struct path *orig_path = kmalloc(sizeof(struct path), GFP_KERNEL);
if (!orig_path) {
ret = -ENOMEM;
goto out_put_wrapper_file;
}
*orig_path = orig_file->f_path;
path_get(orig_path);
// Some applications (such as screen) won't work if the tty's path is weird,
// Therefore, we use d_dname to spoof it to return the path to the original file.
wrapper_file->f_path.dentry->d_fsdata = orig_path;
wrapper_file->f_path.dentry->d_op = &ksu_file_wrapper_d_ops;

fd_install(out_fd, wrapper_file);
ret = out_fd;
goto done;

out_put_wrapper_file:
fput(wrapper_file);
out_release_wrapper:
ksu_release_file_wrapper(data);
out_put_fd:
put_unused_fd(out_fd);
done:
fput(orig_file);

return ret;
}

void ksu_file_wrapper_init(void)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0)
static const struct file_operations tmp = { .owner = THIS_MODULE };
struct file *dummy = anon_inode_getfile("dummy", &tmp, NULL, 0);
if (IS_ERR(dummy)) {
pr_err(
"file_wrapper: initialize anon_inode_mnt failed, can't get file: %ld\n",
PTR_ERR(dummy));
return;
}
anon_inode_mnt = dummy->f_path.mnt;
if (unlikely(!anon_inode_mnt)) {
pr_err("file_wrapper: initialize anon_inode_mnt failed, got NULL\n");
}
fput(dummy);
#endif
}
8 changes: 2 additions & 6 deletions kernel/file_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@
#include <linux/file.h>
#include <linux/fs.h>

struct ksu_file_wrapper {
struct file *orig;
struct file_operations ops;
};
int ksu_install_file_wrapper(int fd);
void ksu_file_wrapper_init(void);

struct ksu_file_wrapper *ksu_create_file_wrapper(struct file *fp);
void ksu_delete_file_wrapper(struct ksu_file_wrapper *data);
#endif // KSU_FILE_WRAPPER_H
3 changes: 3 additions & 0 deletions kernel/ksu.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "ksud.h"
#include "supercalls.h"
#include "ksu.h"
#include "file_wrapper.h"

struct cred *ksu_cred;

Expand Down Expand Up @@ -44,6 +45,8 @@ int __init kernelsu_init(void)

ksu_ksud_init();

ksu_file_wrapper_init();

#ifdef MODULE
#ifndef CONFIG_KSU_DEBUG
kobject_del(&THIS_MODULE->mkobj.kobj);
Expand Down
43 changes: 1 addition & 42 deletions kernel/supercalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
#include "kernel_umount.h"
#include "manager.h"
#include "selinux/selinux.h"
#include "objsec.h"
#include "file_wrapper.h"
#include "syscall_hook_manager.h"

Expand Down Expand Up @@ -338,52 +337,12 @@ static int do_get_wrapper_fd(void __user *arg)
}

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 = ksu_create_file_wrapper(f);
if (data == NULL) {
ret = -ENOMEM;
goto put_orig_file;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
#define getfd_secure anon_inode_create_getfd
#else
#define getfd_secure anon_inode_getfd_secure
#endif
ret = getfd_secure("[ksu_fdwrapper]", &data->ops, data, f->f_flags, NULL);
if (ret < 0) {
pr_err("ksu_fdwrapper: getfd failed: %d\n", ret);
goto put_wrapper_data;
}
struct file *pf = fget(ret);

struct inode *wrapper_inode = file_inode(pf);
// copy original inode mode
wrapper_inode->i_mode = file_inode(f)->i_mode;
struct inode_security_struct *sec = selinux_inode(wrapper_inode);
if (sec) {
sec->sid = ksu_file_sid;
}

fput(pf);
goto put_orig_file;
put_wrapper_data:
ksu_delete_file_wrapper(data);
put_orig_file:
fput(f);

return ret;
return ksu_install_file_wrapper(cmd.fd);
}

static int do_manage_mark(void __user *arg)
Expand Down