Skip to content

scope-minimized manual hooks v1.5 #5

Open
@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:
Ultra Legacy - if you're on 3.X check second post for extras!
devpts hook? Yes! theres no need for devpts hook, this has been moved to LSM.

🟢 sys_execve hook

show patch/diff (3.18+)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1986,11 +1968,26 @@ void set_dumpable(struct mm_struct *mm, int value)
 	} while (cmpxchg(&mm->flags, old, new) != old);
 }
 
+#ifdef CONFIG_KSU
+extern __attribute__((hot)) 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);
 }
 
@@ -2012,6 +2009,10 @@ 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 su 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);
 }

🟢 sys_faccessat hook

  • 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
+extern __attribute__((hot)) 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
+extern __attribute__((hot)) 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_read hook

  • scope minimized
  • you now have to hook sys_read instead of vfs_read().
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 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,6 +568,12 @@ 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 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);
@@ -575,6 +581,10 @@ SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
 
 	if (f.file) {
 		loff_t pos = file_pos_read(f.file);
+#ifdef CONFIG_KSU
+		if (unlikely(ksu_vfs_read_hook)) 
+			ksu_handle_sys_read(fd, &buf, &count);
+#endif
 		ret = vfs_read(f.file, buf, count, &pos);
 		if (ret >= 0)
 			file_pos_write(f.file, pos);

🟢 stat hook

  • 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
+extern __attribute__((hot)) 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;

🟢 selinux hook

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
@@ -2314,6 +2314,12 @@ static u32 ptrace_parent_sid(struct task_struct *task)
 	return sid;
 }
 
+#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)
@@ -2327,6 +2333,10 @@ static int check_nnp_nosuid(const struct linux_binprm *bprm,
 
 	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
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.

🟢 input hook for safemode

  • you now have to hook input_event() instead of input_handle_event()
  • this is just to be in line with upstream hooks
show patch/diff
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -436,11 +436,21 @@ 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 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);

Revisions
  • v1.1, add ksu_handle_compat_execve_ksud for 32-on-64 usecase, depreciate do_execve hooking.
  • v1.2, depreciate 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, depreciate execve_ksud handlers in favor of LSM hooking

show depreciated

sys_execve hook (old, if-else'd with _ksud)

show patch/diff (3.18+)
  • thanks to selfmusing and 0ctobot for testing (4.14, 6.1)
  • v1.1 edit, added ksu_handle_compat_execve_ksud on compat_sys_execve for 32-on-64 support
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1986,11 +1968,26 @@ void set_dumpable(struct mm_struct *mm, int value)
 	} while (cmpxchg(&mm->flags, old, new) != old);
 }
 
+#ifdef CONFIG_KSU
+extern bool ksu_execveat_hook __read_mostly;
+extern __attribute__((hot)) int ksu_handle_execve_sucompat(int *fd,
+			       const char __user **filename_user,
+			       void *__never_use_argv, void *__never_use_envp,
+			       int *__never_use_flags);
+extern int ksu_handle_execve_ksud(const char __user *filename_user,
+			const char __user *const __user *__argv);
+#ifdef CONFIG_COMPAT  // 32-on-64 support
+extern int ksu_handle_compat_execve_ksud(const char __user *filename_user,
+			const compat_uptr_t __user *__argv);
+#endif
+#endif
+
 SYSCALL_DEFINE3(execve,
 		const char __user *, filename,
 		const char __user *const __user *, argv,
 		const char __user *const __user *, envp)
 {
+#ifdef CONFIG_KSU
+	if (unlikely(ksu_execveat_hook))
+		ksu_handle_execve_ksud(filename, argv);
+	else
+		ksu_handle_execve_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#endif
 	return do_execve(getname(filename), argv, envp);
 }
 
@@ -2012,6 +2009,10 @@ 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 su and 32-on-64 support
+	if (unlikely(ksu_execveat_hook))
+		ksu_handle_compat_execve_ksud(filename, argv);
+	else
+		ksu_handle_execve_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#endif
 	return compat_do_execve(getname(filename), argv, envp);
 }

devpts hook

show patch/diff (4.9 and newer)
  • this is just to be in line with upstream hooks
  • take note: struct file *file,
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -711,11 +711,18 @@ static struct tty_struct *ptm_unix98_lookup(struct tty_driver *driver,
  *	This provides our locking for the tty pointer.
  */
 
+#ifdef CONFIG_KSU
+extern int ksu_handle_devpts(struct inode*);
+#endif
+
 static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
 		struct file *file, int idx)
 {
 	struct tty_struct *tty;
 
+#ifdef CONFIG_KSU
+	ksu_handle_devpts((struct inode *)file->f_path.dentry->d_inode);
+#endif
 	mutex_lock(&devpts_mutex);
 	tty = devpts_get_priv(file->f_path.dentry);
 	mutex_unlock(&devpts_mutex);
show patch/diff (4.4 and older)
  • take note of struct inode *pts_inode
  • thanks to sandatjepil for testing
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -648,11 +648,19 @@ static struct tty_struct *ptm_unix98_lookup(struct tty_driver *driver,
  *	This provides our locking for the tty pointer.
  */
 
+#ifdef CONFIG_KSU
+extern int ksu_handle_devpts(struct inode*);
+#endif
+
 static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
 		struct inode *pts_inode, int idx)
 {
 	struct tty_struct *tty;
 
+#ifdef CONFIG_KSU
+	ksu_handle_devpts(pts_inode);
+#endif
+
 	mutex_lock(&devpts_mutex);
 	tty = devpts_get_priv(pts_inode);
 	mutex_unlock(&devpts_mutex);

do_execve hook

  • WARNING: this is being depreciated. If you can use sys_execve hooks, use it.
  • this is kept for reference purposes
show patch/diff
  • scope minimized
  • you now have to hook do_execve(), instead of do_execveat_common()
  • optionally hook compat_do_execve() if 32-bit su is needed, or you're on 32-on-64
  • make sure to hook do_execve not do_execveat, it can be confusing!!
  • if you want this oversimplified, check second one
  • edit, added ksu_handle_execveat on compat_do_execve for 32-on-64 support
--- 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
+extern bool ksu_execveat_hook __read_mostly;
+extern int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
+			void *envp, int *flags);
+extern int ksu_handle_execveat_sucompat(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
+	if (unlikely(ksu_execveat_hook))
+		ksu_handle_execveat((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+	else
+		ksu_handle_execveat_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#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 su, 32-on-64 ksud support
+	if (unlikely(ksu_execveat_hook))
+		ksu_handle_execveat((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+	else
+		ksu_handle_execveat_sucompat((int *)AT_FDCWD, &filename, NULL, NULL, NULL);
+#endif
 	return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
 }
 
show oversimplified
  • This is an oversimplified version.
  • The one above is still better for optimization.
--- 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
+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
+	ksu_handle_execveat((int *)AT_FDCWD, &filename, &argv, &envp, 0);
+#endif
 	return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
 }
 

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