From cc1c66bb6faf85e759e51ab1f7f259ca59fcdc94 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Fri, 19 Sep 2025 21:01:01 +0800 Subject: [PATCH] Add UID scanner functionality and related infrastructure - Introduced a new module `uid_scanner` in userspace for managing UID scanning. - Created a new GitHub Actions workflow for building the `user_scanner`. - Implemented kernel communication in `throne_comm.c` and `throne_comm.h` to handle user space updates and rescan requests. - Developed the `uid_scanner` daemon in C to scan user directories and manage UID whitelists. - Added configuration management for the UID scanner with support for multiple users and auto-scanning. - Implemented logging and error handling throughout the UID scanning process. - Created necessary build files for the `user_scanner` JNI integration. - Added a `.gitignore` file to exclude build artifacts. --- .github/workflows/build-manager.yml | 29 + .github/workflows/user_scanner.yml | 40 + kernel/Makefile | 1 + kernel/core_hook.c | 7 + kernel/kernel_compat.h | 30 - kernel/throne_comm.c | 125 +++ kernel/throne_comm.h | 12 + kernel/throne_tracker.c | 259 +++-- .../src/main/java/com/sukisu/ultra/Natives.kt | 2 + .../com/sukisu/ultra/ui/screen/Settings.kt | 80 +- .../java/com/sukisu/ultra/ui/util/KsuCli.kt | 44 + .../io/sukisu/ultra/UltraToolInstall.java | 6 +- .../src/main/res/values-zh-rCN/strings.xml | 7 + manager/app/src/main/res/values/strings.xml | 7 + userspace/ksud/src/init_event.rs | 6 +- userspace/ksud/src/main.rs | 1 + userspace/ksud/src/uid_scanner.rs | 91 ++ userspace/user_scanner/.gitignore | 2 + userspace/user_scanner/jni/Android.mk | 8 + userspace/user_scanner/jni/Application.mk | 3 + userspace/user_scanner/jni/uid_scanner.c | 984 ++++++++++++++++++ 21 files changed, 1565 insertions(+), 179 deletions(-) create mode 100644 .github/workflows/user_scanner.yml create mode 100644 kernel/throne_comm.c create mode 100644 kernel/throne_comm.h create mode 100644 userspace/ksud/src/uid_scanner.rs create mode 100644 userspace/user_scanner/.gitignore create mode 100644 userspace/user_scanner/jni/Android.mk create mode 100644 userspace/user_scanner/jni/Application.mk create mode 100644 userspace/user_scanner/jni/uid_scanner.c diff --git a/.github/workflows/build-manager.yml b/.github/workflows/build-manager.yml index cee47ccd..b78abe61 100644 --- a/.github/workflows/build-manager.yml +++ b/.github/workflows/build-manager.yml @@ -10,6 +10,7 @@ on: - 'userspace/ksud/**' - 'userspace/susfs/**' - 'userspace/kpmmgr/**' + - 'userspace/user_scanner/**' pull_request: branches: [ "main" ] paths: @@ -109,6 +110,19 @@ jobs: target: ${{ matrix.target }} os: ${{ matrix.os }} + build-user_scanner: + if: ${{ always() }} + needs: [ check-build-lkm, build-lkm ] + strategy: + matrix: + include: + - target: All-linux-android + os: ubuntu-latest + uses: ./.github/workflows/user_scanner.yml + with: + target: ${{ matrix.target }} + os: ${{ matrix.os }} + build-ksud: if: ${{ always() }} needs: [ check-build-lkm, build-lkm ] @@ -176,6 +190,12 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 + - name: Download all userscanner artifacts + uses: actions/download-artifact@v4 + with: + name: userscanner-all-linux-android + path: . + - name: Download arm64 susfs uses: actions/download-artifact@v4 with: @@ -225,6 +245,15 @@ jobs: mkdir -p app/src/main/jniLibs/arm64-v8a cp -f ../arm64-v8a/zakozakozako ../manager/app/src/main/jniLibs/arm64-v8a/libzakozakozako.so + - name: Copy user_scanner to app jniLibs + run: | + mkdir -p app/src/main/jniLibs/arm64-v8a + mkdir -p app/src/main/jniLibs/x86_64 + mkdir -p app/src/main/jniLibs/armeabi-v7a + cp -f ../arm64-v8a/uid_scanner ../manager/app/src/main/jniLibs/arm64-v8a/uid_scanner.so + cp -f ../x86_64/uid_scanner ../manager/app/src/main/jniLibs/x86_64/uid_scanner.so + cp -f ../armeabi-v7a/uid_scanner ../manager/app/src/main/jniLibs/armeabi-v7a/uid_scanner.so + - name: Build with Gradle run: ./gradlew clean assembleRelease diff --git a/.github/workflows/user_scanner.yml b/.github/workflows/user_scanner.yml new file mode 100644 index 00000000..2729c8d3 --- /dev/null +++ b/.github/workflows/user_scanner.yml @@ -0,0 +1,40 @@ +name: Build user_scanner + +on: + push: + branches: [ "mian" ] + paths: + - '.github/workflows/user_scanner.yml' + - 'userspace/user_scanner/**' + workflow_dispatch: + workflow_call: + inputs: + target: + required: true + type: string + os: + required: false + type: string + default: self-hosted + +jobs: + build-user_scanner: + name: Build userspace user_scanner + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build user_scanner + working-directory: ./userspace/user_scanner + run: | + $ANDROID_NDK_HOME/ndk-build + + - name: Upload a Build Artifact + uses: actions/upload-artifact@v4 + with: + name: userscanner-all-linux-android + path: ./userspace/user_scanner/libs \ No newline at end of file diff --git a/kernel/Makefile b/kernel/Makefile index 50e874f6..55bfb784 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -8,6 +8,7 @@ kernelsu-objs += core_hook.o kernelsu-objs += ksud.o kernelsu-objs += embed_ksud.o kernelsu-objs += kernel_compat.o +kernelsu-objs += throne_comm.o ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y) kernelsu-objs += ksu_trace.o diff --git a/kernel/core_hook.c b/kernel/core_hook.c index cb798aea..66a1711d 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -42,6 +42,7 @@ #include "manager.h" #include "selinux/selinux.h" #include "throne_tracker.h" +#include "throne_comm.h" #include "kernel_compat.h" #include "kpm/kpm.h" @@ -223,6 +224,9 @@ int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry) new_dentry->d_iname, buf); track_throne(); + + // Also request userspace scan for next time + ksu_request_userspace_scan(); return 0; } @@ -407,6 +411,8 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, post_fs_data_lock = true; pr_info("post-fs-data triggered\n"); on_post_fs_data(); + // Initialize throne communication + ksu_throne_comm_init(); // Initializing Dynamic Signatures ksu_dynamic_manager_init(); pr_info("Dynamic sign config loaded during post-fs-data\n"); @@ -1027,6 +1033,7 @@ void __init ksu_core_init(void) void ksu_core_exit(void) { + ksu_throne_comm_exit(); #ifdef CONFIG_KPROBE pr_info("ksu_core_kprobe_exit\n"); // we dont use this now diff --git a/kernel/kernel_compat.h b/kernel/kernel_compat.h index 25911c6e..4bcfbf38 100644 --- a/kernel/kernel_compat.h +++ b/kernel/kernel_compat.h @@ -5,36 +5,6 @@ #include #include "ss/policydb.h" #include "linux/key.h" -#include - -/** - * list_count_nodes - count the number of nodes in a list - * @head: the head of the list - * - * This function iterates over the list starting from @head and counts - * the number of nodes in the list. It does not modify the list. - * - * Context: Any context. The function is safe to call in any context, - * including interrupt context, as it does not sleep or allocate - * memory. - * - * Return: the number of nodes in the list (excluding the head) - */ -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) -static inline __maybe_unused size_t list_count_nodes(const struct list_head *head) -{ - const struct list_head *pos; - size_t count = 0; - - if (!head) - return 0; - - list_for_each(pos, head) - count++; - - return count; -} -#endif /* * Adapt to Huawei HISI kernel without affecting other kernels , diff --git a/kernel/throne_comm.c b/kernel/throne_comm.c new file mode 100644 index 00000000..97d8b18c --- /dev/null +++ b/kernel/throne_comm.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include + +#include "klog.h" +#include "throne_comm.h" + +#define PROC_UID_SCANNER "ksu_uid_scanner" + +static struct proc_dir_entry *proc_entry = NULL; +static struct workqueue_struct *scanner_wq = NULL; +static struct work_struct scan_work; + +// Signal userspace to rescan +static bool need_rescan = false; + +static void rescan_work_fn(struct work_struct *work) +{ + // Signal userspace through proc interface + need_rescan = true; + pr_info("requested userspace uid rescan\n"); +} + +void ksu_request_userspace_scan(void) +{ + if (scanner_wq) { + queue_work(scanner_wq, &scan_work); + } +} + +void ksu_handle_userspace_update(void) +{ + // Called when userspace notifies update complete + need_rescan = false; + pr_info("userspace uid list updated\n"); +} + +static int uid_scanner_show(struct seq_file *m, void *v) +{ + if (need_rescan) { + seq_puts(m, "RESCAN\n"); + } else { + seq_puts(m, "OK\n"); + } + return 0; +} + +static int uid_scanner_open(struct inode *inode, struct file *file) +{ + return single_open(file, uid_scanner_show, NULL); +} + +static ssize_t uid_scanner_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + char cmd[16]; + + if (count >= sizeof(cmd)) + return -EINVAL; + + if (copy_from_user(cmd, buffer, count)) + return -EFAULT; + + cmd[count] = '\0'; + + // Remove newline if present + if (count > 0 && cmd[count-1] == '\n') + cmd[count-1] = '\0'; + + if (strcmp(cmd, "UPDATED") == 0) { + ksu_handle_userspace_update(); + pr_info("received userspace update notification\n"); + } + + return count; +} + +static const struct proc_ops uid_scanner_proc_ops = { + .proc_open = uid_scanner_open, + .proc_read = seq_read, + .proc_write = uid_scanner_write, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +int ksu_throne_comm_init(void) +{ + // Create workqueue + scanner_wq = alloc_workqueue("ksu_scanner", WQ_UNBOUND, 1); + if (!scanner_wq) { + pr_err("failed to create scanner workqueue\n"); + return -ENOMEM; + } + + INIT_WORK(&scan_work, rescan_work_fn); + + // Create proc entry + proc_entry = proc_create(PROC_UID_SCANNER, 0600, NULL, &uid_scanner_proc_ops); + if (!proc_entry) { + pr_err("failed to create proc entry\n"); + destroy_workqueue(scanner_wq); + return -ENOMEM; + } + + pr_info("throne communication initialized\n"); + return 0; +} + +void ksu_throne_comm_exit(void) +{ + if (proc_entry) { + proc_remove(proc_entry); + proc_entry = NULL; + } + + if (scanner_wq) { + destroy_workqueue(scanner_wq); + scanner_wq = NULL; + } + + pr_info("throne communication cleaned up\n"); +} \ No newline at end of file diff --git a/kernel/throne_comm.h b/kernel/throne_comm.h new file mode 100644 index 00000000..eedf8c15 --- /dev/null +++ b/kernel/throne_comm.h @@ -0,0 +1,12 @@ +#ifndef __KSU_H_THRONE_COMM +#define __KSU_H_THRONE_COMM + +void ksu_request_userspace_scan(void); + +void ksu_handle_userspace_update(void); + +int ksu_throne_comm_init(void); + +void ksu_throne_comm_exit(void); + +#endif \ No newline at end of file diff --git a/kernel/throne_tracker.c b/kernel/throne_tracker.c index 76932114..83ca487f 100644 --- a/kernel/throne_tracker.c +++ b/kernel/throne_tracker.c @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include "allowlist.h" #include "klog.h" // IWYU pragma: keep @@ -15,12 +13,12 @@ #include "throne_tracker.h" #include "kernel_compat.h" #include "dynamic_manager.h" +#include "throne_comm.h" uid_t ksu_manager_uid = KSU_INVALID_UID; #define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list.tmp" -#define USER_DATA_PATH "/data/user_de/0" -#define USER_DATA_PATH_LEN 256 +#define KSU_UID_LIST_PATH "/data/misc/user_uid/uid_list" struct uid_data { struct list_head list; @@ -28,6 +26,111 @@ struct uid_data { char package[KSU_MAX_PACKAGE_NAME]; }; +// Try read whitelist first, fallback if failed +static int read_uid_whitelist(struct list_head *uid_list) +{ + struct file *fp; + char *file_content = NULL; + char *line, *next_line; + loff_t file_size; + loff_t pos = 0; + int count = 0; + ssize_t bytes_read; + + fp = ksu_filp_open_compat(KSU_UID_LIST_PATH, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_info("whitelist not found, fallback needed\n"); + return -ENOENT; + } + + file_size = fp->f_inode->i_size; + if (file_size <= 0) { + pr_info("whitelist file is empty\n"); + filp_close(fp, NULL); + return -ENODATA; + } + + file_content = kzalloc(file_size + 1, GFP_ATOMIC); + if (!file_content) { + pr_err("failed to allocate memory for whitelist file (%lld bytes)\n", file_size); + filp_close(fp, NULL); + return -ENOMEM; + } + + bytes_read = ksu_kernel_read_compat(fp, file_content, file_size, &pos); + if (bytes_read != file_size) { + pr_err("failed to read whitelist file: read %zd bytes, expected %lld bytes\n", + bytes_read, file_size); + kfree(file_content); + filp_close(fp, NULL); + return -EIO; + } + + file_content[file_size] = '\0'; + filp_close(fp, NULL); + + pr_info("successfully read whitelist file (%lld bytes), parsing lines...\n", file_size); + + line = file_content; + while (line && *line) { + next_line = strchr(line, '\n'); + if (next_line) { + *next_line = '\0'; + next_line++; + } + + char *trimmed_line = line; + while (*trimmed_line == ' ' || *trimmed_line == '\t' || *trimmed_line == '\r') { + trimmed_line++; + } + + if (strlen(trimmed_line) > 0) { + char *line_copy = trimmed_line; + char *uid_str = strsep(&line_copy, " \t"); + char *package_name = line_copy; + + if (package_name) { + while (*package_name == ' ' || *package_name == '\t') { + package_name++; + } + } + + if (uid_str && package_name && strlen(package_name) > 0) { + u32 uid; + if (!kstrtou32(uid_str, 10, &uid)) { + struct uid_data *data = kzalloc(sizeof(struct uid_data), GFP_ATOMIC); + if (data) { + data->uid = uid; + size_t pkg_len = strlen(package_name); + size_t copy_len = min(pkg_len, (size_t)(KSU_MAX_PACKAGE_NAME - 1)); + strncpy(data->package, package_name, copy_len); + data->package[copy_len] = '\0'; + + list_add_tail(&data->list, uid_list); + count++; + + if (count % 100 == 0) { + pr_info("parsed %d packages so far...\n", count); + } + } else { + pr_err("failed to allocate memory for uid_data\n"); + } + } else { + pr_warn("invalid uid format in line: %s\n", trimmed_line); + } + } else { + pr_warn("invalid line format: %s\n", trimmed_line); + } + } + + line = next_line; + } + + kfree(file_content); + pr_info("successfully loaded %d uids from whitelist\n", count); + return count > 0 ? 0 : -ENODATA; +} + static int get_pkg_from_apk_path(char *pkg, const char *path) { int len = strlen(path); @@ -142,138 +245,6 @@ struct my_dir_context { #define FILLDIR_ACTOR_CONTINUE 0 #define FILLDIR_ACTOR_STOP -EINVAL #endif - -struct uid_scan_stats { - size_t total_found; - size_t errors_encountered; -}; - -struct user_data_context { - struct dir_context ctx; - struct list_head *uid_list; - struct uid_scan_stats *stats; -}; - -FILLDIR_RETURN_TYPE user_data_actor(struct dir_context *ctx, const char *name, - int namelen, loff_t off, u64 ino, - unsigned int d_type) -{ - struct user_data_context *my_ctx = - container_of(ctx, struct user_data_context, ctx); - - if (!my_ctx || !my_ctx->uid_list) { - return FILLDIR_ACTOR_STOP; - } - - if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen)) - return FILLDIR_ACTOR_CONTINUE; - - if (d_type != DT_DIR) - return FILLDIR_ACTOR_CONTINUE; - - if (namelen >= KSU_MAX_PACKAGE_NAME) { - pr_warn("Package name too long: %.*s\n", namelen, name); - if (my_ctx->stats) - my_ctx->stats->errors_encountered++; - return FILLDIR_ACTOR_CONTINUE; - } - - char package_path[USER_DATA_PATH_LEN]; - if (snprintf(package_path, sizeof(package_path), "%s/%.*s", - USER_DATA_PATH, namelen, name) >= sizeof(package_path)) { - pr_err("Path too long for package: %.*s\n", namelen, name); - if (my_ctx->stats) - my_ctx->stats->errors_encountered++; - return FILLDIR_ACTOR_CONTINUE; - } - - struct path path; - int err = kern_path(package_path, LOOKUP_FOLLOW, &path); - if (err) { - pr_debug("Package path lookup failed: %s (err: %d)\n", package_path, err); - if (my_ctx->stats) - my_ctx->stats->errors_encountered++; - return FILLDIR_ACTOR_CONTINUE; - } - - struct kstat stat; - err = vfs_getattr(&path, &stat, STATX_UID, AT_STATX_SYNC_AS_STAT); - path_put(&path); - - if (err) { - pr_debug("Failed to get attributes for: %s (err: %d)\n", package_path, err); - if (my_ctx->stats) - my_ctx->stats->errors_encountered++; - return FILLDIR_ACTOR_CONTINUE; - } - - uid_t uid = from_kuid(&init_user_ns, stat.uid); - if (uid == (uid_t)-1) { - pr_warn("Invalid UID for package: %.*s\n", namelen, name); - if (my_ctx->stats) - my_ctx->stats->errors_encountered++; - return FILLDIR_ACTOR_CONTINUE; - } - - struct uid_data *data = kzalloc(sizeof(struct uid_data), GFP_ATOMIC); - if (!data) { - pr_err("Failed to allocate memory for package: %.*s\n", namelen, name); - if (my_ctx->stats) - my_ctx->stats->errors_encountered++; - return FILLDIR_ACTOR_CONTINUE; - } - - data->uid = uid; - size_t copy_len = min(namelen, KSU_MAX_PACKAGE_NAME - 1); - strncpy(data->package, name, copy_len); - data->package[copy_len] = '\0'; - - list_add_tail(&data->list, my_ctx->uid_list); - - if (my_ctx->stats) - my_ctx->stats->total_found++; - - pr_info("UserDE UID: Found package: %s, uid: %u\n", data->package, data->uid); - - return FILLDIR_ACTOR_CONTINUE; -} - -int scan_user_data_for_uids(struct list_head *uid_list) -{ - struct file *dir_file; - struct uid_scan_stats stats = {0}; - int ret = 0; - - if (!uid_list) { - return -EINVAL; - } - - dir_file = ksu_filp_open_compat(USER_DATA_PATH, O_RDONLY, 0); - if (IS_ERR(dir_file)) { - pr_err("UserDE UID: Failed to open %s: %ld\n", USER_DATA_PATH, PTR_ERR(dir_file)); - return PTR_ERR(dir_file); - } - - struct user_data_context ctx = { - .ctx.actor = user_data_actor, - .uid_list = uid_list, - .stats = &stats - }; - - ret = iterate_dir(dir_file, &ctx.ctx); - filp_close(dir_file, NULL); - - if (stats.errors_encountered > 0) { - pr_warn("Encountered %zu errors while scanning user data directory\n", - stats.errors_encountered); - } - - pr_info("UserDE UID: Scanned user data directory, found %zu packages with %zu errors\n", - stats.total_found, stats.errors_encountered); - - return ret; -} - FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name, int namelen, loff_t off, u64 ino, unsigned int d_type) @@ -468,16 +439,22 @@ void track_throne() struct list_head uid_list; INIT_LIST_HEAD(&uid_list); - pr_info("Starting UID scan from user data directory\n"); - int ret = scan_user_data_for_uids(&uid_list); + pr_info("track_throne triggered, attempting whitelist read\n"); + + // Try read whitelist first + int ret = read_uid_whitelist(&uid_list); if (ret < 0) { - pr_warn("Failed to scan user data directory (%d), falling back to packages.list\n", ret); + pr_info("whitelist read failed (%d), request userspace scan\n", ret); + + // Request userspace to rescan + ksu_request_userspace_scan(); // fallback to packages.list method struct file *fp = ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0); if (IS_ERR(fp)) { - pr_err("Both user data scan and packages.list failed: %ld\n", PTR_ERR(fp)); + pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n", + __func__, PTR_ERR(fp)); goto out; } @@ -532,7 +509,7 @@ void track_throne() filp_close(fp, 0); pr_info("Loaded %zu packages from packages.list fallback\n", fallback_count); } else { - pr_info("UserDE UID: Successfully loaded %zu packages from user data directory\n", list_count_nodes(&uid_list)); + pr_info("loaded uids from whitelist successfully\n"); } // now update uid list diff --git a/manager/app/src/main/java/com/sukisu/ultra/Natives.kt b/manager/app/src/main/java/com/sukisu/ultra/Natives.kt index f934d192..222df276 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/Natives.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/Natives.kt @@ -31,6 +31,8 @@ object Natives { const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215 + const val MINIMAL_SUPPORTED_UID_SCANNER = 13347 + const val ROOT_UID = 0 const val ROOT_GID = 0 diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index 7f6314ef..2a38c5ab 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -48,6 +48,8 @@ import com.sukisu.ultra.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardElevation import com.sukisu.ultra.ui.util.LocalSnackbarHost import com.sukisu.ultra.ui.util.getBugreportFile +import com.sukisu.ultra.ui.util.setUidAutoScan +import com.sukisu.ultra.ui.util.setUidMultiUserScan import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -127,7 +129,7 @@ fun SettingScreen(navigator: DestinationsNavigator) { navigator.navigate(AppProfileTemplateScreenDestination) } ) - + // 卸载模块开关 var umountChecked by rememberSaveable { mutableStateOf(Natives.isDefaultUmountModules()) @@ -177,6 +179,80 @@ fun SettingScreen(navigator: DestinationsNavigator) { forceSignatureVerification = enabled } ) + if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER) { + var uidAutoScanEnabled by rememberSaveable { + mutableStateOf(prefs.getBoolean("uid_auto_scan", false)) + } + + var uidMultiUserScanEnabled by rememberSaveable { + mutableStateOf(prefs.getBoolean("uid_multi_user_scan", false)) + } + // 用户态扫描应用列表开关 + SwitchItem( + icon = Icons.Filled.Scanner, + title = stringResource(R.string.uid_auto_scan_title), + summary = stringResource(R.string.uid_auto_scan_summary), + checked = uidAutoScanEnabled, + onCheckedChange = { enabled -> + scope.launch { + try { + if (setUidAutoScan(enabled)) { + uidAutoScanEnabled = enabled + prefs.edit { putBoolean("uid_auto_scan", enabled) } + + // 如果关闭了用户态扫描,则同时关闭多用户扫描 + if (!enabled) { + uidMultiUserScanEnabled = false + prefs.edit { putBoolean("uid_multi_user_scan", false) } + } + } else { + snackBarHost.showSnackbar(context.getString(R.string.uid_scanner_setting_failed)) + } + } catch (e: Exception) { + snackBarHost.showSnackbar( + context.getString( + R.string.uid_scanner_setting_error, + e.message ?: "" + ) + ) + } + } + } + ) + + // 多用户应用扫描开关 - 仅在启用用户态扫描时显示 + AnimatedVisibility( + visible = uidAutoScanEnabled, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + SwitchItem( + icon = Icons.Filled.Groups, + title = stringResource(R.string.uid_multi_user_scan_title), + summary = stringResource(R.string.uid_multi_user_scan_summary), + checked = uidMultiUserScanEnabled, + onCheckedChange = { enabled -> + scope.launch { + try { + if (setUidMultiUserScan(enabled)) { + uidMultiUserScanEnabled = enabled + prefs.edit { putBoolean("uid_multi_user_scan", enabled) } + } else { + snackBarHost.showSnackbar(context.getString(R.string.uid_scanner_setting_failed)) + } + } catch (e: Exception) { + snackBarHost.showSnackbar( + context.getString( + R.string.uid_scanner_setting_error, + e.message ?: "" + ) + ) + } + } + } + ) + } + } } ) } @@ -807,4 +883,4 @@ private fun TopBar( windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), scrollBehavior = scrollBehavior ) -} +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt index 16c90b68..f20add64 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt @@ -569,3 +569,47 @@ fun getZygiskImplement(): String { Log.i(TAG, "Zygisk implement: $result") return result } + +fun getUidScannerDaemonPath(): String { + return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libuid_scanner.so" +} + +fun ensureUidScannerExecutable(): Boolean { + val shell = getRootShell() + val uidScannerPath = getUidScannerDaemonPath() + val targetPath = "/data/adb/uid_scanner" + + if (!ShellUtils.fastCmdResult(shell, "test -f $targetPath")) { + val copyResult = ShellUtils.fastCmdResult(shell, "cp $uidScannerPath $targetPath") + if (!copyResult) { + return false + } + } + + val result = ShellUtils.fastCmdResult(shell, "chmod 755 $targetPath") + return result +} + +fun setUidAutoScan(enabled: Boolean): Boolean { + val shell = getRootShell() + if (!ensureUidScannerExecutable()) { + return false + } + + val enableValue = if (enabled) 1 else 0 + val cmd = "${getUidScannerDaemonPath()} --auto-scan $enableValue && ${getUidScannerDaemonPath()} reload" + val result = ShellUtils.fastCmdResult(shell, cmd) + return result +} + +fun setUidMultiUserScan(enabled: Boolean): Boolean { + val shell = getRootShell() + if (!ensureUidScannerExecutable()) { + return false + } + + val enableValue = if (enabled) 1 else 0 + val cmd = "${getUidScannerDaemonPath()} --multi-user $enableValue && ${getUidScannerDaemonPath()} reload" + val result = ShellUtils.fastCmdResult(shell, cmd) + return result +} diff --git a/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java b/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java index 209dbcb9..84fdddae 100644 --- a/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java +++ b/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java @@ -1,15 +1,13 @@ package io.sukisu.ultra; -import static com.sukisu.ultra.ui.util.KsuCliKt.getKpmmgrPath; -import static com.sukisu.ultra.ui.util.KsuCliKt.getSuSFSDaemonPath; - +import static com.sukisu.ultra.ui.util.KsuCliKt.*; import android.annotation.SuppressLint; public class UltraToolInstall { private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr"; private static final String OUTSIDE_SUSFSD_PATH = "/data/adb/ksu/bin/susfsd"; + @SuppressLint("SetWorldReadable") - @SuppressWarnings("ResultOfMethodCallIgnored") public static void tryToInstall() { String kpmmgrPath = getKpmmgrPath(); if (UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) { diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index aa34e889..3c7602ef 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -653,4 +653,11 @@ 跟随内核 原样使用内核,不进行任何 KPM 修改 + + 用户态扫描应用列表 + 开启后将使用用户态扫描应用列表,提高稳定性 (因内核扫描应用列表出现卡死等问题可以尝试打开此选项) + 多用户应用扫描 + 开启后将扫描所有用户的应用,包括工作资料等 + 设置失败,请检查权限 + 设置失败: %s diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index e06284eb..35988549 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -661,4 +661,11 @@ Important Note:\n Follow Kernel Use kernel as-is without any KPM modifications + + User-mode scanning application list + Enabling this option will use user-mode scanning for the application list, improving stability. (If you encounter issues such as freezing during kernel scanning of the application list, you may try enabling this option.) + Multi-User Application Scanning + When enabled, scans applications for all users, including work profiles + Setting failed, please check permissions + Setting failed: %s \ No newline at end of file diff --git a/userspace/ksud/src/init_event.rs b/userspace/ksud/src/init_event.rs index 478faa87..2c751e3c 100644 --- a/userspace/ksud/src/init_event.rs +++ b/userspace/ksud/src/init_event.rs @@ -1,7 +1,6 @@ use crate::defs::{KSU_MOUNT_SOURCE, NO_MOUNT_PATH, NO_TMPFS_PATH}; -use crate::kpm; use crate::module::{handle_updated_modules, prune_modules}; -use crate::{assets, defs, ksucalls, restorecon, utils}; +use crate::{assets, defs, ksucalls, restorecon, utils, kpm, uid_scanner}; use anyhow::{Context, Result}; use log::{info, warn}; use rustix::fs::{MountFlags, mount}; @@ -35,6 +34,9 @@ pub fn on_post_data_fs() -> Result<()> { assets::ensure_binaries(true).with_context(|| "Failed to extract bin assets")?; + // Start UID scanner daemon with highest priority + uid_scanner::start_uid_scanner_daemon()?; + // tell kernel that we've mount the module, so that it can do some optimization ksucalls::report_module_mounted(); diff --git a/userspace/ksud/src/main.rs b/userspace/ksud/src/main.rs index 4d183907..9e2f35ce 100644 --- a/userspace/ksud/src/main.rs +++ b/userspace/ksud/src/main.rs @@ -15,6 +15,7 @@ mod restorecon; mod sepolicy; mod su; mod utils; +mod uid_scanner; fn main() -> anyhow::Result<()> { cli::run() diff --git a/userspace/ksud/src/uid_scanner.rs b/userspace/ksud/src/uid_scanner.rs new file mode 100644 index 00000000..0a5ea1b1 --- /dev/null +++ b/userspace/ksud/src/uid_scanner.rs @@ -0,0 +1,91 @@ +use crate::utils; +use anyhow::{Context, Result}; +use log::{info, warn}; +use std::{ + fs, + io::Write, + os::unix::{ + fs::{symlink, PermissionsExt}, + process::CommandExt, + }, + path::Path, + process::{Command, Stdio}, +}; + +pub fn start_uid_scanner_daemon() -> Result<()> { + const SCANNER_PATH: &str = "/data/adb/uid_scanner"; + const LINK_DIR: &str = "/data/adb/ksu/bin"; + const LINK_PATH: &str = "/data/adb/ksu/bin/uid_scanner"; + const SERVICE_DIR: &str = "/data/adb/service.d"; + const SERVICE_PATH: &str = "/data/adb/service.d/uid_scanner.sh"; + + if !Path::new(SCANNER_PATH).exists() { + warn!("uid scanner binary not found at {}", SCANNER_PATH); + return Ok(()); + } + + if let Err(e) = fs::set_permissions(SCANNER_PATH, fs::Permissions::from_mode(0o755)) { + warn!("failed to set permissions for {}: {}", SCANNER_PATH, e); + } + + #[cfg(unix)] + { + if let Err(e) = fs::create_dir_all(LINK_DIR) { + warn!("failed to create {}: {}", LINK_DIR, e); + } else if !Path::new(LINK_PATH).exists() { + match symlink(SCANNER_PATH, LINK_PATH) { + Ok(_) => info!("created symlink {} -> {}", SCANNER_PATH, LINK_PATH), + Err(e) => warn!("failed to create symlink: {}", e), + } + } + } + + if let Err(e) = fs::create_dir_all(SERVICE_DIR) { + warn!("failed to create {}: {}", SERVICE_DIR, e); + } else if !Path::new(SERVICE_PATH).exists() { + let content = r#"#!/system/bin/sh +# KSU uid_scanner auto-restart script +until [ -d "/sdcard/Android" ]; do sleep 1; done +sleep 10 +/data/adb/uid_scanner restart +"#; + match fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(SERVICE_PATH) + .and_then(|mut f| { + f.write_all(content.as_bytes())?; + f.sync_all()?; + fs::set_permissions(SERVICE_PATH, fs::Permissions::from_mode(0o755)) + }) { + Ok(_) => info!("created service script {}", SERVICE_PATH), + Err(e) => warn!("failed to write {}: {}", SERVICE_PATH, e), + } + } + + info!("starting uid scanner daemon with highest priority"); + let mut cmd = Command::new(SCANNER_PATH); + cmd.arg("start") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .current_dir("/"); + + unsafe { + cmd.pre_exec(|| { + libc::nice(-20); + libc::setsid(); + Ok(()) + }); + } + + match cmd.spawn() { + Ok(child) => { + info!("uid scanner daemon started with pid: {}", child.id()); + std::mem::drop(child); + } + Err(e) => warn!("failed to start uid scanner daemon: {}", e), + } + + Ok(()) +} diff --git a/userspace/user_scanner/.gitignore b/userspace/user_scanner/.gitignore new file mode 100644 index 00000000..720289cc --- /dev/null +++ b/userspace/user_scanner/.gitignore @@ -0,0 +1,2 @@ +/obj +/libs diff --git a/userspace/user_scanner/jni/Android.mk b/userspace/user_scanner/jni/Android.mk new file mode 100644 index 00000000..4c04fa03 --- /dev/null +++ b/userspace/user_scanner/jni/Android.mk @@ -0,0 +1,8 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := uid_scanner +LOCAL_SRC_FILES := uid_scanner.c +LOCAL_LDLIBS := -llog +LOCAL_CFLAGS := -Wall -Wextra -std=c99 +include $(BUILD_EXECUTABLE) \ No newline at end of file diff --git a/userspace/user_scanner/jni/Application.mk b/userspace/user_scanner/jni/Application.mk new file mode 100644 index 00000000..aadef273 --- /dev/null +++ b/userspace/user_scanner/jni/Application.mk @@ -0,0 +1,3 @@ +APP_ABI := arm64-v8a x86_64 armeabi-v7a +APP_PLATFORM := android-35 +APP_STL := none diff --git a/userspace/user_scanner/jni/uid_scanner.c b/userspace/user_scanner/jni/uid_scanner.c new file mode 100644 index 00000000..433d241e --- /dev/null +++ b/userspace/user_scanner/jni/uid_scanner.c @@ -0,0 +1,984 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "User_UID_Scanner" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +// Paths and constants +#define USER_DATA_BASE_PATH "/data/user_de" +#define KSU_UID_LIST_PATH "/data/misc/user_uid/uid_list" +#define PROC_COMM_PATH "/proc/ksu_uid_scanner" +#define PID_FILE_PATH "/data/misc/user_uid/uid_scanner.pid" +#define LOG_FILE_PATH "/data/misc/user_uid/uid_scanner.log" +#define CONFIG_FILE_PATH "/data/misc/user_uid/uid_scanner.conf" + +#define MAX_PACKAGE_NAME 256 +#define MAX_PATH_LEN 512 +#define MAX_LOG_SIZE (1024 * 1024) // 1MB +#define MAX_USERS 8 +#define MAX_RETRIES 3 +#define RETRY_DELAY 60 + +typedef enum { + LANG_EN = 0, + LANG_ZH = 1 +} language_t; + +struct scanner_config { + language_t language; + int multi_user_scan; + int scan_interval; + int log_level; + int auto_scan; +}; + +struct uid_data { + int uid; + char package[MAX_PACKAGE_NAME]; + struct uid_data *next; +}; + +typedef struct { + const char *en; + const char *zh; +} message_t; + +// Global variables +static volatile int manual_scan_flag = 0; +static volatile int should_exit = 0; +static volatile int should_reload = 0; +static struct uid_data *uid_list_head = NULL; +static int log_fd = -1; + +static struct scanner_config config = { + .language = LANG_EN, + .multi_user_scan = 0, + .scan_interval = 5, + .log_level = 1, + .auto_scan = 0 +}; + +int save_config(void); + +// message dictionary +static const message_t messages[] = { + {"Signal %d received", "收到信号 %d"}, + {"Reload signal", "重载信号"}, + {"User signal", "用户信号"}, + {"Log rotated", "日志轮转"}, + {"Fork failed: %s", "Fork失败: %s"}, + {"setsid failed: %s", "setsid失败: %s"}, + {"Second fork failed: %s", "第二次fork失败: %s"}, + {"chdir failed: %s", "目录切换失败: %s"}, + {"PID file create failed %s: %s", "PID文件创建失败 %s: %s"}, + {"PID file created: %d", "PID文件已创建: %d"}, + {"Daemon not running", "守护进程未运行"}, + {"Stopping daemon (PID: %d)", "停止守护进程 (PID: %d)"}, + {"Kill signal failed: %s", "终止信号失败: %s"}, + {"Daemon stopped", "守护进程已停止"}, + {"Force terminating", "强制终止中"}, + {"Daemon killed", "守护进程已杀死"}, + {"Cannot stop daemon", "无法停止守护进程"}, + {"Restarting daemon", "重启守护进程"}, + {"Cannot stop old daemon", "无法停止旧守护进程"}, + {"Starting new daemon", "启动新守护进程"}, + {"Status: Not running", "状态: 未运行"}, + {"Status: Running (PID: %d)", "状态: 运行中 (PID: %d)"}, + {"Recent logs:", "最近日志:"}, + {"Status: Stopped (stale PID)", "状态: 已停止 (陈旧PID)"}, + {"Sending reload signal (PID: %d)", "发送重载信号 (PID: %d)"}, + {"Reload signal sent", "重载信号已发送"}, + {"Reload signal failed: %s", "重载信号失败: %s"}, + {"Directory open failed %s: %s", "目录打开失败 %s: %s"}, + {"Scan started", "扫描开始"}, + {"Package name too long: %s", "包名过长: %s"}, + {"File stat failed %s: %s", "文件状态获取失败 %s: %s"}, + {"Memory allocation failed", "内存分配失败"}, + {"Scan complete, found %d packages", "扫描完成,发现 %d 个包"}, + {"Whitelist file open failed %s: %s", "白名单文件打开失败 %s: %s"}, + {"Whitelist written %d entries", "白名单写入 %d 个条目"}, + {"Kernel comm file open failed %s: %s", "内核通信文件打开失败 %s: %s"}, + {"Kernel comm write failed %s: %s", "内核通信写入失败 %s: %s"}, + {"Kernel notified", "内核已通知"}, + {"Performing scan and update", "执行扫描和更新"}, + {"Scan failed", "扫描失败"}, + {"Whitelist write failed", "白名单写入失败"}, + {"Scan completed successfully", "扫描成功完成"}, + {"Whitelist not found: %s", "白名单未找到: %s"}, + {"Current whitelist:", "当前白名单:"}, + {"One-time scan", "一次性扫描"}, + {"Invalid argument: %s", "无效参数: %s"}, + {"Daemon already running", "守护进程已运行"}, + {"Starting daemon", "启动守护进程"}, + {"Daemon startup failed", "守护进程启动失败"}, + {"Daemon started", "守护进程已启动"}, + {"Reload request received", "收到重载请求"}, + {"Kernel rescan request", "内核重扫描请求"}, + {"Daemon exiting", "守护进程退出中"}, + {"Daemon exited", "守护进程已退出"}, + {"Config loaded", "配置已加载"}, + {"Config saved", "配置已保存"}, + {"Config load failed: %s", "配置加载失败: %s"}, + {"Config save failed: %s", "配置保存失败: %s"}, + {"Language switched to English", "语言切换到英文"}, + {"Language switched to Chinese", "语言切换到中文"}, + {"Multi-user scan enabled", "多用户扫描启用"}, + {"Multi-user scan disabled", "多用户扫描禁用"}, + {"Scanning directory: %s", "扫描目录: %s"}, + {"Found %d users", "发现 %d 个用户"}, + {"Using fallback user detection", "使用备用用户检测"}, + {"Auto scan enabled", "自动扫描启用"}, + {"Auto scan disabled", "自动扫描禁用"}, + {"Auto scan disabled, daemon loaded", "自动扫描禁用,守护进程已加载"}, + {"Auto scan disabled, skipping", "自动扫描禁用,跳过"}, + {"Auto scan disabled, ignoring kernel request", "自动扫描禁用,忽略内核请求"}, + {"Retry attempt %d/%d", "重试 %d/%d"}, + {"Max retries reached, waiting %d seconds", "达到最大重试次数,等待 %d 秒"}, + {"Operation failed after retries", "重试后操作失败"}, + {"Auto scan disabled, operation not allowed", "自动扫描禁用,操作不被允许"}, + {"Manual scan requested, ignoring auto_scan setting", "手动扫描请求,忽略自动扫描设置"} +}; + +#define MSG_COUNT (sizeof(messages) / sizeof(messages[0])) + +const char* get_message(int msg_id) { + if (msg_id < 0 || msg_id >= (int)MSG_COUNT) { + return "Unknown message"; + } + return (config.language == LANG_ZH) ? messages[msg_id].zh : messages[msg_id].en; +} + +void write_log(const char *level, int msg_id, ...) { + char buffer[1024]; + char formatted_msg[1024]; + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + va_list args; + + va_start(args, msg_id); + vsnprintf(formatted_msg, sizeof(formatted_msg), get_message(msg_id), args); + va_end(args); + + strftime(buffer, 64, "[%H:%M:%S]", tm_info); + snprintf(buffer + strlen(buffer), sizeof(buffer) - strlen(buffer), " %s: %s", level, formatted_msg); + + if (log_fd != -1) { + dprintf(log_fd, "%s\n", buffer); + fsync(log_fd); + } + + if (strcmp(level, "ERROR") == 0) { + LOGE("%s", formatted_msg); + } else { + LOGI("%s", formatted_msg); + } +} + +// Retry wrapper for operations +int retry_operation(int (*operation)(void), const char *op_name) { + (void)op_name; + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { + int result = operation(); + if (result == 0) { + return 0; // Success + } + + if (attempt < MAX_RETRIES) { + write_log("WARN", 69, attempt, MAX_RETRIES); // Retry attempt X/Y + sleep(1); + } else { + write_log("ERROR", 70, RETRY_DELAY); // Max retries reached + sleep(RETRY_DELAY); + write_log("ERROR", 71); // Operation failed after retries + } + } + return -1; +} + +void ensure_directory_exists(void) { + system("mkdir -p /data/misc/user_uid"); +} + +void parse_config_line(const char *key, const char *value) { + if (strcmp(key, "language") == 0) { + config.language = (strcmp(value, "zh") == 0) ? LANG_ZH : LANG_EN; + } else if (strcmp(key, "multi_user_scan") == 0) { + config.multi_user_scan = atoi(value); + } else if (strcmp(key, "scan_interval") == 0) { + config.scan_interval = atoi(value); + if (config.scan_interval < 1) config.scan_interval = 5; + } else if (strcmp(key, "log_level") == 0) { + config.log_level = atoi(value); + } else if (strcmp(key, "auto_scan") == 0) { + config.auto_scan = atoi(value); + } +} + +int load_config(void) { + FILE *fp = fopen(CONFIG_FILE_PATH, "r"); + if (!fp) { + write_log("WARN", 56, "配置文件不存在,使用默认配置"); + return save_config(); + } + + char line[256]; + while (fgets(line, sizeof(line), fp)) { + line[strcspn(line, "\n")] = 0; + + if (line[0] == '#' || line[0] == '\0') continue; + + char key[64], value[64]; + if (sscanf(line, "%63[^=]=%63s", key, value) == 2) { + parse_config_line(key, value); + } + } + + fclose(fp); + write_log("INFO", 54); // Config loaded + write_log("INFO", config.auto_scan ? 64 : 65); // 记录当前自动扫描状态 + return 0; +} + +int save_config(void) { + ensure_directory_exists(); + + FILE *fp = fopen(CONFIG_FILE_PATH, "w"); + if (!fp) { + write_log("ERROR", 57, strerror(errno)); // Config save failed + return -1; + } + + fprintf(fp, "# UID Scanner Configuration\n"); + fprintf(fp, "# Language: en (English) or zh (Chinese)\n"); + fprintf(fp, "language=%s\n", (config.language == LANG_ZH) ? "zh" : "en"); + fprintf(fp, "# Multi-user scanning: 0=disabled, 1=enabled\n"); + fprintf(fp, "multi_user_scan=%d\n", config.multi_user_scan); + fprintf(fp, "# Scan interval in seconds\n"); + fprintf(fp, "scan_interval=%d\n", config.scan_interval); + fprintf(fp, "# Log level: 0=minimal, 1=normal, 2=verbose\n"); + fprintf(fp, "log_level=%d\n", config.log_level); + fprintf(fp, "# Auto scan: 0=disabled, 1=enabled\n"); + fprintf(fp, "auto_scan=%d\n", config.auto_scan); + + fclose(fp); + write_log("INFO", 55); // Config saved + return 0; +} + +void set_language(language_t lang) { + config.language = lang; + save_config(); + write_log("INFO", (lang == LANG_ZH) ? 59 : 58); +} + +void set_multi_user_scan(int enabled) { + config.multi_user_scan = enabled; + save_config(); + write_log("INFO", enabled ? 60 : 61); +} + +void set_auto_scan(int enabled) { + config.auto_scan = enabled; + save_config(); + write_log("INFO", enabled ? 64 : 65); +} + +void signal_handler(int sig) { + switch (sig) { + case SIGTERM: + case SIGINT: + should_exit = 1; + write_log("INFO", 0, sig); + break; + case SIGHUP: + should_reload = 1; + write_log("INFO", 1); + break; + case SIGUSR1: + should_reload = 1; + write_log("INFO", 2); + break; + } +} + +void manage_log_file(void) { + struct stat st; + if (log_fd == -1 || fstat(log_fd, &st) != 0) return; + + if (st.st_size > MAX_LOG_SIZE) { + close(log_fd); + char backup_path[MAX_PATH_LEN]; + snprintf(backup_path, sizeof(backup_path), "%s.old", LOG_FILE_PATH); + rename(LOG_FILE_PATH, backup_path); + log_fd = open(LOG_FILE_PATH, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (log_fd != -1) { + write_log("INFO", 3); // Log rotated + } + } +} + +void setup_daemon_stdio(void) { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); +} + +int daemonize(void) { + pid_t pid = fork(); + if (pid < 0) { + LOGE(get_message(4), strerror(errno)); + return -1; + } + if (pid > 0) exit(0); + + if (setsid() < 0) { + LOGE(get_message(5), strerror(errno)); + return -1; + } + + signal(SIGHUP, SIG_IGN); + pid = fork(); + if (pid < 0) { + LOGE(get_message(6), strerror(errno)); + return -1; + } + if (pid > 0) exit(0); + + umask(0); + if (chdir("/") < 0) { + LOGE(get_message(7), strerror(errno)); + return -1; + } + + setup_daemon_stdio(); + return 0; +} + +int write_pid_file(void) { + ensure_directory_exists(); + FILE *fp = fopen(PID_FILE_PATH, "w"); + if (!fp) { + write_log("ERROR", 8, PID_FILE_PATH, strerror(errno)); + return -1; + } + fprintf(fp, "%d\n", getpid()); + fclose(fp); + write_log("INFO", 9, getpid()); + return 0; +} + +pid_t read_pid_file(void) { + FILE *fp = fopen(PID_FILE_PATH, "r"); + if (!fp) return 0; + + pid_t pid = 0; + if (fscanf(fp, "%d", &pid) != 1) pid = 0; + fclose(fp); + return pid; +} + +int is_daemon_running(void) { + pid_t pid = read_pid_file(); + if (pid <= 0) return 0; + + if (kill(pid, 0) == 0) { + return 1; + } else { + unlink(PID_FILE_PATH); + return 0; + } +} + +int stop_daemon(void) { + pid_t pid = read_pid_file(); + if (pid <= 0) { + printf("%s\n", get_message(10)); + return 0; + } + + printf(get_message(11), pid); + printf("\n"); + + if (kill(pid, SIGTERM) != 0) { + printf(get_message(12), strerror(errno)); + printf("\n"); + return -1; + } + + // Wait up to 30 seconds + for (int i = 0; i < 30; i++) { + if (kill(pid, 0) != 0) { + printf("%s\n", get_message(13)); + unlink(PID_FILE_PATH); + return 0; + } + sleep(1); + } + + printf("%s\n", get_message(14)); + if (kill(pid, SIGKILL) == 0) { + printf("%s\n", get_message(15)); + unlink(PID_FILE_PATH); + return 0; + } + + printf("%s\n", get_message(16)); + return -1; +} + +int restart_daemon(void) { + printf("%s\n", get_message(17)); + stop_daemon(); + sleep(2); + + if (is_daemon_running()) { + printf("%s\n", get_message(18)); + return -1; + } + + printf("%s\n", get_message(19)); + return 0; +} + +void show_status(void) { + pid_t pid = read_pid_file(); + if (pid <= 0) { + printf("%s\n", get_message(20)); + return; + } + + if (kill(pid, 0) == 0) { + printf(get_message(21), pid); + printf("\n"); + + if (access(LOG_FILE_PATH, R_OK) == 0) { + printf("\n%s\n", get_message(22)); + char cmd[512]; + snprintf(cmd, sizeof(cmd), "tail -n 10 %s", LOG_FILE_PATH); + system(cmd); + } + } else { + printf("%s\n", get_message(23)); + unlink(PID_FILE_PATH); + } +} + +void reload_daemon(void) { + pid_t pid = read_pid_file(); + if (pid <= 0 || kill(pid, 0) != 0) { + printf("%s\n", get_message(10)); + return; + } + + printf(get_message(24), pid); + printf("\n"); + + if (kill(pid, SIGUSR1) == 0) { + printf("%s\n", get_message(25)); + } else { + printf(get_message(26), strerror(errno)); + printf("\n"); + } +} + +int get_users_from_pm(char user_dirs[][MAX_PATH_LEN], int max_users) { + FILE *fp = popen("pm list users 2>/dev/null | grep 'UserInfo{' | sed 's/.*UserInfo{\\([0-9]*\\):.*/\\1/'", "r"); + if (!fp) return 0; + + int user_count = 0; + char line[64]; + while (fgets(line, sizeof(line), fp) && user_count < max_users) { + int user_id = atoi(line); + if (user_id >= 0) { + snprintf(user_dirs[user_count], MAX_PATH_LEN, "%s/%d", USER_DATA_BASE_PATH, user_id); + if (access(user_dirs[user_count], F_OK) == 0) { + user_count++; + } + } + } + + pclose(fp); + return user_count; +} + +int get_users_from_directory_scan(char user_dirs[][MAX_PATH_LEN], int max_users) { + DIR *dir = opendir(USER_DATA_BASE_PATH); + if (!dir) { + write_log("ERROR", 27, USER_DATA_BASE_PATH, strerror(errno)); + snprintf(user_dirs[0], MAX_PATH_LEN, "%s/0", USER_DATA_BASE_PATH); + return 1; + } + + int user_count = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL && user_count < max_users) { + if (entry->d_type == DT_DIR) { + char *endptr; + long user_id = strtol(entry->d_name, &endptr, 10); + if (*endptr == '\0' && strlen(entry->d_name) > 0 && user_id >= 0) { + snprintf(user_dirs[user_count], MAX_PATH_LEN, "%s/%s", USER_DATA_BASE_PATH, entry->d_name); + user_count++; + } + } + } + closedir(dir); + + if (user_count == 0) { + snprintf(user_dirs[0], MAX_PATH_LEN, "%s/0", USER_DATA_BASE_PATH); + user_count = 1; + } + + return user_count; +} + +int get_user_directories(char user_dirs[][MAX_PATH_LEN], int max_users) { + if (!config.multi_user_scan) { + snprintf(user_dirs[0], MAX_PATH_LEN, "%s/0", USER_DATA_BASE_PATH); + return 1; + } + + int user_count = get_users_from_pm(user_dirs, max_users); + if (user_count > 0) return user_count; + + return get_users_from_directory_scan(user_dirs, max_users); +} + +void free_uid_list(void) { + struct uid_data *current = uid_list_head; + while (current) { + struct uid_data *next = current->next; + free(current); + current = next; + } + uid_list_head = NULL; +} + +struct uid_data* create_uid_entry(int uid, const char *package_name) { + struct uid_data *data = malloc(sizeof(struct uid_data)); + if (!data) { + write_log("ERROR", 31); + return NULL; + } + + data->uid = uid; + strncpy(data->package, package_name, MAX_PACKAGE_NAME - 1); + data->package[MAX_PACKAGE_NAME - 1] = '\0'; + data->next = uid_list_head; + return data; +} + +int scan_single_directory(const char *dir_path) { + DIR *dir = opendir(dir_path); + if (!dir) { + write_log("ERROR", 27, dir_path, strerror(errno)); + return 0; + } + + int count = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (should_exit) break; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + if (entry->d_type != DT_DIR) continue; + + if (strlen(entry->d_name) >= MAX_PACKAGE_NAME) { + write_log("WARN", 29, entry->d_name); + continue; + } + + char path[MAX_PATH_LEN]; + snprintf(path, sizeof(path), "%s/%s", dir_path, entry->d_name); + + struct stat st; + if (stat(path, &st) != 0) { + write_log("ERROR", 30, path, strerror(errno)); + continue; + } + + struct uid_data *data = create_uid_entry(st.st_uid, entry->d_name); + if (data) { + uid_list_head = data; + count++; + } + } + + closedir(dir); + return count; +} + +int perform_uid_scan(void) { + char user_dirs[MAX_USERS][MAX_PATH_LEN]; + int total_count = 0; + + free_uid_list(); + + int user_count = get_user_directories(user_dirs, MAX_USERS); + if (user_count <= 0) return -1; + + write_log("INFO", 28); + write_log("INFO", 63, user_count); + + for (int i = 0; i < user_count && !should_exit; i++) { + write_log("INFO", 62, user_dirs[i]); + total_count += scan_single_directory(user_dirs[i]); + } + + write_log("INFO", 32, total_count); + return total_count; +} + +int write_uid_whitelist(void) { + ensure_directory_exists(); + + FILE *fp = fopen(KSU_UID_LIST_PATH, "w"); + if (!fp) { + write_log("ERROR", 33, KSU_UID_LIST_PATH, strerror(errno)); + return -1; + } + + int count = 0; + struct uid_data *current = uid_list_head; + while (current) { + fprintf(fp, "%d %s\n", current->uid, current->package); + current = current->next; + count++; + } + + fclose(fp); + write_log("INFO", 34, count); + return count; +} + +void notify_kernel_update(void) { + int fd = open(PROC_COMM_PATH, O_WRONLY); + if (fd < 0) { + write_log("ERROR", 35, PROC_COMM_PATH, strerror(errno)); + return; + } + + if (write(fd, "UPDATED", 7) != 7) { + write_log("ERROR", 36, PROC_COMM_PATH, strerror(errno)); + } else { + write_log("INFO", 37); + } + + close(fd); +} + +int check_kernel_request(void) { + FILE *fp = fopen(PROC_COMM_PATH, "r"); + if (!fp) return 0; + + char status[16]; + int result = 0; + if (fgets(status, sizeof(status), fp) != NULL) { + result = (strncmp(status, "RESCAN", 6) == 0); + } + + fclose(fp); + return result; +} + +// Retry wrapper functions +int scan_operation(void) { + return perform_uid_scan() < 0 ? -1 : 0; +} + +int write_operation(void) { + return write_uid_whitelist() < 0 ? -1 : 0; +} + +void perform_scan_update(void) { + if (!config.auto_scan && !manual_scan_flag) { + write_log("WARN", 72); // Auto scan disabled, operation not allowed + return; + } + + write_log("INFO", 38); + + if (retry_operation(scan_operation, "scan") != 0) { + write_log("ERROR", 39); + return; + } + + if (retry_operation(write_operation, "write") != 0) { + write_log("ERROR", 40); + return; + } + + notify_kernel_update(); + write_log("INFO", 41); +} + +void perform_manual_scan_update(void) { + manual_scan_flag = 1; + write_log("INFO", 73); // Manual scan requested, ignoring auto_scan setting + write_log("INFO", 38); + + if (retry_operation(scan_operation, "scan") != 0) { + write_log("ERROR", 39); + return; + } + + if (retry_operation(write_operation, "write") != 0) { + write_log("ERROR", 40); + return; + } + + notify_kernel_update(); + write_log("INFO", 41); +} + +void print_usage(const char *prog) { + if (config.language == LANG_ZH) { + printf("用法: %s [选项]\n", prog); + printf("KSU UID 扫描器 - 管理UID白名单\n\n"); + printf("选项:\n"); + printf(" start 启动守护进程\n"); + printf(" stop 停止守护进程\n"); + printf(" restart 重启守护进程\n"); + printf(" status 显示守护进程状态\n"); + printf(" reload 重新加载守护进程配置\n"); + printf(" -s, --scan 执行一次扫描并退出 (忽略auto_scan设置)\n"); + printf(" -l, --list 列出当前UID白名单\n"); + printf(" --lang 设置语言 (英文|中文)\n"); + printf(" --multi-user <0|1> 设置多用户扫描 (0=禁用, 1=启用)\n"); + printf(" --auto-scan <0|1> 设置自动扫描 (0=禁用, 1=启用)\n"); + printf(" --config 显示当前配置\n"); + printf(" -h, --help 显示此帮助信息\n"); + } else { + printf("Usage: %s [options]\n", prog); + printf("KSU UID Scanner - Manage UID whitelist\n\n"); + printf("Options:\n"); + printf(" start Start daemon\n"); + printf(" stop Stop daemon\n"); + printf(" restart Restart daemon\n"); + printf(" status Show daemon status\n"); + printf(" reload Reload daemon config\n"); + printf(" -s, --scan Perform one scan and exit (ignore auto_scan setting)\n"); + printf(" -l, --list List current UID whitelist\n"); + printf(" --lang Set language\n"); + printf(" --multi-user <0|1> Set multi-user scanning\n"); + printf(" --auto-scan <0|1> Set auto scanning\n"); + printf(" --config Show current config\n"); + printf(" -h, --help Show this help\n"); + } +} + +void list_whitelist(void) { + FILE *fp = fopen(KSU_UID_LIST_PATH, "r"); + if (!fp) { + printf(get_message(42), strerror(errno)); + printf("\n"); + return; + } + + printf("%s\n", get_message(43)); + printf("%-8s %-40s\n", "UID", (config.language == LANG_ZH) ? "包名" : "Package"); + printf("%-8s %-40s\n", "--------", "----------------------------------------"); + + char line[512]; + while (fgets(line, sizeof(line), fp)) { + int uid; + char package[256]; + if (sscanf(line, "%d %255s", &uid, package) == 2) { + printf("%-8d %-40s\n", uid, package); + } + } + fclose(fp); +} + +void show_config(void) { + if (config.language == LANG_ZH) { + printf("当前配置:\n"); + printf(" 语言: %s\n", (config.language == LANG_ZH) ? "中文" : "英文"); + printf(" 多用户扫描: %s\n", config.multi_user_scan ? "启用" : "禁用"); + printf(" 自动扫描: %s\n", config.auto_scan ? "启用" : "禁用"); + printf(" 扫描间隔: %d 秒\n", config.scan_interval); + printf(" 日志级别: %d\n", config.log_level); + } else { + printf("Current Configuration:\n"); + printf(" Language: %s\n", (config.language == LANG_ZH) ? "Chinese" : "English"); + printf(" Multi-user scan: %s\n", config.multi_user_scan ? "Enabled" : "Disabled"); + printf(" Auto scan: %s\n", config.auto_scan ? "Enabled" : "Disabled"); + printf(" Scan interval: %d seconds\n", config.scan_interval); + printf(" Log level: %d\n", config.log_level); + } +} + +int handle_config_command(int argc, char *argv[]) { + if (strcmp(argv[1], "--lang") == 0) { + if (argc < 3) return 1; + if (strcmp(argv[2], "zh") == 0) { + set_language(LANG_ZH); + } else if (strcmp(argv[2], "en") == 0) { + set_language(LANG_EN); + } else { + return 1; + } + return 0; + } else if (strcmp(argv[1], "--multi-user") == 0) { + if (argc < 3) return 1; + int value = atoi(argv[2]); + if (value != 0 && value != 1) return 1; + set_multi_user_scan(value); + return 0; + } else if (strcmp(argv[1], "--auto-scan") == 0) { + if (argc < 3) return 1; + int value = atoi(argv[2]); + if (value != 0 && value != 1) return 1; + set_auto_scan(value); + return 0; + } else if (strcmp(argv[1], "--config") == 0) { + show_config(); + return 0; + } + return -1; +} + +int handle_single_command(int argc, char *argv[]) { + (void)argc; + if (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--scan") == 0) { + printf("%s\n", get_message(44)); + manual_scan_flag = 1; + perform_manual_scan_update(); + return 0; + } else if (strcmp(argv[1], "-l") == 0 || strcmp(argv[1], "--list") == 0) { + list_whitelist(); + return 0; + } else if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + print_usage(argv[0]); + return 0; + } else if (strcmp(argv[1], "status") == 0) { + show_status(); + return 0; + } else if (strcmp(argv[1], "stop") == 0) { + return stop_daemon(); + } else if (strcmp(argv[1], "reload") == 0) { + reload_daemon(); + return 0; + } + return -1; +} + +void setup_signal_handlers(void) { + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGHUP, signal_handler); + signal(SIGUSR1, signal_handler); + signal(SIGPIPE, SIG_IGN); +} + +void init_daemon_logging(void) { + ensure_directory_exists(); + log_fd = open(LOG_FILE_PATH, O_WRONLY | O_CREAT | O_APPEND, 0644); +} + +void cleanup_daemon_resources(void) { + write_log("INFO", 52); + free_uid_list(); + unlink(PID_FILE_PATH); + if (log_fd != -1) close(log_fd); + write_log("INFO", 53); +} + +void run_daemon_loop(void) { + load_config(); + + write_log("INFO", 49); + + if (!config.auto_scan) { + write_log("INFO", 66); + } else { + perform_scan_update(); + } + + while (!should_exit) { + if (should_reload) { + load_config(); + + if (!config.auto_scan) { + write_log("INFO", 67); + } else { + write_log("INFO", 50); + perform_scan_update(); + } + should_reload = 0; + } + + if (check_kernel_request()) { + if (!config.auto_scan) { + write_log("INFO", 68); + } else { + write_log("INFO", 51); + perform_scan_update(); + } + } + + manage_log_file(); + + int sleep_iterations = config.scan_interval * 10; + for (int i = 0; i < sleep_iterations && !should_exit && !should_reload; i++) { + usleep(100000); + } + } +} + +int main(int argc, char *argv[]) { + load_config(); + + if (argc < 2) { + print_usage(argv[0]); + return 1; + } + + int result = handle_config_command(argc, argv); + if (result >= 0) return result; + + result = handle_single_command(argc, argv); + if (result >= 0) return result; + + if (strcmp(argv[1], "restart") == 0) { + if (restart_daemon() != 0) return 1; + } else if (strcmp(argv[1], "start") != 0) { + printf(get_message(45), argv[1]); + printf("\n"); + print_usage(argv[0]); + return 1; + } + + if (is_daemon_running()) { + printf("%s\n", get_message(46)); + return 1; + } + + printf("%s\n", get_message(47)); + if (daemonize() != 0) { + printf("%s\n", get_message(48)); + return 1; + } + + init_daemon_logging(); + if (write_pid_file() != 0) exit(1); + setup_signal_handlers(); + run_daemon_loop(); + cleanup_daemon_resources(); + return 0; +}