kernel: Rewrite the kernel source code (#554)
* clean unused header * on_module_mounted in ksud.c * refact: use app_profile * unified hook manager * add zygote to hook target * move reboot hook to supercall.c * refactor: kernel_umount setuid_hook * update mark rules, add init mark tracker * remove reboot from check_syscall_fastpath * update setuid_hook, remove uneeded sucompat enable * log freely * kernel: Migrate kprobe hook configuration items * kernel: fix build * cli: add ksud debug mark * Fix rustfmt warning --------- Co-authored-by: Ylarod <me@ylarod.cn> Co-authored-by: Wang Han <416810799@qq.com> Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
kernelsu-objs := ksu.o
|
||||
kernelsu-objs += allowlist.o
|
||||
kernelsu-objs += app_profile.o
|
||||
kernelsu-objs += dynamic_manager.o
|
||||
kernelsu-objs += apk_sign.o
|
||||
kernelsu-objs += sucompat.o
|
||||
kernelsu-objs += syscall_hook_manager.o
|
||||
kernelsu-objs += throne_tracker.o
|
||||
kernelsu-objs += pkg_observer.o
|
||||
kernelsu-objs += throne_tracker.o
|
||||
kernelsu-objs += umount_manager.o
|
||||
kernelsu-objs += core_hook.o
|
||||
kernelsu-objs += kprobe_hook_manager.o
|
||||
kernelsu-objs += setuid_hook.o
|
||||
kernelsu-objs += kernel_umount.o
|
||||
kernelsu-objs += supercalls.o
|
||||
kernelsu-objs += feature.o
|
||||
kernelsu-objs += ksud.o
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <linux/compiler_types.h>
|
||||
#endif
|
||||
|
||||
#include "ksu.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "selinux/selinux.h"
|
||||
#include "allowlist.h"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define __KSU_H_ALLOWLIST
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "ksu.h"
|
||||
#include "app_profile.h"
|
||||
|
||||
void ksu_allowlist_init(void);
|
||||
|
||||
|
||||
273
kernel/app_profile.c
Normal file
273
kernel/app_profile.c
Normal file
@@ -0,0 +1,273 @@
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/uidgid.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "app_profile.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "selinux/selinux.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "sucompat.h"
|
||||
|
||||
#include "sulog.h"
|
||||
#include "kprobe_hook_manager.h"
|
||||
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0)
|
||||
static struct group_info root_groups = { .usage = REFCOUNT_INIT(2), };
|
||||
#else
|
||||
static struct group_info root_groups = { .usage = ATOMIC_INIT(2) };
|
||||
#endif
|
||||
|
||||
static void setup_groups(struct root_profile *profile, struct cred *cred)
|
||||
{
|
||||
if (profile->groups_count > KSU_MAX_GROUPS) {
|
||||
pr_warn("Failed to setgroups, too large group: %d!\n",
|
||||
profile->uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile->groups_count == 1 && profile->groups[0] == 0) {
|
||||
// setgroup to root and return early.
|
||||
if (cred->group_info)
|
||||
put_group_info(cred->group_info);
|
||||
cred->group_info = get_group_info(&root_groups);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 ngroups = profile->groups_count;
|
||||
struct group_info *group_info = groups_alloc(ngroups);
|
||||
if (!group_info) {
|
||||
pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid);
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ngroups; i++) {
|
||||
gid_t gid = profile->groups[i];
|
||||
kgid_t kgid = make_kgid(current_user_ns(), gid);
|
||||
if (!gid_valid(kgid)) {
|
||||
pr_warn("Failed to setgroups, invalid gid: %d\n", gid);
|
||||
put_group_info(group_info);
|
||||
return;
|
||||
}
|
||||
group_info->gid[i] = kgid;
|
||||
}
|
||||
|
||||
groups_sort(group_info);
|
||||
set_groups(cred, group_info);
|
||||
put_group_info(group_info);
|
||||
}
|
||||
|
||||
static void disable_seccomp()
|
||||
{
|
||||
assert_spin_locked(¤t->sighand->siglock);
|
||||
// disable seccomp
|
||||
#if defined(CONFIG_GENERIC_ENTRY) && \
|
||||
LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
clear_syscall_work(SECCOMP);
|
||||
#else
|
||||
clear_thread_flag(TIF_SECCOMP);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SECCOMP
|
||||
current->seccomp.mode = 0;
|
||||
current->seccomp.filter = NULL;
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
void escape_with_root_profile(void)
|
||||
{
|
||||
struct cred *cred;
|
||||
struct task_struct *p = current;
|
||||
struct task_struct *t;
|
||||
|
||||
cred = prepare_creds();
|
||||
if (!cred) {
|
||||
pr_warn("prepare_creds failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root_failed");
|
||||
#endif
|
||||
abort_creds(cred);
|
||||
return;
|
||||
}
|
||||
|
||||
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
|
||||
|
||||
cred->uid.val = profile->uid;
|
||||
cred->suid.val = profile->uid;
|
||||
cred->euid.val = profile->uid;
|
||||
cred->fsuid.val = profile->uid;
|
||||
|
||||
cred->gid.val = profile->gid;
|
||||
cred->fsgid.val = profile->gid;
|
||||
cred->sgid.val = profile->gid;
|
||||
cred->egid.val = profile->gid;
|
||||
cred->securebits = 0;
|
||||
|
||||
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
||||
sizeof(kernel_cap_t));
|
||||
|
||||
// setup capabilities
|
||||
// we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process
|
||||
// we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec!
|
||||
u64 cap_for_ksud =
|
||||
profile->capabilities.effective | CAP_DAC_READ_SEARCH;
|
||||
memcpy(&cred->cap_effective, &cap_for_ksud,
|
||||
sizeof(cred->cap_effective));
|
||||
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_permitted));
|
||||
memcpy(&cred->cap_bset, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_bset));
|
||||
|
||||
setup_groups(profile, cred);
|
||||
|
||||
commit_creds(cred);
|
||||
|
||||
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
|
||||
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
disable_seccomp();
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
setup_selinux(profile->selinux_domain);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root");
|
||||
#endif
|
||||
|
||||
for_each_thread (p, t) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
|
||||
static void disable_seccomp_for_task(struct task_struct *tsk)
|
||||
{
|
||||
if (!tsk->seccomp.filter && tsk->seccomp.mode == SECCOMP_MODE_DISABLED)
|
||||
return;
|
||||
|
||||
if (WARN_ON(!spin_is_locked(&tsk->sighand->siglock)))
|
||||
return;
|
||||
|
||||
#ifdef CONFIG_SECCOMP
|
||||
tsk->seccomp.mode = 0;
|
||||
if (tsk->seccomp.filter) {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
||||
seccomp_filter_release(tsk);
|
||||
atomic_set(&tsk->seccomp.filter_count, 0);
|
||||
#else
|
||||
// for 6.11+ kernel support?
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0)
|
||||
put_seccomp_filter(tsk);
|
||||
#endif
|
||||
tsk->seccomp.filter = NULL;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid)
|
||||
{
|
||||
struct cred *newcreds;
|
||||
struct task_struct *target_task;
|
||||
struct task_struct *p = current;
|
||||
struct task_struct *t;
|
||||
|
||||
pr_info("cmd_su: escape_to_root_for_cmd_su called for UID: %d, PID: %d\n", target_uid, target_pid);
|
||||
|
||||
// Find target task by PID
|
||||
rcu_read_lock();
|
||||
target_task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
||||
if (!target_task) {
|
||||
rcu_read_unlock();
|
||||
pr_err("cmd_su: target task not found for PID: %d\n", target_pid);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
get_task_struct(target_task);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (task_uid(target_task).val == 0) {
|
||||
pr_warn("cmd_su: target task is already root, PID: %d\n", target_pid);
|
||||
put_task_struct(target_task);
|
||||
return;
|
||||
}
|
||||
|
||||
newcreds = prepare_kernel_cred(target_task);
|
||||
if (newcreds == NULL) {
|
||||
pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed");
|
||||
#endif
|
||||
put_task_struct(target_task);
|
||||
return;
|
||||
}
|
||||
|
||||
struct root_profile *profile = ksu_get_root_profile(target_uid);
|
||||
|
||||
newcreds->uid.val = profile->uid;
|
||||
newcreds->suid.val = profile->uid;
|
||||
newcreds->euid.val = profile->uid;
|
||||
newcreds->fsuid.val = profile->uid;
|
||||
|
||||
newcreds->gid.val = profile->gid;
|
||||
newcreds->fsgid.val = profile->gid;
|
||||
newcreds->sgid.val = profile->gid;
|
||||
newcreds->egid.val = profile->gid;
|
||||
newcreds->securebits = 0;
|
||||
|
||||
u64 cap_for_cmd_su = profile->capabilities.effective | CAP_DAC_READ_SEARCH | CAP_SETUID | CAP_SETGID;
|
||||
memcpy(&newcreds->cap_effective, &cap_for_cmd_su, sizeof(newcreds->cap_effective));
|
||||
memcpy(&newcreds->cap_permitted, &profile->capabilities.effective, sizeof(newcreds->cap_permitted));
|
||||
memcpy(&newcreds->cap_bset, &profile->capabilities.effective, sizeof(newcreds->cap_bset));
|
||||
|
||||
setup_groups(profile, newcreds);
|
||||
task_lock(target_task);
|
||||
|
||||
const struct cred *old_creds = get_task_cred(target_task);
|
||||
|
||||
rcu_assign_pointer(target_task->real_cred, newcreds);
|
||||
rcu_assign_pointer(target_task->cred, get_cred(newcreds));
|
||||
task_unlock(target_task);
|
||||
|
||||
if (target_task->sighand) {
|
||||
spin_lock_irq(&target_task->sighand->siglock);
|
||||
disable_seccomp_for_task(target_task);
|
||||
spin_unlock_irq(&target_task->sighand->siglock);
|
||||
}
|
||||
|
||||
setup_selinux(profile->selinux_domain);
|
||||
put_cred(old_creds);
|
||||
wake_up_process(target_task);
|
||||
|
||||
if (target_task->signal->tty) {
|
||||
struct inode *inode = target_task->signal->tty->driver_data;
|
||||
if (inode && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) {
|
||||
__ksu_handle_devpts(inode);
|
||||
}
|
||||
}
|
||||
|
||||
put_task_struct(target_task);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation");
|
||||
#endif
|
||||
for_each_thread (p, t) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
}
|
||||
pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid);
|
||||
}
|
||||
#endif
|
||||
68
kernel/app_profile.h
Normal file
68
kernel/app_profile.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef __KSU_H_APP_PROFILE
|
||||
#define __KSU_H_APP_PROFILE
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
// Forward declarations
|
||||
struct cred;
|
||||
|
||||
#define KSU_APP_PROFILE_VER 2
|
||||
#define KSU_MAX_PACKAGE_NAME 256
|
||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
||||
#define KSU_MAX_GROUPS 32
|
||||
#define KSU_SELINUX_DOMAIN 64
|
||||
|
||||
struct root_profile {
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
u64 effective;
|
||||
u64 permitted;
|
||||
u64 inheritable;
|
||||
} capabilities;
|
||||
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
|
||||
int32_t namespaces;
|
||||
};
|
||||
|
||||
struct non_root_profile {
|
||||
bool umount_modules;
|
||||
};
|
||||
|
||||
struct app_profile {
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
u32 version;
|
||||
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
|
||||
struct {
|
||||
bool use_default;
|
||||
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
};
|
||||
|
||||
// Escalate current process to root with the appropriate profile
|
||||
void escape_with_root_profile(void);
|
||||
|
||||
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid);
|
||||
|
||||
#endif
|
||||
@@ -19,10 +19,7 @@
|
||||
#define __PT_IP_REG pc
|
||||
|
||||
#define REBOOT_SYMBOL "__arm64_sys_reboot"
|
||||
#define PRCTL_SYMBOL "__arm64_sys_prctl"
|
||||
#define SYS_READ_SYMBOL "__arm64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat"
|
||||
#define SYS_EXECVE_SYMBOL "__arm64_sys_execve"
|
||||
|
||||
#elif defined(__x86_64__)
|
||||
@@ -41,10 +38,7 @@
|
||||
#define __PT_SP_REG sp
|
||||
#define __PT_IP_REG ip
|
||||
#define REBOOT_SYMBOL "__x64_sys_reboot"
|
||||
#define PRCTL_SYMBOL "__x64_sys_prctl"
|
||||
#define SYS_READ_SYMBOL "__x64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat"
|
||||
#define SYS_EXECVE_SYMBOL "__x64_sys_execve"
|
||||
|
||||
#else
|
||||
|
||||
@@ -1,895 +0,0 @@
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/init_task.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/uidgid.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/tty.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "core_hook.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "manager.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "supercalls.h"
|
||||
#include "sucompat.h"
|
||||
#include "sulog.h"
|
||||
#include "seccomp_cache.h"
|
||||
#include "ksud.h"
|
||||
|
||||
#include "throne_comm.h"
|
||||
#include "umount_manager.h"
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#include "manual_su.h"
|
||||
#endif
|
||||
|
||||
bool ksu_module_mounted __read_mostly = false;
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
bool ksu_is_compat __read_mostly = false;
|
||||
#endif
|
||||
|
||||
#ifndef DEVPTS_SUPER_MAGIC
|
||||
#define DEVPTS_SUPER_MAGIC 0x1cd1
|
||||
#endif
|
||||
|
||||
extern int __ksu_handle_devpts(struct inode *inode); // sucompat.c
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
static void ksu_try_escalate_for_uid(uid_t uid)
|
||||
{
|
||||
if (!is_pending_root(uid))
|
||||
return;
|
||||
|
||||
pr_info("pending_root: UID=%d temporarily allowed\n", uid);
|
||||
remove_pending_root(uid);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool ksu_kernel_umount_enabled = true;
|
||||
static bool ksu_enhanced_security_enabled = false;
|
||||
|
||||
static int kernel_umount_feature_get(u64 *value)
|
||||
{
|
||||
*value = ksu_kernel_umount_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kernel_umount_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
ksu_kernel_umount_enabled = enable;
|
||||
pr_info("kernel_umount: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler kernel_umount_handler = {
|
||||
.feature_id = KSU_FEATURE_KERNEL_UMOUNT,
|
||||
.name = "kernel_umount",
|
||||
.get_handler = kernel_umount_feature_get,
|
||||
.set_handler = kernel_umount_feature_set,
|
||||
};
|
||||
|
||||
static int enhanced_security_feature_get(u64 *value)
|
||||
{
|
||||
*value = ksu_enhanced_security_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enhanced_security_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
ksu_enhanced_security_enabled = enable;
|
||||
pr_info("enhanced_security: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler enhanced_security_handler = {
|
||||
.feature_id = KSU_FEATURE_ENHANCED_SECURITY,
|
||||
.name = "enhanced_security",
|
||||
.get_handler = enhanced_security_feature_get,
|
||||
.set_handler = enhanced_security_feature_set,
|
||||
};
|
||||
|
||||
static inline bool is_allow_su()
|
||||
{
|
||||
if (is_manager()) {
|
||||
// we are manager, allow!
|
||||
return true;
|
||||
}
|
||||
return ksu_is_allow_uid_for_current(current_uid().val);
|
||||
}
|
||||
|
||||
static inline bool is_unsupported_uid(uid_t uid)
|
||||
{
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
uid_t appid = uid % 100000;
|
||||
return appid > LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0)
|
||||
static struct group_info root_groups = { .usage = REFCOUNT_INIT(2), };
|
||||
#else
|
||||
static struct group_info root_groups = { .usage = ATOMIC_INIT(2) };
|
||||
#endif
|
||||
|
||||
static void setup_groups(struct root_profile *profile, struct cred *cred)
|
||||
{
|
||||
if (profile->groups_count > KSU_MAX_GROUPS) {
|
||||
pr_warn("Failed to setgroups, too large group: %d!\n",
|
||||
profile->uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile->groups_count == 1 && profile->groups[0] == 0) {
|
||||
// setgroup to root and return early.
|
||||
if (cred->group_info)
|
||||
put_group_info(cred->group_info);
|
||||
cred->group_info = get_group_info(&root_groups);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 ngroups = profile->groups_count;
|
||||
struct group_info *group_info = groups_alloc(ngroups);
|
||||
if (!group_info) {
|
||||
pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid);
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < ngroups; i++) {
|
||||
gid_t gid = profile->groups[i];
|
||||
kgid_t kgid = make_kgid(current_user_ns(), gid);
|
||||
if (!gid_valid(kgid)) {
|
||||
pr_warn("Failed to setgroups, invalid gid: %d\n", gid);
|
||||
put_group_info(group_info);
|
||||
return;
|
||||
}
|
||||
group_info->gid[i] = kgid;
|
||||
}
|
||||
|
||||
groups_sort(group_info);
|
||||
set_groups(cred, group_info);
|
||||
put_group_info(group_info);
|
||||
}
|
||||
|
||||
static void disable_seccomp()
|
||||
{
|
||||
assert_spin_locked(¤t->sighand->siglock);
|
||||
// disable seccomp
|
||||
#if defined(CONFIG_GENERIC_ENTRY) && \
|
||||
LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
clear_syscall_work(SECCOMP);
|
||||
#else
|
||||
clear_thread_flag(TIF_SECCOMP);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SECCOMP
|
||||
current->seccomp.mode = 0;
|
||||
current->seccomp.filter = NULL;
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
void escape_to_root(void)
|
||||
{
|
||||
struct cred *cred;
|
||||
struct task_struct *p = current;
|
||||
struct task_struct *t;
|
||||
|
||||
cred = prepare_creds();
|
||||
if (!cred) {
|
||||
pr_warn("prepare_creds failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cred->euid.val == 0) {
|
||||
pr_warn("Already root, don't escape!\n");
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root_failed");
|
||||
#endif
|
||||
abort_creds(cred);
|
||||
return;
|
||||
}
|
||||
|
||||
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
|
||||
|
||||
cred->uid.val = profile->uid;
|
||||
cred->suid.val = profile->uid;
|
||||
cred->euid.val = profile->uid;
|
||||
cred->fsuid.val = profile->uid;
|
||||
|
||||
cred->gid.val = profile->gid;
|
||||
cred->fsgid.val = profile->gid;
|
||||
cred->sgid.val = profile->gid;
|
||||
cred->egid.val = profile->gid;
|
||||
cred->securebits = 0;
|
||||
|
||||
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
||||
sizeof(kernel_cap_t));
|
||||
|
||||
// setup capabilities
|
||||
// we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process
|
||||
// we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec!
|
||||
u64 cap_for_ksud =
|
||||
profile->capabilities.effective | CAP_DAC_READ_SEARCH;
|
||||
memcpy(&cred->cap_effective, &cap_for_ksud,
|
||||
sizeof(cred->cap_effective));
|
||||
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_permitted));
|
||||
memcpy(&cred->cap_bset, &profile->capabilities.effective,
|
||||
sizeof(cred->cap_bset));
|
||||
|
||||
setup_groups(profile, cred);
|
||||
|
||||
commit_creds(cred);
|
||||
|
||||
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
|
||||
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
disable_seccomp();
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
setup_selinux(profile->selinux_domain);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root");
|
||||
#endif
|
||||
|
||||
for_each_thread (p, t) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
|
||||
static void disable_seccomp_for_task(struct task_struct *tsk)
|
||||
{
|
||||
if (!tsk->seccomp.filter && tsk->seccomp.mode == SECCOMP_MODE_DISABLED)
|
||||
return;
|
||||
|
||||
if (WARN_ON(!spin_is_locked(&tsk->sighand->siglock)))
|
||||
return;
|
||||
|
||||
#ifdef CONFIG_SECCOMP
|
||||
tsk->seccomp.mode = 0;
|
||||
if (tsk->seccomp.filter) {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
||||
seccomp_filter_release(tsk);
|
||||
atomic_set(&tsk->seccomp.filter_count, 0);
|
||||
#else
|
||||
// for 6.11+ kernel support?
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0)
|
||||
put_seccomp_filter(tsk);
|
||||
#endif
|
||||
tsk->seccomp.filter = NULL;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid)
|
||||
{
|
||||
struct cred *newcreds;
|
||||
struct task_struct *target_task;
|
||||
struct task_struct *p = current;
|
||||
struct task_struct *t;
|
||||
|
||||
pr_info("cmd_su: escape_to_root_for_cmd_su called for UID: %d, PID: %d\n", target_uid, target_pid);
|
||||
|
||||
// Find target task by PID
|
||||
rcu_read_lock();
|
||||
target_task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
||||
if (!target_task) {
|
||||
rcu_read_unlock();
|
||||
pr_err("cmd_su: target task not found for PID: %d\n", target_pid);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
get_task_struct(target_task);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (task_uid(target_task).val == 0) {
|
||||
pr_warn("cmd_su: target task is already root, PID: %d\n", target_pid);
|
||||
put_task_struct(target_task);
|
||||
return;
|
||||
}
|
||||
|
||||
newcreds = prepare_kernel_cred(target_task);
|
||||
if (newcreds == NULL) {
|
||||
pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed");
|
||||
#endif
|
||||
put_task_struct(target_task);
|
||||
return;
|
||||
}
|
||||
|
||||
struct root_profile *profile = ksu_get_root_profile(target_uid);
|
||||
|
||||
newcreds->uid.val = profile->uid;
|
||||
newcreds->suid.val = profile->uid;
|
||||
newcreds->euid.val = profile->uid;
|
||||
newcreds->fsuid.val = profile->uid;
|
||||
|
||||
newcreds->gid.val = profile->gid;
|
||||
newcreds->fsgid.val = profile->gid;
|
||||
newcreds->sgid.val = profile->gid;
|
||||
newcreds->egid.val = profile->gid;
|
||||
newcreds->securebits = 0;
|
||||
|
||||
u64 cap_for_cmd_su = profile->capabilities.effective | CAP_DAC_READ_SEARCH | CAP_SETUID | CAP_SETGID;
|
||||
memcpy(&newcreds->cap_effective, &cap_for_cmd_su, sizeof(newcreds->cap_effective));
|
||||
memcpy(&newcreds->cap_permitted, &profile->capabilities.effective, sizeof(newcreds->cap_permitted));
|
||||
memcpy(&newcreds->cap_bset, &profile->capabilities.effective, sizeof(newcreds->cap_bset));
|
||||
|
||||
setup_groups(profile, newcreds);
|
||||
task_lock(target_task);
|
||||
|
||||
const struct cred *old_creds = get_task_cred(target_task);
|
||||
|
||||
rcu_assign_pointer(target_task->real_cred, newcreds);
|
||||
rcu_assign_pointer(target_task->cred, get_cred(newcreds));
|
||||
task_unlock(target_task);
|
||||
|
||||
if (target_task->sighand) {
|
||||
spin_lock_irq(&target_task->sighand->siglock);
|
||||
disable_seccomp_for_task(target_task);
|
||||
spin_unlock_irq(&target_task->sighand->siglock);
|
||||
}
|
||||
|
||||
setup_selinux(profile->selinux_domain);
|
||||
put_cred(old_creds);
|
||||
wake_up_process(target_task);
|
||||
|
||||
if (target_task->signal->tty) {
|
||||
struct inode *inode = target_task->signal->tty->driver_data;
|
||||
if (inode && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) {
|
||||
__ksu_handle_devpts(inode);
|
||||
}
|
||||
}
|
||||
|
||||
put_task_struct(target_task);
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation");
|
||||
#endif
|
||||
for_each_thread (p, t) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
}
|
||||
pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef CONFIG_EXT4_FS
|
||||
extern void ext4_unregister_sysfs(struct super_block *sb);
|
||||
void nuke_ext4_sysfs(void)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path("/data/adb/modules", 0, &path);
|
||||
if (err) {
|
||||
pr_err("nuke path err: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
struct super_block *sb = path.dentry->d_inode->i_sb;
|
||||
const char *name = sb->s_type->name;
|
||||
if (strcmp(name, "ext4") != 0) {
|
||||
pr_info("nuke but module aren't mounted\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ext4_unregister_sysfs(sb);
|
||||
path_put(&path);
|
||||
}
|
||||
#else
|
||||
inline void nuke_ext4_sysfs(void)
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
bool is_system_uid(void)
|
||||
{
|
||||
if (!current->mm || current->in_execve) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uid_t caller_uid = current_uid().val;
|
||||
return caller_uid <= 2000;
|
||||
}
|
||||
|
||||
static bool is_appuid(kuid_t uid)
|
||||
{
|
||||
#define PER_USER_RANGE 100000
|
||||
#define FIRST_APPLICATION_UID 10000
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
|
||||
uid_t appid = uid.val % PER_USER_RANGE;
|
||||
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
static bool should_umount(struct path *path)
|
||||
{
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) {
|
||||
pr_info("ignore global mnt namespace process: %d\n",
|
||||
current_uid().val);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) {
|
||||
const char *fstype = path->mnt->mnt_sb->s_type->name;
|
||||
return strcmp(fstype, "overlay") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
extern int path_umount(struct path *path, int flags);
|
||||
static void ksu_umount_mnt(struct path *path, int flags)
|
||||
{
|
||||
int err = path_umount(path, flags);
|
||||
if (err) {
|
||||
pr_info("umount %s failed: %d\n", path->dentry->d_iname, err);
|
||||
}
|
||||
}
|
||||
|
||||
static void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.dentry != path.mnt->mnt_root) {
|
||||
// it is not root mountpoint, maybe umounted by others already.
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
ksu_umount_mnt(&path, flags);
|
||||
}
|
||||
|
||||
struct umount_tw {
|
||||
struct callback_head cb;
|
||||
const struct cred *old_cred;
|
||||
};
|
||||
|
||||
static void umount_tw_func(struct callback_head *cb)
|
||||
{
|
||||
struct umount_tw *tw = container_of(cb, struct umount_tw, cb);
|
||||
const struct cred *saved = NULL;
|
||||
if (tw->old_cred) {
|
||||
saved = override_creds(tw->old_cred);
|
||||
}
|
||||
|
||||
ksu_umount_manager_execute_all(tw->old_cred);
|
||||
|
||||
if (saved)
|
||||
revert_creds(saved);
|
||||
|
||||
if (tw->old_cred)
|
||||
put_cred(tw->old_cred);
|
||||
|
||||
kfree(tw);
|
||||
}
|
||||
|
||||
int ksu_handle_setuid(struct cred *new, const struct cred *old)
|
||||
{
|
||||
struct umount_tw *tw;
|
||||
if (!new || !old) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
kuid_t new_uid = new->uid;
|
||||
kuid_t old_uid = old->uid;
|
||||
// pr_info("handle_setuid from %d to %d\n", old_uid.val, new_uid.val);
|
||||
|
||||
if (0 != old_uid.val) {
|
||||
// old process is not root, ignore it.
|
||||
if (ksu_enhanced_security_enabled) {
|
||||
// disallow any non-ksu domain escalation from non-root to root!
|
||||
if (unlikely(new_uid.val) == 0) {
|
||||
if (!is_ksu_domain()) {
|
||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
||||
current->pid, current->comm, old_uid.val, new_uid.val);
|
||||
force_sig(SIGKILL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// disallow appuid decrease to any other uid if it is allowed to su
|
||||
if (is_appuid(old_uid)) {
|
||||
if (new_uid.val < old_uid.val && !ksu_is_allow_uid_for_current(old_uid.val)) {
|
||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
||||
current->pid, current->comm, old_uid.val, new_uid.val);
|
||||
force_sig(SIGKILL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (new_uid.val == 2000) {
|
||||
if (ksu_su_compat_enabled) {
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_appuid(new_uid) || is_unsupported_uid(new_uid.val)) {
|
||||
// pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid.val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if on private space, see if its possibly the manager
|
||||
if (unlikely(new_uid.val > 100000 && new_uid.val % 100000 == ksu_get_manager_uid())) {
|
||||
ksu_set_manager_uid(new_uid.val);
|
||||
}
|
||||
|
||||
if (unlikely(ksu_get_manager_uid() == new_uid.val)) {
|
||||
pr_info("install fd for: %d\n", new_uid.val);
|
||||
|
||||
ksu_install_fd();
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
||||
#else
|
||||
// we dont have those new fancy things upstream has
|
||||
// lets just do original thing where we disable seccomp
|
||||
disable_seccomp();
|
||||
#endif
|
||||
if (ksu_su_compat_enabled) {
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
}
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (unlikely(ksu_is_allow_uid_for_current(new_uid.val))) {
|
||||
if (current->seccomp.mode == SECCOMP_MODE_FILTER &&
|
||||
current->seccomp.filter) {
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
||||
#else
|
||||
// we don't have those new fancy things upstream has
|
||||
// lets just do original thing where we disable seccomp
|
||||
disable_seccomp();
|
||||
#endif
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
}
|
||||
if (ksu_su_compat_enabled) {
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
}
|
||||
} else {
|
||||
// Disable syscall tracepoint sucompat for non-allowed processes
|
||||
if (ksu_su_compat_enabled) {
|
||||
ksu_clear_task_tracepoint_flag(current);
|
||||
}
|
||||
}
|
||||
|
||||
// this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it!
|
||||
if (!ksu_module_mounted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_kernel_umount_enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_uid_should_umount(new_uid.val)) {
|
||||
return 0;
|
||||
} else {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("uid: %d should not umount!\n", current_uid().val);
|
||||
#endif
|
||||
}
|
||||
|
||||
// check old process's selinux context, if it is not zygote, ignore it!
|
||||
// because some su apps may setuid to untrusted_app but they are in global mount namespace
|
||||
// when we umount for such process, that is a disaster!
|
||||
bool is_zygote_child = is_zygote(old);
|
||||
if (!is_zygote_child) {
|
||||
pr_info("handle umount ignore non zygote child: %d\n", current->pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_syscall(new_uid.val, NULL, "setuid", NULL);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
// umount the target mnt
|
||||
pr_info("handle umount for uid: %d, pid: %d\n", new_uid.val, current->pid);
|
||||
#endif
|
||||
|
||||
tw = kmalloc(sizeof(*tw), GFP_ATOMIC);
|
||||
if (!tw)
|
||||
return 0;
|
||||
|
||||
tw->old_cred = get_current_cred();
|
||||
tw->cb.func = umount_tw_func;
|
||||
|
||||
int err = task_work_add(current, &tw->cb, TWA_RESUME);
|
||||
if (err) {
|
||||
if (tw->old_cred) {
|
||||
put_cred(tw->old_cred);
|
||||
}
|
||||
kfree(tw);
|
||||
pr_warn("unmount add task_work failed\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// downstream: make sure to pass arg as reference, this can allow us to extend things.
|
||||
int ksu_handle_sys_reboot(int magic1, int magic2, unsigned int cmd, void __user **arg)
|
||||
{
|
||||
|
||||
if (magic1 != KSU_INSTALL_MAGIC1)
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("sys_reboot: intercepted call! magic: 0x%x id: %d\n", magic1, magic2);
|
||||
#endif
|
||||
|
||||
// Check if this is a request to install KSU fd
|
||||
if (magic2 == KSU_INSTALL_MAGIC2) {
|
||||
int fd = ksu_install_fd();
|
||||
pr_info("[%d] install ksu fd: %d\n", current->pid, fd);
|
||||
|
||||
// downstream: dereference all arg usage!
|
||||
if (copy_to_user((void __user *)*arg, &fd, sizeof(fd))) {
|
||||
pr_err("install ksu fd reply err\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// extensions
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Init functons - kprobe hooks
|
||||
|
||||
// 1. Reboot hook for installing fd
|
||||
static int reboot_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int magic1 = (int)PT_REGS_PARM1(real_regs);
|
||||
int magic2 = (int)PT_REGS_PARM2(real_regs);
|
||||
int cmd = (int)PT_REGS_PARM3(real_regs);
|
||||
void __user **arg = (void __user **)&PT_REGS_SYSCALL_PARM4(real_regs);
|
||||
|
||||
return ksu_handle_sys_reboot(magic1, magic2, cmd, arg);
|
||||
}
|
||||
|
||||
static struct kprobe reboot_kp = {
|
||||
.symbol_name = REBOOT_SYMBOL,
|
||||
.pre_handler = reboot_handler_pre,
|
||||
};
|
||||
|
||||
// 2. cap_task_fix_setuid hook for handling setuid
|
||||
static int cap_task_fix_setuid_handler_pre(struct kprobe *p,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
struct cred *new = (struct cred *)PT_REGS_PARM1(regs);
|
||||
const struct cred *old = (const struct cred *)PT_REGS_PARM2(regs);
|
||||
|
||||
ksu_handle_setuid(new, old);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kprobe cap_task_fix_setuid_kp = {
|
||||
.symbol_name = "cap_task_fix_setuid",
|
||||
.pre_handler = cap_task_fix_setuid_handler_pre,
|
||||
};
|
||||
|
||||
// 3.inode_permission hook for handling devpts
|
||||
static int ksu_inode_permission_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct inode *inode = (struct inode *)PT_REGS_PARM1(regs);
|
||||
|
||||
if (inode && inode->i_sb && unlikely(inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) {
|
||||
// pr_info("%s: handling devpts for: %s \n", __func__, current->comm);
|
||||
__ksu_handle_devpts(inode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kprobe ksu_inode_permission_kp = {
|
||||
.symbol_name = "security_inode_permission",
|
||||
.pre_handler = ksu_inode_permission_handler_pre,
|
||||
};
|
||||
|
||||
|
||||
// 4. bprm_check_security hook for handling ksud compatibility
|
||||
static int ksu_bprm_check_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct linux_binprm *bprm = (struct linux_binprm *)PT_REGS_PARM1(regs);
|
||||
char *filename = (char *)bprm->filename;
|
||||
|
||||
if (likely(!ksu_execveat_hook))
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static bool compat_check_done __read_mostly = false;
|
||||
if (unlikely(!compat_check_done) && unlikely(!strcmp(filename, "/data/adb/ksud"))
|
||||
&& !memcmp(bprm->buf, "\x7f\x45\x4c\x46", 4)) {
|
||||
if (bprm->buf[4] == 0x01)
|
||||
ksu_is_compat = true;
|
||||
|
||||
pr_info("%s: %s ELF magic found! ksu_is_compat: %d \n", __func__, filename, ksu_is_compat);
|
||||
compat_check_done = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
ksu_handle_pre_ksud(filename);
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
ksu_try_escalate_for_uid(current_uid().val);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kprobe ksu_bprm_check_kp = {
|
||||
.symbol_name = "security_bprm_check",
|
||||
.pre_handler = ksu_bprm_check_handler_pre,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
// 5. task_alloc hook for handling manual su escalation
|
||||
static int ksu_task_alloc_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct task_struct *task = (struct task_struct *)PT_REGS_PARM1(regs);
|
||||
|
||||
ksu_try_escalate_for_uid(task_uid(task).val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kprobe ksu_task_alloc_kp = {
|
||||
.symbol_name = "security_task_alloc",
|
||||
.pre_handler = ksu_task_alloc_handler_pre,
|
||||
};
|
||||
#endif
|
||||
|
||||
__maybe_unused int ksu_kprobe_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
// Register reboot kprobe
|
||||
rc = register_kprobe(&reboot_kp);
|
||||
if (rc) {
|
||||
pr_err("reboot kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("reboot kprobe registered successfully\n");
|
||||
}
|
||||
|
||||
rc = register_kprobe(&cap_task_fix_setuid_kp);
|
||||
if (rc) {
|
||||
pr_err("cap_task_fix_setuid kprobe failed: %d\n", rc);
|
||||
unregister_kprobe(&reboot_kp);
|
||||
} else {
|
||||
pr_info("cap_task_fix_setuid_kp kprobe registered successfully\n");
|
||||
}
|
||||
|
||||
// Register inode_permission kprobe
|
||||
rc = register_kprobe(&ksu_inode_permission_kp);
|
||||
if (rc) {
|
||||
pr_err("inode_permission kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("inode_permission kprobe registered successfully\n");
|
||||
}
|
||||
|
||||
// Register bprm_check_security kprobe
|
||||
rc = register_kprobe(&ksu_bprm_check_kp);
|
||||
if (rc) {
|
||||
pr_err("bprm_check_security kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("bprm_check_security kprobe registered successfully\n");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
// Register task_alloc kprobe
|
||||
rc = register_kprobe(&ksu_task_alloc_kp);
|
||||
if (rc) {
|
||||
pr_err("task_alloc kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("task_alloc kprobe registered successfully\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__maybe_unused int ksu_kprobe_exit(void)
|
||||
{
|
||||
unregister_kprobe(&reboot_kp);
|
||||
unregister_kprobe(&cap_task_fix_setuid_kp);
|
||||
unregister_kprobe(&ksu_inode_permission_kp);
|
||||
unregister_kprobe(&ksu_bprm_check_kp);
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
unregister_kprobe(&ksu_task_alloc_kp);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __init ksu_core_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
#ifdef CONFIG_KPROBES
|
||||
rc = ksu_kprobe_init();
|
||||
if (rc) {
|
||||
pr_err("ksu_kprobe_init failed: %d\n", rc);
|
||||
}
|
||||
#endif
|
||||
rc = ksu_umount_manager_init();
|
||||
if (rc) {
|
||||
pr_err("Failed to initialize umount manager: %d\n", rc);
|
||||
}
|
||||
if (ksu_register_feature_handler(&kernel_umount_handler)) {
|
||||
pr_err("Failed to register umount feature handler\n");
|
||||
}
|
||||
if (ksu_register_feature_handler(&enhanced_security_handler)) {
|
||||
pr_err("Failed to register enhanced security feature handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_core_exit(void)
|
||||
{
|
||||
ksu_uid_exit();
|
||||
ksu_throne_comm_exit();
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_exit();
|
||||
#endif
|
||||
#ifdef CONFIG_KPROBES
|
||||
pr_info("ksu_core_exit\n");
|
||||
ksu_kprobe_exit();
|
||||
#endif
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT);
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY);
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef __KSU_H_KSU_CORE
|
||||
#define __KSU_H_KSU_CORE
|
||||
|
||||
#include <linux/init.h>
|
||||
#include "apk_sign.h"
|
||||
#include <linux/thread_info.h>
|
||||
|
||||
void __init ksu_core_init(void);
|
||||
void ksu_core_exit(void);
|
||||
|
||||
void escape_to_root(void);
|
||||
|
||||
void nuke_ext4_sysfs(void);
|
||||
|
||||
extern bool ksu_module_mounted;
|
||||
|
||||
#endif
|
||||
@@ -10,14 +10,8 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "ksud.h"
|
||||
#include "manager.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "core_hook.h"
|
||||
#include "objsec.h"
|
||||
|
||||
#include "file_wrapper.h"
|
||||
|
||||
|
||||
208
kernel/kernel_umount.c
Normal file
208
kernel/kernel_umount.c
Normal file
@@ -0,0 +1,208 @@
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "kernel_umount.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "allowlist.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "feature.h"
|
||||
#include "ksud.h"
|
||||
|
||||
#include "umount_manager.h"
|
||||
|
||||
static bool ksu_kernel_umount_enabled = true;
|
||||
|
||||
static int kernel_umount_feature_get(u64 *value)
|
||||
{
|
||||
*value = ksu_kernel_umount_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kernel_umount_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
ksu_kernel_umount_enabled = enable;
|
||||
pr_info("kernel_umount: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler kernel_umount_handler = {
|
||||
.feature_id = KSU_FEATURE_KERNEL_UMOUNT,
|
||||
.name = "kernel_umount",
|
||||
.get_handler = kernel_umount_feature_get,
|
||||
.set_handler = kernel_umount_feature_set,
|
||||
};
|
||||
|
||||
static bool should_umount(struct path *path)
|
||||
{
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) {
|
||||
pr_info("ignore global mnt namespace process: %d\n", current_uid().val);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) {
|
||||
const char *fstype = path->mnt->mnt_sb->s_type->name;
|
||||
return strcmp(fstype, "overlay") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
extern int path_umount(struct path *path, int flags);
|
||||
|
||||
static void ksu_umount_mnt(struct path *path, int flags)
|
||||
{
|
||||
int err = path_umount(path, flags);
|
||||
if (err) {
|
||||
pr_info("umount %s failed: %d\n", path->dentry->d_iname, err);
|
||||
}
|
||||
}
|
||||
|
||||
static void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.dentry != path.mnt->mnt_root) {
|
||||
// it is not root mountpoint, maybe umounted by others already.
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
ksu_umount_mnt(&path, flags);
|
||||
}
|
||||
|
||||
struct umount_tw {
|
||||
struct callback_head cb;
|
||||
const struct cred *old_cred;
|
||||
};
|
||||
|
||||
static void umount_tw_func(struct callback_head *cb)
|
||||
{
|
||||
struct umount_tw *tw = container_of(cb, struct umount_tw, cb);
|
||||
const struct cred *saved = NULL;
|
||||
if (tw->old_cred) {
|
||||
saved = override_creds(tw->old_cred);
|
||||
}
|
||||
|
||||
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
|
||||
// filter the mountpoint whose target is `/data/adb`
|
||||
ksu_umount_manager_execute_all(tw->old_cred);
|
||||
|
||||
if (saved)
|
||||
revert_creds(saved);
|
||||
|
||||
if (tw->old_cred)
|
||||
put_cred(tw->old_cred);
|
||||
|
||||
kfree(tw);
|
||||
}
|
||||
|
||||
static inline bool is_appuid(uid_t uid)
|
||||
{
|
||||
#define PER_USER_RANGE 100000
|
||||
#define FIRST_APPLICATION_UID 10000
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
|
||||
uid_t appid = uid % PER_USER_RANGE;
|
||||
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
static inline bool is_unsupported_uid(uid_t uid)
|
||||
{
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
uid_t appid = uid % 100000;
|
||||
return appid > LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
||||
{
|
||||
struct umount_tw *tw;
|
||||
|
||||
// this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it!
|
||||
if (!ksu_module_mounted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_kernel_umount_enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!is_appuid(new_uid) || is_unsupported_uid(new_uid)) {
|
||||
pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ksu_uid_should_umount(new_uid)) {
|
||||
return 0;
|
||||
} else {
|
||||
pr_info("uid: %d should not umount!\n", current_uid().val);
|
||||
}
|
||||
|
||||
// check old process's selinux context, if it is not zygote, ignore it!
|
||||
// because some su apps may setuid to untrusted_app but they are in global mount namespace
|
||||
// when we umount for such process, that is a disaster!
|
||||
bool is_zygote_child = is_zygote(get_current_cred());
|
||||
if (!is_zygote_child) {
|
||||
pr_info("handle umount ignore non zygote child: %d\n", current->pid);
|
||||
return 0;
|
||||
}
|
||||
// umount the target mnt
|
||||
pr_info("handle umount for uid: %d, pid: %d\n", new_uid, current->pid);
|
||||
|
||||
tw = kmalloc(sizeof(*tw), GFP_ATOMIC);
|
||||
if (!tw)
|
||||
return 0;
|
||||
|
||||
tw->old_cred = get_current_cred();
|
||||
tw->cb.func = umount_tw_func;
|
||||
|
||||
int err = task_work_add(current, &tw->cb, TWA_RESUME);
|
||||
if (err) {
|
||||
if (tw->old_cred) {
|
||||
put_cred(tw->old_cred);
|
||||
}
|
||||
kfree(tw);
|
||||
pr_warn("unmount add task_work failed\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_kernel_umount_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
rc = ksu_umount_manager_init();
|
||||
if (rc) {
|
||||
pr_err("Failed to initialize umount manager: %d\n", rc);
|
||||
}
|
||||
if (ksu_register_feature_handler(&kernel_umount_handler)) {
|
||||
pr_err("Failed to register kernel_umount feature handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_kernel_umount_exit(void)
|
||||
{
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT);
|
||||
}
|
||||
12
kernel/kernel_umount.h
Normal file
12
kernel/kernel_umount.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef __KSU_H_KERNEL_UMOUNT
|
||||
#define __KSU_H_KERNEL_UMOUNT
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
void ksu_kernel_umount_init(void);
|
||||
void ksu_kernel_umount_exit(void);
|
||||
|
||||
// Handler function to be called from setresuid hook
|
||||
int ksu_handle_umount(uid_t old_uid, uid_t new_uid);
|
||||
|
||||
#endif
|
||||
163
kernel/kprobe_hook_manager.c
Normal file
163
kernel/kprobe_hook_manager.c
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "kprobe_hook_manager.h"
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/uidgid.h>
|
||||
|
||||
#include "arch.h"
|
||||
#include "klog.h"
|
||||
#include "ksud.h"
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#include "manual_su.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
bool ksu_is_compat __read_mostly = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
static void ksu_try_escalate_for_uid(uid_t uid)
|
||||
{
|
||||
if (!is_pending_root(uid))
|
||||
return;
|
||||
|
||||
pr_info("pending_root: UID=%d temporarily allowed\n", uid);
|
||||
remove_pending_root(uid);
|
||||
}
|
||||
#endif
|
||||
|
||||
// inode_permission hook for handling devpts
|
||||
static int ksu_inode_permission_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct inode *inode = (struct inode *)PT_REGS_PARM1(regs);
|
||||
|
||||
if (inode && inode->i_sb && unlikely(inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC)) {
|
||||
// pr_info("%s: handling devpts for: %s \n", __func__, current->comm);
|
||||
__ksu_handle_devpts(inode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kprobe ksu_inode_permission_kp = {
|
||||
.symbol_name = "security_inode_permission",
|
||||
.pre_handler = ksu_inode_permission_handler_pre,
|
||||
};
|
||||
|
||||
|
||||
// bprm_check_security hook for handling ksud compatibility
|
||||
static int ksu_bprm_check_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct linux_binprm *bprm = (struct linux_binprm *)PT_REGS_PARM1(regs);
|
||||
char *filename = (char *)bprm->filename;
|
||||
|
||||
if (likely(!ksu_execveat_hook))
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static bool compat_check_done __read_mostly = false;
|
||||
if (unlikely(!compat_check_done) && unlikely(!strcmp(filename, "/data/adb/ksud"))
|
||||
&& !memcmp(bprm->buf, "\x7f\x45\x4c\x46", 4)) {
|
||||
if (bprm->buf[4] == 0x01)
|
||||
ksu_is_compat = true;
|
||||
|
||||
pr_info("%s: %s ELF magic found! ksu_is_compat: %d \n", __func__, filename, ksu_is_compat);
|
||||
compat_check_done = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
ksu_handle_pre_ksud(filename);
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
ksu_try_escalate_for_uid(current_uid().val);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kprobe ksu_bprm_check_kp = {
|
||||
.symbol_name = "security_bprm_check",
|
||||
.pre_handler = ksu_bprm_check_handler_pre,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
// task_alloc hook for handling manual su escalation
|
||||
static int ksu_task_alloc_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct task_struct *task = (struct task_struct *)PT_REGS_PARM1(regs);
|
||||
|
||||
ksu_try_escalate_for_uid(task_uid(task).val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kprobe ksu_task_alloc_kp = {
|
||||
.symbol_name = "security_task_alloc",
|
||||
.pre_handler = ksu_task_alloc_handler_pre,
|
||||
};
|
||||
#endif
|
||||
|
||||
__maybe_unused int ksu_kprobe_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
// Register inode_permission kprobe
|
||||
rc = register_kprobe(&ksu_inode_permission_kp);
|
||||
if (rc) {
|
||||
pr_err("inode_permission kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("inode_permission kprobe registered successfully\n");
|
||||
}
|
||||
|
||||
// Register bprm_check_security kprobe
|
||||
rc = register_kprobe(&ksu_bprm_check_kp);
|
||||
if (rc) {
|
||||
pr_err("bprm_check_security kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("bprm_check_security kprobe registered successfully\n");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
// Register task_alloc kprobe
|
||||
rc = register_kprobe(&ksu_task_alloc_kp);
|
||||
if (rc) {
|
||||
pr_err("task_alloc kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("task_alloc kprobe registered successfully\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__maybe_unused int ksu_kprobe_exit(void)
|
||||
{
|
||||
unregister_kprobe(&ksu_inode_permission_kp);
|
||||
unregister_kprobe(&ksu_bprm_check_kp);
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
unregister_kprobe(&ksu_task_alloc_kp);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_kprobe_hook_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
#ifdef CONFIG_KPROBES
|
||||
rc = ksu_kprobe_init();
|
||||
if (rc) {
|
||||
pr_err("ksu_kprobe_init failed: %d\n", rc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_kprobe_hook_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_KPROBES
|
||||
pr_info("ksu_core_exit\n");
|
||||
ksu_kprobe_exit();
|
||||
#endif
|
||||
}
|
||||
23
kernel/kprobe_hook_manager.h
Normal file
23
kernel/kprobe_hook_manager.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef __KSU_H_KSU_KBROBES_HOOK_MANAGER
|
||||
#define __KSU_H_KSU_KBROBES_HOOK_MANAGER
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/fs.h>
|
||||
#include "selinux/selinux.h"
|
||||
#include "objsec.h"
|
||||
|
||||
#ifndef DEVPTS_SUPER_MAGIC
|
||||
#define DEVPTS_SUPER_MAGIC 0x1cd1
|
||||
#endif
|
||||
|
||||
extern bool ksu_is_compat __read_mostly;
|
||||
|
||||
extern int __ksu_handle_devpts(struct inode *inode); // sucompat.c
|
||||
|
||||
void ksu_kprobe_hook_init(void);
|
||||
void ksu_kprobe_hook_exit(void);
|
||||
|
||||
#endif
|
||||
40
kernel/ksu.c
40
kernel/ksu.c
@@ -6,16 +6,17 @@
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "core_hook.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "throne_tracker.h"
|
||||
#include "sucompat.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "ksud.h"
|
||||
#include "supercalls.h"
|
||||
|
||||
#include "sulog.h"
|
||||
#include "throne_comm.h"
|
||||
#include "dynamic_manager.h"
|
||||
#include "kprobe_hook_manager.h"
|
||||
|
||||
static struct workqueue_struct *ksu_workqueue;
|
||||
|
||||
@@ -24,6 +25,22 @@ bool ksu_queue_work(struct work_struct *work)
|
||||
return queue_work(ksu_workqueue, work);
|
||||
}
|
||||
|
||||
void sukisu_custom_config_init(void)
|
||||
{
|
||||
ksu_kprobe_hook_init();
|
||||
}
|
||||
|
||||
void sukisu_custom_config_exit(void)
|
||||
{
|
||||
ksu_kprobe_hook_exit();
|
||||
ksu_uid_exit();
|
||||
ksu_throne_comm_exit();
|
||||
ksu_dynamic_manager_exit();
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_exit();
|
||||
#endif
|
||||
}
|
||||
|
||||
int __init kernelsu_init(void)
|
||||
{
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
@@ -40,7 +57,9 @@ int __init kernelsu_init(void)
|
||||
|
||||
ksu_supercalls_init();
|
||||
|
||||
ksu_core_init();
|
||||
sukisu_custom_config_init();
|
||||
|
||||
ksu_syscall_hook_manager_init();
|
||||
|
||||
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
||||
|
||||
@@ -48,8 +67,6 @@ int __init kernelsu_init(void)
|
||||
|
||||
ksu_throne_tracker_init();
|
||||
|
||||
ksu_sucompat_init();
|
||||
|
||||
#ifdef KSU_KPROBES_HOOK
|
||||
ksu_ksud_init();
|
||||
#else
|
||||
@@ -75,13 +92,16 @@ void kernelsu_exit(void)
|
||||
|
||||
destroy_workqueue(ksu_workqueue);
|
||||
|
||||
ksu_sucompat_exit();
|
||||
|
||||
#ifdef KSU_KPROBES_HOOK
|
||||
ksu_ksud_exit();
|
||||
#endif
|
||||
|
||||
ksu_core_exit();
|
||||
ksu_syscall_hook_manager_exit();
|
||||
|
||||
sukisu_custom_config_exit();
|
||||
|
||||
ksu_supercalls_exit();
|
||||
|
||||
ksu_feature_exit();
|
||||
}
|
||||
|
||||
|
||||
54
kernel/ksu.h
54
kernel/ksu.h
@@ -13,12 +13,6 @@ extern bool ksu_uid_scanner_enabled;
|
||||
#define EVENT_BOOT_COMPLETED 2
|
||||
#define EVENT_MODULE_MOUNTED 3
|
||||
|
||||
#define KSU_APP_PROFILE_VER 2
|
||||
#define KSU_MAX_PACKAGE_NAME 256
|
||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
||||
#define KSU_MAX_GROUPS 32
|
||||
#define KSU_SELINUX_DOMAIN 64
|
||||
|
||||
// SukiSU Ultra kernel su version full strings
|
||||
#ifndef KSU_VERSION_FULL
|
||||
#define KSU_VERSION_FULL "v3.x-00000000@unknown"
|
||||
@@ -47,54 +41,6 @@ struct manager_list_info {
|
||||
} managers[2];
|
||||
};
|
||||
|
||||
struct root_profile {
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
u64 effective;
|
||||
u64 permitted;
|
||||
u64 inheritable;
|
||||
} capabilities;
|
||||
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
|
||||
int32_t namespaces;
|
||||
};
|
||||
|
||||
struct non_root_profile {
|
||||
bool umount_modules;
|
||||
};
|
||||
|
||||
struct app_profile {
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
u32 version;
|
||||
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
|
||||
struct {
|
||||
bool use_default;
|
||||
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
};
|
||||
|
||||
bool ksu_queue_work(struct work_struct *work);
|
||||
|
||||
static inline int startswith(char *s, char *prefix)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/task_work.h>
|
||||
#include "manager.h"
|
||||
#include <asm/current.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/cred.h>
|
||||
@@ -15,8 +14,10 @@
|
||||
#include <linux/printk.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "manager.h"
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
@@ -24,8 +25,10 @@
|
||||
|
||||
#include "kernel_compat.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "sucompat.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
|
||||
bool ksu_module_mounted __read_mostly = false;
|
||||
bool ksu_boot_completed __read_mostly = false;
|
||||
|
||||
static const char KERNEL_SU_RC[] =
|
||||
"\n"
|
||||
@@ -92,6 +95,45 @@ void on_post_fs_data(void)
|
||||
pr_info("ksu_file sid: %d\n", ksu_file_sid);
|
||||
}
|
||||
|
||||
extern void ext4_unregister_sysfs(struct super_block *sb);
|
||||
static void nuke_ext4_sysfs(void)
|
||||
{
|
||||
#ifdef CONFIG_EXT4_FS
|
||||
struct path path;
|
||||
int err = kern_path("/data/adb/modules", 0, &path);
|
||||
if (err) {
|
||||
pr_err("nuke path err: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
struct super_block *sb = path.dentry->d_inode->i_sb;
|
||||
const char *name = sb->s_type->name;
|
||||
if (strcmp(name, "ext4") != 0) {
|
||||
pr_info("nuke but module aren't mounted\n");
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
ext4_unregister_sysfs(sb);
|
||||
path_put(&path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void on_module_mounted(void){
|
||||
pr_info("on_module_mounted!\n");
|
||||
ksu_module_mounted = true;
|
||||
nuke_ext4_sysfs();
|
||||
}
|
||||
|
||||
void on_boot_completed(void){
|
||||
ksu_boot_completed = true;
|
||||
pr_info("on_boot_completed!\n");
|
||||
// remark process, we don't want to mark other init
|
||||
// forked process excepte zygote and adbd
|
||||
ksu_unmark_all_process();
|
||||
ksu_mark_running_process();
|
||||
}
|
||||
|
||||
static void on_post_fs_data_cbfun(struct callback_head *cb)
|
||||
{
|
||||
on_post_fs_data();
|
||||
|
||||
@@ -7,10 +7,14 @@ void ksu_ksud_init();
|
||||
void ksu_ksud_exit();
|
||||
|
||||
void on_post_fs_data(void);
|
||||
void on_module_mounted(void);
|
||||
void on_boot_completed(void);
|
||||
|
||||
bool ksu_is_safe_mode(void);
|
||||
|
||||
extern u32 ksu_file_sid;
|
||||
extern bool ksu_module_mounted;
|
||||
extern bool ksu_boot_completed;
|
||||
|
||||
extern bool ksu_execveat_hook __read_mostly;
|
||||
extern int ksu_handle_pre_ksud(const char *filename);
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
#include "ksu.h"
|
||||
#include "allowlist.h"
|
||||
#include "manager.h"
|
||||
#include "app_profile.h"
|
||||
|
||||
extern void escape_to_root_for_cmd_su(uid_t, pid_t);
|
||||
static bool current_verified = false;
|
||||
static void ksu_cleanup_expired_tokens(void);
|
||||
static bool is_current_verified(void);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "selinux.h"
|
||||
#include "sepolicy.h"
|
||||
#include "ss/services.h"
|
||||
#include "linux/lsm_audit.h"
|
||||
#include "linux/lsm_audit.h" // IWYU pragma: keep
|
||||
#include "xfrm.h"
|
||||
|
||||
#define SELINUX_POLICY_INSTEAD_SELINUX_SS
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "linux/types.h"
|
||||
#include "linux/version.h"
|
||||
#include "linux/sched.h"
|
||||
#include "linux/cred.h"
|
||||
|
||||
void setup_selinux(const char *);
|
||||
|
||||
|
||||
251
kernel/setuid_hook.c
Normal file
251
kernel/setuid_hook.c
Normal file
@@ -0,0 +1,251 @@
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/task_work.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/init_task.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/uidgid.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/tty.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "setuid_hook.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "manager.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "seccomp_cache.h"
|
||||
#include "supercalls.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "kernel_umount.h"
|
||||
|
||||
#include "sulog.h"
|
||||
|
||||
static bool ksu_enhanced_security_enabled = false;
|
||||
|
||||
static int enhanced_security_feature_get(u64 *value)
|
||||
{
|
||||
*value = ksu_enhanced_security_enabled ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enhanced_security_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
ksu_enhanced_security_enabled = enable;
|
||||
pr_info("enhanced_security: set to %d\n", enable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ksu_feature_handler enhanced_security_handler = {
|
||||
.feature_id = KSU_FEATURE_ENHANCED_SECURITY,
|
||||
.name = "enhanced_security",
|
||||
.get_handler = enhanced_security_feature_get,
|
||||
.set_handler = enhanced_security_feature_set,
|
||||
};
|
||||
|
||||
static inline bool is_allow_su()
|
||||
{
|
||||
if (is_manager()) {
|
||||
// we are manager, allow!
|
||||
return true;
|
||||
}
|
||||
return ksu_is_allow_uid_for_current(current_uid().val);
|
||||
}
|
||||
|
||||
static inline bool is_unsupported_uid(uid_t uid)
|
||||
{
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
uid_t appid = uid % 100000;
|
||||
return appid > LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
// ksu_handle_prctl removed - now using ioctl via reboot hook
|
||||
|
||||
static bool is_appuid(uid_t uid)
|
||||
{
|
||||
#define PER_USER_RANGE 100000
|
||||
#define FIRST_APPLICATION_UID 10000
|
||||
#define LAST_APPLICATION_UID 19999
|
||||
|
||||
uid_t appid = uid % PER_USER_RANGE;
|
||||
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
|
||||
}
|
||||
|
||||
static bool should_umount(struct path *path)
|
||||
{
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) {
|
||||
pr_info("ignore global mnt namespace process: %d\n",
|
||||
current_uid().val);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) {
|
||||
const char *fstype = path->mnt->mnt_sb->s_type->name;
|
||||
return strcmp(fstype, "overlay") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
extern int path_umount(struct path *path, int flags);
|
||||
static void ksu_umount_mnt(struct path *path, int flags)
|
||||
{
|
||||
int err = path_umount(path, flags);
|
||||
if (err) {
|
||||
pr_info("umount %s failed: %d\n", path->dentry->d_iname, err);
|
||||
}
|
||||
}
|
||||
|
||||
static void try_umount(const char *mnt, bool check_mnt, int flags)
|
||||
{
|
||||
struct path path;
|
||||
int err = kern_path(mnt, 0, &path);
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.dentry != path.mnt->mnt_root) {
|
||||
// it is not root mountpoint, maybe umounted by others already.
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interest in some specific mounts
|
||||
if (check_mnt && !should_umount(&path)) {
|
||||
path_put(&path);
|
||||
return;
|
||||
}
|
||||
|
||||
ksu_umount_mnt(&path, flags);
|
||||
}
|
||||
|
||||
int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid)
|
||||
{
|
||||
uid_t new_uid = ruid;
|
||||
uid_t old_uid = current_uid().val;
|
||||
pr_info("handle_setuid from %d to %d\n", old_uid, new_uid);
|
||||
|
||||
if (0 != old_uid) {
|
||||
// old process is not root, ignore it.
|
||||
if (ksu_enhanced_security_enabled) {
|
||||
// disallow any non-ksu domain escalation from non-root to root!
|
||||
if (unlikely(new_uid) == 0) {
|
||||
if (!is_ksu_domain()) {
|
||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
||||
current->pid, current->comm, old_uid, new_uid);
|
||||
force_sig(SIGKILL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// disallow appuid decrease to any other uid if it is allowed to su
|
||||
if (is_appuid(old_uid)) {
|
||||
if (new_uid < old_uid && !ksu_is_allow_uid_for_current(old_uid)) {
|
||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
||||
current->pid, current->comm, old_uid, new_uid);
|
||||
force_sig(SIGKILL);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (new_uid == 2000) {
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
}
|
||||
|
||||
if (!is_appuid(new_uid) || is_unsupported_uid(new_uid)) {
|
||||
pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid);
|
||||
ksu_clear_task_tracepoint_flag(current);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if on private space, see if its possibly the manager
|
||||
if (unlikely(new_uid > 100000 && new_uid % 100000 == ksu_get_manager_uid())) {
|
||||
ksu_set_manager_uid(new_uid);
|
||||
}
|
||||
|
||||
if (unlikely(ksu_get_manager_uid() == new_uid)) {
|
||||
pr_info("install fd for manager: %d\n", new_uid);
|
||||
ksu_install_fd();
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
||||
#else
|
||||
// we dont have those new fancy things upstream has
|
||||
// lets just do original thing where we disable seccomp
|
||||
disable_seccomp();
|
||||
#endif
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (unlikely(ksu_is_allow_uid_for_current(new_uid))) {
|
||||
if (current->seccomp.mode == SECCOMP_MODE_FILTER &&
|
||||
current->seccomp.filter) {
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
||||
#else
|
||||
// we don't have those new fancy things upstream has
|
||||
// lets just do original thing where we disable seccomp
|
||||
disable_seccomp();
|
||||
#endif
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
}
|
||||
ksu_set_task_tracepoint_flag(current);
|
||||
} else {
|
||||
ksu_clear_task_tracepoint_flag(current);
|
||||
}
|
||||
|
||||
// Handle kernel umount
|
||||
ksu_handle_umount(old_uid, new_uid);
|
||||
|
||||
#if __SULOG_GATE
|
||||
ksu_sulog_report_syscall(new_uid, NULL, "setuid", NULL);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_setuid_hook_init(void)
|
||||
{
|
||||
ksu_kernel_umount_init();
|
||||
if (ksu_register_feature_handler(&enhanced_security_handler)) {
|
||||
pr_err("Failed to register enhanced security feature handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_setuid_hook_exit(void)
|
||||
{
|
||||
pr_info("ksu_core_exit\n");
|
||||
ksu_kernel_umount_exit();
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY);
|
||||
}
|
||||
14
kernel/setuid_hook.h
Normal file
14
kernel/setuid_hook.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef __KSU_H_KSU_CORE
|
||||
#define __KSU_H_KSU_CORE
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include "apk_sign.h"
|
||||
#include <linux/thread_info.h>
|
||||
|
||||
void ksu_setuid_hook_init(void);
|
||||
void ksu_setuid_hook_exit(void);
|
||||
|
||||
int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid);
|
||||
|
||||
#endif
|
||||
@@ -1,85 +1,30 @@
|
||||
#include "linux/compiler.h"
|
||||
#include "linux/printk.h"
|
||||
#include "linux/sched.h"
|
||||
#include "selinux/selinux.h"
|
||||
#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/tracepoint.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/sched/task_stack.h>
|
||||
#include <asm/syscall.h>
|
||||
#include <trace/events/syscalls.h>
|
||||
#include <linux/ptrace.h>
|
||||
|
||||
#include "objsec.h"
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "sucompat.h"
|
||||
#include "core_hook.h"
|
||||
#include "app_profile.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
|
||||
|
||||
#include "sulog.h"
|
||||
#include "kprobe_hook_manager.h"
|
||||
|
||||
#define SU_PATH "/system/bin/su"
|
||||
#define SH_PATH "/system/bin/sh"
|
||||
|
||||
bool ksu_su_compat_enabled __read_mostly = true;
|
||||
extern void escape_to_root();
|
||||
void ksu_sucompat_enable();
|
||||
void ksu_sucompat_disable();
|
||||
|
||||
void ksu_mark_running_process(void)
|
||||
{
|
||||
struct task_struct *p, *t;
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread (p, t) {
|
||||
if (!t->mm) { // only user processes
|
||||
continue;
|
||||
}
|
||||
int uid = task_uid(t).val;
|
||||
bool ksu_root_process =
|
||||
uid == 0 && is_task_ksu_domain(get_task_cred(t));
|
||||
if (ksu_root_process || ksu_is_allow_uid(uid)) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
pr_info("sucompat: mark process: pid:%d, uid: %d, comm:%s\n",
|
||||
t->pid, uid, t->comm);
|
||||
}
|
||||
}
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
static void handle_process_mark(bool mark)
|
||||
{
|
||||
struct task_struct *p, *t;
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread(p, t) {
|
||||
if (mark)
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
else
|
||||
ksu_clear_task_tracepoint_flag(t);
|
||||
}
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
static void mark_all_process(void)
|
||||
{
|
||||
handle_process_mark(true);
|
||||
pr_info("sucompat: mark all user process done!\n");
|
||||
}
|
||||
|
||||
static void unmark_all_process(void)
|
||||
{
|
||||
handle_process_mark(false);
|
||||
pr_info("sucompat: unmark all user process done!\n");
|
||||
}
|
||||
|
||||
static int su_compat_feature_get(u64 *value)
|
||||
{
|
||||
@@ -90,21 +35,8 @@ static int su_compat_feature_get(u64 *value)
|
||||
static int su_compat_feature_set(u64 value)
|
||||
{
|
||||
bool enable = value != 0;
|
||||
|
||||
if (enable == ksu_su_compat_enabled) {
|
||||
pr_info("su_compat: no need to change\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
ksu_sucompat_enable();
|
||||
} else {
|
||||
ksu_sucompat_disable();
|
||||
}
|
||||
|
||||
ksu_su_compat_enabled = enable;
|
||||
pr_info("su_compat: set to %d\n", enable);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -115,10 +47,6 @@ static const struct ksu_feature_handler su_compat_handler = {
|
||||
.set_handler = su_compat_feature_set,
|
||||
};
|
||||
|
||||
#ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
static bool ksu_sucompat_hook_state __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
|
||||
@@ -148,7 +76,7 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
if (!ksu_su_compat_enabled) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -178,7 +106,7 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
if (!ksu_su_compat_enabled) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -230,7 +158,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
if (!ksu_su_compat_enabled) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -263,7 +191,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
pr_info("do_execveat_common su found\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
|
||||
escape_to_root();
|
||||
escape_with_root_profile();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -282,7 +210,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
char path[sizeof(su) + 1];
|
||||
|
||||
#ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
if (!ksu_sucompat_hook_state){
|
||||
if (!ksu_su_compat_enabled){
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -312,7 +240,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
pr_info("sys_execve su found\n");
|
||||
*filename_user = ksud_user_path();
|
||||
|
||||
escape_to_root();
|
||||
escape_with_root_profile();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -321,7 +249,7 @@ int __ksu_handle_devpts(struct inode *inode)
|
||||
{
|
||||
|
||||
#ifndef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
if (!ksu_sucompat_hook_state)
|
||||
if (!ksu_su_compat_enabled)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
@@ -346,185 +274,15 @@ int __ksu_handle_devpts(struct inode *inode)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
|
||||
// Tracepoint probe for sys_enter
|
||||
static void sucompat_sys_enter_handler(void *data, struct pt_regs *regs,
|
||||
long id)
|
||||
{
|
||||
// Handle newfstatat
|
||||
if (unlikely(id == __NR_newfstatat)) {
|
||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM2(regs);
|
||||
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(regs);
|
||||
ksu_handle_stat(dfd, filename_user, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle faccessat
|
||||
if (unlikely(id == __NR_faccessat)) {
|
||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM2(regs);
|
||||
int *mode = (int *)&PT_REGS_PARM3(regs);
|
||||
ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle execve
|
||||
if (unlikely(id == __NR_execve)) {
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM1(regs);
|
||||
ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
|
||||
static struct kretprobe *init_kretprobe(const char *name,
|
||||
kretprobe_handler_t handler)
|
||||
{
|
||||
struct kretprobe *rp = kzalloc(sizeof(struct kretprobe), GFP_KERNEL);
|
||||
if (!rp)
|
||||
return NULL;
|
||||
rp->kp.symbol_name = name;
|
||||
rp->handler = handler;
|
||||
rp->data_size = 0;
|
||||
rp->maxactive = 0;
|
||||
|
||||
int ret = register_kretprobe(rp);
|
||||
pr_info("sucompat: register_%s kretprobe: %d\n", name, ret);
|
||||
if (ret) {
|
||||
kfree(rp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return rp;
|
||||
}
|
||||
|
||||
static void destroy_kretprobe(struct kretprobe **rp_ptr)
|
||||
{
|
||||
struct kretprobe *rp = *rp_ptr;
|
||||
if (!rp)
|
||||
return;
|
||||
unregister_kretprobe(rp);
|
||||
synchronize_rcu();
|
||||
kfree(rp);
|
||||
*rp_ptr = NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
|
||||
static int tracepoint_reg_count = 0;
|
||||
static DEFINE_SPINLOCK(tracepoint_reg_lock);
|
||||
|
||||
static int syscall_regfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
if (tracepoint_reg_count < 1) {
|
||||
// while install our tracepoint, mark our processes
|
||||
unmark_all_process();
|
||||
ksu_mark_running_process();
|
||||
} else {
|
||||
// while installing other tracepoint, mark all processes
|
||||
mark_all_process();
|
||||
}
|
||||
tracepoint_reg_count++;
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int syscall_unregfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
if (tracepoint_reg_count <= 1) {
|
||||
// while uninstall our tracepoint, unmark all processes
|
||||
unmark_all_process();
|
||||
} else {
|
||||
// while uninstalling other tracepoint, mark our processes
|
||||
unmark_all_process();
|
||||
ksu_mark_running_process();
|
||||
}
|
||||
tracepoint_reg_count--;
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kretprobe *syscall_regfunc_rp = NULL;
|
||||
static struct kretprobe *syscall_unregfunc_rp = NULL;
|
||||
#endif
|
||||
|
||||
void ksu_sucompat_enable()
|
||||
{
|
||||
int ret;
|
||||
pr_info("sucompat: ksu_sucompat_enable called\n");
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
// Register kretprobe for syscall_regfunc
|
||||
syscall_regfunc_rp = init_kretprobe("syscall_regfunc", syscall_regfunc_handler);
|
||||
// Register kretprobe for syscall_unregfunc
|
||||
syscall_unregfunc_rp = init_kretprobe("syscall_unregfunc", syscall_unregfunc_handler);
|
||||
#endif
|
||||
|
||||
#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
ret = register_trace_sys_enter(sucompat_sys_enter_handler, NULL);
|
||||
#ifndef CONFIG_KRETPROBES
|
||||
unmark_all_process();
|
||||
ksu_mark_running_process();
|
||||
#endif
|
||||
if (ret) {
|
||||
pr_err("sucompat: failed to register sys_enter tracepoint: %d\n", ret);
|
||||
} else {
|
||||
pr_info("sucompat: sys_enter tracepoint registered\n");
|
||||
}
|
||||
#else
|
||||
ksu_sucompat_hook_state = true;
|
||||
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_sucompat_disable()
|
||||
{
|
||||
pr_info("sucompat: ksu_sucompat_disable called\n");
|
||||
#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
unregister_trace_sys_enter(sucompat_sys_enter_handler, NULL);
|
||||
tracepoint_synchronize_unregister();
|
||||
pr_info("sucompat: sys_enter tracepoint unregistered\n");
|
||||
#else
|
||||
ksu_sucompat_hook_state = false;
|
||||
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
destroy_kretprobe(&syscall_regfunc_rp);
|
||||
destroy_kretprobe(&syscall_unregfunc_rp);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// sucompat: permited process can execute 'su' to gain root access.
|
||||
void ksu_sucompat_init()
|
||||
{
|
||||
if (ksu_register_feature_handler(&su_compat_handler)) {
|
||||
pr_err("Failed to register su_compat feature handler\n");
|
||||
}
|
||||
if (ksu_su_compat_enabled) {
|
||||
ksu_sucompat_enable();
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_sucompat_exit()
|
||||
{
|
||||
if (ksu_su_compat_enabled) {
|
||||
ksu_sucompat_disable();
|
||||
}
|
||||
ksu_unregister_feature_handler(KSU_FEATURE_SU_COMPAT);
|
||||
}
|
||||
@@ -1,34 +1,18 @@
|
||||
#ifndef __KSU_H_SUCOMPAT
|
||||
#define __KSU_H_SUCOMPAT
|
||||
#include <linux/sched.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
extern bool ksu_su_compat_enabled;
|
||||
|
||||
void ksu_sucompat_init(void);
|
||||
void ksu_sucompat_exit(void);
|
||||
|
||||
void ksu_sucompat_enable(void);
|
||||
void ksu_sucompat_disable(void);
|
||||
// Handler functions exported for hook_manager
|
||||
int ksu_handle_faccessat(int *dfd, const char __user **filename_user,
|
||||
int *mode, int *__unused_flags);
|
||||
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||
int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags);
|
||||
|
||||
void ksu_mark_running_process(void);
|
||||
|
||||
static inline void ksu_set_task_tracepoint_flag(struct task_struct *t)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
set_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
||||
#else
|
||||
set_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void ksu_clear_task_tracepoint_flag(struct task_struct *t)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
clear_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
||||
#else
|
||||
clear_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "arch.h"
|
||||
#include "allowlist.h"
|
||||
#include "feature.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
@@ -17,9 +18,9 @@
|
||||
#include "manager.h"
|
||||
#include "sulog.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "core_hook.h"
|
||||
#include "objsec.h"
|
||||
#include "file_wrapper.h"
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "throne_comm.h"
|
||||
#include "dynamic_manager.h"
|
||||
#include "umount_manager.h"
|
||||
@@ -78,7 +79,7 @@ static int do_grant_root(void __user *arg)
|
||||
// we already check uid above on allowed_for_su()
|
||||
|
||||
pr_info("allow root for: %d\n", current_uid().val);
|
||||
escape_to_root();
|
||||
escape_with_root_profile();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -131,13 +132,13 @@ static int do_report_event(void __user *arg)
|
||||
if (!boot_complete_lock) {
|
||||
boot_complete_lock = true;
|
||||
pr_info("boot_complete triggered\n");
|
||||
on_boot_completed();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVENT_MODULE_MOUNTED: {
|
||||
ksu_module_mounted = true;
|
||||
pr_info("module mounted!\n");
|
||||
nuke_ext4_sysfs();
|
||||
on_module_mounted();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -409,6 +410,71 @@ put_orig_file:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_manage_mark(void __user *arg)
|
||||
{
|
||||
struct ksu_manage_mark_cmd cmd;
|
||||
int ret = 0;
|
||||
|
||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
||||
pr_err("manage_mark: copy_from_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (cmd.operation) {
|
||||
case KSU_MARK_GET: {
|
||||
// Get task mark status
|
||||
ret = ksu_get_task_mark(cmd.pid);
|
||||
if (ret < 0) {
|
||||
pr_err("manage_mark: get failed for pid %d: %d\n", cmd.pid, ret);
|
||||
return ret;
|
||||
}
|
||||
cmd.result = (u32)ret;
|
||||
break;
|
||||
}
|
||||
case KSU_MARK_MARK: {
|
||||
if (cmd.pid == 0) {
|
||||
ksu_mark_all_process();
|
||||
} else {
|
||||
ret = ksu_set_task_mark(cmd.pid, true);
|
||||
if (ret < 0) {
|
||||
pr_err("manage_mark: set_mark failed for pid %d: %d\n", cmd.pid,
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KSU_MARK_UNMARK: {
|
||||
if (cmd.pid == 0) {
|
||||
ksu_unmark_all_process();
|
||||
} else {
|
||||
ret = ksu_set_task_mark(cmd.pid, false);
|
||||
if (ret < 0) {
|
||||
pr_err("manage_mark: set_unmark failed for pid %d: %d\n",
|
||||
cmd.pid, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KSU_MARK_REFRESH: {
|
||||
ksu_mark_running_process();
|
||||
pr_info("manage_mark: refreshed running processes\n");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pr_err("manage_mark: invalid operation %u\n", cmd.operation);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
|
||||
pr_err("manage_mark: copy_to_user failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 100. GET_FULL_VERSION - Get full version string
|
||||
static int do_get_full_version(void __user *arg)
|
||||
{
|
||||
@@ -656,6 +722,7 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
|
||||
{ .cmd = KSU_IOCTL_GET_FEATURE, .name = "GET_FEATURE", .handler = do_get_feature, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_SET_FEATURE, .name = "SET_FEATURE", .handler = do_set_feature, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_WRAPPER_FD, .name = "GET_WRAPPER_FD", .handler = do_get_wrapper_fd, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_MANAGE_MARK, .name = "MANAGE_MARK", .handler = do_manage_mark, .perm_check = manager_or_root },
|
||||
{ .cmd = KSU_IOCTL_GET_FULL_VERSION,.name = "GET_FULL_VERSION", .handler = do_get_full_version, .perm_check = always_allow},
|
||||
{ .cmd = KSU_IOCTL_HOOK_TYPE,.name = "GET_HOOK_TYPE", .handler = do_get_hook_type, .perm_check = manager_or_root},
|
||||
{ .cmd = KSU_IOCTL_ENABLE_KPM, .name = "GET_ENABLE_KPM", .handler = do_enable_kpm, .perm_check = manager_or_root},
|
||||
@@ -672,6 +739,52 @@ static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
|
||||
{ .cmd = 0, .name = NULL, .handler = NULL, .perm_check = NULL} // Sentine
|
||||
};
|
||||
|
||||
// downstream: make sure to pass arg as reference, this can allow us to extend things.
|
||||
int ksu_handle_sys_reboot(int magic1, int magic2, unsigned int cmd, void __user **arg)
|
||||
{
|
||||
|
||||
if (magic1 != KSU_INSTALL_MAGIC1)
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("sys_reboot: intercepted call! magic: 0x%x id: %d\n", magic1, magic2);
|
||||
#endif
|
||||
|
||||
// Check if this is a request to install KSU fd
|
||||
if (magic2 == KSU_INSTALL_MAGIC2) {
|
||||
int fd = ksu_install_fd();
|
||||
pr_info("[%d] install ksu fd: %d\n", current->pid, fd);
|
||||
|
||||
// downstream: dereference all arg usage!
|
||||
if (copy_to_user((void __user *)*arg, &fd, sizeof(fd))) {
|
||||
pr_err("install ksu fd reply err\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// extensions
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reboot hook for installing fd
|
||||
static int reboot_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int magic1 = (int)PT_REGS_PARM1(real_regs);
|
||||
int magic2 = (int)PT_REGS_PARM2(real_regs);
|
||||
int cmd = (int)PT_REGS_PARM3(real_regs);
|
||||
void __user **arg = (void __user **)&PT_REGS_SYSCALL_PARM4(real_regs);
|
||||
|
||||
return ksu_handle_sys_reboot(magic1, magic2, cmd, arg);
|
||||
}
|
||||
|
||||
static struct kprobe reboot_kp = {
|
||||
.symbol_name = REBOOT_SYMBOL,
|
||||
.pre_handler = reboot_handler_pre,
|
||||
};
|
||||
|
||||
void ksu_supercalls_init(void)
|
||||
{
|
||||
int i;
|
||||
@@ -680,6 +793,17 @@ void ksu_supercalls_init(void)
|
||||
for (i = 0; ksu_ioctl_handlers[i].handler; i++) {
|
||||
pr_info(" %-18s = 0x%08x\n", ksu_ioctl_handlers[i].name, ksu_ioctl_handlers[i].cmd);
|
||||
}
|
||||
|
||||
int rc = register_kprobe(&reboot_kp);
|
||||
if (rc) {
|
||||
pr_err("reboot kprobe failed: %d\n", rc);
|
||||
} else {
|
||||
pr_info("reboot kprobe registered successfully\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_supercalls_exit(void){
|
||||
unregister_kprobe(&reboot_kp);
|
||||
}
|
||||
|
||||
static inline void ksu_ioctl_audit(unsigned int cmd, const char *cmd_name, uid_t uid, int ret)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include "ksu.h"
|
||||
#include "app_profile.h"
|
||||
|
||||
#ifdef CONFIG_KPM
|
||||
#include "kpm/kpm.h"
|
||||
@@ -82,6 +83,17 @@ struct ksu_get_wrapper_fd_cmd {
|
||||
__u32 flags; // Input: flags of userspace fd
|
||||
};
|
||||
|
||||
struct ksu_manage_mark_cmd {
|
||||
__u32 operation; // Input: KSU_MARK_*
|
||||
__s32 pid; // Input: target pid (0 for all processes)
|
||||
__u32 result; // Output: for get operation - mark status or reg_count
|
||||
};
|
||||
|
||||
#define KSU_MARK_GET 1
|
||||
#define KSU_MARK_MARK 2
|
||||
#define KSU_MARK_UNMARK 3
|
||||
#define KSU_MARK_REFRESH 4
|
||||
|
||||
// Other command structures
|
||||
struct ksu_get_full_version_cmd {
|
||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
||||
@@ -134,6 +146,7 @@ struct ksu_manual_su_cmd {
|
||||
#define KSU_IOCTL_GET_FEATURE _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0)
|
||||
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0)
|
||||
#define KSU_IOCTL_GET_WRAPPER_FD _IOC(_IOC_WRITE, 'K', 15, 0)
|
||||
#define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
|
||||
// Other IOCTL command definitions
|
||||
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
|
||||
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)
|
||||
@@ -161,6 +174,7 @@ struct ksu_ioctl_cmd_map {
|
||||
// Install KSU fd to current process
|
||||
int ksu_install_fd(void);
|
||||
|
||||
void ksu_supercalls_init();
|
||||
void ksu_supercalls_init(void);
|
||||
void ksu_supercalls_exit(void);
|
||||
|
||||
#endif // __KSU_H_SUPERCALLS
|
||||
333
kernel/syscall_hook_manager.c
Normal file
333
kernel/syscall_hook_manager.c
Normal file
@@ -0,0 +1,333 @@
|
||||
#include "linux/compiler.h"
|
||||
#include "linux/cred.h"
|
||||
#include "linux/printk.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/tracepoint.h>
|
||||
#include <asm/syscall.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <trace/events/syscalls.h>
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "arch.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "syscall_hook_manager.h"
|
||||
#include "sucompat.h"
|
||||
#include "setuid_hook.h"
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
// Tracepoint registration count management
|
||||
static int tracepoint_reg_count = 0;
|
||||
static DEFINE_SPINLOCK(tracepoint_reg_lock);
|
||||
|
||||
// Process marking management
|
||||
static void handle_process_mark(bool mark)
|
||||
{
|
||||
struct task_struct *p, *t;
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread(p, t) {
|
||||
if (mark)
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
else
|
||||
ksu_clear_task_tracepoint_flag(t);
|
||||
}
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
void ksu_mark_all_process(void)
|
||||
{
|
||||
handle_process_mark(true);
|
||||
pr_info("hook_manager: mark all user process done!\n");
|
||||
}
|
||||
|
||||
void ksu_unmark_all_process(void)
|
||||
{
|
||||
handle_process_mark(false);
|
||||
pr_info("hook_manager: unmark all user process done!\n");
|
||||
}
|
||||
|
||||
void ksu_mark_running_process(void)
|
||||
{
|
||||
struct task_struct *p, *t;
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread (p, t) {
|
||||
if (!t->mm) { // only user processes
|
||||
continue;
|
||||
}
|
||||
int uid = task_uid(t).val;
|
||||
const struct cred *cred = get_task_cred(t);
|
||||
bool ksu_root_process =
|
||||
uid == 0 && is_task_ksu_domain(cred);
|
||||
bool is_zygote_process = is_zygote(cred);
|
||||
bool is_shell = uid == 2000;
|
||||
// before boot completed, we shall mark init for marking zygote
|
||||
bool is_init = t->pid == 1;
|
||||
if (ksu_root_process || is_zygote_process || is_shell || is_init
|
||||
|| ksu_is_allow_uid(uid)) {
|
||||
ksu_set_task_tracepoint_flag(t);
|
||||
pr_info("hook_manager: mark process: pid:%d, uid: %d, comm:%s\n",
|
||||
t->pid, uid, t->comm);
|
||||
}
|
||||
put_cred(cred);
|
||||
}
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
// Get task mark status
|
||||
// Returns: 1 if marked, 0 if not marked, -ESRCH if task not found
|
||||
int ksu_get_task_mark(pid_t pid)
|
||||
{
|
||||
struct task_struct *task;
|
||||
int marked = -ESRCH;
|
||||
|
||||
rcu_read_lock();
|
||||
task = find_task_by_vpid(pid);
|
||||
if (task) {
|
||||
get_task_struct(task);
|
||||
rcu_read_unlock();
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
marked = test_task_syscall_work(task, SYSCALL_TRACEPOINT) ? 1 : 0;
|
||||
#else
|
||||
marked = test_tsk_thread_flag(task, TIF_SYSCALL_TRACEPOINT) ? 1 : 0;
|
||||
#endif
|
||||
put_task_struct(task);
|
||||
} else {
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
return marked;
|
||||
}
|
||||
|
||||
// Set task mark status
|
||||
// Returns: 0 on success, -ESRCH if task not found
|
||||
int ksu_set_task_mark(pid_t pid, bool mark)
|
||||
{
|
||||
struct task_struct *task;
|
||||
int ret = -ESRCH;
|
||||
|
||||
rcu_read_lock();
|
||||
task = find_task_by_vpid(pid);
|
||||
if (task) {
|
||||
get_task_struct(task);
|
||||
rcu_read_unlock();
|
||||
if (mark) {
|
||||
ksu_set_task_tracepoint_flag(task);
|
||||
pr_info("hook_manager: marked task pid=%d comm=%s\n", pid, task->comm);
|
||||
} else {
|
||||
ksu_clear_task_tracepoint_flag(task);
|
||||
pr_info("hook_manager: unmarked task pid=%d comm=%s\n", pid, task->comm);
|
||||
}
|
||||
put_task_struct(task);
|
||||
ret = 0;
|
||||
} else {
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
|
||||
static struct kretprobe *init_kretprobe(const char *name,
|
||||
kretprobe_handler_t handler)
|
||||
{
|
||||
struct kretprobe *rp = kzalloc(sizeof(struct kretprobe), GFP_KERNEL);
|
||||
if (!rp)
|
||||
return NULL;
|
||||
rp->kp.symbol_name = name;
|
||||
rp->handler = handler;
|
||||
rp->data_size = 0;
|
||||
rp->maxactive = 0;
|
||||
|
||||
int ret = register_kretprobe(rp);
|
||||
pr_info("hook_manager: register_%s kretprobe: %d\n", name, ret);
|
||||
if (ret) {
|
||||
kfree(rp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return rp;
|
||||
}
|
||||
|
||||
static void destroy_kretprobe(struct kretprobe **rp_ptr)
|
||||
{
|
||||
struct kretprobe *rp = *rp_ptr;
|
||||
if (!rp)
|
||||
return;
|
||||
unregister_kretprobe(rp);
|
||||
synchronize_rcu();
|
||||
kfree(rp);
|
||||
*rp_ptr = NULL;
|
||||
}
|
||||
|
||||
static int syscall_regfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
if (tracepoint_reg_count < 1) {
|
||||
// while install our tracepoint, mark our processes
|
||||
ksu_unmark_all_process();
|
||||
ksu_mark_running_process();
|
||||
} else {
|
||||
// while installing other tracepoint, mark all processes
|
||||
ksu_mark_all_process();
|
||||
}
|
||||
tracepoint_reg_count++;
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int syscall_unregfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
||||
if (tracepoint_reg_count <= 1) {
|
||||
// while uninstall our tracepoint, unmark all processes
|
||||
ksu_unmark_all_process();
|
||||
} else {
|
||||
// while uninstalling other tracepoint, mark our processes
|
||||
ksu_unmark_all_process();
|
||||
ksu_mark_running_process();
|
||||
}
|
||||
tracepoint_reg_count--;
|
||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kretprobe *syscall_regfunc_rp = NULL;
|
||||
static struct kretprobe *syscall_unregfunc_rp = NULL;
|
||||
#endif
|
||||
|
||||
static inline bool check_syscall_fastpath(int nr)
|
||||
{
|
||||
switch (nr) {
|
||||
case __NR_newfstatat:
|
||||
case __NR_faccessat:
|
||||
case __NR_execve:
|
||||
case __NR_setresuid:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int ksu_handle_init_mark_tracker(int *fd, const char __user **filename_user,
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags)
|
||||
{
|
||||
char path[64];
|
||||
|
||||
if (unlikely(!filename_user))
|
||||
return 0;
|
||||
|
||||
memset(path, 0, sizeof(path));
|
||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
if (likely(strstr(path, "adbd") == NULL)){
|
||||
ksu_clear_task_tracepoint_flag(current);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
// Generic sys_enter handler that dispatches to specific handlers
|
||||
static void ksu_sys_enter_handler(void *data, struct pt_regs *regs, long id)
|
||||
{
|
||||
if (unlikely(check_syscall_fastpath(id))) {
|
||||
if (ksu_su_compat_enabled) {
|
||||
// Handle newfstatat
|
||||
if (id == __NR_newfstatat) {
|
||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM2(regs);
|
||||
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(regs);
|
||||
ksu_handle_stat(dfd, filename_user, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle faccessat
|
||||
if (id == __NR_faccessat) {
|
||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM2(regs);
|
||||
int *mode = (int *)&PT_REGS_PARM3(regs);
|
||||
ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle execve
|
||||
if (id == __NR_execve) {
|
||||
const char __user **filename_user =
|
||||
(const char __user **)&PT_REGS_PARM1(regs);
|
||||
if (current->pid == 1) {
|
||||
ksu_handle_init_mark_tracker(AT_FDCWD, filename_user,
|
||||
NULL, NULL, NULL);
|
||||
} else {
|
||||
ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL,
|
||||
NULL, NULL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle setresuid
|
||||
if (id == __NR_setresuid) {
|
||||
uid_t ruid = (uid_t)PT_REGS_PARM1(regs);
|
||||
uid_t euid = (uid_t)PT_REGS_PARM2(regs);
|
||||
uid_t suid = (uid_t)PT_REGS_PARM3(regs);
|
||||
ksu_handle_setresuid(ruid, euid, suid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ksu_syscall_hook_manager_init(void)
|
||||
{
|
||||
int ret;
|
||||
pr_info("hook_manager: ksu_hook_manager_init called\n");
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
// Register kretprobe for syscall_regfunc
|
||||
syscall_regfunc_rp = init_kretprobe("syscall_regfunc", syscall_regfunc_handler);
|
||||
// Register kretprobe for syscall_unregfunc
|
||||
syscall_unregfunc_rp = init_kretprobe("syscall_unregfunc", syscall_unregfunc_handler);
|
||||
#endif
|
||||
|
||||
#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
ret = register_trace_sys_enter(ksu_sys_enter_handler, NULL);
|
||||
#ifndef CONFIG_KRETPROBES
|
||||
unmark_all_process();
|
||||
ksu_mark_running_process();
|
||||
#endif
|
||||
if (ret) {
|
||||
pr_err("hook_manager: failed to register sys_enter tracepoint: %d\n", ret);
|
||||
} else {
|
||||
pr_info("hook_manager: sys_enter tracepoint registered\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
ksu_setuid_hook_init();
|
||||
ksu_sucompat_init();
|
||||
}
|
||||
|
||||
void ksu_syscall_hook_manager_exit(void)
|
||||
{
|
||||
pr_info("hook_manager: ksu_hook_manager_exit called\n");
|
||||
#ifdef KSU_HAVE_SYSCALL_TRACEPOINTS_HOOK
|
||||
unregister_trace_sys_enter(ksu_sys_enter_handler, NULL);
|
||||
tracepoint_synchronize_unregister();
|
||||
pr_info("hook_manager: sys_enter tracepoint unregistered\n");
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
destroy_kretprobe(&syscall_regfunc_rp);
|
||||
destroy_kretprobe(&syscall_unregfunc_rp);
|
||||
#endif
|
||||
|
||||
ksu_sucompat_exit();
|
||||
ksu_setuid_hook_exit();
|
||||
}
|
||||
40
kernel/syscall_hook_manager.h
Normal file
40
kernel/syscall_hook_manager.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef __KSU_H_HOOK_MANAGER
|
||||
#define __KSU_H_HOOK_MANAGER
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/thread_info.h>
|
||||
|
||||
// Hook manager initialization and cleanup
|
||||
void ksu_syscall_hook_manager_init(void);
|
||||
void ksu_syscall_hook_manager_exit(void);
|
||||
|
||||
// Process marking for tracepoint
|
||||
void ksu_mark_all_process(void);
|
||||
void ksu_unmark_all_process(void);
|
||||
void ksu_mark_running_process(void);
|
||||
|
||||
// Per-task mark operations
|
||||
int ksu_get_task_mark(pid_t pid);
|
||||
int ksu_set_task_mark(pid_t pid, bool mark);
|
||||
|
||||
|
||||
static inline void ksu_set_task_tracepoint_flag(struct task_struct *t)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
set_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
||||
#else
|
||||
set_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void ksu_clear_task_tracepoint_flag(struct task_struct *t)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
clear_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
||||
#else
|
||||
clear_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include "allowlist.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "manager.h"
|
||||
#include "throne_tracker.h"
|
||||
#include "apk_sign.h"
|
||||
|
||||
Reference in New Issue
Block a user