From f5c236c9250fc4eaa51c22ef236be2efabb666fc Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sat, 5 Jul 2025 15:56:19 +0800 Subject: [PATCH] Add dynamic signature support --- kernel/apk_sign.c | 286 +++++++++++++++++++++++++++++++++++++++++- kernel/apk_sign.h | 14 ++- kernel/core_hook.c | 36 +++++- kernel/core_hook.h | 1 + kernel/ksu.h | 13 +- kernel/manager_sign.h | 4 + 6 files changed, 350 insertions(+), 4 deletions(-) diff --git a/kernel/apk_sign.c b/kernel/apk_sign.c index 59d9a790..c30e9819 100644 --- a/kernel/apk_sign.c +++ b/kernel/apk_sign.c @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef CONFIG_KSU_DEBUG #include #endif @@ -19,6 +20,279 @@ #include "kernel_compat.h" #include "manager_sign.h" +// Expected sizes and hashes for various APK signatures +#define DYNAMIC_SIGN_FILE_MAGIC 0x7f445347 // 'DSG', u32 +#define DYNAMIC_SIGN_FILE_VERSION 1 // u32 +#define KERNEL_SU_DYNAMIC_SIGN "/data/adb/ksu/.dynamic_sign" + +static struct dynamic_sign_config dynamic_sign = { + .size = 0x300, + .hash = "0000000000000000000000000000000000000000000000000000000000000000", + .is_set = 0 +}; + +static DEFINE_SPINLOCK(dynamic_sign_lock); +static struct work_struct ksu_save_dynamic_sign_work; +static struct work_struct ksu_load_dynamic_sign_work; +static struct work_struct ksu_clear_dynamic_sign_work; + +static void do_save_dynamic_sign(struct work_struct *work) +{ + u32 magic = DYNAMIC_SIGN_FILE_MAGIC; + u32 version = DYNAMIC_SIGN_FILE_VERSION; + struct dynamic_sign_config config_to_save; + loff_t off = 0; + unsigned long flags; + struct file *fp; + + spin_lock_irqsave(&dynamic_sign_lock, flags); + config_to_save = dynamic_sign; + spin_unlock_irqrestore(&dynamic_sign_lock, flags); + + if (!config_to_save.is_set) { + pr_info("Dynamic sign config not set, skipping save\n"); + return; + } + + fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (IS_ERR(fp)) { + pr_err("save_dynamic_sign create file failed: %ld\n", PTR_ERR(fp)); + return; + } + + if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) { + pr_err("save_dynamic_sign write magic failed.\n"); + goto exit; + } + + if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { + pr_err("save_dynamic_sign write version failed.\n"); + goto exit; + } + + if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) { + pr_err("save_dynamic_sign write config failed.\n"); + goto exit; + } + + pr_info("Dynamic sign config saved successfully\n"); + +exit: + filp_close(fp, 0); +} + +// Loading dynamic signatures from persistent storage +static void do_load_dynamic_sign(struct work_struct *work) +{ + loff_t off = 0; + ssize_t ret = 0; + struct file *fp = NULL; + u32 magic; + u32 version; + struct dynamic_sign_config loaded_config; + unsigned long flags; + int i; + + fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_RDONLY, 0); + if (IS_ERR(fp)) { + if (PTR_ERR(fp) == -ENOENT) { + pr_info("No saved dynamic sign config found\n"); + } else { + pr_err("load_dynamic_sign open file failed: %ld\n", PTR_ERR(fp)); + } + return; + } + + if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) || + magic != DYNAMIC_SIGN_FILE_MAGIC) { + pr_err("dynamic sign file invalid magic: %x!\n", magic); + goto exit; + } + + if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { + pr_err("dynamic sign read version failed\n"); + goto exit; + } + + pr_info("dynamic sign file version: %d\n", version); + + ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off); + if (ret <= 0) { + pr_info("load_dynamic_sign read err: %zd\n", ret); + goto exit; + } + + if (ret != sizeof(loaded_config)) { + pr_err("load_dynamic_sign read incomplete config: %zd/%zu\n", ret, sizeof(loaded_config)); + goto exit; + } + + if (loaded_config.size < 0x100 || loaded_config.size > 0x1000) { + pr_err("Invalid saved config size: 0x%x\n", loaded_config.size); + goto exit; + } + + if (strlen(loaded_config.hash) != 64) { + pr_err("Invalid saved config hash length: %zu\n", strlen(loaded_config.hash)); + goto exit; + } + + for (i = 0; i < 64; i++) { + char c = loaded_config.hash[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) { + pr_err("Invalid saved config hash character at position %d: %c\n", i, c); + goto exit; + } + } + + spin_lock_irqsave(&dynamic_sign_lock, flags); + dynamic_sign = loaded_config; + spin_unlock_irqrestore(&dynamic_sign_lock, flags); + + pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n", + loaded_config.size, loaded_config.hash); + +exit: + filp_close(fp, 0); +} + +static bool persistent_dynamic_sign(void) +{ + return ksu_queue_work(&ksu_save_dynamic_sign_work); +} + +// Clear dynamic sign config file using the same method as do_save_dynamic_sign +static void do_clear_dynamic_sign_file(struct work_struct *work) +{ + loff_t off = 0; + struct file *fp; + char zero_buffer[512]; + + memset(zero_buffer, 0, sizeof(zero_buffer)); + + fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (IS_ERR(fp)) { + pr_err("clear_dynamic_sign create file failed: %ld\n", PTR_ERR(fp)); + return; + } + + // Write null bytes to overwrite the file content + if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) { + pr_err("clear_dynamic_sign write null bytes failed.\n"); + } else { + pr_info("Dynamic sign config file cleared successfully\n"); + } + + filp_close(fp, 0); +} + +static bool clear_dynamic_sign_file(void) +{ + return ksu_queue_work(&ksu_clear_dynamic_sign_work); +} + +int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config) +{ + unsigned long flags; + int ret = 0; + int i; + + if (!config) { + return -EINVAL; + } + + switch (config->operation) { + case DYNAMIC_SIGN_OP_SET: + if (config->size < 0x100 || config->size > 0x1000) { + pr_err("invalid size: 0x%x\n", config->size); + return -EINVAL; + } + + if (strlen(config->hash) != 64) { + pr_err("invalid hash length: %zu\n", strlen(config->hash)); + return -EINVAL; + } + + for (i = 0; i < 64; i++) { + char c = config->hash[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) { + pr_err("invalid hash character at position %d: %c\n", i, c); + return -EINVAL; + } + } + + spin_lock_irqsave(&dynamic_sign_lock, flags); + dynamic_sign.size = config->size; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) + strscpy(dynamic_sign.hash, config->hash, sizeof(dynamic_sign.hash)); +#else + strlcpy(dynamic_sign.hash, config->hash, sizeof(dynamic_sign.hash)); +#endif + dynamic_sign.is_set = 1; + spin_unlock_irqrestore(&dynamic_sign_lock, flags); + + persistent_dynamic_sign(); + pr_info("dynamic sign updated: size=0x%x, hash=%.16s...\n", config->size, config->hash); + break; + + case DYNAMIC_SIGN_OP_GET: + // Getting Dynamic Signatures + spin_lock_irqsave(&dynamic_sign_lock, flags); + if (dynamic_sign.is_set) { + config->size = dynamic_sign.size; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) + strscpy(config->hash, dynamic_sign.hash, sizeof(config->hash)); +#else + strlcpy(config->hash, dynamic_sign.hash, sizeof(config->hash)); +#endif + ret = 0; + } else { + ret = -ENODATA; + } + spin_unlock_irqrestore(&dynamic_sign_lock, flags); + break; + + case DYNAMIC_SIGN_OP_CLEAR: + // Clearing dynamic signatures + spin_lock_irqsave(&dynamic_sign_lock, flags); + dynamic_sign.size = 0x300; + strcpy(dynamic_sign.hash, "0000000000000000000000000000000000000000000000000000000000000000"); + dynamic_sign.is_set = 0; + spin_unlock_irqrestore(&dynamic_sign_lock, flags); + + // Clear file using the same method as save + clear_dynamic_sign_file(); + + pr_info("Dynamic sign config cleared\n"); + break; + + default: + pr_err("Invalid dynamic sign operation: %d\n", config->operation); + return -EINVAL; + } + + return ret; +} + +bool ksu_load_dynamic_sign(void) +{ + return ksu_queue_work(&ksu_load_dynamic_sign_work); +} + +void ksu_dynamic_sign_init(void) +{ + INIT_WORK(&ksu_save_dynamic_sign_work, do_save_dynamic_sign); + INIT_WORK(&ksu_load_dynamic_sign_work, do_load_dynamic_sign); + INIT_WORK(&ksu_clear_dynamic_sign_work, do_clear_dynamic_sign_file); + pr_info("Dynamic sign initialized with persistent storage\n"); +} + +void ksu_dynamic_sign_exit(void) +{ + do_save_dynamic_sign(NULL); + pr_info("Dynamic sign exited with persistent storage\n"); +} + struct sdesc { struct shash_desc shash; char ctx[]; @@ -30,6 +304,7 @@ static struct apk_sign_key { } apk_sign_keys[] = { {EXPECTED_SIZE, EXPECTED_HASH}, {EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // SukiSU + {EXPECTED_SIZE_OTHER, EXPECTED_HASH_OTHER}, // Dynamic Sign #ifdef CONFIG_KSU_MULTI_MANAGER_SUPPORT {EXPECTED_SIZE_RSUNTK, EXPECTED_HASH_RSUNTK}, // RKSU {EXPECTED_SIZE_NEKO, EXPECTED_HASH_NEKO}, // Neko/KernelSU @@ -110,6 +385,16 @@ static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset) for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) { sign_key = apk_sign_keys[i]; + if (i == 2) { + unsigned long flags; + spin_lock_irqsave(&dynamic_sign_lock, flags); + if (dynamic_sign.is_set) { + sign_key.size = dynamic_sign.size; + sign_key.sha256 = dynamic_sign.hash; + } + spin_unlock_irqrestore(&dynamic_sign_lock, flags); + } + if (*size4 != sign_key.size) continue; *offset += *size4; @@ -332,7 +617,6 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops, #endif - bool ksu_is_manager_apk(char *path) { return check_v2_signature(path); diff --git a/kernel/apk_sign.h b/kernel/apk_sign.h index e02aa514..a1f0d0fb 100644 --- a/kernel/apk_sign.h +++ b/kernel/apk_sign.h @@ -2,7 +2,19 @@ #define __KSU_H_APK_V2_SIGN #include +#include "ksu.h" bool ksu_is_manager_apk(char *path); -#endif +struct dynamic_sign_config { + unsigned int size; + char hash[65]; + int is_set; +}; + +int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config); +void ksu_dynamic_sign_init(void); +void ksu_dynamic_sign_exit(void); +bool ksu_load_dynamic_sign(void); + +#endif \ No newline at end of file diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 5fdbd14c..d90f64d1 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -424,6 +424,36 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, return 0; } + // Allow the root manager to configure dynamic signatures + if (arg2 == CMD_DYNAMIC_SIGN) { + if (!from_root && !from_manager) { + return 0; + } + + struct dynamic_sign_user_config config; + + if (copy_from_user(&config, (void __user *)arg3, sizeof(config))) { + pr_err("copy dynamic sign config failed\n"); + return 0; + } + + int ret = ksu_handle_dynamic_sign(&config); + + if (ret == 0 && config.operation == DYNAMIC_SIGN_OP_GET) { + if (copy_to_user((void __user *)arg3, &config, sizeof(config))) { + pr_err("copy dynamic sign config back failed\n"); + return 0; + } + } + + if (ret == 0) { + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("dynamic_sign: prctl reply error\n"); + } + } + return 0; + } + if (arg2 == CMD_REPORT_EVENT) { if (!from_root) { return 0; @@ -438,6 +468,10 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, post_fs_data_lock = true; pr_info("post-fs-data triggered\n"); ksu_on_post_fs_data(); + // Initializing Dynamic Signatures + ksu_dynamic_sign_init(); + ksu_load_dynamic_sign(); + pr_info("Dynamic sign config loaded during post-fs-data\n"); } break; } @@ -1589,4 +1623,4 @@ void ksu_core_exit(void) // we dont use this now // ksu_kprobe_exit(); #endif -} +} \ No newline at end of file diff --git a/kernel/core_hook.h b/kernel/core_hook.h index 616951e8..6ed328a0 100644 --- a/kernel/core_hook.h +++ b/kernel/core_hook.h @@ -2,6 +2,7 @@ #define __KSU_H_KSU_CORE #include +#include "apk_sign.h" void __init ksu_core_init(void); void ksu_core_exit(void); diff --git a/kernel/ksu.h b/kernel/ksu.h index 6620843a..c2cb5587 100644 --- a/kernel/ksu.h +++ b/kernel/ksu.h @@ -29,6 +29,7 @@ #define CMD_ENABLE_KPM 100 #define CMD_HOOK_TYPE 101 #define CMD_GET_SUSFS_FEATURE_STATUS 102 +#define CMD_DYNAMIC_SIGN 103 #define EVENT_POST_FS_DATA 1 #define EVENT_BOOT_COMPLETED 2 @@ -46,6 +47,16 @@ #endif #define KSU_FULL_VERSION_STRING 255 +#define DYNAMIC_SIGN_OP_SET 0 +#define DYNAMIC_SIGN_OP_GET 1 +#define DYNAMIC_SIGN_OP_CLEAR 2 + +struct dynamic_sign_user_config { + unsigned int operation; + unsigned int size; + char hash[65]; +}; + // SUSFS Functional State Structures struct susfs_feature_status { bool status_sus_path; @@ -158,4 +169,4 @@ static inline int endswith(const char *s, const char *t) return strcmp(s + slen - tlen, t); } -#endif +#endif \ No newline at end of file diff --git a/kernel/manager_sign.h b/kernel/manager_sign.h index 44e18280..c632022f 100644 --- a/kernel/manager_sign.h +++ b/kernel/manager_sign.h @@ -21,4 +21,8 @@ #define EXPECTED_SIZE_NEKO 0x29c #define EXPECTED_HASH_NEKO "946b0557e450a6430a0ba6b6bccee5bc12953ec8735d55e26139b0ec12303b21" +// Dynamic Sign +#define EXPECTED_SIZE_OTHER 0x300 +#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000" + #endif /* MANAGER_SIGN_H */