From b8e463b532180ac5aa442f11a26354872e67ce5c Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:21:58 +0800 Subject: [PATCH] Kernel: Implement sulog for enhanced logging of SU attempts and permissions --- kernel/Makefile | 1 + kernel/core_hook.c | 18 +- kernel/kernel_compat.c | 37 ++++ kernel/kernel_compat.h | 4 + kernel/sucompat.c | 19 +- kernel/sulog.c | 393 +++++++++++++++++++++++++++++++++++++++++ kernel/sulog.h | 20 +++ 7 files changed, 489 insertions(+), 3 deletions(-) create mode 100644 kernel/sulog.c create mode 100644 kernel/sulog.h diff --git a/kernel/Makefile b/kernel/Makefile index 98f3d4f2..d0ebd02d 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -8,6 +8,7 @@ kernelsu-objs += ksud.o kernelsu-objs += embed_ksud.o kernelsu-objs += kernel_compat.o kernelsu-objs += throne_comm.o +kernelsu-objs += sulog.o ifeq ($(CONFIG_KSU_MANUAL_SU), y) kernelsu-objs += manual_su.o endif diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 16225532..38a83315 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -54,6 +54,7 @@ #include "throne_comm.h" #include "kernel_compat.h" #include "dynamic_manager.h" +#include "sulog.h" #ifdef CONFIG_KSU_MANUAL_SU #include "manual_su.h" @@ -253,6 +254,7 @@ static void disable_seccomp(struct task_struct *tsk) void escape_to_root(void) { struct cred *newcreds; + uid_t original_uid = current_euid().val; if (current_euid().val == 0) { pr_warn("Already root, don't escape!\n"); @@ -262,6 +264,7 @@ void escape_to_root(void) newcreds = prepare_creds(); if (newcreds == NULL) { pr_err("%s: failed to allocate new cred.\n", __func__); + ksu_sulog_report_su_grant(original_uid, NULL, "escape_to_root_failed"); return; } @@ -302,6 +305,8 @@ void escape_to_root(void) spin_unlock_irq(¤t->sighand->siglock); setup_selinux(profile->selinux_domain); + + ksu_sulog_report_su_grant(original_uid, NULL, "escape_to_root"); } #ifdef CONFIG_KSU_MANUAL_SU @@ -341,6 +346,7 @@ void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid) if (!target_task) { rcu_read_unlock(); pr_err("cmd_su: target task not found for PID: %d\n", target_pid); + ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found"); return; } get_task_struct(target_task); @@ -355,6 +361,7 @@ void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid) newcreds = prepare_kernel_cred(target_task); if (newcreds == NULL) { pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid); + ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed"); put_task_struct(target_task); return; } @@ -405,6 +412,7 @@ void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid) put_task_struct(target_task); + ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation"); pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid); } #endif @@ -579,7 +587,10 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, } if (arg2 == CMD_GRANT_ROOT) { - if (is_allow_su()) { + bool is_allowed = is_allow_su(); + ksu_sulog_report_permission_check(current_uid().val, current->comm, is_allowed); + + if (is_allowed) { pr_info("allow root for: %d\n", current_uid().val); escape_to_root(); if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { @@ -686,6 +697,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, susfs_on_post_fs_data(); #endif on_post_fs_data(); + ksu_sulog_init(); // Initialize UID scanner if enabled init_uid_scanner(); // Initializing Dynamic Signatures @@ -1149,6 +1161,9 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, // todo: validate the params if (ksu_set_app_profile(&profile, true)) { + ksu_sulog_report_manager_operation("SET_APP_PROFILE", + current_uid().val, profile.current_uid); + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { pr_err("prctl reply error, cmd: %lu\n", arg2); } @@ -1873,6 +1888,7 @@ void ksu_core_exit(void) { ksu_uid_exit(); ksu_throne_comm_exit(); + ksu_sulog_exit(); #ifdef CONFIG_KSU_KPROBES_HOOK pr_info("ksu_core_kprobe_exit\n"); diff --git a/kernel/kernel_compat.c b/kernel/kernel_compat.c index 440ca238..af08c615 100644 --- a/kernel/kernel_compat.c +++ b/kernel/kernel_compat.c @@ -238,3 +238,40 @@ long ksu_copy_from_user_nofault(void *dst, const void __user *src, size_t size) return 0; #endif } + +int ksu_vfs_unlink(struct inode *dir, struct dentry *dentry) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + struct inode *delegated_inode = NULL; + return vfs_unlink(&nop_mnt_idmap, dir, dentry, &delegated_inode); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + struct inode *delegated_inode = NULL; + return vfs_unlink(&init_user_ns, dir, dentry, &delegated_inode); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) + struct inode *delegated_inode = NULL; + return vfs_unlink(dir, dentry, &delegated_inode); +#else + return vfs_unlink(dir, dentry); +#endif +} + +int ksu_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + struct renamedata rd = { + .old_dir = old_dir, + .old_dentry = old_dentry, + .new_dir = new_dir, + .new_dentry = new_dentry, + .delegated_inode = NULL, + .flags = 0, + }; + return vfs_rename(&rd); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) + struct inode *delegated_inode = NULL; + return vfs_rename(old_dir, old_dentry, new_dir, new_dentry, &delegated_inode, 0); +#else + return vfs_rename(old_dir, old_dentry, new_dir, new_dentry); +#endif +} \ No newline at end of file diff --git a/kernel/kernel_compat.h b/kernel/kernel_compat.h index a3e63c74..f258e4c3 100644 --- a/kernel/kernel_compat.h +++ b/kernel/kernel_compat.h @@ -117,4 +117,8 @@ static long ksu_copy_from_user_retry(void *to, #define ksu_access_ok(addr, size) access_ok(VERIFY_READ, addr, size) #endif +extern int ksu_vfs_unlink(struct inode *dir, struct dentry *dentry); +extern int ksu_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry); + #endif diff --git a/kernel/sucompat.c b/kernel/sucompat.c index 6653657f..f03002dd 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -24,6 +24,7 @@ #include "klog.h" // IWYU pragma: keep #include "ksud.h" #include "kernel_compat.h" +#include "sulog.h" #define SU_PATH "/system/bin/su" #define SH_PATH "/system/bin/sh" @@ -162,6 +163,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, int *__never_use_flags) { struct filename *filename; + uid_t current_uid_val = current_uid().val; #ifndef CONFIG_KSU_KPROBES_HOOK if (!ksu_sucompat_hook_state) { @@ -180,11 +182,19 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, if (likely(memcmp(filename->name, su, sizeof(su)))) return 0; + bool is_allowed = ksu_is_allow_uid(current_uid_val); + #ifndef CONFIG_KSU_SUSFS_SUS_SU - if (!ksu_is_allow_uid(current_uid().val)) + if (!is_allowed) return 0; #endif + ksu_sulog_report_su_attempt(current_uid_val, NULL, filename->name, is_allowed); + + if (!is_allowed) { + return 0; + } + pr_info("do_execveat_common su found\n"); memcpy((void *)filename->name, ksud_path, sizeof(ksud_path)); @@ -197,6 +207,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, void *__never_use_argv, void *__never_use_envp, int *__never_use_flags) { + uid_t current_uid_val = current_uid().val; //const char su[] = SU_PATH; #ifdef CONFIG_KSU_SUSFS_SUS_SU char path[sizeof(su) + 1] = {0}; @@ -231,7 +242,11 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, if (likely(memcmp(path, su, sizeof(su)))) return 0; - if (!ksu_is_allow_uid(current_uid().val)) + bool is_allowed = ksu_is_allow_uid(current_uid_val); + + ksu_sulog_report_su_attempt(current_uid_val, NULL, path, is_allowed); + + if (!is_allowed) return 0; pr_info("sys_execve su found\n"); diff --git a/kernel/sulog.c b/kernel/sulog.c new file mode 100644 index 00000000..25981c21 --- /dev/null +++ b/kernel/sulog.c @@ -0,0 +1,393 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "klog.h" +#include "kernel_compat.h" +#include "sulog.h" +#include "ksu.h" + +#define SULOG_PATH "/data/adb/ksu/log/sulog.log" +#define SULOG_OLD_PATH "/data/adb/ksu/log/sulog.log.old" +#define SULOG_MAX_SIZE (16 * 1024 * 1024) // 16MB +#define SULOG_ENTRY_MAX_LEN 512 +#define SULOG_COMM_LEN 256 + +struct sulog_entry { + struct list_head list; + char content[SULOG_ENTRY_MAX_LEN]; +}; + +static LIST_HEAD(sulog_queue); +static DEFINE_MUTEX(sulog_mutex); +static struct workqueue_struct *sulog_workqueue; +static struct work_struct sulog_work; +static bool sulog_enabled = true; + +static void get_timestamp(char *buf, size_t len) +{ + struct timespec64 ts, boottime; + struct tm tm; + s64 real_time; + + ktime_get_boottime_ts64(&boottime); + ktime_get_ts64(&ts); + + real_time = boottime.tv_sec; + if (real_time < 946684800) { + real_time = ts.tv_sec; + } + + time64_to_tm(real_time, 0, &tm); + + snprintf(buf, len, "%04ld-%02d-%02d %02d:%02d:%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +static void get_full_comm(char *comm_buf, size_t buf_len) +{ + struct mm_struct *mm; + char *cmdline = NULL; + unsigned long arg_start, arg_end; + int len; + + mm = get_task_mm(current); + if (mm) { + arg_start = mm->arg_start; + arg_end = mm->arg_end; + + if (arg_end > arg_start) { + len = arg_end - arg_start; + if (len > 0 && len < buf_len) { + cmdline = kmalloc(len + 1, GFP_ATOMIC); + if (cmdline) { + if (ksu_copy_from_user_retry(cmdline, (void __user *)arg_start, len) == 0) { + cmdline[len] = '\0'; + char *space = strchr(cmdline, ' '); + if (space) *space = '\0'; + + char *slash = strrchr(cmdline, '/'); + if (slash && *(slash + 1)) { + strncpy(comm_buf, slash + 1, buf_len - 1); + } else { + strncpy(comm_buf, cmdline, buf_len - 1); + } + comm_buf[buf_len - 1] = '\0'; + kfree(cmdline); + mmput(mm); + return; + } + kfree(cmdline); + } + } + } + mmput(mm); + } + + strncpy(comm_buf, current->comm, buf_len - 1); + comm_buf[buf_len - 1] = '\0'; +} + +static void sulog_work_handler(struct work_struct *work) +{ + struct file *fp; + struct sulog_entry *entry, *tmp; + LIST_HEAD(local_queue); + loff_t pos = 0; + + mutex_lock(&sulog_mutex); + list_splice_init(&sulog_queue, &local_queue); + mutex_unlock(&sulog_mutex); + + if (list_empty(&local_queue)) + return; + + fp = ksu_filp_open_compat(SULOG_PATH, O_WRONLY | O_CREAT | O_APPEND, 0640); + if (IS_ERR(fp)) { + pr_err("sulog: failed to open log file: %ld\n", PTR_ERR(fp)); + goto cleanup; + } + + if (fp->f_inode->i_size > SULOG_MAX_SIZE) { + pr_info("sulog: rotating log file, size: %lld\n", fp->f_inode->i_size); + filp_close(fp, 0); + + struct path old_path; + if (!kern_path(SULOG_OLD_PATH, 0, &old_path)) { + ksu_vfs_unlink(old_path.dentry->d_parent->d_inode, old_path.dentry); + path_put(&old_path); + } + + struct path current_path, parent_path; + if (!kern_path(SULOG_PATH, 0, ¤t_path)) { + parent_path = current_path; + path_get(&parent_path); + parent_path.dentry = current_path.dentry->d_parent; + + struct dentry *old_dentry = lookup_one_len("sulog.log.old", + parent_path.dentry, strlen("sulog.log.old")); + if (!IS_ERR(old_dentry)) { + ksu_vfs_rename(parent_path.dentry->d_inode, current_path.dentry, + parent_path.dentry->d_inode, old_dentry); + dput(old_dentry); + } + path_put(¤t_path); + path_put(&parent_path); + } + + fp = ksu_filp_open_compat(SULOG_PATH, O_WRONLY | O_CREAT | O_EXCL, 0640); + if (IS_ERR(fp)) { + pr_err("sulog: failed to create new log file: %ld\n", PTR_ERR(fp)); + goto cleanup; + } + + const char *rotate_msg = "=== Log file rotated, old log saved as sulog.log.old ===\n"; + ksu_kernel_write_compat(fp, rotate_msg, strlen(rotate_msg), &pos); + } else { + pos = fp->f_inode->i_size; + } + + list_for_each_entry(entry, &local_queue, list) { + ksu_kernel_write_compat(fp, entry->content, strlen(entry->content), &pos); + } + + vfs_fsync(fp, 0); + filp_close(fp, 0); + +cleanup: + list_for_each_entry_safe(entry, tmp, &local_queue, list) { + list_del(&entry->list); + kfree(entry); + } +} + +static void sulog_add_entry(const char *content) +{ + struct sulog_entry *entry; + + if (!sulog_enabled || !content) + return; + + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + pr_err("sulog: failed to allocate memory for log entry\n"); + return; + } + + strncpy(entry->content, content, SULOG_ENTRY_MAX_LEN - 1); + entry->content[SULOG_ENTRY_MAX_LEN - 1] = '\0'; + + mutex_lock(&sulog_mutex); + list_add_tail(&entry->list, &sulog_queue); + mutex_unlock(&sulog_mutex); + + if (sulog_workqueue) + queue_work(sulog_workqueue, &sulog_work); +} + +void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method) +{ + char *timestamp, *full_comm, *log_buf; + + if (!sulog_enabled) + return; + + timestamp = kmalloc(32, GFP_ATOMIC); + full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); + log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); + + if (!timestamp || !full_comm || !log_buf) { + pr_err("sulog: failed to allocate memory for su_grant log\n"); + goto cleanup_grant; + } + + get_timestamp(timestamp, 32); + + if (comm && strlen(comm) > 0) { + strncpy(full_comm, comm, SULOG_COMM_LEN - 1); + full_comm[SULOG_COMM_LEN - 1] = '\0'; + } else { + get_full_comm(full_comm, SULOG_COMM_LEN); + } + + snprintf(log_buf, SULOG_ENTRY_MAX_LEN, + "[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n", + timestamp, uid, full_comm, + method ? method : "unknown", current->pid); + + sulog_add_entry(log_buf); + pr_info("sulog: %s", log_buf); + +cleanup_grant: + if (timestamp) kfree(timestamp); + if (full_comm) kfree(full_comm); + if (log_buf) kfree(log_buf); +} + +void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success) +{ + char *timestamp, *full_comm, *log_buf; + + if (!sulog_enabled) + return; + + timestamp = kmalloc(32, GFP_ATOMIC); + full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); + log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); + + if (!timestamp || !full_comm || !log_buf) { + pr_err("sulog: failed to allocate memory for su_attempt log\n"); + goto cleanup_attempt; + } + + get_timestamp(timestamp, 32); + + if (comm && strlen(comm) > 0) { + strncpy(full_comm, comm, SULOG_COMM_LEN - 1); + full_comm[SULOG_COMM_LEN - 1] = '\0'; + } else { + get_full_comm(full_comm, SULOG_COMM_LEN); + } + + snprintf(log_buf, SULOG_ENTRY_MAX_LEN, + "[%s] SU_EXEC: UID=%d COMM=%s TARGET=%s RESULT=%s PID=%d\n", + timestamp, uid, full_comm, + target_path ? target_path : "unknown", + success ? "SUCCESS" : "DENIED", current->pid); + + sulog_add_entry(log_buf); + pr_info("sulog: %s", log_buf); + +cleanup_attempt: + if (timestamp) kfree(timestamp); + if (full_comm) kfree(full_comm); + if (log_buf) kfree(log_buf); +} + +void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed) +{ + char *timestamp, *full_comm, *log_buf; + + if (!sulog_enabled) + return; + + timestamp = kmalloc(32, GFP_ATOMIC); + full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); + log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); + + if (!timestamp || !full_comm || !log_buf) { + pr_err("sulog: failed to allocate memory for permission_check log\n"); + goto cleanup_perm; + } + + get_timestamp(timestamp, 32); + + if (comm && strlen(comm) > 0) { + strncpy(full_comm, comm, SULOG_COMM_LEN - 1); + full_comm[SULOG_COMM_LEN - 1] = '\0'; + } else { + get_full_comm(full_comm, SULOG_COMM_LEN); + } + + snprintf(log_buf, SULOG_ENTRY_MAX_LEN, + "[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n", + timestamp, uid, full_comm, + allowed ? "ALLOWED" : "DENIED", current->pid); + + sulog_add_entry(log_buf); + +cleanup_perm: + if (timestamp) kfree(timestamp); + if (full_comm) kfree(full_comm); + if (log_buf) kfree(log_buf); +} + +void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid) +{ + char *timestamp, *full_comm, *log_buf; + + if (!sulog_enabled) + return; + + timestamp = kmalloc(32, GFP_ATOMIC); + full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); + log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); + + if (!timestamp || !full_comm || !log_buf) { + pr_err("sulog: failed to allocate memory for manager_operation log\n"); + goto cleanup_mgr; + } + + get_timestamp(timestamp, 32); + get_full_comm(full_comm, SULOG_COMM_LEN); + + snprintf(log_buf, SULOG_ENTRY_MAX_LEN, + "[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d PID=%d\n", + timestamp, operation ? operation : "unknown", + manager_uid, target_uid, current->pid); + + sulog_add_entry(log_buf); + pr_info("sulog: %s", log_buf); + +cleanup_mgr: + if (timestamp) kfree(timestamp); + if (full_comm) kfree(full_comm); + if (log_buf) kfree(log_buf); +} + +void ksu_sulog_set_enabled(bool enabled) +{ + sulog_enabled = enabled; + pr_info("sulog: logging %s\n", enabled ? "enabled" : "disabled"); +} + +bool ksu_sulog_is_enabled(void) +{ + return sulog_enabled; +} + +int ksu_sulog_init(void) +{ + sulog_workqueue = create_singlethread_workqueue("ksu_sulog"); + if (!sulog_workqueue) { + pr_err("sulog: failed to create workqueue\n"); + return -ENOMEM; + } + + INIT_WORK(&sulog_work, sulog_work_handler); + + pr_info("sulog: initialized successfully\n"); + return 0; +} + +void ksu_sulog_exit(void) +{ + struct sulog_entry *entry, *tmp; + + sulog_enabled = false; + + if (sulog_workqueue) { + flush_workqueue(sulog_workqueue); + destroy_workqueue(sulog_workqueue); + sulog_workqueue = NULL; + } + + mutex_lock(&sulog_mutex); + list_for_each_entry_safe(entry, tmp, &sulog_queue, list) { + list_del(&entry->list); + kfree(entry); + } + mutex_unlock(&sulog_mutex); + + pr_info("sulog: cleaned up successfully\n"); +} \ No newline at end of file diff --git a/kernel/sulog.h b/kernel/sulog.h new file mode 100644 index 00000000..4498a8c1 --- /dev/null +++ b/kernel/sulog.h @@ -0,0 +1,20 @@ +#ifndef __KSU_SULOG_H +#define __KSU_SULOG_H + +#include + +void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method); + +void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success); + +void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed); + +void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid); + +void ksu_sulog_set_enabled(bool enabled); +bool ksu_sulog_is_enabled(void); + +int ksu_sulog_init(void); +void ksu_sulog_exit(void); + +#endif /* __KSU_SULOG_H */ \ No newline at end of file