|
| 1 | +#include <linux/dcache.h> |
| 2 | +#include <linux/errno.h> |
| 3 | +#include <linux/fdtable.h> |
| 4 | +#include <linux/file.h> |
| 5 | +#include <linux/fs.h> |
| 6 | +#include <linux/fs_struct.h> |
| 7 | +#include <linux/limits.h> |
| 8 | +#include <linux/namei.h> |
| 9 | +#include <linux/proc_ns.h> |
| 10 | +#include <linux/pid.h> |
| 11 | +#include <linux/sched/task.h> |
| 12 | +#include <linux/slab.h> |
| 13 | +#include <linux/syscalls.h> |
| 14 | +#include <linux/task_work.h> |
| 15 | +#include <linux/version.h> |
| 16 | +#include <uapi/linux/mount.h> |
| 17 | + |
| 18 | +#include "arch.h" |
| 19 | +#include "klog.h" // IWYU pragma: keep |
| 20 | +#include "ksu.h" |
| 21 | +#include "su_mount_ns.h" |
| 22 | + |
| 23 | +extern int path_mount(const char *dev_name, struct path *path, |
| 24 | + const char *type_page, unsigned long flags, |
| 25 | + void *data_page); |
| 26 | + |
| 27 | +#if defined(__aarch64__) |
| 28 | +extern long __arm64_sys_setns(const struct pt_regs *regs); |
| 29 | +#elif defined(__x86_64__) |
| 30 | +extern long __x64_sys_setns(const struct pt_regs *regs); |
| 31 | +#endif |
| 32 | + |
| 33 | +static long ksu_sys_setns(int fd, int flags) |
| 34 | +{ |
| 35 | + struct pt_regs regs; |
| 36 | + memset(®s, 0, sizeof(regs)); |
| 37 | + |
| 38 | + PT_REGS_PARM1(®s) = fd; |
| 39 | + PT_REGS_PARM2(®s) = flags; |
| 40 | + |
| 41 | +#if defined(__aarch64__) |
| 42 | + return __arm64_sys_setns(®s); |
| 43 | +#elif defined(__x86_64__) |
| 44 | + return __x64_sys_setns(®s); |
| 45 | +#else |
| 46 | +#error "Unsupported arch" |
| 47 | +#endif |
| 48 | +} |
| 49 | + |
| 50 | +// global mode , need CAP_SYS_ADMIN and CAP_SYS_CHROOT to perform setns |
| 51 | +static void ksu_mnt_ns_global(void) |
| 52 | +{ |
| 53 | + // save current working directory as absolute path before setns |
| 54 | + char *pwd_path = NULL; |
| 55 | + char *pwd_buf = kmalloc(PATH_MAX, GFP_KERNEL); |
| 56 | + if (!pwd_buf) { |
| 57 | + pr_warn("no mem for pwd buffer, skip restore pwd!!\n"); |
| 58 | + goto try_setns; |
| 59 | + } |
| 60 | + |
| 61 | + struct path saved_pwd; |
| 62 | + get_fs_pwd(current->fs, &saved_pwd); |
| 63 | + pwd_path = d_path(&saved_pwd, pwd_buf, PATH_MAX); |
| 64 | + path_put(&saved_pwd); |
| 65 | + |
| 66 | + if (IS_ERR(pwd_path)) { |
| 67 | + if (PTR_ERR(pwd_path) == -ENAMETOOLONG) { |
| 68 | + pr_warn("absolute pwd longer than: %d, skip restore pwd!!\n", |
| 69 | + PATH_MAX); |
| 70 | + } else { |
| 71 | + pr_warn("get absolute pwd failed: %ld\n", PTR_ERR(pwd_path)); |
| 72 | + } |
| 73 | + pwd_path = NULL; |
| 74 | + } |
| 75 | + |
| 76 | +try_setns: |
| 77 | + |
| 78 | + rcu_read_lock(); |
| 79 | + // &init_task is not init, but swapper/idle, which forks the init process |
| 80 | + // so we need find init process |
| 81 | + struct pid *pid_struct = find_pid_ns(1, &init_pid_ns); |
| 82 | + if (unlikely(!pid_struct)) { |
| 83 | + rcu_read_unlock(); |
| 84 | + pr_warn("failed to find pid_struct for PID 1\n"); |
| 85 | + goto out; |
| 86 | + } |
| 87 | + |
| 88 | + struct task_struct *pid1_task = get_pid_task(pid_struct, PIDTYPE_PID); |
| 89 | + rcu_read_unlock(); |
| 90 | + if (unlikely(!pid1_task)) { |
| 91 | + pr_warn("failed to get task_struct for PID 1\n"); |
| 92 | + goto out; |
| 93 | + } |
| 94 | + struct path ns_path; |
| 95 | + long ret = ns_get_path(&ns_path, pid1_task, &mntns_operations); |
| 96 | + put_task_struct(pid1_task); |
| 97 | + if (ret) { |
| 98 | + pr_warn("failed get path for init mount namespace: %ld\n", ret); |
| 99 | + goto out; |
| 100 | + } |
| 101 | + struct file *ns_file = dentry_open(&ns_path, O_RDONLY, ksu_cred); |
| 102 | + |
| 103 | + path_put(&ns_path); |
| 104 | + if (IS_ERR(ns_file)) { |
| 105 | + pr_warn("failed open file for init mount namespace: %ld\n", |
| 106 | + PTR_ERR(ns_file)); |
| 107 | + goto out; |
| 108 | + } |
| 109 | + |
| 110 | + int fd = get_unused_fd_flags(O_CLOEXEC); |
| 111 | + if (fd < 0) { |
| 112 | + pr_warn("failed to get an unused fd: %d\n", fd); |
| 113 | + fput(ns_file); |
| 114 | + goto out; |
| 115 | + } |
| 116 | + |
| 117 | + fd_install(fd, ns_file); |
| 118 | + ret = ksu_sys_setns(fd, CLONE_NEWNS); |
| 119 | + |
| 120 | +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0) |
| 121 | + ksys_close(fd); |
| 122 | +#else |
| 123 | + close_fd(fd); |
| 124 | +#endif |
| 125 | + |
| 126 | + if (ret) { |
| 127 | + pr_warn("call setns failed: %ld\n", ret); |
| 128 | + goto out; |
| 129 | + } |
| 130 | + // try to restore working directory using absolute path after setns |
| 131 | + if (pwd_path) { |
| 132 | + struct path new_pwd; |
| 133 | + int err = kern_path(pwd_path, 0, &new_pwd); |
| 134 | + if (!err) { |
| 135 | + set_fs_pwd(current->fs, &new_pwd); |
| 136 | + path_put(&new_pwd); |
| 137 | + } else { |
| 138 | + pr_warn("restore pwd failed: %d, path: %s\n", err, pwd_path); |
| 139 | + } |
| 140 | + } |
| 141 | +out: |
| 142 | + kfree(pwd_buf); |
| 143 | +} |
| 144 | + |
| 145 | +// individual mode , need CAP_SYS_ADMIN to perform unshare and remount |
| 146 | +static void ksu_mnt_ns_individual(void) |
| 147 | +{ |
| 148 | + long ret = ksys_unshare(CLONE_NEWNS); |
| 149 | + if (ret) { |
| 150 | + pr_warn("call ksys_unshare failed: %ld\n", ret); |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + // make root mount private |
| 155 | + struct path root_path; |
| 156 | + get_fs_root(current->fs, &root_path); |
| 157 | + int pm_ret = path_mount(NULL, &root_path, NULL, MS_PRIVATE | MS_REC, NULL); |
| 158 | + path_put(&root_path); |
| 159 | + |
| 160 | + if (pm_ret < 0) { |
| 161 | + pr_err("failed to make root private, err: %d\n", pm_ret); |
| 162 | + } |
| 163 | +} |
| 164 | + |
| 165 | +static void ksu_setup_mount_ns_tw_func(struct callback_head *cb) |
| 166 | +{ |
| 167 | + struct ksu_mns_tw *tw = container_of(cb, struct ksu_mns_tw, cb); |
| 168 | + const struct cred *old_cred = override_creds(ksu_cred); |
| 169 | + if (tw->ns_mode == KSU_NS_GLOBAL) { |
| 170 | + ksu_mnt_ns_global(); |
| 171 | + } else { |
| 172 | + ksu_mnt_ns_individual(); |
| 173 | + } |
| 174 | + revert_creds(old_cred); |
| 175 | + kfree(tw); |
| 176 | +} |
| 177 | + |
| 178 | +void setup_mount_ns(int32_t ns_mode) |
| 179 | +{ |
| 180 | + // inherit mode |
| 181 | + if (ns_mode == KSU_NS_INHERITED) { |
| 182 | + // do nothing |
| 183 | + return; |
| 184 | + } |
| 185 | + |
| 186 | + if (ns_mode != KSU_NS_GLOBAL && ns_mode != KSU_NS_INDIVIDUAL) { |
| 187 | + pr_warn("pid: %d ,unknown mount namespace mode: %d\n", current->pid, |
| 188 | + ns_mode); |
| 189 | + return; |
| 190 | + } |
| 191 | + |
| 192 | + if (!ksu_cred) { |
| 193 | + pr_err("no ksu cred! skip mnt_ns magic for pid: %d.\n", current->pid); |
| 194 | + return; |
| 195 | + } |
| 196 | + |
| 197 | + struct ksu_mns_tw *tw = kzalloc(sizeof(*tw), GFP_ATOMIC); |
| 198 | + if (!tw) { |
| 199 | + pr_err("no mem for tw! skip mnt_ns magic for pid: %d.\n", current->pid); |
| 200 | + return; |
| 201 | + } |
| 202 | + tw->cb.func = ksu_setup_mount_ns_tw_func; |
| 203 | + tw->ns_mode = ns_mode; |
| 204 | + if (task_work_add(current, &tw->cb, TWA_RESUME)) { |
| 205 | + kfree(tw); |
| 206 | + pr_err("add task work failed! skip mnt_ns magic for pid: %d.\n", |
| 207 | + current->pid); |
| 208 | + } |
| 209 | +} |
0 commit comments