This is done like how vfs_read_hook, input_hook and execve_hook is disabled.
While this is not exactly the same thing, this CAN achieve the same results.
The complete disabling of all KernelSU hooks.
While this is likely unneeded, It keeps feature parity to non-kprobe builds.
adapted from upstream:
kernel: Allow to re-enable sucompat - 4593ae81c7
Rejected: https://github.com/tiann/KernelSU/pull/2506
Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
282 lines
6.6 KiB
C
282 lines
6.6 KiB
C
#include <linux/dcache.h>
|
|
#include <linux/security.h>
|
|
#include <asm/current.h>
|
|
#include <linux/cred.h>
|
|
#include <linux/err.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/types.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/version.h>
|
|
#include <linux/sched/task_stack.h>
|
|
|
|
#include "objsec.h"
|
|
#include "allowlist.h"
|
|
#include "arch.h"
|
|
#include "klog.h" // IWYU pragma: keep
|
|
#include "ksud.h"
|
|
#include "kernel_compat.h"
|
|
|
|
#define SU_PATH "/system/bin/su"
|
|
#define SH_PATH "/system/bin/sh"
|
|
|
|
extern void escape_to_root();
|
|
|
|
#ifndef CONFIG_KPROBES
|
|
static bool ksu_sucompat_non_kp __read_mostly = true;
|
|
#endif
|
|
|
|
static void __user *userspace_stack_buffer(const void *d, size_t len)
|
|
{
|
|
/* To avoid having to mmap a page in userspace, just write below the stack
|
|
* pointer. */
|
|
char __user *p = (void __user *)current_user_stack_pointer() - len;
|
|
|
|
return copy_to_user(p, d, len) ? NULL : p;
|
|
}
|
|
|
|
static char __user *sh_user_path(void)
|
|
{
|
|
static const char sh_path[] = "/system/bin/sh";
|
|
|
|
return userspace_stack_buffer(sh_path, sizeof(sh_path));
|
|
}
|
|
|
|
static char __user *ksud_user_path(void)
|
|
{
|
|
static const char ksud_path[] = KSUD_PATH;
|
|
|
|
return userspace_stack_buffer(ksud_path, sizeof(ksud_path));
|
|
}
|
|
|
|
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
|
int *__unused_flags)
|
|
{
|
|
const char su[] = SU_PATH;
|
|
|
|
#ifndef CONFIG_KPROBES
|
|
if (!ksu_sucompat_non_kp) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
|
return 0;
|
|
}
|
|
|
|
char path[sizeof(su) + 1];
|
|
memset(path, 0, sizeof(path));
|
|
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
|
|
|
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
|
pr_info("faccessat su->sh!\n");
|
|
*filename_user = sh_user_path();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
|
{
|
|
// const char sh[] = SH_PATH;
|
|
const char su[] = SU_PATH;
|
|
|
|
#ifndef CONFIG_KPROBES
|
|
if (!ksu_sucompat_non_kp) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
|
return 0;
|
|
}
|
|
|
|
if (unlikely(!filename_user)) {
|
|
return 0;
|
|
}
|
|
|
|
char path[sizeof(su) + 1];
|
|
memset(path, 0, sizeof(path));
|
|
// Remove this later!! we use syscall hook, so this will never happen!!!!!
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && 0
|
|
// it becomes a `struct filename *` after 5.18
|
|
// https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216
|
|
const char sh[] = SH_PATH;
|
|
struct filename *filename = *((struct filename **)filename_user);
|
|
if (IS_ERR(filename)) {
|
|
return 0;
|
|
}
|
|
if (likely(memcmp(filename->name, su, sizeof(su))))
|
|
return 0;
|
|
pr_info("vfs_statx su->sh!\n");
|
|
memcpy((void *)filename->name, sh, sizeof(sh));
|
|
#else
|
|
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
|
|
|
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
|
pr_info("newfstatat su->sh!\n");
|
|
*filename_user = sh_user_path();
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
// the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code
|
|
int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
|
void *__never_use_argv, void *__never_use_envp,
|
|
int *__never_use_flags)
|
|
{
|
|
struct filename *filename;
|
|
const char sh[] = KSUD_PATH;
|
|
const char su[] = SU_PATH;
|
|
|
|
#ifndef CONFIG_KPROBES
|
|
if (!ksu_sucompat_non_kp) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
if (unlikely(!filename_ptr))
|
|
return 0;
|
|
|
|
filename = *filename_ptr;
|
|
if (IS_ERR(filename)) {
|
|
return 0;
|
|
}
|
|
|
|
if (likely(memcmp(filename->name, su, sizeof(su))))
|
|
return 0;
|
|
|
|
if (!ksu_is_allow_uid(current_uid().val))
|
|
return 0;
|
|
|
|
pr_info("do_execveat_common su found\n");
|
|
memcpy((void *)filename->name, sh, sizeof(sh));
|
|
|
|
escape_to_root();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
|
void *__never_use_argv, void *__never_use_envp,
|
|
int *__never_use_flags)
|
|
{
|
|
const char su[] = SU_PATH;
|
|
char path[sizeof(su) + 1];
|
|
|
|
#ifndef CONFIG_KPROBES
|
|
if (!ksu_sucompat_non_kp){
|
|
return 0;
|
|
}
|
|
#endif
|
|
if (unlikely(!filename_user))
|
|
return 0;
|
|
|
|
memset(path, 0, sizeof(path));
|
|
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
|
|
|
if (likely(memcmp(path, su, sizeof(su))))
|
|
return 0;
|
|
|
|
if (!ksu_is_allow_uid(current_uid().val))
|
|
return 0;
|
|
|
|
pr_info("sys_execve su found\n");
|
|
*filename_user = ksud_user_path();
|
|
|
|
escape_to_root();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_KPROBES
|
|
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
|
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
|
const char __user **filename_user =
|
|
(const char **)&PT_REGS_PARM2(real_regs);
|
|
int *mode = (int *)&PT_REGS_PARM3(real_regs);
|
|
|
|
return ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
|
}
|
|
|
|
static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
|
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
|
const char __user **filename_user =
|
|
(const char **)&PT_REGS_PARM2(real_regs);
|
|
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(real_regs);
|
|
|
|
return ksu_handle_stat(dfd, filename_user, flags);
|
|
}
|
|
|
|
static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
|
const char __user **filename_user =
|
|
(const char **)&PT_REGS_PARM1(real_regs);
|
|
|
|
return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL,
|
|
NULL);
|
|
}
|
|
|
|
static struct kprobe *init_kprobe(const char *name,
|
|
kprobe_pre_handler_t handler)
|
|
{
|
|
struct kprobe *kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL);
|
|
if (!kp)
|
|
return NULL;
|
|
kp->symbol_name = name;
|
|
kp->pre_handler = handler;
|
|
|
|
int ret = register_kprobe(kp);
|
|
pr_info("sucompat: register_%s kprobe: %d\n", name, ret);
|
|
if (ret) {
|
|
kfree(kp);
|
|
return NULL;
|
|
}
|
|
|
|
return kp;
|
|
}
|
|
|
|
static void destroy_kprobe(struct kprobe **kp_ptr)
|
|
{
|
|
struct kprobe *kp = *kp_ptr;
|
|
if (!kp)
|
|
return;
|
|
unregister_kprobe(kp);
|
|
synchronize_rcu();
|
|
kfree(kp);
|
|
*kp_ptr = NULL;
|
|
}
|
|
|
|
static struct kprobe *su_kps[3];
|
|
#endif
|
|
|
|
// sucompat: permited process can execute 'su' to gain root access.
|
|
void ksu_sucompat_init()
|
|
{
|
|
#ifdef CONFIG_KPROBES
|
|
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
|
|
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
|
|
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
|
#else
|
|
ksu_sucompat_non_kp = true;
|
|
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
|
|
#endif
|
|
}
|
|
|
|
void ksu_sucompat_exit()
|
|
{
|
|
#ifdef CONFIG_KPROBES
|
|
for (int i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
|
destroy_kprobe(&su_kps[i]);
|
|
}
|
|
#else
|
|
ksu_sucompat_non_kp = false;
|
|
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
|
|
#endif
|
|
}
|