Skip to content

scope-minimized manual hooks v1.6 #5

@backslashxx

Description

@backslashxx

This refactors original KSU hooks to replace deep kernel function hooks with targeted hooks.
This backports KernelSU pr#1657 and having pr#2084 elements (32-bit sucompat).
It reduces the scope of kernel function interception and still maintains full fucntionality.

notes:

  • devpts hook? Yes! theres no need for devpts hook, just use: su -c pm
  • per task sucompat works fine.
  • I added emojis so YOU can see important notices.
  • These hooks are made for the driver on THIS REPO. These hooks working on others are not assured.

🟢 sys_execve hook

  • for sucompat
  • choose one which suits your kernel version
  • these are ordered via performance and preference.
show patch/diff (3.18+ via do_execve)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1886,12 +1886,26 @@ static int do_execveat_common(int fd, struct filename *filename,
 	return retval;
 }
 
+#ifdef CONFIG_KSU
+__attribute__((hot))
+extern int ksu_handle_execveat(int *fd, struct filename **filename_ptr,
+				void *argv, void *envp, int *flags);
+#endif
+
 int do_execve(struct filename *filename,
 	const char __user *const __user *__argv,
 	const char __user *const __user *__envp)
 {
 	struct user_arg_ptr argv = { .ptr.native = __argv };
 	struct user_arg_ptr envp = { .ptr.native = __envp };
+#ifdef CONFIG_KSU
+	ksu_handle_execveat((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
 	return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
 }
 
@@ -1919,6 +1933,10 @@ static int compat_do_execve(struct filename *filename,
 		.is_compat = true,
 		.ptr.compat = __envp,
 	};
+#ifdef CONFIG_KSU // 32-bit ksud and 32-on-64 support
+	ksu_handle_execveat((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
 	return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
 }
 
show patch/diff (3.18, via do_execve_common)
  • for 3.18, we can repurpose upstream's do_execveat_common hook for do_execve_common
  • no sys_execveat on <= 3.18, so this makes sense instead of hooking sys_execve + compat_sys_execve
  • take note: struct filename *filename
--- a/fs/exec.c
+++ b/fs/exec.c
/*
 * sys_execve() executes a new program.
 */
+#ifdef CONFIG_KSU
+__attribute__((hot))
+extern int ksu_handle_execveat(int *fd,
+			struct filename **filename_ptr,
+			void *argv, void *envp, int *flags);
+#endif
+
static int do_execve_common(struct filename *filename,
				struct user_arg_ptr argv,
				struct user_arg_ptr envp)
{
	struct linux_binprm *bprm;
	struct file *file;
	struct files_struct *displaced;
	int retval;

	if (IS_ERR(filename))
		return PTR_ERR(filename);

+#ifdef CONFIG_KSU
+	ksu_handle_execveat((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
	/*
	 * We move the actual failure in case of RLIMIT_NPROC excess from
	 * set*uid() to execve() because too many poorly written programs
show patch/diff (3.0 - 3.10, via do_execve_common)
  • for <= 3.10, this repo provides a handler for do_execve_common
  • no sys_execveat on <= 3.18, so this makes sense instead of hooking sys_execve + compat_sys_execve
  • take note: const char *filename
/*
 * sys_execve() executes a new program.
 */
+
+#ifdef CONFIG_KSU
+__attribute__((hot)) 
+extern int ksu_legacy_execve_sucompat(const char **filename_ptr,
+				 void *__never_use_argv,
+				 void *__never_use_envp);
+#endif
+
static int do_execve_common(const char *filename,
				struct user_arg_ptr argv,
				struct user_arg_ptr envp)
{
	struct linux_binprm *bprm;
	struct file *file;
	struct files_struct *displaced;
	bool clear_in_exec;
	int retval;
	const struct cred *cred = current_cred();
+
+#ifdef CONFIG_KSU
+	ksu_legacy_execve_sucompat(&filename, &argv, &envp);
+#endif
+
	/*
	 * We move the actual failure in case of RLIMIT_NPROC excess from
	 * set*uid() to execve() because too many poorly written programs
show patch/diff (3.18+ via sys_execve)
  • just put the hook right at syscall entry
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1968,11 +1968,22 @@ void set_dumpable(struct mm_struct *mm, int value)
 	} while (cmpxchg(&mm->flags, old, new) != old);
 }
 
+#ifdef CONFIG_KSU
+__attribute__((hot))
+extern int ksu_handle_execve_sucompat(int *fd,	const char __user **filename_user,
+				void *__never_use_argv,	void *__never_use_envp,
+				int *__never_use_flags);
+#endif
+
 SYSCALL_DEFINE3(execve,
 		const char __user *, filename,
 		const char __user *const __user *, argv,
 		const char __user *const __user *, envp)
 {
+#ifdef CONFIG_KSU
+	ksu_handle_execve_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#endif
 	return do_execve(getname(filename), argv, envp);
 }
 
@@ -1994,6 +2005,9 @@ COMPAT_SYSCALL_DEFINE3(execve, const char __user *, filename,
 	const compat_uptr_t __user *, argv,
 	const compat_uptr_t __user *, envp)
 {
+#ifdef CONFIG_KSU // 32-bit ksud and 32-on-64 support
+	ksu_handle_execve_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#endif
 	return compat_do_execve(getname(filename), argv, envp);
 }
show patch/diff (3.10, via sys_execve)
  • for 3.10, just hook right at the entrance of sys_execve / compat_sys_execve
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1736,11 +1736,22 @@ int get_dumpable(struct mm_struct *mm)
 	return __get_dumpable(mm->flags);
 }
 
+#ifdef CONFIG_KSU
+__attribute__((hot)) 
+extern int ksu_handle_execve_sucompat(int *fd,	const char __user **filename_user,
+				void *__never_use_argv, void *__never_use_envp,
+				int *__never_use_flags);
+#endif
+
 SYSCALL_DEFINE3(execve,
 		const char __user *, filename,
 		const char __user *const __user *, argv,
 		const char __user *const __user *, envp)
 {
+#ifdef CONFIG_KSU
+	ksu_handle_execve_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#endif
 	struct filename *path = getname(filename);
 	int error = PTR_ERR(path);
 	if (!IS_ERR(path)) {
@@ -1754,6 +1765,9 @@ asmlinkage long compat_sys_execve(const char __user * filename,
 	const compat_uptr_t __user * argv,
 	const compat_uptr_t __user * envp)
 {
+#ifdef CONFIG_KSU // 32-bit sucompat and 32-on-64 support
+	ksu_handle_execve_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#endif
 	struct filename *path = getname(filename);
 	int error = PTR_ERR(path);
 	if (!IS_ERR(path)) {
show patch/diff (3.0 / 3.4, via sys_execve)
  • for 3.0 / 3.4, handling this is a bit different.
  • The location of the syscall is on arch/$ARCH/kernel/sys_$ARCH.c
  • you must pass filenamei to the hook which is what the syscall received.
--- a/arch/arm/kernel/sys_arm.c
+++ b/arch/arm/kernel/sys_arm.c
@@ -62,13 +62,26 @@ asmlinkage int sys_vfork(struct pt_regs *regs)
 /* sys_execve() executes a new program.
  * This is called indirectly via a small wrapper
  */
+#ifdef CONFIG_KSU
+__attribute__((hot))
+extern int ksu_handle_execve_sucompat(int *fd,	const char __user **filename_user,
+				void *__never_use_argv, void *__never_use_envp,
+				int *__never_use_flags);
+#endif
+ 
asmlinkage int sys_execve(const char __user *filenamei,
 			  const char __user *const __user *argv,
 			  const char __user *const __user *envp, struct pt_regs *regs)
 {
 	int error;
 	struct filename *filename;
-
+#ifdef CONFIG_KSU
+	ksu_handle_execve_sucompat((int *)AT_FDCWD, &filenamei, NULL, NULL, NULL);
+#endif
 	filename = getname(filenamei);
 	error = PTR_ERR(filename);
 	if (IS_ERR(filename))

🟢 sys_faccessat hook

  • for sucompat
  • from original guide
  • hook sys_faccessat even if you have do_faccessat, this is for scope minimization.
show patch/diff (4.19 and newer)
--- a/fs/open.c
+++ b/fs/open.c
@@ -450,8 +450,16 @@ long do_faccessat(int dfd, const char __user *filename, int mode)
 	return res;
 }
 
+#ifdef CONFIG_KSU
+__attribute__((hot)) 
+extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user,
+				int *mode, int *flags);
+#endif
+
 SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
 {
+#ifdef CONFIG_KSU
+	ksu_handle_faccessat(&dfd, &filename, &mode, NULL);
+#endif
 	return do_faccessat(dfd, filename, mode);
 }
 
show patch/diff (4.14 and older)
--- a/fs/open.c
+++ b/fs/open.c
@@ -354,6 +354,11 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len)
 	return error;
 }
 
+#ifdef CONFIG_KSU
+__attribute__((hot)) 
+extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user,
+				int *mode, int *flags);
+#endif
+
 /*
  * access() needs to use the real uid/gid, not the effective uid/gid.
  * We do this by temporarily clearing all FS-related capabilities and
@@ -369,6 +374,10 @@ SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
 	int res;
 	unsigned int lookup_flags = LOOKUP_FOLLOW;
 
+#ifdef CONFIG_KSU
+	ksu_handle_faccessat(&dfd, &filename, &mode, NULL);
+#endif
+
 	if (mode & ~S_IRWXO)	/* where's F_OK, X_OK, W_OK, R_OK? */
 		return -EINVAL;
 

🟢 sys_newfstatat hook

  • for sucompat
  • scope minimized
  • you now have to hook sys_newfstatat, instead of vfs_statx()
  • optionally hook sys_fstatat64 if 32-bit su is needed.
show patch/diff
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -353,6 +353,10 @@ SYSCALL_DEFINE2(newlstat, const char __user *, filename,
 	return cp_new_stat(&stat, statbuf);
 }
 
+#ifdef CONFIG_KSU
+__attribute__((hot)) 
+extern int ksu_handle_stat(int *dfd, const char __user **filename_user,
+				int *flags);
+#endif
+
 #if !defined(__ARCH_WANT_STAT64) || defined(__ARCH_WANT_SYS_NEWFSTATAT)
 SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
 		struct stat __user *, statbuf, int, flag)
@@ -360,6 +364,9 @@ SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
 	struct kstat stat;
 	int error;
 
+#ifdef CONFIG_KSU
+	ksu_handle_stat(&dfd, &filename, &flag);
+#endif
 	error = vfs_fstatat(dfd, filename, &stat, flag);
 	if (error)
 		return error;
@@ -504,6 +511,9 @@ SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
 	struct kstat stat;
 	int error;
 
+#ifdef CONFIG_KSU // 32-bit su
+	ksu_handle_stat(&dfd, &filename, &flag); 
+#endif
 	error = vfs_fstatat(dfd, filename, &stat, flag);
 	if (error)
 		return error;

🟢 sys_reboot hook

  • this is needed by new KernelSU supercall introduced at 12143
  • just go to where sys_reboot is and hook right on its entry.
  • ⚠️ if KPROBES is enabled and working on your kernel, this manual hook is not needed.
show patch/diff (3.18+)
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -277,6 +277,11 @@ static DEFINE_MUTEX(reboot_mutex);
  *
  * reboot doesn't sync: do that yourself before calling this.
  */
+
+#ifdef CONFIG_KSU
+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)
 {
@@ -284,6 +289,9 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
 	char buffer[256];
 	int ret = 0;
 
+#ifdef CONFIG_KSU 
+	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;
show patch/diff (3.0~3.10)
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -463,6 +463,11 @@ static DEFINE_MUTEX(reboot_mutex);
  *
  * reboot doesn't sync: do that yourself before calling this.
  */
+
+#ifdef CONFIG_KSU
+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)
 {
@@ -470,6 +475,10 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
 	char buffer[256];
 	int ret = 0;
 
+#ifdef CONFIG_KSU
+	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;

🟢 sys_read hook

  • for ksud
  • scope minimized
  • you now have to hook sys_read instead of vfs_read().
  • ⚠️ if KPROBES is enabled and working on your kernel, this manual hook is not needed.
show patch/diff (4.19 and newer)
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -586,8 +586,18 @@ ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
 	return ret;
 }
 
+#ifdef CONFIG_KSU
+extern bool ksu_vfs_read_hook __read_mostly;
+extern __attribute__((cold)) int ksu_handle_sys_read(unsigned int fd,
+				char __user **buf_ptr, size_t *count_ptr);
+#endif
+
 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
 {
+#ifdef CONFIG_KSU
+	if (unlikely(ksu_vfs_read_hook)) 
+		ksu_handle_sys_read(fd, &buf, &count);
+#endif
 	return ksys_read(fd, buf, count);
 }
show patch/diff (4.14 and older)
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -568,11 +568,21 @@ static inline void file_pos_write(struct file *file, loff_t pos)
 		file->f_pos = pos;
 }
 
+#ifdef CONFIG_KSU
+extern bool ksu_vfs_read_hook __read_mostly;
+extern __attribute__((cold)) int ksu_handle_sys_read(unsigned int fd,
+				char __user **buf_ptr, size_t *count_ptr);
+#endif
+
 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
 {
 	struct fd f = fdget_pos(fd);
 	ssize_t ret = -EBADF;
 
+#ifdef CONFIG_KSU
+	if (unlikely(ksu_vfs_read_hook)) 
+		ksu_handle_sys_read(fd, &buf, &count);
+#endif
 	if (f.file) {
 		loff_t pos = file_pos_read(f.file);
 		ret = vfs_read(f.file, buf, count, &pos);

🟢 input hook

  • for safemode feature
  • you now have to hook input_event() instead of input_handle_event()
  • this is just to be in line with upstream hooks
  • ⚠️ if KPROBES is enabled and working on your kernel, this manual hook is not needed.
show patch/diff
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -436,11 +436,22 @@ static void input_handle_event(struct input_dev *dev,
  * to 'seed' initial state of a switch or initial position of absolute
  * axis, etc.
  */
+#ifdef CONFIG_KSU
+extern bool ksu_input_hook __read_mostly;
+extern __attribute__((cold)) int ksu_handle_input_handle_event(
+			unsigned int *type, unsigned int *code, int *value);
+#endif
+
 void input_event(struct input_dev *dev,
 		 unsigned int type, unsigned int code, int value)
 {
 	unsigned long flags;
 
+#ifdef CONFIG_KSU
+	if (unlikely(ksu_input_hook))
+		ksu_handle_input_handle_event(&type, &code, &value);
+#endif
+
 	if (is_event_supported(type, dev->evbit, EV_MAX)) {
 
 		spin_lock_irqsave(&dev->event_lock, flags);

🟢 selinux hook

  • ‼️ ONLY FOR 4.9 AND OLDER
  • this is needed so we can force allow init -> su transitions for ksud
  • from allow init exec ksud under nosuid by @F-19-F
  • ⚠️ if KPROBES / KRETPROBES is enabled and working on your 3.18/4.4/4.9 kernel, this manual hook is not needed.
show patch/diff (3.18 to 4.9)
  • you will be adding it to check_nnp_nosuid
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
+#ifdef CONFIG_KSU
+extern bool is_ksu_transition(const struct task_security_struct *old_tsec, 
+				const struct task_security_struct *new_tsec);
+#endif

static int check_nnp_nosuid(const struct linux_binprm *bprm,
			    const struct task_security_struct *old_tsec,
			    const struct task_security_struct *new_tsec)
{
	int nnp = (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS);
	int nosuid = !mnt_may_suid(bprm->file->f_path.mnt);
	int rc;

	if (!nnp && !nosuid)
		return 0; /* neither NNP nor nosuid */

	if (new_tsec->sid == old_tsec->sid)
		return 0; /* No change in credentials */
+
+#ifdef CONFIG_KSU
+	if (is_ksu_transition(old_tsec, new_tsec))
+		return 0;
+#endif

	/*
	 * The only transitions we permit under NNP or nosuid
	 * are transitions to bounded SIDs, i.e. SIDs that are
show patch/diff (3.10 and older)
  • you will be adding it to selinux_bprm_set_creds
  • make sure to put it after it after execve sid reset
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2100,6 +2100,12 @@ static int selinux_vm_enough_memory(struct mm_struct *mm, long pages)
 
 /* binprm security operations */
 
+#ifdef CONFIG_KSU
+extern bool is_ksu_transition(const struct task_security_struct *old_tsec,
+			const struct task_security_struct *new_tsec);
+#endif
+
 static int selinux_bprm_set_creds(struct linux_binprm *bprm)
 {
 	const struct task_security_struct *old_tsec;
@@ -2136,6 +2142,11 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm)
 		/* Reset exec SID on execve. */
 		new_tsec->exec_sid = 0;
 
+#ifdef CONFIG_KSU
+		if (is_ksu_transition(old_tsec, new_tsec))
+			return 0;
+#endif
 		/*
 		 * Minimize confusion: if no_new_privs and a transition is
 		 * explicitly requested, then fail the exec.

🟢 walk_component

  • ‼️ ONLY FOR 3.10 and 3.18, possibly 3.4
  • a kthreaded throne_tracker is required so we have current->comm to strstr
  • from kernelsu, fs: do not do lookup_slow if caller is throne_tracker kthread by @acroreiser
show patch/diff (3.10 / 3.18)
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1609,6 +1609,13 @@ static inline int walk_component(struct nameidata *nd, struct path *path,
 		return handle_dots(nd, nd->last_type);
 	err = lookup_fast(nd, path, &inode);
 	if (unlikely(err)) {
+#ifdef CONFIG_KSU
+		if (unlikely(current->flags & PF_KTHREAD)
+			&& !strcmp(current->comm, "throne_tracker")) {
+			err = -ENOENT;
+			goto out_err;
+		}
+#endif
 		if (err < 0)
 			goto out_err;
 
show patch/diff (3.4 - backported from 3.5)
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1543,7 +1543,11 @@ static inline int walk_component(struct nameidata *nd, struct path *path,
 		if (err < 0)
 			goto out_err;
 
-		err = lookup_slow(nd, name, path);
+		if (strstr(current->comm, "throne_tracker") == NULL)
+			err = lookup_slow(nd, name, path);
+		else
+			err = -ENOENT;
+
 		if (err < 0)
 			goto out_err;
 

Revisions
  • v1.1, add ksu_handle_compat_execve_ksud for 32-on-64 usecase, deprecate do_execve hooking.
  • v1.2, deprecate devpts hooking
  • v1.3, add is_ksu_transition handler (selinux "hook")
    • 250611, edit: remove "ksu_execveat_hook" check for selinux hook
    • reported by @edenadversary
  • v1.4, multiple changes
    • add walk_component for UL
    • mark sucompat hooks as __attribute__((hot, always_inline))
      • ksu_handle_execve_sucompat, ksu_handle_faccessat, ksu_handle_stat
      • 250612, edit: remove always_inline for old compiler compatibility.
  • v1.5, multiple changes
    • deprecate execve_ksud handlers in favor of LSM hooking
    • edit walk_component for 3.10 for clarity.
    • add ksu_legacy_execve_sucompat for do_execve_common as another option for 3.0~3.10
    • add getname_flags handlers and hooks
    • added hybridization notes
      • 250922, kprobe support added for sys_read and input_event
      • 250925, kprobe replacement for selinux_hook
      • 250925, added cold attribute to sys_read and input_event
    • added vfs_statx >= 5.18 handler documentation
    • remove old commit links, people are likely smart enough to look for them anyway
  • v1.6, sys_reboot hook for new supercall
    • 251123, added a kthread flag for walk_component's check

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions