This repository was archived by the owner on Oct 27, 2025. It is now read-only.
forked from tiann/KernelSU
-
Couldn't load subscription status.
- Fork 36
This repository was archived by the owner on Oct 27, 2025. It is now read-only.
scope-minimized manual hooks v1.5 #5
Copy link
Copy link
Open
Labels
documentationImprovements or additions to documentationImprovements or additions to documentation
Description
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 - For commits/patches linked here, I assume you are able to adapt them yourself. Its just too hard to make the change universal.
- For older hook styles, They will still work on this repo, do NOT worry. Those are also still available on depreciated.
- the links for the commits here are likely not updated, for that, you can always check master
- ultimatum hook on second post.
🟢 sys_execve hook
- if you are building this repo's KernelSU, you can hook execve this way
- execve_ksud handlers are depreciated now in favor of LSM hooking (250619)
- if you want old if-else'd sys_execve and do_execve hooks, check depreciated
- 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 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.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
@@ -1505,6 +1505,14 @@ EXPORT_SYMBOL(search_binary_handler);
/*
* 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)
@@ -1519,6 +1527,12 @@ static int do_execve_common(struct filename *filename,
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 programsshow 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
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1514,6 +1514,14 @@ EXPORT_SYMBOL(search_binary_handler);
/*
* 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)
@@ -1525,6 +1533,9 @@ static int do_execve_common(const char *filename,
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 programsshow 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
- 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
- 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;- for 6.1 / 6.6 (>=5.18) theres also a good option to just hook vfs_statx
- since vfs_statx now gets struct filename, we can operate purely
on kernel space and skip usercopies - while this will hook all stat, this is likely worth it just due to that
- double check that your vfs_statx is accepting struct filename!
show patch/diff
--- a/fs/stat.c
+++ b/fs/stat.c
+#ifdef CONFIG_KSU
+extern int ksu_handle_vfs_statx(void *__never_use_dfd, struct filename **filename_ptr,
+ void *__never_use_flags, void **__never_use_stat,
+ void *__never_use_request_mask);
+#endif
static int vfs_statx(int dfd, struct filename *filename, int flags,
struct kstat *stat, u32 request_mask)
{
struct path path;
unsigned int lookup_flags = getname_statx_lookup_flags(flags);
int error;
+#ifdef CONFIG_KSU
+ ksu_handle_vfs_statx((void *)&dfd, &filename, (void *)&flags,
+ (void **)&stat, (void *)&request_mask);
+#endif
if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH |
AT_STATX_SYNC_TYPE))
return -EINVAL;
🟢 sys_read hook
- scope minimized
- you now have to hook sys_read instead of vfs_read().
- if kprobes is enabled on your kernel, this hook is optional.
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
- 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 on your kernel, this hook is optional.
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- if kprobes is enabled on your 3.18/4.4/4.9 kernel, this hook is optional.
- if you are building this repo's KernelSU, you can use this "hook"
- from
allow init exec ksud under nosuidby @F-19-F
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 nosuidshow 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.18 AND OLDER- if you are building this repo's KernelSU, you can use this
- a kthreaded throne_tracker is required so we have
current->commto strstr - from
kernelsu, fs: do not do lookup_slow if caller is throne_tracker kthreadby @acroreiser
show patch/diff (3.10 / 3.18)
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1609,6 +1609,12 @@ 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(strstr(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, 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, multiple changes
- depreciate execve_ksud handlers in favor of LSM hooking
- edit walk_component for 3.10 for clarity.
- add
ksu_legacy_execve_sucompatfor do_execve_common as another option for 3.0~3.10 - add
getname_flagshandlers 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
Metadata
Metadata
Assignees
Labels
documentationImprovements or additions to documentationImprovements or additions to documentation