diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 8dc36c6f..12bfdee7 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -485,16 +485,12 @@ static void sulog_prctl_cmd(uid_t uid, unsigned long cmd) int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { - - // if success, we modify the arg5 as result! bool is_manual_su_cmd = false; u32 *result = (u32 *)arg5; u32 reply_ok = KERNEL_SU_OPTION; uid_t current_uid_val = current_uid().val; - sulog_prctl_cmd(current_uid().val, arg2); - #ifdef CONFIG_KSU_MANUAL_SU is_manual_su_cmd = (arg2 == CMD_SU_ESCALATION_REQUEST || arg2 == CMD_ADD_PENDING_ROOT); @@ -526,6 +522,8 @@ skip_check: bool from_root = !current_uid().val; bool from_manager = is_manager(); + sulog_prctl_cmd(current_uid().val, arg2); + if (!from_root && !from_manager && !(is_manual_su_cmd ? is_system_uid(): (is_allow_su() && is_system_bin_su()))) { diff --git a/kernel/sulog.c b/kernel/sulog.c index a720d6d9..69261163 100644 --- a/kernel/sulog.c +++ b/kernel/sulog.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include "klog.h" #include "kernel_compat.h" @@ -21,6 +23,36 @@ #define SULOG_MAX_SIZE (128 * 1024 * 1024) // 128MB #define SULOG_ENTRY_MAX_LEN 512 #define SULOG_COMM_LEN 256 +#define DEDUP_ENTRIES 256 +#define DEDUP_SECS 60 + +struct dedup_key { + u32 crc; + uid_t uid; + u8 type; + u8 _pad[1]; +}; + +struct dedup_entry { + struct dedup_key key; + u64 ts_ns; +}; + +static struct dedup_entry dedup_tbl[DEDUP_ENTRIES]; +static DEFINE_SPINLOCK(dedup_lock); + +enum { + DEDUP_SU_GRANT = 0, + DEDUP_SU_ATTEMPT, + DEDUP_PERM_CHECK, + DEDUP_MANAGER_OP, + DEDUP_SYSCALL, +}; + +static inline u32 dedup_calc_hash(const char *content, size_t len) +{ + return crc32(0, content, len); +} struct sulog_entry { struct list_head list; @@ -94,6 +126,35 @@ static void get_full_comm(char *comm_buf, size_t buf_len) comm_buf[buf_len - 1] = '\0'; } +static bool dedup_should_print(uid_t uid, u8 type, + const char *content, size_t len) +{ + struct dedup_key key = { + .crc = dedup_calc_hash(content, len), + .uid = uid, + .type = type, + }; + u64 now = ktime_get_ns(); + u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC; + + u32 idx = key.crc & (DEDUP_ENTRIES - 1); + spin_lock(&dedup_lock); + + struct dedup_entry *e = &dedup_tbl[idx]; + if (e->key.crc == key.crc && + e->key.uid == key.uid && + e->key.type == key.type && + (now - e->ts_ns) < delta_ns) { + spin_unlock(&dedup_lock); + return false; + } + + e->key = key; + e->ts_ns = now; + spin_unlock(&dedup_lock); + return true; +} + static void sulog_work_handler(struct work_struct *work) { struct file *fp; @@ -220,9 +281,11 @@ void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method) "[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n", timestamp, uid, full_comm, method ? method : "unknown", current->pid); + + if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf))) + goto cleanup_grant; sulog_add_entry(log_buf); - pr_info("sulog: %s", log_buf); cleanup_grant: if (timestamp) kfree(timestamp); @@ -260,9 +323,11 @@ void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target timestamp, uid, full_comm, target_path ? target_path : "unknown", success ? "SUCCESS" : "DENIED", current->pid); + + if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf))) + goto cleanup_attempt; sulog_add_entry(log_buf); - pr_info("sulog: %s", log_buf); cleanup_attempt: if (timestamp) kfree(timestamp); @@ -299,6 +364,9 @@ void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed "[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n", timestamp, uid, full_comm, allowed ? "ALLOWED" : "DENIED", current->pid); + + if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf))) + goto cleanup_perm; sulog_add_entry(log_buf); @@ -331,9 +399,11 @@ void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid "[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d PID=%d\n", timestamp, operation ? operation : "unknown", manager_uid, target_uid, current->pid); + + if (!dedup_should_print(manager_uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf))) + goto cleanup_mgr; sulog_add_entry(log_buf); - pr_info("sulog: %s", log_buf); cleanup_mgr: if (timestamp) kfree(timestamp); @@ -368,8 +438,10 @@ void ksu_sulog_report_syscall(uid_t uid, const char *comm, args ? args : "none", current->pid); + if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf))) + goto cleanup_mgr; + sulog_add_entry(log_buf); - pr_info("sulog: %s", log_buf); cleanup_mgr: if (timestamp) kfree(timestamp); diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/LogViewerScreen.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/LogViewerScreen.kt index d14134f5..ce4734cb 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/LogViewerScreen.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/LogViewerScreen.kt @@ -309,6 +309,8 @@ private fun LogControlPanel( excludedSubTypes: Set, onExcludeToggle: (LogExclType) -> Unit ) { + var isExpanded by rememberSaveable { mutableStateOf(true) } + Card( modifier = Modifier .fillMaxWidth() @@ -316,128 +318,150 @@ private fun LogControlPanel( colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerLow), elevation = getCardElevation() ) { - Column( - modifier = Modifier.padding(SPACING_LARGE) - ) { - // 文件选择 - Text( - text = stringResource(R.string.log_viewer_select_file), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.height(SPACING_MEDIUM)) - + Column { + // 标题栏(点击展开/收起) Row( - horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM) - ) { - FilterChip( - onClick = { onLogFileSelected("current") }, - label = { Text(stringResource(R.string.log_viewer_current_log)) }, - selected = selectedLogFile == "current" - ) - FilterChip( - onClick = { onLogFileSelected("old") }, - label = { Text(stringResource(R.string.log_viewer_old_log)) }, - selected = selectedLogFile == "old" - ) - } - - Spacer(modifier = Modifier.height(SPACING_LARGE)) - - // 类型过滤 - Text( - text = stringResource(R.string.log_viewer_filter_type), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.height(SPACING_MEDIUM)) - - LazyRow( - horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM) - ) { - item { - FilterChip( - onClick = { onFilterTypeSelected(null) }, - label = { Text(stringResource(R.string.log_viewer_all_types)) }, - selected = filterType == null - ) - } - items(LogType.entries.toTypedArray()) { type -> - FilterChip( - onClick = { onFilterTypeSelected(if (filterType == type) null else type) }, - label = { Text(type.displayName) }, - selected = filterType == type, - leadingIcon = { - Box( - modifier = Modifier - .size(8.dp) - .background(type.color, RoundedCornerShape(4.dp)) - ) - } - ) - } - } - - Spacer(modifier = Modifier.height(SPACING_MEDIUM)) - - Text( - text = stringResource(R.string.log_viewer_exclude_subtypes), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.height(SPACING_MEDIUM)) - - LazyRow(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) { - items(LogExclType.entries.toTypedArray()) { excl -> - val label = if (excl == LogExclType.CURRENT_APP) - stringResource(R.string.log_viewer_exclude_current_app) - else excl.displayName - - FilterChip( - onClick = { onExcludeToggle(excl) }, - label = { Text(label) }, - selected = excl in excludedSubTypes, - leadingIcon = { - Box( - modifier = Modifier - .size(8.dp) - .background(excl.color, RoundedCornerShape(4.dp)) - ) - } - ) - } - } - Spacer(modifier = Modifier.height(SPACING_MEDIUM)) - - // 统计信息和分页信息 - Column( - verticalArrangement = Arrangement.spacedBy(SPACING_SMALL) + modifier = Modifier + .fillMaxWidth() + .clickable { isExpanded = !isExpanded } + .padding(SPACING_LARGE), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { Text( - text = stringResource(R.string.log_viewer_showing_entries, logCount, totalCount), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant + text = stringResource(R.string.settings), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary ) + Icon( + imageVector = if (isExpanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } - if (pageInfo.totalPages > 0) { + AnimatedVisibility( + visible = isExpanded, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut() + ) { + Column( + modifier = Modifier.padding(horizontal = SPACING_LARGE) + ) { + // 文件选择 Text( - text = stringResource( - R.string.log_viewer_page_info, - pageInfo.currentPage + 1, - pageInfo.totalPages, - pageInfo.totalLogs - ), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant + text = stringResource(R.string.log_viewer_select_file), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary ) - } + Spacer(modifier = Modifier.height(SPACING_MEDIUM)) + Row(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) { + FilterChip( + onClick = { onLogFileSelected("current") }, + label = { Text(stringResource(R.string.log_viewer_current_log)) }, + selected = selectedLogFile == "current" + ) + FilterChip( + onClick = { onLogFileSelected("old") }, + label = { Text(stringResource(R.string.log_viewer_old_log)) }, + selected = selectedLogFile == "old" + ) + } - if (pageInfo.totalLogs >= MAX_TOTAL_LOGS) { + Spacer(modifier = Modifier.height(SPACING_LARGE)) + + // 类型过滤 Text( - text = stringResource(R.string.log_viewer_too_many_logs, MAX_TOTAL_LOGS), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.error + text = stringResource(R.string.log_viewer_filter_type), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary ) + Spacer(modifier = Modifier.height(SPACING_MEDIUM)) + LazyRow(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) { + item { + FilterChip( + onClick = { onFilterTypeSelected(null) }, + label = { Text(stringResource(R.string.log_viewer_all_types)) }, + selected = filterType == null + ) + } + items(LogType.entries.toTypedArray()) { type -> + FilterChip( + onClick = { onFilterTypeSelected(if (filterType == type) null else type) }, + label = { Text(type.displayName) }, + selected = filterType == type, + leadingIcon = { + Box( + modifier = Modifier + .size(8.dp) + .background(type.color, RoundedCornerShape(4.dp)) + ) + } + ) + } + } + + Spacer(modifier = Modifier.height(SPACING_MEDIUM)) + + // 排除子类型 + Text( + text = stringResource(R.string.log_viewer_exclude_subtypes), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(SPACING_MEDIUM)) + LazyRow(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) { + items(LogExclType.entries.toTypedArray()) { excl -> + val label = if (excl == LogExclType.CURRENT_APP) + stringResource(R.string.log_viewer_exclude_current_app) + else excl.displayName + + FilterChip( + onClick = { onExcludeToggle(excl) }, + label = { Text(label) }, + selected = excl in excludedSubTypes, + leadingIcon = { + Box( + modifier = Modifier + .size(8.dp) + .background(excl.color, RoundedCornerShape(4.dp)) + ) + } + ) + } + } + + Spacer(modifier = Modifier.height(SPACING_MEDIUM)) + + // 统计信息 + Column(verticalArrangement = Arrangement.spacedBy(SPACING_SMALL)) { + Text( + text = stringResource(R.string.log_viewer_showing_entries, logCount, totalCount), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + if (pageInfo.totalPages > 0) { + Text( + text = stringResource( + R.string.log_viewer_page_info, + pageInfo.currentPage + 1, + pageInfo.totalPages, + pageInfo.totalLogs + ), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + if (pageInfo.totalLogs >= MAX_TOTAL_LOGS) { + Text( + text = stringResource(R.string.log_viewer_too_many_logs, MAX_TOTAL_LOGS), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error + ) + } + } + + Spacer(modifier = Modifier.height(SPACING_LARGE)) } } }