From 662196de01947aea355911cb78ce00e1ba7c54fa Mon Sep 17 00:00:00 2001 From: Nicholas Andrew Date: Fri, 18 Sep 2026 19:45:36 -0400 Subject: [PATCH 1/7] milanf: integrate KernelSU-Next with manual hooks --- .../vendor/ext_config/moto-holi-milanf.config | 5 ++++ drivers/Kconfig | 1 + drivers/Makefile | 2 ++ drivers/input/input.c | 13 +++++++++- drivers/kernelsu | 1 + fs/exec.c | 8 ++++++ fs/open.c | 10 +++++++ fs/read_write.c | 10 +++++++ fs/stat.c | 26 +++++++++++++++++++ kernel/reboot.c | 9 +++++++ 10 files changed, 84 insertions(+), 1 deletion(-) create mode 120000 drivers/kernelsu diff --git a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config index 921287b0132d..edaad1202d95 100644 --- a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config +++ b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config @@ -91,3 +91,8 @@ CONFIG_BOARD_USES_DOUBLE_TAP_CTRL=y # Vibrator CONFIG_LDO_VIBRATOR_MMI=m + +# KernelSU-Next +CONFIG_KSU=y +CONFIG_KPROBE_EVENTS=y +CONFIG_KSU_MANUAL_HOOK=y diff --git a/drivers/Kconfig b/drivers/Kconfig index adcc62cb72d1..2d644a6e8dfa 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -244,4 +244,5 @@ source "drivers/mmi_relay/Kconfig" source "drivers/sensors/Kconfig" +source "drivers/kernelsu/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 56f8bdc58920..544781f99d8f 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -194,3 +194,5 @@ obj-$(CONFIG_MMI_ANNOTATE) += mmi_annotate/ obj-$(CONFIG_MMI_INFO) += mmi_info/ obj-$(CONFIG_MMI_RELAY) += mmi_relay/ obj-$(CONFIG_SENSORS_CLASS) += sensors/ + +obj-$(CONFIG_KSU) += kernelsu/ diff --git a/drivers/input/input.c b/drivers/input/input.c index 45fdb9bdf08d..dfc44ee93cec 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -375,10 +375,21 @@ static int input_get_disposition(struct input_dev *dev, return disposition; } +#ifdef CONFIG_KSU_MANUAL_HOOK +extern int ksu_handle_input_handle_event(unsigned int *type, + unsigned int *code, + int *value); +#endif + static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { - int disposition = input_get_disposition(dev, type, code, &value); + int disposition; + +#ifdef CONFIG_KSU_MANUAL_HOOK + ksu_handle_input_handle_event(&type, &code, &value); +#endif + disposition = input_get_disposition(dev, type, code, &value); if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) add_input_randomness(type, code, value); diff --git a/drivers/kernelsu b/drivers/kernelsu new file mode 120000 index 000000000000..b32a3654a683 --- /dev/null +++ b/drivers/kernelsu @@ -0,0 +1 @@ +../KernelSU-Next/kernel \ No newline at end of file diff --git a/fs/exec.c b/fs/exec.c index 910b407d267e..57301577d551 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1904,11 +1904,19 @@ out_ret: return retval; } +#ifdef CONFIG_KSU_MANUAL_HOOK +extern int ksu_handle_execveat(int *fd, struct filename **filename_ptr, + void *argv, void *envp, int *flags); +#endif + static int do_execveat_common(int fd, struct filename *filename, struct user_arg_ptr argv, struct user_arg_ptr envp, int flags) { +#ifdef CONFIG_KSU_MANUAL_HOOK + ksu_handle_execveat(&fd, &filename, &argv, &envp, &flags); +#endif return __do_execve_file(fd, filename, argv, envp, flags, NULL); } diff --git a/fs/open.c b/fs/open.c index 3f9f5fda8ebf..8f79da0318e7 100644 --- a/fs/open.c +++ b/fs/open.c @@ -345,6 +345,12 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len) * We do this by temporarily clearing all FS-related capabilities and * switching the fsuid/fsgid around to the real ones. */ +#ifdef CONFIG_KSU_MANUAL_HOOK +extern int ksu_handle_faccessat(int *dfd, + const char __user **filename_user, + int *mode, int *flags); +#endif + long do_faccessat(int dfd, const char __user *filename, int mode) { const struct cred *old_cred; @@ -354,6 +360,10 @@ long do_faccessat(int dfd, const char __user *filename, int mode) int res; unsigned int lookup_flags = LOOKUP_FOLLOW; +#ifdef CONFIG_KSU_MANUAL_HOOK + ksu_handle_faccessat(&dfd, &filename, &mode, NULL); +#endif + if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL; diff --git a/fs/read_write.c b/fs/read_write.c index 301c4a4ba1d0..438363d9bfd6 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -443,10 +443,20 @@ ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos) } EXPORT_SYMBOL_NS(kernel_read, ANDROID_GKI_VFS_EXPORT_ONLY); +#ifdef CONFIG_KSU_MANUAL_HOOK +extern int ksu_handle_vfs_read(struct file **file_ptr, + char __user **buf_ptr, + size_t *count_ptr, loff_t **pos); +#endif + ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { ssize_t ret; +#ifdef CONFIG_KSU_MANUAL_HOOK + ksu_handle_vfs_read(&file, &buf, &count, &pos); +#endif + if (!(file->f_mode & FMODE_READ)) return -EBADF; if (!(file->f_mode & FMODE_CAN_READ)) diff --git a/fs/stat.c b/fs/stat.c index 298eb77668a7..fee429033ff0 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -165,6 +165,18 @@ EXPORT_SYMBOL(vfs_statx_fd); * * 0 will be returned on success, and a -ve error code if unsuccessful. */ +#ifdef CONFIG_KSU_MANUAL_HOOK +extern int ksu_handle_stat(int *dfd, + const char __user **filename_user, + int *flags); +extern void ksu_handle_newfstat_ret(unsigned int *fd, + struct stat __user **statbuf_ptr); +#if defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) +extern void ksu_handle_fstat64_ret(unsigned long *fd, + struct stat64 __user **statbuf_ptr); +#endif +#endif + int vfs_statx(int dfd, const char __user *filename, int flags, struct kstat *stat, u32 request_mask) { @@ -172,6 +184,10 @@ int vfs_statx(int dfd, const char __user *filename, int flags, int error = -EINVAL; unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT; +#ifdef CONFIG_KSU_MANUAL_HOOK + ksu_handle_stat(&dfd, &filename, &flags); +#endif + if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH | KSTAT_QUERY_FLAGS)) != 0) return -EINVAL; @@ -379,6 +395,11 @@ SYSCALL_DEFINE2(newfstat, unsigned int, fd, struct stat __user *, statbuf) if (!error) error = cp_new_stat(&stat, statbuf); +#ifdef CONFIG_KSU_MANUAL_HOOK + if (!error) + ksu_handle_newfstat_ret(&fd, &statbuf); +#endif + return error; } #endif @@ -506,6 +527,11 @@ SYSCALL_DEFINE2(fstat64, unsigned long, fd, struct stat64 __user *, statbuf) if (!error) error = cp_new_stat64(&stat, statbuf); +#ifdef CONFIG_KSU_MANUAL_HOOK + if (!error) + ksu_handle_fstat64_ret(&fd, &statbuf); +#endif + return error; } diff --git a/kernel/reboot.c b/kernel/reboot.c index 790c2f514a55..1bd622a83446 100644 --- a/kernel/reboot.c +++ b/kernel/reboot.c @@ -310,6 +310,11 @@ DEFINE_MUTEX(system_transition_mutex); * * reboot doesn't sync: do that yourself before calling this. */ +#ifdef CONFIG_KSU_MANUAL_HOOK +extern int ksu_handle_sys_reboot(int magic1, int magic2, unsigned int cmd, + void __user **arg); +#endif + SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, void __user *, arg) { @@ -317,6 +322,10 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, char buffer[256]; int ret = 0; +#ifdef CONFIG_KSU_MANUAL_HOOK + ksu_handle_sys_reboot(magic1, magic2, cmd, &arg); +#endif + /* We only trust the superuser with rebooting the system. */ if (!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT)) return -EPERM; From c3c71b8ff67a99de918ca38c4dcda55c227e7db6 Mon Sep 17 00:00:00 2001 From: Nicholas Andrew Date: Fri, 18 Sep 2026 19:55:23 -0400 Subject: [PATCH 2/7] milanf: add KernelSU-Next SUSFS support KernelSU-Next commit: 9635f39b43792a448fc58f97d6d63b89b083a7e0 --- .../vendor/ext_config/moto-holi-milanf.config | 13 + fs/Makefile | 7 +- fs/internal.h | 5 + fs/namei.c | 118 ++ fs/open.c | 46 + fs/proc/base.c | 90 ++ fs/proc/task_mmu.c | 32 + fs/proc_namespace.c | 50 + fs/readdir.c | 9 + fs/stat.c | 13 + fs/susfs.c | 1427 +++++++++++++++++ include/linux/susfs.h | 216 +++ kernel/sys.c | 17 + mm/memfd.c | 11 + 14 files changed, 2052 insertions(+), 2 deletions(-) create mode 100644 fs/susfs.c create mode 100644 include/linux/susfs.h diff --git a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config index edaad1202d95..d8f2c78443eb 100644 --- a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config +++ b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config @@ -96,3 +96,16 @@ CONFIG_LDO_VIBRATOR_MMI=m CONFIG_KSU=y CONFIG_KPROBE_EVENTS=y CONFIG_KSU_MANUAL_HOOK=y + +# KernelSU-Next SUSFS +CONFIG_KSU_SUSFS=y +CONFIG_KSU_SUSFS_SUS_PATH=y +CONFIG_KSU_SUSFS_SUS_MOUNT=y +# CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER is not set +CONFIG_KSU_SUSFS_SUS_KSTAT=y +CONFIG_KSU_SUSFS_SUS_MAPS=y +# CONFIG_KSU_SUSFS_SUS_PROC_FD_LINK is not set +# CONFIG_KSU_SUSFS_SUS_MEMFD is not set +CONFIG_KSU_SUSFS_TRY_UMOUNT=y +CONFIG_KSU_SUSFS_SPOOF_UNAME=y +CONFIG_KSU_SUSFS_ENABLE_LOG=y diff --git a/fs/Makefile b/fs/Makefile index 1c949ac25dbb..aff1cd7061e7 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -14,8 +14,11 @@ obj-y := open.o read_write.o file_table.o super.o \ attr.o bad_inode.o file.o filesystems.o namespace.o \ seq_file.o xattr.o libfs.o fs-writeback.o \ pnode.o splice.o sync.o utimes.o d_path.o \ - stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \ - fs_types.o fs_context.o fs_parser.o fsopen.o + stack.o fs_struct.o statfs.o fs_pin.o nsfs.o + +obj-$(CONFIG_KSU_SUSFS) += susfs.o + +obj-y += fs_types.o fs_context.o fs_parser.o fsopen.o ifeq ($(CONFIG_BLOCK),y) obj-y += buffer.o block_dev.o direct-io.o mpage.o diff --git a/fs/internal.h b/fs/internal.h index 377f984e9226..f11fef6128dc 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -90,6 +90,11 @@ extern int __mnt_want_write_file(struct file *); extern void __mnt_drop_write_file(struct file *); extern void dissolve_on_fput(struct vfsmount *); + +#ifdef CONFIG_KSU_SUSFS +int path_umount(struct path *path, int flags); +#endif + /* * fs_struct.c */ diff --git a/fs/namei.c b/fs/namei.c index 60b0dc43e745..8394f327fa92 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -46,6 +46,10 @@ #define CREATE_TRACE_POINTS #include +#ifdef CONFIG_KSU_SUSFS +#include +#endif + /* [Feb-1997 T. Schoebel-Theuer] * Fundamental changes in the pathname lookup mechanisms (namei) * were necessary because of omirr. The reason is that omirr needs @@ -3709,6 +3713,13 @@ struct file *do_filp_open(int dfd, struct filename *pathname, int flags = op->lookup_flags; struct file *filp; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + int error; + if (susfs_sus_path_by_filename(pathname, &error, SYSCALL_FAMILY_ALL_ENOENT)) { + return ERR_PTR(error); + } +#endif + set_nameidata(&nd, dfd, pathname); filp = path_openat(&nd, op, flags | LOOKUP_RCU); if (unlikely(filp == ERR_PTR(-ECHILD))) @@ -3897,6 +3908,19 @@ long do_mknodat(int dfd, const char __user *filename, umode_t mode, int error; unsigned int lookup_flags = 0; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(filename); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_MKNOD); + putname_safe(fname); + + if (status) { + return error; + } +#endif + error = may_mknod(mode); if (error) return error; @@ -3976,6 +4000,19 @@ long do_mkdirat(int dfd, const char __user *pathname, umode_t mode) int error; unsigned int lookup_flags = LOOKUP_DIRECTORY; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(pathname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_MKDIRAT); + putname_safe(fname); + + if (status) { + return error; + } +#endif + retry: dentry = user_path_create(dfd, pathname, &path, lookup_flags); if (IS_ERR(dentry)) @@ -4051,6 +4088,21 @@ long do_rmdir(int dfd, const char __user *pathname) struct qstr last; int type; unsigned int lookup_flags = 0; + +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(pathname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_RMDIR); + putname_safe(fname); + + if (status) { + return error; + } + error = 0; +#endif + retry: name = filename_parentat(dfd, getname(pathname), lookup_flags, &path, &last, &type); @@ -4182,6 +4234,17 @@ long do_unlinkat(int dfd, struct filename *name) struct inode *inode = NULL; struct inode *delegated_inode = NULL; unsigned int lookup_flags = 0; + +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + int status; + + status = susfs_sus_path_by_filename(name, &error, SYSCALL_FAMILY_UNLINKAT); + + if (status) { + return error; + } +#endif + retry: name = filename_parentat(dfd, name, lookup_flags, &path, &last, &type); if (IS_ERR(name)) @@ -4289,6 +4352,19 @@ long do_symlinkat(const char __user *oldname, int newdfd, struct path path; unsigned int lookup_flags = 0; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(newname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_SYMLINKAT_NEWNAME); + putname_safe(fname); + + if (status) { + return error; + } +#endif + from = getname(oldname); if (IS_ERR(from)) return PTR_ERR(from); @@ -4420,6 +4496,27 @@ int do_linkat(int olddfd, const char __user *oldname, int newdfd, int how = 0; int error; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(oldname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_LINKAT_OLDNAME); + putname_safe(fname); + + if (status) { + return error; + } + + fname = getname_safe(newname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_LINKAT_NEWNAME); + putname_safe(fname); + + if (status) { + return error; + } +#endif + if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0) return -EINVAL; /* @@ -4702,6 +4799,27 @@ static int do_renameat2(int olddfd, const char __user *oldname, int newdfd, bool should_retry = false; int error; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(oldname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_RENAMEAT2_OLDNAME); + putname_safe(fname); + + if (status) { + return error; + } + + fname = getname_safe(newname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_RENAMEAT2_NEWNAME); + putname_safe(fname); + + if (status) { + return error; + } +#endif + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT)) return -EINVAL; diff --git a/fs/open.c b/fs/open.c index 8f79da0318e7..5111d26893f2 100644 --- a/fs/open.c +++ b/fs/open.c @@ -35,6 +35,10 @@ #include "internal.h" +#ifdef CONFIG_KSU_SUSFS +#include +#endif + int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, struct file *filp) { @@ -123,6 +127,18 @@ long do_sys_truncate(const char __user *pathname, loff_t length) unsigned int lookup_flags = LOOKUP_FOLLOW; struct path path; int error; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(pathname); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_ALL_ENOENT); + putname_safe(fname); + + if (status) { + return error; + } +#endif if (length < 0) /* sorry, but loff_t says... */ return -EINVAL; @@ -360,10 +376,26 @@ long do_faccessat(int dfd, const char __user *filename, int mode) int res; unsigned int lookup_flags = LOOKUP_FOLLOW; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename *fname; + int status; + int error; +#endif + #ifdef CONFIG_KSU_MANUAL_HOOK ksu_handle_faccessat(&dfd, &filename, &mode, NULL); #endif +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + fname = getname_safe(filename); + status = susfs_sus_path_by_filename(fname, &error, + SYSCALL_FAMILY_ALL_ENOENT); + putname_safe(fname); + + if (status) + return error; +#endif + if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL; @@ -465,6 +497,20 @@ int ksys_chdir(const char __user *filename) struct path path; int error; unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY; + +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + struct filename* fname; + int status; + + fname = getname_safe(filename); + status = susfs_sus_path_by_filename(fname, &error, SYSCALL_FAMILY_ALL_ENOENT); + putname_safe(fname); + + if (status) { + return error; + } +#endif + retry: error = user_path_at(AT_FDCWD, filename, lookup_flags, &path); if (error) diff --git a/fs/proc/base.c b/fs/proc/base.c index 791ade1d1019..4b4dc0e7ec77 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -103,6 +103,10 @@ #include "../../lib/kstrtox.h" +#ifdef CONFIG_KSU_SUSFS +#include +#endif + /* NOTE: * Implementing inode permission operations in /proc is almost * certainly an error. Permission checks need to happen during @@ -1793,6 +1797,15 @@ static int do_proc_readlink(struct path *path, char __user *buffer, int buflen) char *pathname; int len; +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + struct mm_struct *mm; + struct vm_area_struct *vma; + struct file *vma_file; + struct dentry *vma_dentry; + struct inode *vma_inode; + unsigned long ino; +#endif + if (!tmp) return -ENOMEM; @@ -1804,6 +1817,39 @@ static int do_proc_readlink(struct path *path, char __user *buffer, int buflen) if (len > buflen) len = buflen; + +#ifdef CONFIG_KSU_SUSFS_SUS_PROC_FD_LINK + if (!susfs_is_sus_proc_fd_link_list_empty()) { + if (susfs_sus_proc_fd_link(pathname, len)) + goto orig_flow; + } +#endif + + +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + if (!susfs_is_sus_maps_list_empty()) { + mm = current->mm; + down_read(&mm->mmap_sem); + for (vma = mm->mmap; vma; vma = vma->vm_next) { + if (vma->vm_file) { + vma_file = vma->vm_file; + vma_dentry = vma_file->f_path.dentry; + if (vma_dentry == path->dentry) { + vma_inode = file_inode(vma_file); + ino = vma_inode->i_ino; + susfs_sus_map_files_readlink(ino, pathname); + break; + } + } + } + up_read(&mm->mmap_sem); + } +#endif + +#ifdef CONFIG_KSU_SUSFS_SUS_PROC_FD_LINK +orig_flow: +#endif + if (copy_to_user(buffer, pathname, len)) len = -EFAULT; out: @@ -2208,6 +2254,9 @@ struct map_files_info { unsigned long start; unsigned long end; fmode_t mode; +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + int susfs_action; +#endif }; /* @@ -2268,6 +2317,10 @@ static struct dentry *proc_map_files_lookup(struct inode *dir, struct dentry *result; struct mm_struct *mm; +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + int ret = 0; +#endif + result = ERR_PTR(-ENOENT); task = get_proc_task(dir); if (!task) @@ -2294,6 +2347,23 @@ static struct dentry *proc_map_files_lookup(struct inode *dir, if (!vma) goto out_no_vma; +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + if (vma->vm_file) { + ret = susfs_sus_map_files_instantiate(vma); + if (ret == 1) { + if (vma->vm_file->f_mode & FMODE_WRITE) { + vma->vm_file->f_mode &= ~FMODE_WRITE; + } + goto orig_flow; + } + if (ret == 2) { + result = ERR_PTR(-ENOENT); + goto out_no_vma; + } + } +orig_flow: +#endif + if (vma->vm_file) result = proc_map_files_instantiate(dentry, task, (void *)(unsigned long)vma->vm_file->f_mode); @@ -2379,6 +2449,10 @@ proc_map_files_readdir(struct file *file, struct dir_context *ctx) p->start = vma->vm_start; p->end = VMA_PAD_START(vma); p->mode = vma->vm_file->f_mode; + +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + p->susfs_action = susfs_sus_map_files_instantiate(vma); +#endif } up_read(&mm->mmap_sem); mmput(mm); @@ -2389,12 +2463,28 @@ proc_map_files_readdir(struct file *file, struct dir_context *ctx) p = genradix_ptr(&fa, i); len = snprintf(buf, sizeof(buf), "%lx-%lx", p->start, p->end); + +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + if (p->susfs_action == SUSFS_MAP_FILES_ACTION_REMOVE_WRITE_PERM) { + if (p->mode & FMODE_WRITE) { + p->mode &= ~FMODE_WRITE; + } + } else if (p->susfs_action == SUSFS_MAP_FILES_ACTION_HIDE_DENTRY) { + goto skip_proc_fill_cache; + } +#endif + if (!proc_fill_cache(file, ctx, buf, len, proc_map_files_instantiate, task, (void *)(unsigned long)p->mode)) break; + +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS +skip_proc_fill_cache: +#endif + ctx->pos++; } diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index d17cce42fe30..480502327f8a 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -28,6 +28,10 @@ #include #include "internal.h" +#ifdef CONFIG_KSU_SUSFS +#include +#endif + #define SEQ_PUT_DEC(str, val) \ seq_put_decimal_ull_width(m, str, (val) << (PAGE_SHIFT-10), 8) void task_mem(struct seq_file *m, struct mm_struct *mm) @@ -360,6 +364,10 @@ show_map_vma(struct seq_file *m, struct vm_area_struct *vma) unsigned long start, end; dev_t dev = 0; const char *name = NULL; +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + char *out_name = NULL; + int ret = 0; +#endif if (file) { struct inode *inode = file_inode(vma->vm_file); @@ -370,8 +378,32 @@ show_map_vma(struct seq_file *m, struct vm_area_struct *vma) start = vma->vm_start; end = VMA_PAD_START(vma); + +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + out_name = kmalloc(SUSFS_MAX_LEN_PATHNAME, GFP_KERNEL); + if (!out_name) + goto orig_flow; + + ret = susfs_sus_maps(ino, vma->vm_end - vma->vm_start, + &ino, &dev, &flags, &pgoff, vma, out_name); + +orig_flow: +#endif + show_vma_header_prefix(m, start, end, flags, pgoff, dev, ino); +#ifdef CONFIG_KSU_SUSFS_SUS_MAPS + if (ret == 2) { + seq_pad(m, ' '); + seq_puts(m, out_name); + seq_putc(m, '\n'); + kfree(out_name); + return; + } + + kfree(out_name); +#endif + /* * Print the dentry name for named mappings, and a * special [heap] marker for the heap: diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c index 5b8d065fa83c..dc6ba954ba50 100644 --- a/fs/proc_namespace.c +++ b/fs/proc_namespace.c @@ -18,6 +18,10 @@ #include "pnode.h" #include "internal.h" +#ifdef CONFIG_KSU_SUSFS +#include +#endif + static __poll_t mounts_poll(struct file *file, poll_table *wait) { struct seq_file *m = file->private_data; @@ -102,6 +106,11 @@ static int show_vfsmnt(struct seq_file *m, struct vfsmount *mnt) struct super_block *sb = mnt_path.dentry->d_sb; int err; +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT + if (susfs_sus_mount(mnt, &p->root)) + return 0; +#endif + if (sb->s_op->show_devname) { err = sb->s_op->show_devname(m, mnt_path.dentry); if (err) @@ -138,8 +147,31 @@ static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt) struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt }; int err; +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER + int out_mnt_id = 0, out_parent_mnt_id = 0; + int status = 1; +#endif +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT + if (susfs_sus_mount(mnt, &p->root)) + return 0; +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER + if (!uid_matches_proc_need_to_reorder_mnt_id()) + goto orig_flow; + status = susfs_get_fake_mnt_id(r->mnt_id, &out_mnt_id, &out_parent_mnt_id); + if (status) + goto orig_flow; + seq_printf(m, "%i %i %u:%u ", out_mnt_id, out_parent_mnt_id, + MAJOR(sb->s_dev), MINOR(sb->s_dev)); + goto bypass_orig_flow; +orig_flow: +#endif //#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER +#endif //#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT seq_printf(m, "%i %i %u:%u ", r->mnt_id, r->mnt_parent->mnt_id, MAJOR(sb->s_dev), MINOR(sb->s_dev)); +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER +bypass_orig_flow: +#endif + if (sb->s_op->show_path) { err = sb->s_op->show_path(m, mnt->mnt_root); if (err) @@ -202,6 +234,11 @@ static int show_vfsstat(struct seq_file *m, struct vfsmount *mnt) struct super_block *sb = mnt_path.dentry->d_sb; int err; +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT + if (susfs_sus_mount(mnt, &p->root)) + return 0; +#endif + /* device */ if (sb->s_op->show_devname) { seq_puts(m, "device "); @@ -285,6 +322,12 @@ static int mounts_open_common(struct inode *inode, struct file *file, p->show = show; p->cached_event = ~0ULL; +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER + if (uid_matches_proc_need_to_reorder_mnt_id()) { + susfs_add_mnt_id_recorder(p->ns); + } +#endif + return 0; err_put_path: @@ -299,6 +342,13 @@ static int mounts_release(struct inode *inode, struct file *file) { struct seq_file *m = file->private_data; struct proc_mounts *p = m->private; + +#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER + if (uid_matches_proc_need_to_reorder_mnt_id()) { + susfs_remove_mnt_id_recorder(); + } +#endif + path_put(&p->root); put_mnt_ns(p->ns); return seq_release_private(inode, file); diff --git a/fs/readdir.c b/fs/readdir.c index 07a3b5baa404..a3d709fa5312 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -22,6 +22,10 @@ #include #include +#ifdef CONFIG_KSU_SUSFS +#include +#endif + #include /* @@ -328,6 +332,11 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen, prev_reclen = buf->prev_reclen; if (prev_reclen && signal_pending(current)) return -EINTR; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + if (susfs_sus_ino_for_filldir64(ino)) { + return 0; + } +#endif dirent = buf->current_dir; prev = (void __user *)dirent - prev_reclen; if (!user_access_begin(prev, reclen + prev_reclen)) diff --git a/fs/stat.c b/fs/stat.c index fee429033ff0..254deb53fe88 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -21,6 +21,10 @@ #include #include +#ifdef CONFIG_KSU_SUSFS +#include +#endif + /** * generic_fillattr - Fill in the basic attributes from the inode struct * @inode: Inode to use as the source @@ -112,6 +116,12 @@ int vfs_getattr(const struct path *path, struct kstat *stat, { int retval; +#ifdef CONFIG_KSU_SUSFS_SUS_PATH + if (susfs_sus_path_by_path(path, &retval, SYSCALL_FAMILY_ALL_ENOENT)) { + return retval; + } +#endif + retval = security_inode_getattr(path); if (retval) return retval; @@ -346,6 +356,9 @@ static int cp_new_stat(struct kstat *stat, struct stat __user *statbuf) #endif tmp.st_blocks = stat->blocks; tmp.st_blksize = stat->blksize; +#ifdef CONFIG_KSU_SUSFS_SUS_KSTAT + susfs_sus_kstat(tmp.st_ino, &tmp); +#endif return copy_to_user(statbuf,&tmp,sizeof(tmp)) ? -EFAULT : 0; } diff --git a/fs/susfs.c b/fs/susfs.c new file mode 100644 index 000000000000..586909bdc63f --- /dev/null +++ b/fs/susfs.c @@ -0,0 +1,1427 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" +#include "mount.h" +#include + +LIST_HEAD(LH_SUS_PATH); +LIST_HEAD(LH_SUS_KSTAT_SPOOFER); +LIST_HEAD(LH_SUS_MOUNT); +LIST_HEAD(LH_SUS_MAPS_SPOOFER); +LIST_HEAD(LH_SUS_PROC_FD_LINK); +LIST_HEAD(LH_SUS_MEMFD); +LIST_HEAD(LH_TRY_UMOUNT_PATH); +LIST_HEAD(LH_MOUNT_ID_RECORDER); + +struct st_susfs_uname my_uname; + +spinlock_t susfs_spin_lock; +spinlock_t susfs_mnt_id_recorder_spin_lock; + +bool is_log_enable = true; +#ifdef CONFIG_KSU_SUSFS_ENABLE_LOG +#define SUSFS_LOGI(fmt, ...) if (is_log_enable) pr_info("susfs:[%u][%u][%s] " fmt, current_uid().val, current->pid, __func__, ##__VA_ARGS__) +#define SUSFS_LOGE(fmt, ...) if (is_log_enable) pr_err("susfs:[%u][%u][%s]" fmt, current_uid().val, current->pid, __func__, ##__VA_ARGS__) +#else +#define SUSFS_LOGI(fmt, ...) +#define SUSFS_LOGE(fmt, ...) +#endif + +int susfs_add_sus_path(struct st_susfs_sus_path* __user user_info) { + struct st_susfs_sus_path_list *cursor, *temp; + struct st_susfs_sus_path_list *new_list = NULL; + struct st_susfs_sus_path info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_path))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_SUS_PATH, list) { + if (unlikely(!strcmp(info.target_pathname, cursor->info.target_pathname))) { + SUSFS_LOGE("target_pathname: '%s' is already created in LH_SUS_PATH\n", info.target_pathname); + return 1; + } + } + + new_list = kmalloc(sizeof(struct st_susfs_sus_path_list), GFP_KERNEL); + if (!new_list) { + SUSFS_LOGE("no enough memory\n"); + return 1; + } + + memcpy(&new_list->info, &info, sizeof(struct st_susfs_sus_path)); + + INIT_LIST_HEAD(&new_list->list); + spin_lock(&susfs_spin_lock); + list_add_tail(&new_list->list, &LH_SUS_PATH); + spin_unlock(&susfs_spin_lock); + SUSFS_LOGI("target_pathname: '%s' is successfully added to LH_SUS_PATH\n", info.target_pathname); + return 0; +} + +int susfs_add_sus_mount(struct st_susfs_sus_mount* __user user_info) { + struct st_susfs_sus_mount_list *cursor, *temp; + struct st_susfs_sus_mount_list *new_list = NULL; + struct st_susfs_sus_mount info; + int list_count = 0; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_mount))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MOUNT, list) { + if (unlikely(!strcmp(cursor->info.target_pathname, info.target_pathname))) { + SUSFS_LOGE("target_pathname: '%s' is already created in LH_SUS_MOUNT\n", cursor->info.target_pathname); + return 1; + } + list_count++; + } + + if (list_count == SUSFS_MAX_SUS_MNTS) { + SUSFS_LOGE("LH_SUS_MOUNT has reached the list limit of %d\n", SUSFS_MAX_SUS_MNTS); + return 1; + } + + new_list = kmalloc(sizeof(struct st_susfs_sus_mount_list), GFP_KERNEL); + if (!new_list) { + SUSFS_LOGE("no enough memory\n"); + return 1; + } + + memcpy(&new_list->info, &info, sizeof(struct st_susfs_sus_mount)); + + INIT_LIST_HEAD(&new_list->list); + spin_lock(&susfs_spin_lock); + list_add_tail(&new_list->list, &LH_SUS_MOUNT); + spin_unlock(&susfs_spin_lock); + SUSFS_LOGI("target_pathname: '%s', is successfully added to LH_SUS_MOUNT\n", new_list->info.target_pathname); + return 0; +} + +int susfs_add_sus_kstat(struct st_susfs_sus_kstat* __user user_info) { + struct st_susfs_sus_kstat_list *cursor, *temp; + struct st_susfs_sus_kstat_list *new_list = NULL; + struct st_susfs_sus_kstat info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_kstat))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_SUS_KSTAT_SPOOFER, list) { + if (cursor->info.target_ino == info.target_ino) { + if (info.target_pathname[0] != '\0') { + SUSFS_LOGE("target_pathname: '%s' is already created in LH_SUS_KSTAT_SPOOFER\n", info.target_pathname); + } else { + SUSFS_LOGE("target_ino: '%lu' is already created in LH_SUS_KSTAT_SPOOFER\n", info.target_ino); + } + return 1; + } + } + + new_list = kmalloc(sizeof(struct st_susfs_sus_kstat_list), GFP_KERNEL); + if (!new_list) { + SUSFS_LOGE("no enough memory\n"); + return 1; + } + + memcpy(&new_list->info, &info, sizeof(struct st_susfs_sus_kstat)); + /* Seems the dev number issue is finally solved, the userspace stat we see is already a encoded dev + * which is set by new_encode_dev() / huge_encode_dev() function for 64bit system and + * old_encode_dev() for 32bit only system, that's why we need to decode it in kernel as well, + * and different kernel may have different function to encode the dev number, be cautious! + * Also check your encode_dev() macro in fs/stat.c to determine which one to use + */ +#if defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) +#ifdef CONFIG_MIPS + new_list->info.spoofed_dev = new_decode_dev(new_list->info.spoofed_dev); +#else + new_list->info.spoofed_dev = huge_decode_dev(new_list->info.spoofed_dev); +#endif /* CONFIG_MIPS */ +#else + new_list->info.spoofed_dev = old_decode_dev(new_list->info.spoofed_dev); +#endif /* defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) */ + INIT_LIST_HEAD(&new_list->list); + spin_lock(&susfs_spin_lock); + list_add_tail(&new_list->list, &LH_SUS_KSTAT_SPOOFER); + spin_unlock(&susfs_spin_lock); + SUSFS_LOGI("target_ino: '%lu', target_pathname: '%s', spoofed_pathname: '%s', spoofed_ino: '%lu', spoofed_dev: '%lu', spoofed_nlink: '%u', spoofed_atime_tv_sec: '%ld', spoofed_mtime_tv_sec: '%ld', spoofed_ctime_tv_sec: '%ld', spoofed_atime_tv_nsec: '%ld', spoofed_mtime_tv_nsec: '%ld', spoofed_ctime_tv_nsec: '%ld', is successfully added to LH_SUS_KSTAT_SPOOFER\n", + new_list->info.target_ino , new_list->info.target_pathname, new_list->info.spoofed_pathname, + new_list->info.spoofed_ino, new_list->info.spoofed_dev, new_list->info.spoofed_nlink, + new_list->info.spoofed_atime_tv_sec, new_list->info.spoofed_mtime_tv_sec, new_list->info.spoofed_ctime_tv_sec, + new_list->info.spoofed_atime_tv_nsec, new_list->info.spoofed_mtime_tv_nsec, new_list->info.spoofed_ctime_tv_nsec); + return 0; +} + +int susfs_update_sus_kstat(struct st_susfs_sus_kstat* __user user_info) { + struct st_susfs_sus_kstat_list *cursor, *temp; + struct st_susfs_sus_kstat info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_kstat))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_SUS_KSTAT_SPOOFER, list) { + if (unlikely(!strcmp(info.target_pathname, cursor->info.target_pathname))) { + SUSFS_LOGI("updating target_ino from '%lu' to '%lu' for pathname: '%s' in LH_SUS_KSTAT_SPOOFER\n", cursor->info.target_ino, info.target_ino, info.target_pathname); + cursor->info.target_ino = info.target_ino; + return 0; + } + } + + SUSFS_LOGE("target_pathname: '%s' is not found in LH_SUS_KSTAT_SPOOFER\n", info.target_pathname); + return 1; +} + +int susfs_add_sus_maps(struct st_susfs_sus_maps* __user user_info) { + struct st_susfs_sus_maps_list *cursor, *temp; + struct st_susfs_sus_maps_list *new_list = NULL; + struct st_susfs_sus_maps info; + int list_count = 0; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_maps))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + +#if defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) +#ifdef CONFIG_MIPS + info.target_dev = new_decode_dev(info.target_dev); +#else + info.target_dev = huge_decode_dev(info.target_dev); +#endif /* CONFIG_MIPS */ +#else + info.target_dev = old_decode_dev(info.target_dev); +#endif /* defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) */ + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MAPS_SPOOFER, list) { + if (cursor->info.is_statically == info.is_statically && !info.is_statically) { + if (cursor->info.target_ino == info.target_ino) { + SUSFS_LOGE("is_statically: '%d', target_ino: '%lu', is already created in LH_SUS_MAPS_SPOOFER\n", + info.is_statically, info.target_ino); + return 1; + } + } else if (cursor->info.is_statically == info.is_statically && info.is_statically) { + if (cursor->info.compare_mode == info.compare_mode && info.compare_mode == 1) { + if (cursor->info.target_ino == info.target_ino) { + SUSFS_LOGE("is_statically: '%d', compare_mode: '%d', target_ino: '%lu', is already created in LH_SUS_MAPS_SPOOFER\n", + info.is_statically, info.compare_mode, info.target_ino); + return 1; + } + } else if (cursor->info.compare_mode == info.compare_mode && info.compare_mode == 2) { + if (cursor->info.target_ino == info.target_ino && + cursor->info.is_isolated_entry == info.is_isolated_entry && + cursor->info.target_addr_size == info.target_addr_size && + cursor->info.target_pgoff == info.target_pgoff && + cursor->info.target_prot == info.target_prot) { + SUSFS_LOGE("is_statically: '%d', compare_mode: '%d', target_ino: '%lu', is_isolated_entry: '%d', target_pgoff: '0x%x', target_prot: '0x%x', is already created in LH_SUS_MAPS_SPOOFER\n", + info.is_statically, info.compare_mode, info.target_ino, + info.is_isolated_entry, info.target_pgoff, info.target_prot); + return 1; + } + } else if (cursor->info.compare_mode == info.compare_mode && info.compare_mode == 3) { + if (info.target_ino == 0 && + cursor->info.prev_target_ino == info.prev_target_ino && + cursor->info.next_target_ino == info.next_target_ino) { + SUSFS_LOGE("is_statically: '%d', compare_mode: '%d', target_ino: '%lu', prev_target_ino: '%lu', next_target_ino: '%lu', is already created in LH_SUS_MAPS_SPOOFER\n", + info.is_statically, info.compare_mode, info.target_ino, + info.prev_target_ino, info.next_target_ino); + return 1; + } + } else if (cursor->info.compare_mode == info.compare_mode && info.compare_mode == 4) { + if (cursor->info.is_file == info.is_file && + cursor->info.target_dev == info.target_dev && + cursor->info.target_pgoff == info.target_pgoff && + cursor->info.target_prot == info.target_prot && + cursor->info.target_addr_size == info.target_addr_size) { + SUSFS_LOGE("is_statically: '%d', compare_mode: '%d', is_file: '%d', target_dev: '0x%x', target_pgoff: '0x%x', target_prot: '0x%x', target_addr_size: '0x%x', is already created in LH_SUS_MAPS_SPOOFER\n", + info.is_statically, info.compare_mode, info.is_file, + info.target_dev, info.target_pgoff, info.target_prot, + info.target_addr_size); + return 1; + } + } + } + list_count++; + } + + if (list_count == SUSFS_MAX_SUS_MAPS) { + SUSFS_LOGE("LH_SUS_MOUNT has reached the list limit of %d\n", SUSFS_MAX_SUS_MAPS); + return 1; + } + + new_list = kmalloc(sizeof(struct st_susfs_sus_maps_list), GFP_KERNEL); + if (!new_list) { + SUSFS_LOGE("no enough memory\n"); + return 1; + } + + memcpy(&new_list->info, &info, sizeof(struct st_susfs_sus_maps)); +#if defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) +#ifdef CONFIG_MIPS + new_list->info.spoofed_dev = new_decode_dev(new_list->info.spoofed_dev); +#else + new_list->info.spoofed_dev = huge_decode_dev(new_list->info.spoofed_dev); +#endif /* CONFIG_MIPS */ +#else + new_list->info.spoofed_dev = old_decode_dev(new_list->info.spoofed_dev); +#endif /* defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) */ + INIT_LIST_HEAD(&new_list->list); + spin_lock(&susfs_spin_lock); + list_add_tail(&new_list->list, &LH_SUS_MAPS_SPOOFER); + spin_unlock(&susfs_spin_lock); + + SUSFS_LOGI("is_statically: '%d', compare_mode: '%d', is_isolated_entry: '%d', is_file: '%d', prev_target_ino: '%lu', next_target_ino: '%lu', target_ino: '%lu', target_dev: '0x%x', target_pgoff: '0x%x', target_prot: '0x%x', target_addr_size: '0x%x', spoofed_pathname: '%s', spoofed_ino: '%lu', spoofed_dev: '0x%x', spoofed_pgoff: '0x%x', spoofed_prot: '0x%x', is successfully added to LH_SUS_MAPS_SPOOFER\n", + new_list->info.is_statically, new_list->info.compare_mode, new_list->info.is_isolated_entry, + new_list->info.is_file, new_list->info.prev_target_ino, new_list->info.next_target_ino, + new_list->info.target_ino, new_list->info.target_dev, new_list->info.target_pgoff, + new_list->info.target_prot, new_list->info.target_addr_size, new_list->info.spoofed_pathname, + new_list->info.spoofed_ino, new_list->info.spoofed_dev, new_list->info.spoofed_pgoff, + new_list->info.spoofed_prot); + + return 0; +} + +int susfs_update_sus_maps(struct st_susfs_sus_maps* __user user_info) { + struct st_susfs_sus_maps_list *cursor, *temp; + struct st_susfs_sus_maps info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_maps))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MAPS_SPOOFER, list) { + if (cursor->info.is_statically == info.is_statically && !info.is_statically) { + if (unlikely(!strcmp(info.target_pathname, cursor->info.target_pathname))) { + SUSFS_LOGI("updating target_ino from '%lu' to '%lu' for pathname: '%s' in LH_SUS_MAPS_SPOOFER\n", cursor->info.target_ino, info.target_ino, info.target_pathname); + cursor->info.target_ino = info.target_ino; + return 0; + } + } + } + + SUSFS_LOGE("target_pathname: '%s' is not found in LH_SUS_MAPS_SPOOFER\n", info.target_pathname); + return 1; +} + +int susfs_add_sus_proc_fd_link(struct st_susfs_sus_proc_fd_link* __user user_info) { + struct st_susfs_sus_proc_fd_link_list *cursor, *temp; + struct st_susfs_sus_proc_fd_link_list *new_list = NULL; + struct st_susfs_sus_proc_fd_link info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_proc_fd_link))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_SUS_PROC_FD_LINK, list) { + if (unlikely(!strcmp(info.target_link_name, cursor->info.target_link_name))) { + SUSFS_LOGE("target_link_name: '%s' is already created in LH_SUS_PROC_FD_LINK\n", info.target_link_name); + return 1; + } + } + + new_list = kmalloc(sizeof(struct st_susfs_sus_proc_fd_link_list), GFP_KERNEL); + if (!new_list) { + SUSFS_LOGE("no enough memory\n"); + return 1; + } + + memcpy(&new_list->info, &info, sizeof(struct st_susfs_sus_proc_fd_link)); + + INIT_LIST_HEAD(&new_list->list); + spin_lock(&susfs_spin_lock); + list_add_tail(&new_list->list, &LH_SUS_PROC_FD_LINK); + spin_unlock(&susfs_spin_lock); + SUSFS_LOGI("target_link_name: '%s', spoofed_link_name: '%s', is successfully added to LH_SUS_PROC_FD_LINK\n", + new_list->info.target_link_name, new_list->info.spoofed_link_name); + return 0; +} + +int susfs_add_sus_memfd(struct st_susfs_sus_memfd* __user user_info) { + struct st_susfs_sus_memfd_list *cursor, *temp; + struct st_susfs_sus_memfd_list *new_list = NULL; + struct st_susfs_sus_memfd info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_sus_memfd))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MEMFD, list) { + if (unlikely(!strcmp(info.target_pathname, cursor->info.target_pathname))) { + SUSFS_LOGE("target_pathname: '%s' is already created in LH_SUS_MEMFD\n", info.target_pathname); + return 1; + } + } + + new_list = kmalloc(sizeof(struct st_susfs_sus_memfd_list), GFP_KERNEL); + if (!new_list) { + SUSFS_LOGE("no enough memory\n"); + return 1; + } + + memcpy(&new_list->info, &info, sizeof(struct st_susfs_sus_memfd)); + + INIT_LIST_HEAD(&new_list->list); + spin_lock(&susfs_spin_lock); + list_add_tail(&new_list->list, &LH_SUS_MEMFD); + spin_unlock(&susfs_spin_lock); + SUSFS_LOGI("target_pathname: '%s', is successfully added to LH_SUS_MEMFD\n", + new_list->info.target_pathname); + return 0; +} + +int susfs_add_try_umount(struct st_susfs_try_umount* __user user_info) { + struct st_susfs_try_umount_list *cursor, *temp; + struct st_susfs_try_umount_list *new_list = NULL; + struct st_susfs_try_umount info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_try_umount))) { + SUSFS_LOGE("failed copying from userspace\n"); + return 1; + } + + list_for_each_entry_safe(cursor, temp, &LH_TRY_UMOUNT_PATH, list) { + if (unlikely(!strcmp(info.target_pathname, cursor->info.target_pathname))) { + SUSFS_LOGE("target_pathname: '%s' is already created in LH_TRY_UMOUNT_PATH\n", info.target_pathname); + return 1; + } + } + + new_list = kmalloc(sizeof(struct st_susfs_try_umount_list), GFP_KERNEL); + if (!new_list) { + SUSFS_LOGE("no enough memory\n"); + return 1; + } + + memcpy(&new_list->info, &info, sizeof(struct st_susfs_try_umount)); + + INIT_LIST_HEAD(&new_list->list); + spin_lock(&susfs_spin_lock); + list_add_tail(&new_list->list, &LH_TRY_UMOUNT_PATH); + spin_unlock(&susfs_spin_lock); + SUSFS_LOGI("target_pathname: '%s', mnt_mode: %d, is successfully added to LH_TRY_UMOUNT_PATH\n", new_list->info.target_pathname, new_list->info.mnt_mode); + return 0; +} + +int susfs_set_uname(struct st_susfs_uname* __user user_info) { + struct st_susfs_uname info; + + if (copy_from_user(&info, user_info, sizeof(struct st_susfs_uname))) { + SUSFS_LOGE("failed copying from userspace.\n"); + return 1; + } + + spin_lock(&susfs_spin_lock); + strncpy(my_uname.sysname, info.sysname, __NEW_UTS_LEN); + strncpy(my_uname.nodename, info.nodename, __NEW_UTS_LEN); + strncpy(my_uname.release, info.release, __NEW_UTS_LEN); + strncpy(my_uname.version, info.version, __NEW_UTS_LEN); + strncpy(my_uname.machine, info.machine, __NEW_UTS_LEN); + SUSFS_LOGI("setting sysname: '%s', nodename: '%s', release: '%s', version: '%s', machine: '%s'\n", + my_uname.sysname, my_uname.nodename, my_uname.release, my_uname.version, my_uname.machine); + spin_unlock(&susfs_spin_lock); + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0) +int susfs_sus_path_by_path(struct path* file, int* errno_to_be_changed, int syscall_family) +#else +int susfs_sus_path_by_path(const struct path* file, int* errno_to_be_changed, int syscall_family) +#endif +{ + int res = 0; + int status = 0; + char* path = NULL; + char* ptr = NULL; + char* end = NULL; + struct st_susfs_sus_path_list *cursor, *temp; + + if (!uid_matches_suspicious_path() || file == NULL) { + return status; + } + + path = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (path == NULL) { + SUSFS_LOGE("no enough memory\n"); + return status; + } + ptr = d_path(file, path, PAGE_SIZE); + if (IS_ERR(ptr)) { + SUSFS_LOGE("d_path() failed\n"); + goto out_free_path; + } + end = mangle_path(path, ptr, " \t\n\\"); + if (!end) { + goto out_free_path; + } + res = end - path; + path[(size_t) res] = '\0'; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_PATH, list) { + if (unlikely(!strcmp(cursor->info.target_pathname, path))) { + SUSFS_LOGI("hiding target_pathname: '%s', target_ino: '%lu'\n", cursor->info.target_pathname, cursor->info.target_ino); + if (errno_to_be_changed != NULL) { + susfs_change_error_no_by_pathname(path, errno_to_be_changed, syscall_family); + } + status = 1; + goto out_free_path; + } + } + +out_free_path: + kfree(path); + return status; +} + +int susfs_sus_path_by_filename(struct filename* name, int* errno_to_be_changed, int syscall_family) { + int status = 0; + int ret = 0; + struct path path; + + if (IS_ERR(name)) { + return status; + } + + if (!uid_matches_suspicious_path() || name == NULL) { + return status; + } + + ret = kern_path(name->name, LOOKUP_FOLLOW, &path); + + if (!ret) { + status = susfs_sus_path_by_path(&path, errno_to_be_changed, syscall_family); + path_put(&path); + } + + return status; +} + +int susfs_sus_ino_for_filldir64(unsigned long ino) { + struct st_susfs_sus_path_list *cursor, *temp; + + if (!uid_matches_suspicious_path()) + return 0; + list_for_each_entry_safe(cursor, temp, &LH_SUS_PATH, list) { + if (cursor->info.target_ino == ino) { + SUSFS_LOGI("hiding target_pathname: '%s', target_ino: '%lu'\n", cursor->info.target_pathname, cursor->info.target_ino); + return 1; + } + } + return 0; +} + +int susfs_sus_mount(struct vfsmount* mnt, struct path* root) { + struct st_susfs_sus_mount_list *cursor, *temp; + char* path = NULL; + char* ptr = NULL; + char* end = NULL; + int res = 0; + int status = 0; + struct path mnt_path = { + .dentry = mnt->mnt_root, + .mnt = mnt + }; + + path = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (path == NULL) { + SUSFS_LOGE("no enough memory\n"); + return 0; + } + ptr = __d_path(&mnt_path, root, path, PAGE_SIZE); + if (IS_ERR(ptr)) { + SUSFS_LOGE("__d_path() failed\n"); + goto out_free_path; + } + end = mangle_path(path, ptr, " \t\n\\"); + if (!end) { + goto out_free_path; + } + res = end - path; + path[(size_t) res] = '\0'; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MOUNT, list) { + if (unlikely(!strcmp(path, cursor->info.target_pathname))) { + SUSFS_LOGI("hide target_pathname '%s' from mounts\n", + cursor->info.target_pathname); + status = 1; + goto out_free_path; + } + } +out_free_path: + kfree(path); + return status; +} + +/* This function records the original mnt_id and parent_mnt_id of all mounts of + * current process and save to a list of corresponding spoofed mnt_id and parent_mnt_id + * once process with uid >= 10000 opens /proc/self/mountinfo + */ +void susfs_add_mnt_id_recorder(struct mnt_namespace *ns) { + struct st_susfs_mnt_id_recorder_list *new_recorder_list = NULL; + struct st_susfs_mnt_id_recorder_list *recorder_cursor, *recorder_temp; + struct st_susfs_sus_mount_list *sus_mount_cursor, *sus_mount_temp; + struct mount *mnt_cursor, *mnt_temp; + struct path mnt_path; + char *path = NULL; + char *p_path = NULL; + char *end = NULL; + int res = 0; + int cur_pid = current->pid; + int i = 0, count = 0; + + if (!ns) + return; + + // if there exists the same pid already, increase the reference + list_for_each_entry_safe(recorder_cursor, recorder_temp, &LH_MOUNT_ID_RECORDER, list) { + if (recorder_cursor->pid == cur_pid) { + recorder_cursor->opened_count++; + SUSFS_LOGI("mountinfo opened by the same pid: '%d', recorder_cursor->opened_count: '%d'\n", + cur_pid, recorder_cursor->opened_count); + return; + } + } + + new_recorder_list = kzalloc(sizeof(struct st_susfs_mnt_id_recorder_list), GFP_KERNEL); + if (!new_recorder_list) { + SUSFS_LOGE("no enough memory\n"); + return; + } + new_recorder_list->info.count = 0; + + path = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!path) { + SUSFS_LOGE("no enough memory\n"); + goto out_free_new_recorder_list; + } + + list_for_each_entry_safe(mnt_cursor, mnt_temp, &ns->list, mnt_list) { + // Avoid overflow + if (count == SUSFS_MAX_SUS_MNTS) { + SUSFS_LOGE("LH_MOUNT_ID_RECORDER has reached the list limit of %d\n", SUSFS_MAX_SUS_MNTS); + goto out_free_path; + } + // if this is the first mount entry + if (count == 0) { + new_recorder_list->info.target_mnt_id[count] = mnt_cursor->mnt_id; + new_recorder_list->info.spoofed_mnt_id[count] = mnt_cursor->mnt_id; + new_recorder_list->info.spoofed_parent_mnt_id[count] = mnt_cursor->mnt_parent->mnt_id; + new_recorder_list->info.count = ++count; + continue; + } + + mntget(&mnt_cursor->mnt); + dget(mnt_cursor->mnt.mnt_root); + mnt_path.mnt = &mnt_cursor->mnt; + mnt_path.dentry = mnt_cursor->mnt.mnt_root; + + p_path = d_path(&mnt_path, path, PAGE_SIZE); + if (IS_ERR(p_path)) { + SUSFS_LOGE("d_path() failed\n"); + goto out_continue; + } + end = mangle_path(path, p_path, " \t\n\\"); + if (!end) { + goto out_continue; + } + res = end - path; + path[(size_t) res] = '\0'; + + // check if the mount is suspicious + list_for_each_entry_safe(sus_mount_cursor, sus_mount_temp, &LH_SUS_MOUNT, list) { + // skip adding this mount to recorder list if it is suspicious + if (unlikely(!strcmp(path, sus_mount_cursor->info.target_pathname))) { + SUSFS_LOGI("skip adding target_mnt_id: '%d', target_pathname: '%s' to LH_MOUNT_ID_RECORDER\n", + mnt_cursor->mnt_id, sus_mount_cursor->info.target_pathname); + goto out_continue; + } + } + // if the mount entry is NOT suspicioius + new_recorder_list->info.target_mnt_id[count] = mnt_cursor->mnt_id; + new_recorder_list->info.spoofed_mnt_id[count] = new_recorder_list->info.spoofed_mnt_id[0] + count; + for (i = 0; i < count; i++) { + if (mnt_cursor->mnt_parent->mnt_id == new_recorder_list->info.target_mnt_id[i]) { + new_recorder_list->info.spoofed_parent_mnt_id[count] = new_recorder_list->info.spoofed_mnt_id[i]; + break; + } + } + // if no match from above, use the original parent mnt_id + if (new_recorder_list->info.spoofed_parent_mnt_id[count] == 0) { + new_recorder_list->info.spoofed_parent_mnt_id[count] = mnt_cursor->mnt_parent->mnt_id; + } + new_recorder_list->info.count = ++count; +out_continue: + dput(mnt_cursor->mnt.mnt_root); + mntput(&mnt_cursor->mnt); + } + + new_recorder_list->pid = cur_pid; + new_recorder_list->opened_count = 1; + kfree(path); + + /* + for (i = 0; iinfo.count; i++) { + SUSFS_LOGI("target_mnt_id: %d, spoofed_mnt_id: %d, spoofed_parent_mnt_id: %d\n", + new_recorder_list->info.target_mnt_id[i], + new_recorder_list->info.spoofed_mnt_id[i], + new_recorder_list->info.spoofed_parent_mnt_id[i]); + } + */ + + INIT_LIST_HEAD(&new_recorder_list->list); + spin_lock(&susfs_mnt_id_recorder_spin_lock); + list_add_tail(&new_recorder_list->list, &LH_MOUNT_ID_RECORDER); + spin_unlock(&susfs_mnt_id_recorder_spin_lock); + SUSFS_LOGI("recording pid '%u' to LH_MOUNT_ID_RECORDER\n", new_recorder_list->pid); + return; +out_free_path: + kfree(path); +out_free_new_recorder_list: + kfree(new_recorder_list); +} + +int susfs_get_fake_mnt_id(int mnt_id, int *out_mnt_id, int *out_parent_mnt_id) { + struct st_susfs_mnt_id_recorder_list *cursor, *temp; + int cur_pid = current->pid; + int i; + + list_for_each_entry_safe(cursor, temp, &LH_MOUNT_ID_RECORDER, list) { + if (cursor->pid == cur_pid) { + for (i = 0; i < cursor->info.count; i++) { + if (cursor->info.target_mnt_id[i] == mnt_id) { + *out_mnt_id = cursor->info.spoofed_mnt_id[i]; + *out_parent_mnt_id = cursor->info.spoofed_parent_mnt_id[i]; + return 0; + } + } + return 1; + } + } + return 1; +} + +void susfs_remove_mnt_id_recorder(void) { + struct st_susfs_mnt_id_recorder_list *cursor, *temp; + int cur_pid = current->pid; + + spin_lock(&susfs_mnt_id_recorder_spin_lock); + list_for_each_entry_safe(cursor, temp, &LH_MOUNT_ID_RECORDER, list) { + if (cursor->pid == cur_pid) { + cursor->opened_count--; + if (cursor->opened_count != 0) + goto out_spin_unlock; + list_del(&cursor->list); + kfree(cursor); + SUSFS_LOGI("removing pid '%u' from LH_MOUNT_ID_RECORDER\n", cur_pid); + goto out_spin_unlock; + } + } +out_spin_unlock: + spin_unlock(&susfs_mnt_id_recorder_spin_lock); +} + +void susfs_sus_kstat(unsigned long ino, struct stat* out_stat) { + struct st_susfs_sus_kstat_list *cursor, *temp; + + if (!uid_matches_suspicious_kstat()) + return; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_KSTAT_SPOOFER, list) { + if (cursor->info.target_ino == ino) { + SUSFS_LOGI("spoofing kstat for pathname '%s' for UID %i\n", cursor->info.target_pathname, current_uid().val); + out_stat->st_ino = cursor->info.spoofed_ino; +#if defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) +#ifdef CONFIG_MIPS + out_stat->st_dev = new_encode_dev(cursor->info.spoofed_dev); +#else + out_stat->st_dev = huge_encode_dev(cursor->info.spoofed_dev); +#endif /* CONFIG_MIPS */ +#else + out_stat->st_dev = old_encode_dev(cursor->info.spoofed_dev); +#endif /* defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_COMPAT_STAT64) */ + out_stat->st_nlink = cursor->info.spoofed_nlink; + out_stat->st_atime = cursor->info.spoofed_atime_tv_sec; + out_stat->st_mtime = cursor->info.spoofed_mtime_tv_sec; + out_stat->st_ctime = cursor->info.spoofed_ctime_tv_sec; +#ifdef _STRUCT_TIMESPEC + out_stat->st_atime_nsec = cursor->info.spoofed_atime_tv_nsec; + out_stat->st_mtime_nsec = cursor->info.spoofed_mtime_tv_nsec; + out_stat->st_ctime_nsec = cursor->info.spoofed_ctime_tv_nsec; +#endif + return; + } + } +} + +/* for non statically, it only compare with target_ino, and spoof only the ino, dev to the matched entry + * for staticially, it compares depending on the mode user chooses + * compare mode: + * 1 -> target_ino is 'non-zero', all entries match with target_ino will be spoofed with user defined entry + * 2 -> target_ino is 'non-zero', all entries match with [target_ino,target_addr_size,target_prot,target_pgoff,is_isolated_entry] will be spoofed with user defined entry + * 3 -> target_ino is 'zero', which is not file, all entries match with [prev_target_ino,next_target_ino] will be spoofed with user defined entry + * 4 -> target_ino is 'zero' or 'non-zero', all entries match with [is_file,target_addr_size,target_prot,target_pgoff,target_dev] will be spoofed with user defined entry + */ +int susfs_sus_maps(unsigned long target_ino, unsigned long target_addr_size, unsigned long* orig_ino, dev_t* orig_dev, vm_flags_t* flags, unsigned long long* pgoff, struct vm_area_struct* vma, char* out_name) { + struct st_susfs_sus_maps_list *cursor, *temp; + struct inode *tmp_inode, *tmp_inode_prev, *tmp_inode_next; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MAPS_SPOOFER, list) { + // if it is NOT statically + if (!cursor->info.is_statically) { + if (target_ino != 0 && cursor->info.target_ino == target_ino) { + *orig_ino = cursor->info.spoofed_ino; + *orig_dev = cursor->info.spoofed_dev; + SUSFS_LOGI("spoofing maps -> is_statically: '%d', compare_mode: '%d', is_file: '%d', is_isolated_entry: '%d', prev_target_ino: '%lu', next_target_ino: '%lu', target_ino: '%lu', target_dev: '0x%x', target_pgoff: '0x%x', target_prot: '0x%x', target_addr_size: '0x%x', spoofed_pathname: '%s', spoofed_ino: '%lu', spoofed_dev: '0x%x', spoofed_pgoff: '0x%x', spoofed_prot: '0x%x'\n", + cursor->info.is_statically, cursor->info.compare_mode, cursor->info.is_file, + cursor->info.is_isolated_entry, cursor->info.prev_target_ino, cursor->info.next_target_ino, + cursor->info.target_ino, cursor->info.target_dev, cursor->info.target_pgoff, + cursor->info.target_prot, cursor->info.target_addr_size, cursor->info.spoofed_pathname, + cursor->info.spoofed_ino, cursor->info.spoofed_dev, cursor->info.spoofed_pgoff, + cursor->info.spoofed_prot); + return 1; + } + // if it is statically, then compare with compare_mode + } else if (cursor->info.compare_mode > 0) { + switch(cursor->info.compare_mode) { + case 1: + if (target_ino != 0 && cursor->info.target_ino == target_ino) { + goto do_spoof; + } + break; + case 2: + if (target_ino != 0 && cursor->info.target_ino == target_ino && + ((cursor->info.target_prot & VM_READ) == (*flags & VM_READ)) && + ((cursor->info.target_prot & VM_WRITE) == (*flags & VM_WRITE)) && + ((cursor->info.target_prot & VM_EXEC) == (*flags & VM_EXEC)) && + ((cursor->info.target_prot & VM_MAYSHARE) == (*flags & VM_MAYSHARE)) && + cursor->info.target_addr_size == target_addr_size && + cursor->info.target_pgoff == *pgoff) { + // if is NOT isolated_entry, check for vma->vm_next and vma->vm_prev to see if they have the same inode + if (!cursor->info.is_isolated_entry) { + if (vma && vma->vm_next) { + if (vma->vm_next->vm_file) { + tmp_inode = file_inode(vma->vm_next->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) + goto do_spoof; + } + } + if (vma && vma->vm_prev) { + if (vma->vm_prev->vm_file) { + tmp_inode = file_inode(vma->vm_prev->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) + goto do_spoof; + } + } + continue; + // if it is isolated_entry + } else { + if (vma && vma->vm_next) { + if (vma->vm_next->vm_file) { + tmp_inode = file_inode(vma->vm_next->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) { + continue; + } + } + } + if (vma && vma->vm_prev) { + if (vma->vm_prev->vm_file) { + tmp_inode = file_inode(vma->vm_prev->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) { + continue; + } + } + } + // both prev and next don't have the same indoe as current entry, we can spoof now + goto do_spoof; + } + } + break; + case 3: + // if current vma is a file, it is not our target + if (vma->vm_file) continue; + // compare next target ino only + if (cursor->info.prev_target_ino == 0 && cursor->info.next_target_ino > 0) { + if (vma->vm_next && vma->vm_next->vm_file) { + tmp_inode_next = file_inode(vma->vm_next->vm_file); + if (tmp_inode_next->i_ino == cursor->info.next_target_ino) { + goto do_spoof; + } + } + // compare prev target ino only + } else if (cursor->info.prev_target_ino > 0 && cursor->info.next_target_ino == 0) { + if (vma->vm_prev && vma->vm_prev->vm_file) { + tmp_inode_prev = file_inode(vma->vm_prev->vm_file); + if (tmp_inode_prev->i_ino == cursor->info.prev_target_ino) { + goto do_spoof; + } + } + // compare both prev ino and next ino + } else if (cursor->info.prev_target_ino > 0 && cursor->info.next_target_ino > 0) { + if (vma->vm_prev && vma->vm_prev->vm_file && + vma->vm_next && vma->vm_next->vm_file) { + tmp_inode_prev = file_inode(vma->vm_prev->vm_file); + tmp_inode_next = file_inode(vma->vm_next->vm_file); + if (tmp_inode_prev->i_ino == cursor->info.prev_target_ino && + tmp_inode_next->i_ino == cursor->info.next_target_ino) { + goto do_spoof; + } + } + } + break; + case 4: + if ((cursor->info.is_file && vma->vm_file)||(!cursor->info.is_file && !vma->vm_file)) { + if (cursor->info.target_dev == *orig_dev && + cursor->info.target_pgoff == *pgoff && + ((cursor->info.target_prot & VM_READ) == (*flags & VM_READ) && + (cursor->info.target_prot & VM_WRITE) == (*flags & VM_WRITE) && + (cursor->info.target_prot & VM_EXEC) == (*flags & VM_EXEC) && + (cursor->info.target_prot & VM_MAYSHARE) == (*flags & VM_MAYSHARE)) && + cursor->info.target_addr_size == target_addr_size) { + goto do_spoof; + } + } + break; + default: + break; + } + } + continue; +do_spoof: + if (cursor->info.need_to_spoof_pathname) { + strncpy(out_name, cursor->info.spoofed_pathname, SUSFS_MAX_LEN_PATHNAME-1); + } + if (cursor->info.need_to_spoof_ino) { + *orig_ino = cursor->info.spoofed_ino; + } + if (cursor->info.need_to_spoof_dev) { + *orig_dev = cursor->info.spoofed_dev; + } + if (cursor->info.need_to_spoof_prot) { + if (cursor->info.spoofed_prot & VM_READ) *flags |= VM_READ; + else *flags = ((*flags | VM_READ) ^ VM_READ); + if (cursor->info.spoofed_prot & VM_WRITE) *flags |= VM_WRITE; + else *flags = ((*flags | VM_WRITE) ^ VM_WRITE); + if (cursor->info.spoofed_prot & VM_EXEC) *flags |= VM_EXEC; + else *flags = ((*flags | VM_EXEC) ^ VM_EXEC); + if (cursor->info.spoofed_prot & VM_MAYSHARE) *flags |= VM_MAYSHARE; + else *flags = ((*flags | VM_MAYSHARE) ^ VM_MAYSHARE); + } + if (cursor->info.need_to_spoof_pgoff) { + *pgoff = cursor->info.spoofed_pgoff; + } + SUSFS_LOGI("spoofing maps -> is_statically: '%d', compare_mode: '%d', is_file: '%d', is_isolated_entry: '%d', prev_target_ino: '%lu', next_target_ino: '%lu', target_ino: '%lu', target_dev: '0x%x', target_pgoff: '0x%x', target_prot: '0x%x', target_addr_size: '0x%x', spoofed_pathname: '%s', spoofed_ino: '%lu', spoofed_dev: '0x%x', spoofed_pgoff: '0x%x', spoofed_prot: '0x%x'\n", + cursor->info.is_statically, cursor->info.compare_mode, cursor->info.is_file, + cursor->info.is_isolated_entry, cursor->info.prev_target_ino, cursor->info.next_target_ino, + cursor->info.target_ino, cursor->info.target_dev, cursor->info.target_pgoff, + cursor->info.target_prot, cursor->info.target_addr_size, cursor->info.spoofed_pathname, + cursor->info.spoofed_ino, cursor->info.spoofed_dev, cursor->info.spoofed_pgoff, + cursor->info.spoofed_prot); + return 2; + } + return 0; +} + +/* @ This function only does the following: + * 1. Spoof the symlink name of a target_ino listed in /proc/self/map_files + * + * @Note + * - It has limitation as there is no way to check which + * vma address it belongs by passing dentry* only, so it just + * checks for matched dentry* and its target_ino in sus_maps list, + * then spoof the symlink name of the target_ino defined by user. + * - Also user cannot see the effects in map_files from other root session, + * because it uses current->mm to compare the dentry, the only way to test + * is to check within its own pid. + * - So the BEST practise here is: + * Do NOT spoof the map entries which share the same name to different name + * seperately unless the other spoofed name is empty of which spoofed_ino is 0, + * otherwise there will be inconsistent entries between maps and map_files. + */ +void susfs_sus_map_files_readlink(unsigned long target_ino, char* pathname) { + struct st_susfs_sus_maps_list *cursor, *temp; + + if (!pathname) + return; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MAPS_SPOOFER, list) { + // We are only interested in statically and target_ino > 0 + if (cursor->info.is_statically && cursor->info.compare_mode > 0 && + target_ino > 0 && cursor->info.target_ino == target_ino) + { + if (cursor->info.need_to_spoof_pathname) { + SUSFS_LOGI("spoofing symlink name of ino '%lu' to '%s' in map_files\n", + target_ino, cursor->info.spoofed_pathname); + // Don't need to check buffer size as 'pathname' is allocated with 'PAGE_SIZE' + // which is way bigger than SUSFS_MAX_LEN_PATHNAME + strcpy(pathname, cursor->info.spoofed_pathname); + return; + } + } + } + return; +} + +/* @ This function mainly does the following: + * 1. Remove the user write access for spoofed symlink name in /proc/self/map_files + * 2. Prevent the dentry from being seen in /proc/self/map_files + * + * @Note + * - anon files are supposed to be not shown in /proc/self/map_files and + * spoofing from memfd name to non-memfd name should not have write + * permission on that target dentry + */ +int susfs_sus_map_files_instantiate(struct vm_area_struct* vma) { + struct inode *inode = file_inode(vma->vm_file); + unsigned long target_ino = inode->i_ino; + dev_t target_dev = inode->i_sb->s_dev; + unsigned long long target_pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT; + unsigned long target_addr_size = vma->vm_end - vma->vm_start; + vm_flags_t target_flags = vma->vm_flags; + struct st_susfs_sus_maps_list *cursor, *temp; + struct inode *tmp_inode, *tmp_inode_prev, *tmp_inode_next; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MAPS_SPOOFER, list) { + // We are only interested in statically + if (!cursor->info.is_statically) { + continue; + // if it is statically, then compare with compare_mode + } else if (cursor->info.compare_mode > 0) { + switch(cursor->info.compare_mode) { + case 1: + if (target_ino != 0 && cursor->info.target_ino == target_ino) { + goto do_spoof; + } + break; + case 2: + if (target_ino != 0 && cursor->info.target_ino == target_ino && + ((cursor->info.target_prot & VM_READ) == (target_flags & VM_READ)) && + ((cursor->info.target_prot & VM_WRITE) == (target_flags & VM_WRITE)) && + ((cursor->info.target_prot & VM_EXEC) == (target_flags & VM_EXEC)) && + ((cursor->info.target_prot & VM_MAYSHARE) == (target_flags & VM_MAYSHARE)) && + cursor->info.target_addr_size == target_addr_size && + cursor->info.target_pgoff == target_pgoff) { + // if is NOT isolated_entry, check for vma->vm_next and vma->vm_prev to see if they have the same inode + if (!cursor->info.is_isolated_entry) { + if (vma && vma->vm_next) { + if (vma->vm_next->vm_file) { + tmp_inode = file_inode(vma->vm_next->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) + goto do_spoof; + } + } + if (vma && vma->vm_prev) { + if (vma->vm_prev->vm_file) { + tmp_inode = file_inode(vma->vm_prev->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) + goto do_spoof; + } + } + continue; + // if it is isolated_entry + } else { + if (vma && vma->vm_next) { + if (vma->vm_next->vm_file) { + tmp_inode = file_inode(vma->vm_next->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) { + continue; + } + } + } + if (vma && vma->vm_prev) { + if (vma->vm_prev->vm_file) { + tmp_inode = file_inode(vma->vm_prev->vm_file); + if (tmp_inode->i_ino == cursor->info.target_ino) { + continue; + } + } + } + // both prev and next don't have the same indoe as current entry, we can spoof now + goto do_spoof; + } + } + break; + case 3: + // if current vma is a file, it is not our target + if (vma->vm_file) continue; + // compare next target ino only + if (cursor->info.prev_target_ino == 0 && cursor->info.next_target_ino > 0) { + if (vma->vm_next && vma->vm_next->vm_file) { + tmp_inode_next = file_inode(vma->vm_next->vm_file); + if (tmp_inode_next->i_ino == cursor->info.next_target_ino) { + goto do_spoof; + } + } + // compare prev target ino only + } else if (cursor->info.prev_target_ino > 0 && cursor->info.next_target_ino == 0) { + if (vma->vm_prev && vma->vm_prev->vm_file) { + tmp_inode_prev = file_inode(vma->vm_prev->vm_file); + if (tmp_inode_prev->i_ino == cursor->info.prev_target_ino) { + goto do_spoof; + } + } + // compare both prev ino and next ino + } else if (cursor->info.prev_target_ino > 0 && cursor->info.next_target_ino > 0) { + if (vma->vm_prev && vma->vm_prev->vm_file && + vma->vm_next && vma->vm_next->vm_file) { + tmp_inode_prev = file_inode(vma->vm_prev->vm_file); + tmp_inode_next = file_inode(vma->vm_next->vm_file); + if (tmp_inode_prev->i_ino == cursor->info.prev_target_ino && + tmp_inode_next->i_ino == cursor->info.next_target_ino) { + goto do_spoof; + } + } + } + break; + case 4: + if ((cursor->info.is_file && vma->vm_file)||(!cursor->info.is_file && !vma->vm_file)) { + if (cursor->info.target_dev == target_dev && + cursor->info.target_pgoff == target_pgoff && + ((cursor->info.target_prot & VM_READ) == (target_flags & VM_READ) && + (cursor->info.target_prot & VM_WRITE) == (target_flags & VM_WRITE) && + (cursor->info.target_prot & VM_EXEC) == (target_flags & VM_EXEC) && + (cursor->info.target_prot & VM_MAYSHARE) == (target_flags & VM_MAYSHARE)) && + cursor->info.target_addr_size == target_addr_size) { + goto do_spoof; + } + } + break; + default: + break; + } + } + continue; +do_spoof: + if (!(cursor->info.spoofed_ino == 0 || + (MAJOR(cursor->info.spoofed_dev) == 0 && + (MINOR(cursor->info.spoofed_dev) == 0 || MINOR(cursor->info.spoofed_dev) == 1)))) + { + SUSFS_LOGI("remove user write permission of spoofed symlink '%s' in map_files\n", cursor->info.spoofed_pathname); + return 1; + } else { + SUSFS_LOGI("drop dentry of target_ino '%lu' with spoofed_ino '%lu' in map_files\n", + cursor->info.target_ino, cursor->info.spoofed_ino); + return 2; + } + return 0; + } + return 0; +} + +int susfs_is_sus_maps_list_empty(void) { + return list_empty(&LH_SUS_MAPS_SPOOFER); +} + +int susfs_sus_proc_fd_link(char *pathname, int len) { + struct st_susfs_sus_proc_fd_link_list *cursor, *temp; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_PROC_FD_LINK, list) { + if (unlikely(!strcmp(pathname, cursor->info.target_link_name))) { + SUSFS_LOGI("[uid:%u] spoofing fd link: '%s' -> '%s'\n", current_uid().val, pathname, cursor->info.spoofed_link_name); + memset(pathname, 0, len); + strcpy(pathname, cursor->info.spoofed_link_name); + return 1; + } + } + return 0; +} + +int susfs_is_sus_proc_fd_link_list_empty(void) { + return list_empty(&LH_SUS_PROC_FD_LINK); +} + +int susfs_sus_memfd(char *memfd_name) { + struct st_susfs_sus_memfd_list *cursor, *temp; + + list_for_each_entry_safe(cursor, temp, &LH_SUS_MEMFD, list) { + if (unlikely(!strcmp(memfd_name, cursor->info.target_pathname))) { + SUSFS_LOGI("prevent memfd_name: '%s' from being created\n", memfd_name); + return 1; + } + } + return 0; +} + +static void umount_mnt(struct path *path, int flags) { + int err = path_umount(path, flags); + if (err) { + SUSFS_LOGI("umount %s failed: %d\n", path->dentry->d_iname, err); + } +} + +static bool should_umount(struct path *path) +{ + if (!path) { + return false; + } + + if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) { + SUSFS_LOGI("ignore global mnt namespace process: %d\n", + current_uid().val); + return false; + } + + if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) { + const char *fstype = path->mnt->mnt_sb->s_type->name; + return strcmp(fstype, "overlay") == 0; + } + return false; +} + +static void try_umount(const char *mnt, bool check_mnt, int flags) { + struct path path; + int err = kern_path(mnt, 0, &path); + + if (err) { + return; + } + + if (path.dentry != path.mnt->mnt_root) { + // it is not root mountpoint, maybe umounted by others already. + return; + } + + // we are only interest in some specific mounts + if (check_mnt && !should_umount(&path)) { + return; + } + + umount_mnt(&path, flags); +} + +void susfs_try_umount(uid_t target_uid) { + struct st_susfs_try_umount_list *cursor, *temp; + + list_for_each_entry_safe(cursor, temp, &LH_TRY_UMOUNT_PATH, list) { + SUSFS_LOGI("umounting '%s' for uid: %d\n", cursor->info.target_pathname, target_uid); + if (cursor->info.mnt_mode == 0) { + try_umount(cursor->info.target_pathname, false, 0); + } else if (cursor->info.mnt_mode == 1) { + try_umount(cursor->info.target_pathname, false, MNT_DETACH); + } + } +} + +void susfs_spoof_uname(struct new_utsname* tmp) { + if (strcmp(my_uname.sysname, "default")) { + memset(tmp->sysname, 0, __NEW_UTS_LEN); + strncpy(tmp->sysname, my_uname.sysname, __NEW_UTS_LEN); + } + if (strcmp(my_uname.nodename, "default")) { + memset(tmp->nodename, 0, __NEW_UTS_LEN); + strncpy(tmp->nodename, my_uname.nodename, __NEW_UTS_LEN); + } + if (likely(strcmp(my_uname.release, "default"))) { + memset(tmp->release, 0, __NEW_UTS_LEN); + strncpy(tmp->release, my_uname.release, __NEW_UTS_LEN); + } + if (likely(strcmp(my_uname.version, "default"))) { + memset(tmp->version, 0, __NEW_UTS_LEN); + strncpy(tmp->version, my_uname.version, __NEW_UTS_LEN); + } + if (strcmp(my_uname.machine, "default")) { + memset(tmp->machine, 0, __NEW_UTS_LEN); + strncpy(tmp->machine, my_uname.machine, __NEW_UTS_LEN); + } +} + +void susfs_set_log(bool enabled) { + spin_lock(&susfs_spin_lock); + is_log_enable = enabled; + spin_unlock(&susfs_spin_lock); + if (is_log_enable) { + pr_info("susfs: enable logging to kernel"); + } else { + pr_info("susfs: disable logging to kernel"); + } +} + +/* For files/directories in /sdcard/ but not in /sdcard/Android/data/, please delete it + * by yourself + */ +void susfs_change_error_no_by_pathname(char* const pathname, int* const errno_to_be_changed, int const syscall_family) { + if (!strncmp(pathname, "/system/", 8)|| + !strncmp(pathname, "/vendor/", 8)) { + switch(syscall_family) { + case SYSCALL_FAMILY_ALL_ENOENT: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_LINKAT_OLDNAME: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_RENAMEAT2_OLDNAME: + *errno_to_be_changed = -EXDEV; + return; + //case SYSCALL_FAMILY_RENAMEAT2_NEWNAME: + // if (!strncmp(pathname, "/system/", 8)) { + // *errno_to_be_changed = -EROFS; + // } else { + // *errno_to_be_changed = -EXDEV; + // } + // return; + default: + *errno_to_be_changed = -EROFS; + return; + } + } else if (!strncmp(pathname, "/storage/emulated/0/Android/data/", 33)) { + switch(syscall_family) { + case SYSCALL_FAMILY_ALL_ENOENT: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_MKNOD: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_MKDIRAT: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_RMDIR: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_UNLINKAT: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_SYMLINKAT_NEWNAME: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_LINKAT_OLDNAME: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_LINKAT_NEWNAME: + *errno_to_be_changed = -EXDEV; + return; + case SYSCALL_FAMILY_RENAMEAT2_OLDNAME: + *errno_to_be_changed = -EXDEV; + return; + case SYSCALL_FAMILY_RENAMEAT2_NEWNAME: + *errno_to_be_changed = -EXDEV; + return; + default: + *errno_to_be_changed = -ENOENT; + return; + } + } else if (!strncmp(pathname, "/dev/", 5)) { + switch(syscall_family) { + case SYSCALL_FAMILY_ALL_ENOENT: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_MKNOD: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_MKDIRAT: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_RMDIR: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_UNLINKAT: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_SYMLINKAT_NEWNAME: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_LINKAT_OLDNAME: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_LINKAT_NEWNAME: + *errno_to_be_changed = -EXDEV; + return; + case SYSCALL_FAMILY_RENAMEAT2_OLDNAME: + *errno_to_be_changed = -EXDEV; + return; + case SYSCALL_FAMILY_RENAMEAT2_NEWNAME: + *errno_to_be_changed = -EXDEV; + return; + default: + *errno_to_be_changed = -ENOENT; + return; + } + } else if (!strncmp(pathname, "/data/", 6)) { + switch(syscall_family) { + case SYSCALL_FAMILY_ALL_ENOENT: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_MKNOD: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_MKDIRAT: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_RMDIR: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_UNLINKAT: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_SYMLINKAT_NEWNAME: + *errno_to_be_changed = -EACCES; + return; + case SYSCALL_FAMILY_LINKAT_OLDNAME: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_LINKAT_NEWNAME: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_RENAMEAT2_OLDNAME: + *errno_to_be_changed = -ENOENT; + return; + case SYSCALL_FAMILY_RENAMEAT2_NEWNAME: + *errno_to_be_changed = -EXDEV; + return; + default: + *errno_to_be_changed = -ENOENT; + return; + } + } +} + +/* +static int susfs_get_cur_fd_counts() { + struct fdtable *files_table; + int fd_count = 0; + + files_table = files_fdtable(current->files); + for (i = 0; i < files_table->max_fds; i++) { + if (files_table->fd[i] != NULL) { + fd_count++; + } + } + return fd_count; +} +*/ + +static void susfs_my_uname_init(void) { + memset(&my_uname, 0, sizeof(struct st_susfs_uname)); + strncpy(my_uname.sysname, "default", __NEW_UTS_LEN); + strncpy(my_uname.nodename, "default", __NEW_UTS_LEN); + strncpy(my_uname.release, "default", __NEW_UTS_LEN); + strncpy(my_uname.version, "default", __NEW_UTS_LEN); + strncpy(my_uname.machine, "default", __NEW_UTS_LEN); +} + +void __init susfs_init(void) { + spin_lock_init(&susfs_spin_lock); + spin_lock_init(&susfs_mnt_id_recorder_spin_lock); + susfs_my_uname_init(); +} + +/* No module exit is needed becuase it should never be a loadable kernel module */ +//void __init susfs_exit(void) diff --git a/include/linux/susfs.h b/include/linux/susfs.h new file mode 100644 index 000000000000..c6cb4a032cac --- /dev/null +++ b/include/linux/susfs.h @@ -0,0 +1,216 @@ +#ifndef KSU_SUSFS_H +#define KSU_SUSFS_H + +#include +#include +#include +#include + +/* shared with userspace ksu_susfs tool */ +#define CMD_SUSFS_ADD_SUS_PATH 0x55555 +#define CMD_SUSFS_ADD_SUS_MOUNT 0x55556 +#define CMD_SUSFS_ADD_SUS_KSTAT 0x55558 +#define CMD_SUSFS_UPDATE_SUS_KSTAT 0x55559 +#define CMD_SUSFS_ADD_TRY_UMOUNT 0x5555a +#define CMD_SUSFS_SET_UNAME 0x5555b +#define CMD_SUSFS_ADD_SUS_KSTAT_STATICALLY 0x5555c +#define CMD_SUSFS_ENABLE_LOG 0x5555d +#define CMD_SUSFS_ADD_SUS_MAPS_STATICALLY 0x5555e +#define CMD_SUSFS_ADD_SUS_PROC_FD_LINK 0x5555f +#define CMD_SUSFS_ADD_SUS_MAPS 0x55560 +#define CMD_SUSFS_UPDATE_SUS_MAPS 0x55561 +#define CMD_SUSFS_ADD_SUS_MEMFD 0x55562 + +#define SUSFS_MAX_LEN_PATHNAME 256 // 256 should address many paths already unless you are doing some strange experimental stuff, then set your own desired length +#define SUSFS_MAX_LEN_MFD_NAME 248 +#define SUSFS_MAX_SUS_MNTS 300 // I think 300 is now enough? This includes the mount entries for each process and sus mounts added by user +#define SUSFS_MAX_SUS_MAPS 200 // I think 200 is now enough? Tell me why if you have over 200 entries + +#define SUSFS_MAP_FILES_ACTION_REMOVE_WRITE_PERM 1 +#define SUSFS_MAP_FILES_ACTION_HIDE_DENTRY 2 + +/* non shared to userspace ksu_susfs tool */ +#define SYSCALL_FAMILY_ALL_ENOENT 0 +#define SYSCALL_FAMILY_OPENAT 1 +#define SYSCALL_FAMILY_MKNOD 2 +#define SYSCALL_FAMILY_MKDIRAT 3 +#define SYSCALL_FAMILY_RMDIR 4 +#define SYSCALL_FAMILY_UNLINKAT 5 +#define SYSCALL_FAMILY_SYMLINKAT_NEWNAME 6 +#define SYSCALL_FAMILY_LINKAT_OLDNAME 7 +#define SYSCALL_FAMILY_LINKAT_NEWNAME 8 +#define SYSCALL_FAMILY_RENAMEAT2_OLDNAME 9 +#define SYSCALL_FAMILY_RENAMEAT2_NEWNAME 10 +#define SYSCALL_FAMILY_TRUNCATE 11 +#define SYSCALL_FAMILY_FACCESSAT 12 +#define SYSCALL_FAMILY_CHDIR 13 + +#define getname_safe(name) (name == NULL ? ERR_PTR(-EINVAL) : getname(name)) +#define putname_safe(name) (IS_ERR(name) ? NULL : putname(name)) + +#define uid_matches_suspicious_path() (current_uid().val >= 2000) +#define uid_matches_suspicious_kstat() (current_uid().val >= 2000) +#define uid_matches_proc_need_to_reorder_mnt_id() (current_uid().val >= 10000) + +struct st_susfs_sus_path { + char target_pathname[SUSFS_MAX_LEN_PATHNAME]; + unsigned long target_ino; +}; + +struct st_susfs_sus_mount { + char target_pathname[SUSFS_MAX_LEN_PATHNAME]; +}; + +struct st_susfs_sus_kstat { + unsigned long target_ino; // the ino after bind mounted or overlayed + char target_pathname[SUSFS_MAX_LEN_PATHNAME]; + char spoofed_pathname[SUSFS_MAX_LEN_PATHNAME]; + unsigned long spoofed_ino; + unsigned long spoofed_dev; + unsigned int spoofed_nlink; + long spoofed_atime_tv_sec; + long spoofed_mtime_tv_sec; + long spoofed_ctime_tv_sec; + long spoofed_atime_tv_nsec; + long spoofed_mtime_tv_nsec; + long spoofed_ctime_tv_nsec; +}; + +struct st_susfs_sus_maps { + bool is_statically; + int compare_mode; + bool is_isolated_entry; + bool is_file; + unsigned long prev_target_ino; + unsigned long next_target_ino; + char target_pathname[SUSFS_MAX_LEN_PATHNAME]; + unsigned long target_ino; + unsigned long target_dev; + unsigned long long target_pgoff; + unsigned long target_prot; + unsigned long target_addr_size; + char spoofed_pathname[SUSFS_MAX_LEN_PATHNAME]; + unsigned long spoofed_ino; + unsigned long spoofed_dev; + unsigned long long spoofed_pgoff; + unsigned long spoofed_prot; + bool need_to_spoof_pathname; + bool need_to_spoof_ino; + bool need_to_spoof_dev; + bool need_to_spoof_pgoff; + bool need_to_spoof_prot; +}; + +struct st_susfs_try_umount { + char target_pathname[SUSFS_MAX_LEN_PATHNAME]; + int mnt_mode; +}; + +struct st_susfs_sus_proc_fd_link { + char target_link_name[SUSFS_MAX_LEN_PATHNAME]; + char spoofed_link_name[SUSFS_MAX_LEN_PATHNAME]; +}; + +struct st_susfs_sus_memfd { + char target_pathname[SUSFS_MAX_LEN_MFD_NAME]; +}; + +struct st_susfs_mnt_id_recorder { + int target_mnt_id[SUSFS_MAX_SUS_MNTS]; + int spoofed_mnt_id[SUSFS_MAX_SUS_MNTS]; + int spoofed_parent_mnt_id[SUSFS_MAX_SUS_MNTS]; + int count; +}; + +struct st_susfs_sus_path_list { + struct list_head list; + struct st_susfs_sus_path info; +}; + +struct st_susfs_sus_mount_list { + struct list_head list; + struct st_susfs_sus_mount info; +}; + +struct st_susfs_sus_kstat_list { + struct list_head list; + struct st_susfs_sus_kstat info; +}; + +struct st_susfs_sus_maps_list { + struct list_head list; + struct st_susfs_sus_maps info; +}; + +struct st_susfs_try_umount_list { + struct list_head list; + struct st_susfs_try_umount info; +}; + +struct st_susfs_sus_proc_fd_link_list { + struct list_head list; + struct st_susfs_sus_proc_fd_link info; +}; + +struct st_susfs_sus_memfd_list { + struct list_head list; + struct st_susfs_sus_memfd info; +}; + +struct st_susfs_mnt_id_recorder_list { + struct list_head list; + int pid; + int opened_count; + struct st_susfs_mnt_id_recorder info; +}; + +struct st_susfs_uname { + char sysname[__NEW_UTS_LEN+1]; + char nodename[__NEW_UTS_LEN+1]; + char release[__NEW_UTS_LEN+1]; + char version[__NEW_UTS_LEN+1]; + char machine[__NEW_UTS_LEN+1]; +}; + +int susfs_add_sus_path(struct st_susfs_sus_path* __user user_info); +int susfs_add_sus_mount(struct st_susfs_sus_mount* __user user_info); +int susfs_add_sus_kstat(struct st_susfs_sus_kstat* __user user_info); +int susfs_update_sus_kstat(struct st_susfs_sus_kstat* __user user_info); +int susfs_add_sus_maps(struct st_susfs_sus_maps* __user user_info); +int susfs_update_sus_maps(struct st_susfs_sus_maps* __user user_info); +int susfs_add_sus_proc_fd_link(struct st_susfs_sus_proc_fd_link* __user user_info); +int susfs_add_sus_memfd(struct st_susfs_sus_memfd* __user user_info); +int susfs_add_try_umount(struct st_susfs_try_umount* __user user_info); +int susfs_set_uname(struct st_susfs_uname* __user user_info); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0) +int susfs_sus_path_by_path(struct path* file, int* errno_to_be_changed, int syscall_family); +#else +int susfs_sus_path_by_path(const struct path* file, int* errno_to_be_changed, int syscall_family); +#endif +int susfs_sus_path_by_filename(struct filename* name, int* errno_to_be_changed, int syscall_family); +int susfs_sus_mount(struct vfsmount* mnt, struct path* root); +int susfs_sus_ino_for_filldir64(unsigned long ino); +void susfs_sus_kstat(unsigned long ino, struct stat* out_stat); +int susfs_sus_maps(unsigned long target_ino, unsigned long target_addr_size, + unsigned long* orig_ino, dev_t* orig_dev, vm_flags_t* flags, + unsigned long long* pgoff, struct vm_area_struct* vma, char* out_name); +void susfs_sus_map_files_readlink(unsigned long target_ino, char* pathname); +int susfs_sus_map_files_instantiate(struct vm_area_struct* vma); +int susfs_is_sus_maps_list_empty(void); +int susfs_sus_proc_fd_link(char *pathname, int len); +int susfs_is_sus_proc_fd_link_list_empty(void); +int susfs_sus_memfd(char *memfd_name); +void susfs_try_umount(uid_t target_uid); +void susfs_spoof_uname(struct new_utsname* tmp); +void susfs_add_mnt_id_recorder(struct mnt_namespace *ns); +int susfs_get_fake_mnt_id(int mnt_id, int *out_mnt_id, int *out_parent_mnt_id); +void susfs_remove_mnt_id_recorder(void); + +void susfs_set_log(bool enabled); + +void susfs_change_error_no_by_pathname(char* pathname, int* errno_to_be_changed, int syscall_family); + +void __init susfs_init(void); + +#endif diff --git a/kernel/sys.c b/kernel/sys.c index 3ed05f046c92..b8ec1a8b87fd 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -75,6 +75,14 @@ #include "uid16.h" +#ifdef CONFIG_KSU_SUSFS +#include + +extern long ksu_handle_susfs_prctl(int option, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5); +#endif + #include #ifndef SET_UNALIGN_CTL @@ -1247,6 +1255,9 @@ SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) down_read(&uts_sem); memcpy(&tmp, utsname(), sizeof(tmp)); up_read(&uts_sem); +#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME + susfs_spoof_uname(&tmp); +#endif if (copy_to_user(name, &tmp, sizeof(tmp))) return -EFAULT; @@ -2430,6 +2441,12 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, unsigned char comm[sizeof(me->comm)]; long error; +#ifdef CONFIG_KSU_SUSFS + error = ksu_handle_susfs_prctl(option, arg2, arg3, arg4, arg5); + if (error != -ENOSYS) + return error; +#endif + error = security_task_prctl(option, arg2, arg3, arg4, arg5); if (error != -ENOSYS) return error; diff --git a/mm/memfd.c b/mm/memfd.c index fae4142f7d25..093d9b041d46 100644 --- a/mm/memfd.c +++ b/mm/memfd.c @@ -20,6 +20,10 @@ #include #include +#ifdef CONFIG_KSU_SUSFS +#include +#endif + /* * We need a tag: a new tag would expand every xa_node by 8 bytes, * so reuse a tag which we firmly believe is never set or cleared on tmpfs @@ -306,6 +310,13 @@ SYSCALL_DEFINE2(memfd_create, goto err_name; } +#ifdef CONFIG_KSU_SUSFS_SUS_MEMFD + if (susfs_sus_memfd(name)) { + error = -EFAULT; + goto err_name; + } +#endif + fd = get_unused_fd_flags((flags & MFD_CLOEXEC) ? O_CLOEXEC : 0); if (fd < 0) { error = fd; From 14cfb1b990b4068f33ebd9b2a1d85bdd0e1e3921 Mon Sep 17 00:00:00 2001 From: Nicholas Andrew Date: Fri, 18 Sep 2026 19:56:56 -0400 Subject: [PATCH 3/7] fs: add KernelSU umount compatibility helpers --- fs/namespace.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index 176bccdf6f30..baa802d6cba3 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1756,6 +1756,43 @@ SYSCALL_DEFINE1(oldumount, char __user *, name) #endif + +static int can_umount(const struct path *path, int flags) +{ + struct mount *mnt = real_mount(path->mnt); + + if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW)) + return -EINVAL; + if (!may_mount()) + return -EPERM; + if (path->dentry != path->mnt->mnt_root) + return -EINVAL; + if (!check_mnt(mnt)) + return -EINVAL; + if (mnt->mnt.mnt_flags & MNT_LOCKED) + return -EINVAL; + if (flags & MNT_FORCE && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + return 0; +} + + +int path_umount(struct path *path, int flags) +{ + struct mount *mnt = real_mount(path->mnt); + int ret; + + ret = can_umount(path, flags); + if (!ret) + ret = do_umount(mnt, flags); + + dput(path->dentry); + mntput_no_expire(mnt); + + return ret; +} + static bool is_mnt_ns_file(struct dentry *dentry) { /* Is this a proxy for a mount namespace? */ From d622d068b8650954a662d1c709fe65ee002a6ae5 Mon Sep 17 00:00:00 2001 From: Nicholas Andrew Date: Sat, 19 Sep 2026 00:22:31 -0400 Subject: [PATCH 4/7] susfs: preserve pathname for metadata-only maps spoofing --- fs/susfs.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/susfs.c b/fs/susfs.c index 586909bdc63f..91840543b5b6 100644 --- a/fs/susfs.c +++ b/fs/susfs.c @@ -936,7 +936,16 @@ do_spoof: cursor->info.target_prot, cursor->info.target_addr_size, cursor->info.spoofed_pathname, cursor->info.spoofed_ino, cursor->info.spoofed_dev, cursor->info.spoofed_pgoff, cursor->info.spoofed_prot); - return 2; + /* + * show_map_vma() treats return value 2 as a request to print + * out_name and skip normal pathname handling. Only return 2 + * when SUSFS actually supplied a spoofed pathname. + * + * For metadata-only spoofing, return 1 so the modified maps + * fields are retained while seq_file_path() still prints the + * original mapping pathname. + */ + return cursor->info.need_to_spoof_pathname ? 2 : 1; } return 0; } From 676d5d17dfbc7dc3c3a229ab763cf932850365f2 Mon Sep 17 00:00:00 2001 From: Nicholas Andrew Date: Sat, 19 Sep 2026 00:45:44 -0400 Subject: [PATCH 5/7] susfs: fix try_umount path reference lifetime --- fs/susfs.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/susfs.c b/fs/susfs.c index 91840543b5b6..1efb3a619821 100644 --- a/fs/susfs.c +++ b/fs/susfs.c @@ -1172,8 +1172,13 @@ int susfs_sus_memfd(char *memfd_name) { static void umount_mnt(struct path *path, int flags) { int err = path_umount(path, flags); + + /* + * path_umount() consumes the path references, so do not + * dereference path after it returns. + */ if (err) { - SUSFS_LOGI("umount %s failed: %d\n", path->dentry->d_iname, err); + SUSFS_LOGI("umount failed: %d\n", err); } } @@ -1206,14 +1211,19 @@ static void try_umount(const char *mnt, bool check_mnt, int flags) { if (path.dentry != path.mnt->mnt_root) { // it is not root mountpoint, maybe umounted by others already. + path_put(&path); return; } // we are only interest in some specific mounts if (check_mnt && !should_umount(&path)) { + path_put(&path); return; } - + + /* + * Successful handoff: path_umount() consumes these references. + */ umount_mnt(&path, flags); } From b95186ce174e3183c430814dd22a3a083f92ade1 Mon Sep 17 00:00:00 2001 From: Nicholas Andrew Date: Sat, 19 Sep 2026 17:59:03 -0400 Subject: [PATCH 6/7] milanf: drop redundant KPROBE_EVENTS override --- arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config index d8f2c78443eb..b8195076b2bb 100644 --- a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config +++ b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config @@ -94,7 +94,6 @@ CONFIG_LDO_VIBRATOR_MMI=m # KernelSU-Next CONFIG_KSU=y -CONFIG_KPROBE_EVENTS=y CONFIG_KSU_MANUAL_HOOK=y # KernelSU-Next SUSFS From 543775015aaf43a12b00bec513d2a5314fb4298c Mon Sep 17 00:00:00 2001 From: Nicholas Andrew Date: Sat, 19 Sep 2026 19:42:15 -0400 Subject: [PATCH 7/7] sm6375: enable KernelSU-Next SUSFS in common Lineage config --- .../vendor/ext_config/lineage_moto-holi.config | 17 +++++++++++++++++ .../vendor/ext_config/moto-holi-milanf.config | 17 ----------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/arch/arm64/configs/vendor/ext_config/lineage_moto-holi.config b/arch/arm64/configs/vendor/ext_config/lineage_moto-holi.config index 388ca4fa9f3a..99356b14a93a 100644 --- a/arch/arm64/configs/vendor/ext_config/lineage_moto-holi.config +++ b/arch/arm64/configs/vendor/ext_config/lineage_moto-holi.config @@ -48,3 +48,20 @@ CONFIG_QCA_CLD_WLAN=m # ZRAM CONFIG_ZRAM_WRITEBACK=y + +# KernelSU-Next +CONFIG_KSU=y +CONFIG_KSU_MANUAL_HOOK=y + +# KernelSU-Next SUSFS +CONFIG_KSU_SUSFS=y +CONFIG_KSU_SUSFS_SUS_PATH=y +CONFIG_KSU_SUSFS_SUS_MOUNT=y +# CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER is not set +CONFIG_KSU_SUSFS_SUS_KSTAT=y +CONFIG_KSU_SUSFS_SUS_MAPS=y +# CONFIG_KSU_SUSFS_SUS_PROC_FD_LINK is not set +# CONFIG_KSU_SUSFS_SUS_MEMFD is not set +CONFIG_KSU_SUSFS_TRY_UMOUNT=y +CONFIG_KSU_SUSFS_SPOOF_UNAME=y +CONFIG_KSU_SUSFS_ENABLE_LOG=y diff --git a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config index b8195076b2bb..921287b0132d 100644 --- a/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config +++ b/arch/arm64/configs/vendor/ext_config/moto-holi-milanf.config @@ -91,20 +91,3 @@ CONFIG_BOARD_USES_DOUBLE_TAP_CTRL=y # Vibrator CONFIG_LDO_VIBRATOR_MMI=m - -# KernelSU-Next -CONFIG_KSU=y -CONFIG_KSU_MANUAL_HOOK=y - -# KernelSU-Next SUSFS -CONFIG_KSU_SUSFS=y -CONFIG_KSU_SUSFS_SUS_PATH=y -CONFIG_KSU_SUSFS_SUS_MOUNT=y -# CONFIG_KSU_SUSFS_SUS_MOUNT_MNT_ID_REORDER is not set -CONFIG_KSU_SUSFS_SUS_KSTAT=y -CONFIG_KSU_SUSFS_SUS_MAPS=y -# CONFIG_KSU_SUSFS_SUS_PROC_FD_LINK is not set -# CONFIG_KSU_SUSFS_SUS_MEMFD is not set -CONFIG_KSU_SUSFS_TRY_UMOUNT=y -CONFIG_KSU_SUSFS_SPOOF_UNAME=y -CONFIG_KSU_SUSFS_ENABLE_LOG=y