diff --git a/kernel/apk_sign.c b/kernel/apk_sign.c index 2ccbf2fc..67f2d3c9 100644 --- a/kernel/apk_sign.c +++ b/kernel/apk_sign.c @@ -121,8 +121,7 @@ static bool has_v1_signature_file(struct file *fp) fileName[header.file_name_length] = '\0'; // Check if the entry matches META-INF/MANIFEST.MF - if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == - 0) { + if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == 0) { return true; } } else { diff --git a/kernel/apk_sign.h b/kernel/apk_sign.h index 36ca9ec5..e8997752 100644 --- a/kernel/apk_sign.h +++ b/kernel/apk_sign.h @@ -6,7 +6,4 @@ bool is_manager_apk(char *path); -// Get dynamic sign configuration for signature verification -bool ksu_get_dynamic_sign_config(unsigned int *size, const char **hash); - #endif diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 55649356..99e57138 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -410,8 +410,6 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, on_post_fs_data(); // Initializing Dynamic Signatures ksu_dynamic_sign_init(); - ksu_load_dynamic_sign(); - ksu_trigger_manager_rescan(); pr_info("Dynamic sign config loaded during post-fs-data\n"); } break; diff --git a/kernel/dynamic_sign.c b/kernel/dynamic_sign.c index b36c7884..5bc2414b 100644 --- a/kernel/dynamic_sign.c +++ b/kernel/dynamic_sign.c @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #ifdef CONFIG_KSU_DEBUG #include #endif @@ -22,8 +25,10 @@ #include "throne_tracker.h" #define MAX_MANAGERS 2 +#define MAX_RETRY_COUNT 3 +#define RETRY_DELAY_MS 100 -// Dynamic sign configuration +// Dynamic sign configuration with atomic operations support static struct dynamic_sign_config dynamic_sign = { .size = 0x300, .hash = "0000000000000000000000000000000000000000000000000000000000000000", @@ -36,11 +41,20 @@ static DEFINE_SPINLOCK(managers_lock); static DEFINE_SPINLOCK(dynamic_sign_lock); // Work queues for persistent storage and manager rescanning +static struct workqueue_struct *ksu_dynamic_wq; 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 struct work_struct ksu_rescan_manager_work; +// Completion for synchronous operations when needed +static struct completion save_completion; +static struct completion load_completion; + +// Error recovery state +static atomic_t save_retry_count = ATOMIC_INIT(0); +static atomic_t load_retry_count = ATOMIC_INIT(0); + bool ksu_is_dynamic_sign_enabled(void) { unsigned long flags; @@ -219,7 +233,7 @@ int ksu_get_active_managers(struct manager_list_info *info) // Manager rescanning work handler -void ksu_rescan_manager_work_handler(struct work_struct *work) +static void ksu_rescan_manager_work_handler(struct work_struct *work) { pr_info("Starting manager rescan for dynamic sign changes\n"); @@ -237,107 +251,247 @@ void ksu_rescan_manager_work_handler(struct work_struct *work) bool ksu_trigger_manager_rescan(void) { - return ksu_queue_work(&ksu_rescan_manager_work); + if (!ksu_dynamic_wq) { + pr_err("Dynamic sign workqueue not initialized\n"); + return false; + } + return queue_work(ksu_dynamic_wq, &ksu_rescan_manager_work); } -static void do_save_dynamic_sign(struct work_struct *work) +// Enhanced file operations with error recovery +static int safe_file_write(struct file *fp, const void *data, size_t size, loff_t *pos) +{ + ssize_t written; + int retry = 0; + + while (retry < MAX_RETRY_COUNT) { + written = ksu_kernel_write_compat(fp, data, size, pos); + if (written == size) { + return 0; + } + + pr_warn("File write failed, attempt %d/%d, written=%zd, expected=%zu\n", + retry + 1, MAX_RETRY_COUNT, written, size); + + if (written < 0) { + return written; + } + + retry++; + if (retry < MAX_RETRY_COUNT) { + msleep(RETRY_DELAY_MS); + } + } + + return -EIO; +} + +static int safe_file_read(struct file *fp, void *data, size_t size, loff_t *pos) +{ + ssize_t read_bytes; + int retry = 0; + + while (retry < MAX_RETRY_COUNT) { + read_bytes = ksu_kernel_read_compat(fp, data, size, pos); + if (read_bytes == size) { + return 0; + } + + pr_warn("File read failed, attempt %d/%d, read=%zd, expected=%zu\n", + retry + 1, MAX_RETRY_COUNT, read_bytes, size); + + if (read_bytes < 0) { + return read_bytes; + } + + retry++; + if (retry < MAX_RETRY_COUNT) { + msleep(RETRY_DELAY_MS); + } + } + + return -EIO; +} + +static void do_save_dynamic_sign_with_recovery(struct work_struct *work) { u32 magic = DYNAMIC_SIGN_FILE_MAGIC; u32 version = DYNAMIC_SIGN_FILE_VERSION; struct dynamic_sign_config config_to_save; + struct dynamic_sign_config backup_config; loff_t off = 0; unsigned long flags; - struct file *fp; + struct file *fp = NULL; + int ret = 0; + int current_retry; + // Get current retry count + current_retry = atomic_read(&save_retry_count); + + // Backup current state before any operations spin_lock_irqsave(&dynamic_sign_lock, flags); config_to_save = dynamic_sign; + backup_config = 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; + goto complete; } + pr_info("Saving dynamic sign config (attempt %d/%d)\n", + current_retry + 1, MAX_RETRY_COUNT); + 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; + ret = PTR_ERR(fp); + pr_err("save_dynamic_sign create file failed: %d\n", ret); + goto retry_or_fail; } - if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) { - pr_err("save_dynamic_sign write magic failed.\n"); - goto exit; + // Write with error checking + ret = safe_file_write(fp, &magic, sizeof(magic), &off); + if (ret) { + pr_err("save_dynamic_sign write magic failed: %d\n", ret); + goto cleanup_and_retry; } - if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { - pr_err("save_dynamic_sign write version failed.\n"); - goto exit; + ret = safe_file_write(fp, &version, sizeof(version), &off); + if (ret) { + pr_err("save_dynamic_sign write version failed: %d\n", ret); + goto cleanup_and_retry; } - 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; + ret = safe_file_write(fp, &config_to_save, sizeof(config_to_save), &off); + if (ret) { + pr_err("save_dynamic_sign write config failed: %d\n", ret); + goto cleanup_and_retry; } - pr_info("Dynamic sign config saved successfully\n"); + // Force sync to ensure data is written + if (fp->f_op && fp->f_op->fsync) { + ret = fp->f_op->fsync(fp, 0, LLONG_MAX, 1); + if (ret) { + pr_warn("save_dynamic_sign fsync failed: %d\n", ret); + // Continue anyway, fsync failure is not critical + } + } -exit: filp_close(fp, 0); + fp = NULL; + + // Reset retry count on success + atomic_set(&save_retry_count, 0); + pr_info("Dynamic sign config saved successfully\n"); + goto complete; + +cleanup_and_retry: + if (fp && !IS_ERR(fp)) { + filp_close(fp, 0); + fp = NULL; + } + +retry_or_fail: + if (current_retry < MAX_RETRY_COUNT - 1) { + atomic_inc(&save_retry_count); + pr_info("Retrying save operation in %dms\n", RETRY_DELAY_MS); + + // Schedule retry + if (ksu_dynamic_wq) { + queue_delayed_work(ksu_dynamic_wq, + (struct delayed_work *)&ksu_save_dynamic_sign_work, + msecs_to_jiffies(RETRY_DELAY_MS)); + } + return; + } else { + // All retries failed, restore backup state + pr_err("Save operation failed after %d attempts, restoring backup state\n", + MAX_RETRY_COUNT); + + spin_lock_irqsave(&dynamic_sign_lock, flags); + dynamic_sign = backup_config; + spin_unlock_irqrestore(&dynamic_sign_lock, flags); + + atomic_set(&save_retry_count, 0); + } + +complete: + complete(&save_completion); } -static void do_load_dynamic_sign(struct work_struct *work) +static void do_load_dynamic_sign_with_recovery(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; + struct dynamic_sign_config backup_config; unsigned long flags; + int ret = 0; int i; + int current_retry; + + // Get current retry count + current_retry = atomic_read(&load_retry_count); + + pr_info("Loading dynamic sign config (attempt %d/%d)\n", + current_retry + 1, MAX_RETRY_COUNT); + + // Backup current state + spin_lock_irqsave(&dynamic_sign_lock, flags); + backup_config = dynamic_sign; + spin_unlock_irqrestore(&dynamic_sign_lock, flags); fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_RDONLY, 0); if (IS_ERR(fp)) { - if (PTR_ERR(fp) == -ENOENT) { + ret = PTR_ERR(fp); + if (ret == -ENOENT) { pr_info("No saved dynamic sign config found\n"); + atomic_set(&load_retry_count, 0); + goto complete; } else { - pr_err("load_dynamic_sign open file failed: %ld\n", PTR_ERR(fp)); + pr_err("load_dynamic_sign open file failed: %d\n", ret); + goto retry_or_fail; } - 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; + // Read and validate magic + ret = safe_file_read(fp, &magic, sizeof(magic), &off); + if (ret || magic != DYNAMIC_SIGN_FILE_MAGIC) { + pr_err("dynamic sign file invalid magic: %x (expected: %x)\n", + magic, DYNAMIC_SIGN_FILE_MAGIC); + ret = -EINVAL; + goto cleanup_and_retry; } - if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { - pr_err("dynamic sign read version failed\n"); - goto exit; + // Read version + ret = safe_file_read(fp, &version, sizeof(version), &off); + if (ret) { + pr_err("dynamic sign read version failed: %d\n", ret); + goto cleanup_and_retry; } 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; + // Read config + ret = safe_file_read(fp, &loaded_config, sizeof(loaded_config), &off); + if (ret) { + pr_err("load_dynamic_sign read config failed: %d\n", ret); + goto cleanup_and_retry; } + // Validate loaded config if (loaded_config.size < 0x100 || loaded_config.size > 0x1000) { pr_err("Invalid saved config size: 0x%x\n", loaded_config.size); - goto exit; + ret = -EINVAL; + goto cleanup_and_retry; } if (strlen(loaded_config.hash) != 64) { pr_err("Invalid saved config hash length: %zu\n", strlen(loaded_config.hash)); - goto exit; + ret = -EINVAL; + goto cleanup_and_retry; } // Validate hash format @@ -345,24 +499,63 @@ static void do_load_dynamic_sign(struct work_struct *work) 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; + ret = -EINVAL; + goto cleanup_and_retry; } } + filp_close(fp, 0); + fp = NULL; + + // Apply loaded config spin_lock_irqsave(&dynamic_sign_lock, flags); dynamic_sign = loaded_config; spin_unlock_irqrestore(&dynamic_sign_lock, flags); + // Reset retry count on success + atomic_set(&load_retry_count, 0); pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n", loaded_config.size, loaded_config.hash); + goto complete; -exit: - filp_close(fp, 0); +cleanup_and_retry: + if (fp && !IS_ERR(fp)) { + filp_close(fp, 0); + fp = NULL; + } + +retry_or_fail: + if (current_retry < MAX_RETRY_COUNT - 1) { + atomic_inc(&load_retry_count); + pr_info("Retrying load operation in %dms\n", RETRY_DELAY_MS); + + // Schedule retry + if (ksu_dynamic_wq) { + queue_delayed_work(ksu_dynamic_wq, + (struct delayed_work *)&ksu_load_dynamic_sign_work, + msecs_to_jiffies(RETRY_DELAY_MS)); + } + return; + } else { + // All retries failed, keep backup state + pr_err("Load operation failed after %d attempts, keeping current state\n", + MAX_RETRY_COUNT); + atomic_set(&load_retry_count, 0); + } + +complete: + complete(&load_completion); } static bool persistent_dynamic_sign(void) { - return ksu_queue_work(&ksu_save_dynamic_sign_work); + if (!ksu_dynamic_wq) { + pr_err("Dynamic sign workqueue not initialized\n"); + return false; + } + + reinit_completion(&save_completion); + return queue_work(ksu_dynamic_wq, &ksu_save_dynamic_sign_work); } static void do_clear_dynamic_sign_file(struct work_struct *work) @@ -370,6 +563,7 @@ static void do_clear_dynamic_sign_file(struct work_struct *work) loff_t off = 0; struct file *fp; char zero_buffer[512]; + int ret; memset(zero_buffer, 0, sizeof(zero_buffer)); @@ -380,8 +574,9 @@ static void do_clear_dynamic_sign_file(struct work_struct *work) } // 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"); + ret = safe_file_write(fp, zero_buffer, sizeof(zero_buffer), &off); + if (ret) { + pr_err("clear_dynamic_sign write null bytes failed: %d\n", ret); } else { pr_info("Dynamic sign config file cleared successfully\n"); } @@ -391,7 +586,11 @@ static void do_clear_dynamic_sign_file(struct work_struct *work) static bool clear_dynamic_sign_file(void) { - return ksu_queue_work(&ksu_clear_dynamic_sign_work); + if (!ksu_dynamic_wq) { + pr_err("Dynamic sign workqueue not initialized\n"); + return false; + } + return queue_work(ksu_dynamic_wq, &ksu_clear_dynamic_sign_work); } int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config) @@ -425,6 +624,7 @@ int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config) } } + // Update configuration atomically spin_lock_irqsave(&dynamic_sign_lock, flags); dynamic_sign.size = config->size; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) @@ -435,6 +635,7 @@ int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config) dynamic_sign.is_set = 1; spin_unlock_irqrestore(&dynamic_sign_lock, flags); + // Trigger async save persistent_dynamic_sign(); pr_info("dynamic sign updated: size=0x%x, hash=%.16s... (multi-manager enabled)\n", config->size, config->hash); @@ -470,7 +671,7 @@ int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config) // Clear only dynamic managers, preserve default manager clear_dynamic_managers_only(); - // Clear file using the same method as save + // Clear file using async operation clear_dynamic_sign_file(); pr_info("Dynamic sign config cleared (multi-manager disabled)\n"); @@ -490,24 +691,53 @@ int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config) bool ksu_load_dynamic_sign(void) { - return ksu_queue_work(&ksu_load_dynamic_sign_work); + if (!ksu_dynamic_wq) { + pr_err("Dynamic sign workqueue not initialized\n"); + return false; + } + + reinit_completion(&load_completion); + return queue_work(ksu_dynamic_wq, &ksu_load_dynamic_sign_work); } void ksu_dynamic_sign_init(void) { int i; - INIT_WORK(&ksu_save_dynamic_sign_work, do_save_dynamic_sign); - INIT_WORK(&ksu_load_dynamic_sign_work, do_load_dynamic_sign); + // Create dedicated workqueue for dynamic sign operations + ksu_dynamic_wq = alloc_workqueue("ksu_dynamic", WQ_UNBOUND | WQ_MEM_RECLAIM, 0); + if (!ksu_dynamic_wq) { + pr_err("Failed to create dynamic sign workqueue\n"); + return; + } + + // Initialize work structures + INIT_WORK(&ksu_save_dynamic_sign_work, do_save_dynamic_sign_with_recovery); + INIT_WORK(&ksu_load_dynamic_sign_work, do_load_dynamic_sign_with_recovery); INIT_WORK(&ksu_clear_dynamic_sign_work, do_clear_dynamic_sign_file); INIT_WORK(&ksu_rescan_manager_work, ksu_rescan_manager_work_handler); + // Initialize completions + init_completion(&save_completion); + init_completion(&load_completion); + // Initialize manager slots for (i = 0; i < MAX_MANAGERS; i++) { active_managers[i].is_active = false; } - pr_info("Dynamic sign initialized with conditional multi-manager support and rescan capability\n"); + // Reset retry counters + atomic_set(&save_retry_count, 0); + atomic_set(&load_retry_count, 0); + + pr_info("Dynamic sign initialized with enhanced error recovery and dedicated workqueue\n"); + + // Auto-load existing dynamic sign configuration after initialization + if (ksu_load_dynamic_sign()) { + pr_info("Auto-loading dynamic sign configuration...\n"); + } else { + pr_warn("Failed to schedule auto-load of dynamic sign configuration\n"); + } } void ksu_dynamic_sign_exit(void) @@ -515,8 +745,13 @@ void ksu_dynamic_sign_exit(void) // Clear only dynamic managers on exit, preserve default manager clear_dynamic_managers_only(); - // Save current config before exit - do_save_dynamic_sign(NULL); + // Wait for any pending operations to complete + if (ksu_dynamic_wq) { + flush_workqueue(ksu_dynamic_wq); + destroy_workqueue(ksu_dynamic_wq); + ksu_dynamic_wq = NULL; + } + pr_info("Dynamic sign exited, cleared dynamic managers, preserved default manager\n"); } diff --git a/kernel/dynamic_sign.h b/kernel/dynamic_sign.h index 2c1cc67b..ca77e3df 100644 --- a/kernel/dynamic_sign.h +++ b/kernel/dynamic_sign.h @@ -2,6 +2,7 @@ #define __KSU_H_DYNAMIC_SIGN #include +#include #include "ksu.h" #define DYNAMIC_SIGN_FILE_MAGIC 0x7f445347 // 'DSG', u32 @@ -20,7 +21,7 @@ struct manager_info { bool is_active; }; -// Dynamic sign operations +// Enhanced dynamic sign operations with error recovery int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config); void ksu_dynamic_sign_init(void); void ksu_dynamic_sign_exit(void); @@ -34,10 +35,13 @@ bool ksu_is_any_manager(uid_t uid); int ksu_get_manager_signature_index(uid_t uid); int ksu_get_active_managers(struct manager_list_info *info); -// Multi-manager APK verification +// Multi-manager APK verification with timeout protection bool ksu_is_multi_manager_apk(char *path, int *signature_index); // Manager rescanning functionality bool ksu_trigger_manager_rescan(void); +// Configuration access for signature verification +bool ksu_get_dynamic_sign_config(unsigned int *size, const char **hash); + #endif \ No newline at end of file