Skip to content

scope-minimized manual hooks v2.2 #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:

  • If you're somehow building for 6.8+ make sure to also apply: manual security hooks v2.0 #7
  • if you're on a custom rom and wants to hide adb root on selinuxfs, install this ksu module
  • on 3.0 to 5.4 ARM / ARM64[1][2], try CONFIG_KSU_TAMPER_SYSCALL_TABLE=y and you can skip[3] the following hooks:
    • execve
    • sys_faccessat
    • sys_newfstatat
    • sys_newfstat ret_hook
    • sys_reboot

[1] make sure to disable CFI / CLANG_CFI
[2] it also works on 5.10+, but you need to disable CFI, a no go for GKI.
[3] make sure to remove listed hooks when doing this!

🟢 execve hook

  • for sucompat
  • choose one which suits your kernel version
show patch/diff (7.0+ via sys_execve)
--- fs/exec.c
+++ fs/exec.c
@@ -1921,11 +1921,20 @@
 
+#ifdef CONFIG_KSU
+__attribute__((hot))
+extern int ksu_handle_execve(const char __user **filename_user,
+			void *argv, void *envp);
+#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(&filename, &argv, &envp);
+#endif
	CLASS(filename, name)(filename);
	return do_execveat_common(AT_FDCWD, name,
				  native_arg(argv), native_arg(envp), 0);
}

@@ -1953,6 +1962,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
+	ksu_handle_execve(&filename, &argv, &envp);
+#endif
	CLASS(filename, name)(filename);
	return do_execveat_common(AT_FDCWD, name,
				  compat_arg(argv), compat_arg(envp), 0);
}
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 *argv, void *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

🟢 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
+#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
+#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
 * switching the fsuid/fsgid around to the real ones.
 */
SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode)
{
	const struct cred *old_cred;
	struct cred *override_cred;
	struct path path;
	struct inode *inode;
	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)
{
	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 stat64 __user *, statbuf, int, flag)
{
	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_newfstat ret_hook

show patch/diff
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -364,X +364,XX @@  
+#if defined(CONFIG_KSU) && !defined(CONFIG_KSU_KPROBES_KSUD)
+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); // for 32-bit
+#endif
+#endif
+
SYSCALL_DEFINE2(newfstat, unsigned int, fd, struct stat __user *, statbuf)
{
	struct kstat stat;
	int error = vfs_fstat(fd, &stat);

	if (!error)
		error = cp_new_stat(&stat, statbuf);

+#if defined(CONFIG_KSU) && !defined(CONFIG_KSU_KPROBES_KSUD)
+	ksu_handle_newfstat_ret(&fd, &statbuf);
+#endif
	return error;

 
@@ -490,X +497,X @@
SYSCALL_DEFINE2(fstat64, unsigned long, fd, struct stat64 __user *, statbuf)
{
	struct kstat stat;
	int error = vfs_fstat(fd, &stat);

	if (!error)
		error = cp_new_stat64(&stat, statbuf);

+#if defined(CONFIG_KSU) && !defined(CONFIG_KSU_KPROBES_KSUD) // for 32-bit
+	ksu_handle_fstat64_ret(&fd, &statbuf);
+#endif
	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.
show patch/diff (3.18+)
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -277,6 +277,11 @@ 
  *
  * reboot doesn't sync: do that yourself before calling this.
  */
+
+#if defined(CONFIG_KSU) && !defined(CONFIG_KSU_KPROBES_KSUD)
+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)
{
	struct pid_namespace *pid_ns = task_active_pid_ns(current);
	char buffer[256];
	int ret = 0;
 
+#if defined(CONFIG_KSU) && !defined(CONFIG_KSU_KPROBES_KSUD)
+	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 @@ 
  *
  * reboot doesn't sync: do that yourself before calling this.
  */
+
+#if defined(CONFIG_KSU) && !defined(CONFIG_KSU_KPROBES_KSUD)
+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)
{
	struct pid_namespace *pid_ns = task_active_pid_ns(current);
	char buffer[256];
	int ret = 0;
 
+#if defined(CONFIG_KSU) && !defined(CONFIG_KSU_KPROBES_KSUD)
+	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;

🟡 policy_rwlock

  • this is OPTIONAL, nice to have but NOT required.
  • ‼️ mostly only for 3.0 ~ 4.9, maybe 4.14
  • most 4.14 kernels does NOT need this though (due to selinux_state backported by ACK)
  • if you can't find it or too lazy don't bother with it
  • basically you just remove "static" on policy_rwlock's definition
show patch/diff
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -74,7 +74,7 @@
 int selinux_android_netlink_route;
 int selinux_policycap_netpeer;
 int selinux_policycap_openperm;
 
-static DEFINE_RWLOCK(policy_rwlock);
+DEFINE_RWLOCK(policy_rwlock);
 
 static struct sidtab sidtab;
 struct policydb policydb;

🟡 slow_avc_audit

  • this is OPTIONAL, and can be handled on userspace
  • this is not a standard ksu feature, but its here if you want it
show patch/diff
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c

+#if defined(CONFIG_KSU) && !defined(CONFIG_KPROBES)
+extern void ksu_slow_avc_audit(u32 *tsid);
+#endif

/* This is the slow part of avc audit with big stack footprint */
noinline int slow_avc_audit(struct selinux_state *state,
			    u32 ssid, u32 tsid, u16 tclass,
			    u32 requested, u32 audited, u32 denied, int result,
			    struct common_audit_data *a,
			    unsigned int flags)
{
	struct common_audit_data stack_data;
	struct selinux_audit_data sad;

+#if defined(CONFIG_KSU) && !defined(CONFIG_KPROBES)
+	ksu_slow_avc_audit(&tsid);
+#endif
	if (!a) {
		a = &stack_data;
		a->type = LSM_AUDIT_DATA_NONE;

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
  • v1.7, deprecate sys_read / vfs_read
  • v1.8, deprecate input_hook
  • v1.9, multiple changes
    • deprecate is_ksu_transition
      • this is now deprecated in favor of escaping ksud exec by init to root
      • dummies will be kept for two months (260114) , removed 260314
    • add sys_newfstat ret hook
    • deprecate walk_component "hook" after fixing deadlocks
  • v2.0, multiple changes
    • tell about optional policy rwlock's exposure
    • deprecate getname_flags handlers
    • move "expose selinux_ops" here
    • remove sys_execve hooking from suggestions, to lessen confusion
    • tweak ksu_legacy_execve_sucompat a little
    • mark selinux_ops exposure as optional
    • proper guards for newfstat_ret and sys_reboot hooks to prevent double hooking
  • v2.1, multiple changes
    • add execve handling for k7.0
      • NOTE: kexecve was considered, but sys_execve is much more assured going forward tiann@0b106c7
    • add selinux_hide handlers
    • add selinux_setprocattr handler
    • deprecate selinux_setprocattr for < 6.8, we just unhook it on LSM instead
    • deprecate selinux_ops exposure, scanner is pretty reliable already
    • move ksu_hide_setprocattr to manual security hooks
    • break selinux_hide to selinux_transaction_write and slow_avc_audit
  • v2.2, deprecate selinux_hide hooks
    • we hijack selinuxfs f_op instead
    • ksu_sel_write_context on selinux_transaction_write manual hook will be kept in a month (260520)
    • yes basically, v2.2 is same as 2.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