# How to integrate KernelSU for non-GKI kernels? KernelSU can be integrated into non-GKI kernels, and was backported to 4.14 and below. Due to the fragmentization of non-GKI kernels, we do not have a universal way to build it, so we cannot provide non-GKI boot images. But you can build the kernel yourself with KernelSU integrated. First, you should be able to build a bootable kernel from kernel source code. If the kernel is not open source, then it is difficult to run KernelSU for your device. If you can build a bootable kernel, there are two ways to integrate KernelSU to the kernel source code: 1. Automatically with `kprobe` 2. Manually ## Integrate with kprobe KernelSU uses kprobe to do kernel hooks, if the *kprobe* runs well in your kernel, it is recommended to use this way. First, add KernelSU to your kernel source tree: ```sh curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5 ``` :::info [KernelSU 1.0 and later versions no longer support non-GKI kernels](https://github.com/tiann/KernelSU/issues/1705). The last supported version is `v0.9.5`, please make sure to use the correct version. ::: Then, you should check if *kprobe* is enabled in your kernel config, if it is not, please add these configs to it: ```txt CONFIG_KPROBES=y CONFIG_HAVE_KPROBES=y CONFIG_KPROBE_EVENTS=y ``` And now when you re-build your kernel, KernelSU should work well. If you find that KPROBES is still not activated, you can try enabling `CONFIG_MODULES`. If it still doesn't take effect, use `make menuconfig` to search for other dependencies of KPROBES. But if you encounter a boot loop when integrated KernelSU, it might be because *kprobe is broken in your kernel*, which means that you should fix the kprobe bug or use another way. :::tip How to check if kprobe is broken? comment out `ksu_enable_sucompat()` and `ksu_enable_ksud()` in `KernelSU/kernel/ksu.c`, if the device boots normally, then kprobe may be broken. ::: :::info How to get module umount feature working on pre-GKI? If your kernel is older than 5.9, you should backport `path_umount` to `fs/namespace.c`. This is required to get module umount feature working. If you don't backport `path_umount`, module umount feature won't work. You can get more info on how to achieve this at the end of this page. ::: ## Manually modify the kernel source If kprobe does not work in your kernel (may be an upstream or kernel bug below 4.8), then you can try the following: First, add KernelSU to your kernel source tree: ::: code-group ```sh[Latest tag(stable)] curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash - ``` ```sh[ main branch(dev)] curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s main ``` ```sh[Select tag(Such as v0.5.2)] curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.5.2 ``` ::: Keep in mind that on some devices, your defconfig may be in `arch/arm64/configs` or in other cases `arch/arm64/configs/vendor/your_defconfig`. For whichever defconfig you're using, make sure to enable `CONFIG_KSU` with `y` to enable or `n` to disable it. For example, in case you chose to enable it, you defconfig should contain the following string: ```txt # KernelSU CONFIG_KSU=y ``` Then, add KernelSU calls to the kernel source, here are some patches for reference: ::: code-group ```diff[exec.c] diff --git a/fs/exec.c b/fs/exec.c index ac59664eaecf..bdd585e1d2cc 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1890,11 +1890,14 @@ static int __do_execve_file(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 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 + if (unlikely(ksu_execveat_hook)) + ksu_handle_execveat(&fd, &filename, &argv, &envp, &flags); + else + ksu_handle_execveat_sucompat(&fd, &filename, &argv, &envp, &flags); + #endif return __do_execve_file(fd, filename, argv, envp, flags, NULL); } ``` ```diff[open.c] diff --git a/fs/open.c b/fs/open.c index 05036d819197..965b84d486b8 100644 --- a/fs/open.c +++ b/fs/open.c @@ -348,6 +348,8 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len) return ksys_fallocate(fd, mode, offset, len); } +#ifdef CONFIG_KSU +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 @@ -355,6 +357,7 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len) */ long do_faccessat(int dfd, const char __user *filename, int mode) { const struct cred *old_cred; struct cred *override_cred; struct path path; struct inode *inode; struct vfsmount *mnt; 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; ``` ```diff[read_write.c] diff --git a/fs/read_write.c b/fs/read_write.c index 650fc7e0f3a6..55be193913b6 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -434,10 +434,14 @@ ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos) } EXPORT_SYMBOL(kernel_read); +#ifdef CONFIG_KSU +extern bool ksu_vfs_read_hook __read_mostly; +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 + if (unlikely(ksu_vfs_read_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[stat.c] diff --git a/fs/stat.c b/fs/stat.c index 376543199b5a..82adcef03ecc 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -148,6 +148,8 @@ int vfs_statx_fd(unsigned int fd, struct kstat *stat, } EXPORT_SYMBOL(vfs_statx_fd); +#ifdef CONFIG_KSU +extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); +#endif + /** * vfs_statx - Get basic and extra attributes by filename * @dfd: A file descriptor representing the base dir for a relative filename @@ -170,6 +172,7 @@ 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 + ksu_handle_stat(&dfd, &filename, &flags); + #endif if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH | KSTAT_QUERY_FLAGS)) != 0) return -EINVAL; ``` ::: You should find the four functions in kernel source: 1. `do_faccessat`, usually in `fs/open.c` 2. `do_execveat_common`, usually in `fs/exec.c` 3. `vfs_read`, usually in `fs/read_write.c` 4. `vfs_statx`, usually in `fs/stat.c` If your kernel does not have the `vfs_statx` function, use `vfs_fstatat` instead: ```diff diff --git a/fs/stat.c b/fs/stat.c index 068fdbcc9e26..5348b7bb9db2 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -87,6 +87,8 @@ int vfs_fstat(unsigned int fd, struct kstat *stat) } EXPORT_SYMBOL(vfs_fstat); +#ifdef CONFIG_KSU +extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); +#endif int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat, int flag) { @@ -94,6 +96,8 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat, int error = -EINVAL; unsigned int lookup_flags = 0; + #ifdef CONFIG_KSU + ksu_handle_stat(&dfd, &filename, &flag); + #endif + if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH)) != 0) goto out; ``` For kernels eariler than 4.17, if you cannot find `do_faccessat`, just go to the definition of the `faccessat` syscall and place the call there: ```diff diff --git a/fs/open.c b/fs/open.c index 2ff887661237..e758d7db7663 100644 --- a/fs/open.c +++ b/fs/open.c @@ -355,6 +355,9 @@ SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len) return error; } +#ifdef CONFIG_KSU +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 @@ -370,6 +373,8 @@ 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; ``` ### Safe Mode To enable KernelSU's built-in SafeMode, you should additionally modify `input_handle_event` function in `drivers/input/input.c`: :::tip It is strongly recommended to enable this feature, it is very helpful in preventing bootloops! ::: ```diff diff --git a/drivers/input/input.c b/drivers/input/input.c index 45306f9ef247..815091ebfca4 100755 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -367,10 +367,13 @@ static int input_get_disposition(struct input_dev *dev, return disposition; } +#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 + 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); + #ifdef CONFIG_KSU + if (unlikely(ksu_input_hook)) + ksu_handle_input_handle_event(&type, &code, &value); + #endif if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) add_input_randomness(type, code, value); ``` :::info Entering safe mode accidentally? If you use manual integration and do not disable `CONFIG_KPROBES`, then the user may trigger safe mode by pressing the volume down button after booting! Therefore if using manual integration you need to disable `CONFIG_KPROBES`! ::: ### Failed to execute `pm` in terminal? You should modify `fs/devpts/inode.c`, reference: ```diff diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 32f6f1c68..d69d8eca2 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -602,6 +602,8 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) return dentry; } +#ifdef CONFIG_KSU +extern int ksu_handle_devpts(struct inode*); +#endif + /** * devpts_get_priv -- get private data for a slave * @pts_inode: inode of the slave @@ -610,6 +612,7 @@ struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) */ void *devpts_get_priv(struct dentry *dentry) { + #ifdef CONFIG_KSU + ksu_handle_devpts(dentry->d_inode); + #endif if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) return NULL; return dentry->d_fsdata; ``` ### How to backport path_umount You can get module umount feature working on pre-GKI kernels by manually backporting `path_umount` from 5.9. You can use this patch as reference: ```diff --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1739,6 +1739,39 @@ static inline bool may_mandlock(void) } #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) /* Check optimistically */ + 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); + + /* we mustn't call path_put() as that would clear mnt_expiry_mark */ + dput(path->dentry); + mntput_no_expire(mnt); + return ret; +} /* * Now umount can handle mount points as well as block devices. * This is important for filesystems which use unnamed block devices. ``` Finally, build your kernel again, KernelSU should work well.