diff --git a/kernel/Makefile b/kernel/Makefile index 12bd0ec8..65477f94 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -9,6 +9,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 61097971..d274fd82 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -47,6 +47,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" @@ -148,6 +149,7 @@ static void disable_seccomp() void escape_to_root(void) { struct cred *cred; + uid_t original_uid = current_euid().val; cred = prepare_creds(); if (!cred) { @@ -157,6 +159,7 @@ void escape_to_root(void) if (cred->euid.val == 0) { pr_warn("Already root, don't escape!\n"); + ksu_sulog_report_su_grant(original_uid, NULL, "escape_to_root_failed"); abort_creds(cred); return; } @@ -200,6 +203,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 @@ -242,6 +247,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); @@ -256,6 +262,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; } @@ -306,6 +313,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 @@ -474,7 +482,10 @@ skip_check: } 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))) { @@ -578,6 +589,7 @@ skip_check: post_fs_data_lock = true; pr_info("post-fs-data triggered\n"); on_post_fs_data(); + ksu_sulog_init(); // Initialize UID scanner if enabled init_uid_scanner(); // Initializing Dynamic Signatures @@ -813,6 +825,9 @@ skip_check: // 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); } @@ -1376,6 +1391,7 @@ void ksu_core_exit(void) { ksu_uid_exit(); ksu_throne_comm_exit(); + ksu_sulog_exit(); #ifdef CONFIG_KPROBE pr_info("ksu_core_kprobe_exit\n"); // we dont use this now diff --git a/kernel/kernel_compat.c b/kernel/kernel_compat.c index 3fb7ecd2..961c7c19 100644 --- a/kernel/kernel_compat.c +++ b/kernel/kernel_compat.c @@ -92,3 +92,40 @@ long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, { return strncpy_from_user_nofault(dst, unsafe_addr, count); } + +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 +} diff --git a/kernel/kernel_compat.h b/kernel/kernel_compat.h index 11ee9023..9952e35a 100644 --- a/kernel/kernel_compat.h +++ b/kernel/kernel_compat.h @@ -94,4 +94,8 @@ static long ksu_copy_from_user_retry(void *to, return copy_from_user(to, from, count); } +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 ca94a60f..c14f31ed 100644 --- a/kernel/sucompat.c +++ b/kernel/sucompat.c @@ -16,6 +16,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" @@ -133,6 +134,8 @@ 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; + bool is_allowed = ksu_is_allow_uid(current_uid_val); const char sh[] = KSUD_PATH; const char su[] = SU_PATH; @@ -152,8 +155,11 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, if (likely(memcmp(filename->name, su, sizeof(su)))) return 0; - if (!ksu_is_allow_uid(current_uid().val)) + 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, sh, sizeof(sh)); @@ -167,6 +173,8 @@ 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; + bool is_allowed = ksu_is_allow_uid(current_uid_val); const char su[] = SU_PATH; char path[sizeof(su) + 1]; @@ -183,8 +191,10 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, if (likely(memcmp(path, su, sizeof(su)))) return 0; + + ksu_sulog_report_su_attempt(current_uid_val, NULL, path, is_allowed); - if (!ksu_is_allow_uid(current_uid().val)) + 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