From cd4edf97bde958b5f157c631a8ae00f45d589317 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sun, 3 Aug 2025 18:50:20 +0800 Subject: [PATCH] manager: Refinement of module signatures again --- .../java/com/sukisu/ultra/ui/screen/Flash.kt | 49 ++++++- .../java/com/sukisu/ultra/ui/screen/Module.kt | 65 +++++++-- .../com/sukisu/ultra/ui/util/ModuleUtils.kt | 111 ++++++++++++++- .../ui/util/ModuleVerificationManager.kt | 127 ++++++++++++++++++ .../ultra/ui/viewmodel/ModuleViewModel.kt | 58 +++++++- .../app/src/main/res/values-vi/strings.xml | 14 +- .../src/main/res/values-zh-rCN/strings.xml | 3 + manager/app/src/main/res/values/strings.xml | 3 + 8 files changed, 408 insertions(+), 22 deletions(-) create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt index 23562612..55d83faf 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt @@ -70,11 +70,15 @@ data class ModuleInstallStatus( val totalModules: Int = 0, val currentModule: Int = 0, val currentModuleName: String = "", - val failedModules: MutableList = mutableListOf() + val failedModules: MutableList = mutableListOf(), + val verifiedModules: MutableList = mutableListOf() // 添加已验证模块列表 ) private var moduleInstallStatus = mutableStateOf(ModuleInstallStatus()) +// 存储模块URI和验证状态的映射 +private var moduleVerificationMap = mutableMapOf() + fun setFlashingStatus(status: FlashingStatus) { currentFlashingStatus.value = status } @@ -83,7 +87,8 @@ fun updateModuleInstallStatus( totalModules: Int? = null, currentModule: Int? = null, currentModuleName: String? = null, - failedModule: String? = null + failedModule: String? = null, + verifiedModule: String? = null ) { val current = moduleInstallStatus.value moduleInstallStatus.value = current.copy( @@ -99,6 +104,18 @@ fun updateModuleInstallStatus( failedModules = updatedFailedModules ) } + + if (verifiedModule != null) { + val updatedVerifiedModules = current.verifiedModules.toMutableList() + updatedVerifiedModules.add(verifiedModule) + moduleInstallStatus.value = moduleInstallStatus.value.copy( + verifiedModules = updatedVerifiedModules + ) + } +} + +fun setModuleVerificationStatus(uri: Uri, isVerified: Boolean) { + moduleVerificationMap[uri] = isVerified } @OptIn(ExperimentalMaterial3Api::class) @@ -142,6 +159,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { ) hasFlashCompleted = false hasExecuted = false + moduleVerificationMap.clear() } } is FlashIt.FlashModuleUpdate -> { @@ -179,6 +197,11 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { setFlashingStatus(FlashingStatus.FAILED) } else { setFlashingStatus(FlashingStatus.SUCCESS) + + // 处理模块更新成功后的验证标志 + val isVerified = moduleVerificationMap[flashIt.uri] ?: false + ModuleOperationUtils.handleModuleUpdate(context, flashIt.uri, isVerified) + viewModel.markNeedRefresh() } if (showReboot) { @@ -239,6 +262,28 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { } } else { setFlashingStatus(FlashingStatus.SUCCESS) + + // 处理模块安装成功后的验证标志 + when (flashIt) { + is FlashIt.FlashModule -> { + val isVerified = moduleVerificationMap[flashIt.uri] ?: false + ModuleOperationUtils.handleModuleInstallSuccess(context, flashIt.uri, isVerified) + if (isVerified) { + updateModuleInstallStatus(verifiedModule = moduleInstallStatus.value.currentModuleName) + } + } + is FlashIt.FlashModules -> { + val currentUri = flashIt.uris[flashIt.currentIndex] + val isVerified = moduleVerificationMap[currentUri] ?: false + ModuleOperationUtils.handleModuleInstallSuccess(context, currentUri, isVerified) + if (isVerified) { + updateModuleInstallStatus(verifiedModule = moduleInstallStatus.value.currentModuleName) + } + } + + else -> {} + } + viewModel.markNeedRefresh() } if (showReboot) { diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt index 80a4ea77..1322db66 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt @@ -189,6 +189,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) { for (uri in selectedModules) { val isVerified = verifyModuleSignature(context, uri) verificationResults[uri] = isVerified + // 存储验证状态 + setModuleVerificationStatus(uri, isVerified) if (forceVerification && !isVerified) { withContext(Dispatchers.Main) { @@ -253,6 +255,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) { // 验证模块签名 val forceVerification = prefs.getBoolean("force_signature_verification", false) val isVerified = verifyModuleSignature(context, uri) + // 存储验证状态 + setModuleVerificationStatus(uri, isVerified) if (forceVerification && !isVerified) { signatureDialogMessage = context.getString(R.string.module_signature_invalid_message) @@ -835,7 +839,12 @@ private fun ModuleList( downloadUrl, fileName, downloading, - onDownloaded = onUpdateModule, + onDownloaded = { uri -> + // 验证更新模块的签名 + val isVerified = verifyModuleSignature(context, uri) + setModuleVerificationStatus(uri, isVerified) + onUpdateModule(uri) + }, onDownloading = { launch(Dispatchers.Main) { Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show() @@ -867,6 +876,8 @@ private fun ModuleList( val success = loadingDialog.withLoading { withContext(Dispatchers.IO) { if (isUninstall) { + // 卸载时移除验证标志 + ModuleOperationUtils.handleModuleUninstall(module.dirId) uninstallModule(module.dirId) } else { restoreModule(module.dirId) @@ -1075,14 +1086,48 @@ fun ModuleItem( Column( modifier = Modifier.fillMaxWidth(0.8f) ) { - Text( - text = module.name, - fontSize = MaterialTheme.typography.titleMedium.fontSize, - fontWeight = FontWeight.SemiBold, - lineHeight = MaterialTheme.typography.bodySmall.lineHeight, - fontFamily = MaterialTheme.typography.titleMedium.fontFamily, - textDecoration = textDecoration, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = module.name, + fontSize = MaterialTheme.typography.titleMedium.fontSize, + fontWeight = FontWeight.SemiBold, + lineHeight = MaterialTheme.typography.bodySmall.lineHeight, + fontFamily = MaterialTheme.typography.titleMedium.fontFamily, + textDecoration = textDecoration, + modifier = Modifier.weight(1f, false) + ) + + // 显示验证标签 + if (module.isVerified) { + Surface( + shape = RoundedCornerShape(12.dp), + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp) + ) { + Icon( + imageVector = Icons.Default.Verified, + contentDescription = stringResource(R.string.module_signature_verified), + tint = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.size(12.dp) + ) + Spacer(modifier = Modifier.width(2.dp)) + Text( + text = stringResource(R.string.module_verified), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onPrimary, + fontWeight = FontWeight.Medium + ) + } + } + } + } Text( text = "$moduleVersion: ${module.version}", @@ -1309,6 +1354,8 @@ fun ModuleItemPreview() { hasActionScript = false, dirId = "dirId", config = ModuleConfig(), + isVerified = true, + verificationTimestamp = System.currentTimeMillis() ) ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {}) } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt index 332a100c..d64f93f6 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleUtils.kt @@ -36,7 +36,7 @@ object ModuleUtils { } }?.removeSuffix(".zip") ?: context.getString(R.string.unknown_module) - var formattedFileName = fileName.replace(Regex("[^a-zA-Z0-9\\s\\-_.@()\\u4e00-\\u9fa5]"), "").trim() + val formattedFileName = fileName.replace(Regex("[^a-zA-Z0-9\\s\\-_.@()\\u4e00-\\u9fa5]"), "").trim() var moduleName = formattedFileName try { @@ -55,12 +55,10 @@ object ModuleUtils { if (entry.name == "module.prop") { val reader = BufferedReader(InputStreamReader(zipInputStream, StandardCharsets.UTF_8)) var line: String? - var nameFound = false while (reader.readLine().also { line = it } != null) { if (line?.startsWith("name=") == true) { moduleName = line.substringAfter("=") moduleName = moduleName.replace(Regex("[^a-zA-Z0-9\\s\\-_.@()\\u4e00-\\u9fa5]"), "").trim() - nameFound = true break } } @@ -105,6 +103,45 @@ object ModuleUtils { Log.e(TAG, "Unable to get persistent permissions on URIs: $uri, Error: ${e.message}") } } + + fun extractModuleId(context: Context, uri: Uri): String? { + if (uri == Uri.EMPTY) { + return null + } + + return try { + + val inputStream = context.contentResolver.openInputStream(uri) + if (inputStream == null) { + return null + } + + val zipInputStream = ZipInputStream(inputStream) + var entry = zipInputStream.nextEntry + var moduleId: String? = null + + // 遍历ZIP文件中的条目,查找module.prop文件 + while (entry != null) { + if (entry.name == "module.prop") { + val reader = BufferedReader(InputStreamReader(zipInputStream, StandardCharsets.UTF_8)) + var line: String? + while (reader.readLine().also { line = it } != null) { + if (line?.startsWith("id=") == true) { + moduleId = line.substringAfter("=").trim() + break + } + } + break + } + entry = zipInputStream.nextEntry + } + zipInputStream.close() + moduleId + } catch (e: Exception) { + Log.e(TAG, "提取模块ID时发生异常: ${e.message}", e) + null + } + } } // 模块签名验证工具类 @@ -143,3 +180,71 @@ object ModuleSignatureUtils { fun verifyModuleSignature(context: Context, moduleUri: Uri): Boolean { return ModuleSignatureUtils.verifyModuleSignature(context, moduleUri) } + +object ModuleOperationUtils { + private const val TAG = "ModuleOperationUtils" + + fun handleModuleInstallSuccess(context: Context, moduleUri: Uri, isSignatureVerified: Boolean) { + if (!isSignatureVerified) { + Log.d(TAG, "模块签名未验证,跳过创建验证标志") + return + } + + try { + // 从ZIP文件提取模块ID + val moduleId = ModuleUtils.extractModuleId(context, moduleUri) + if (moduleId == null) { + Log.e(TAG, "无法提取模块ID,无法创建验证标志") + return + } + + // 创建验证标志文件 + val success = ModuleVerificationManager.createVerificationFlag(moduleId) + if (success) { + Log.d(TAG, "模块 $moduleId 验证标志创建成功") + } else { + Log.e(TAG, "模块 $moduleId 验证标志创建失败") + } + } catch (e: Exception) { + Log.e(TAG, "处理模块安装成功时发生异常", e) + } + } + + fun handleModuleUninstall(moduleId: String) { + try { + val success = ModuleVerificationManager.removeVerificationFlag(moduleId) + if (success) { + Log.d(TAG, "模块 $moduleId 验证标志移除成功") + } else { + Log.d(TAG, "模块 $moduleId 验证标志移除失败或不存在") + } + } catch (e: Exception) { + Log.e(TAG, "处理模块卸载时发生异常: $moduleId", e) + } + } + fun handleModuleUpdate(context: Context, moduleUri: Uri, isSignatureVerified: Boolean) { + try { + val moduleId = ModuleUtils.extractModuleId(context, moduleUri) + if (moduleId == null) { + Log.e(TAG, "无法提取模块ID,无法处理验证标志") + return + } + + if (isSignatureVerified) { + // 签名验证通过,创建或更新验证标志 + val success = ModuleVerificationManager.createVerificationFlag(moduleId) + if (success) { + Log.d(TAG, "模块 $moduleId 更新后验证标志已更新") + } else { + Log.e(TAG, "模块 $moduleId 更新后验证标志更新失败") + } + } else { + // 签名验证失败,移除验证标志 + ModuleVerificationManager.removeVerificationFlag(moduleId) + Log.d(TAG, "模块 $moduleId 更新后签名未验证,验证标志已移除") + } + } catch (e: Exception) { + Log.e(TAG, "处理模块更新时发生异常", e) + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt new file mode 100644 index 00000000..4a8b35e9 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt @@ -0,0 +1,127 @@ +package com.sukisu.ultra.ui.util + +import android.util.Log + +/** + + * @author ShirkNeko + * @date 2025/8/3 + */ +object ModuleVerificationManager { + private const val TAG = "ModuleVerificationManager" + private const val VERIFICATION_FLAGS_DIR = "/data/adb/ksu/verified_modules" + + /** + * 为指定模块创建验证标志文件 + * + * @param moduleId 模块文件夹名称 + * @return 是否成功创建标志文件 + */ + fun createVerificationFlag(moduleId: String): Boolean { + return try { + val shell = getRootShell() + val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId" + + // 确保目录存在 + val createDirCommand = "mkdir -p '$VERIFICATION_FLAGS_DIR'" + shell.newJob().add(createDirCommand).exec() + + // 创建验证标志文件,写入验证时间戳 + val timestamp = System.currentTimeMillis() + val command = "echo '$timestamp' > '$flagFilePath'" + + val result = shell.newJob().add(command).exec() + + if (result.isSuccess) { + Log.d(TAG, "验证标志文件创建成功: $flagFilePath") + true + } else { + Log.e(TAG, "验证标志文件创建失败: $moduleId") + false + } + } catch (e: Exception) { + Log.e(TAG, "创建验证标志文件时发生异常: $moduleId", e) + false + } + } + + fun removeVerificationFlag(moduleId: String): Boolean { + return try { + val shell = getRootShell() + val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId" + + val command = "rm -f '$flagFilePath'" + val result = shell.newJob().add(command).exec() + + if (result.isSuccess) { + Log.d(TAG, "验证标志文件移除成功: $flagFilePath") + true + } else { + Log.e(TAG, "验证标志文件移除失败: $moduleId") + false + } + } catch (e: Exception) { + Log.e(TAG, "移除验证标志文件时发生异常: $moduleId", e) + false + } + } + + fun getVerificationTimestamp(moduleId: String): Long { + return try { + val shell = getRootShell() + val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId" + + val command = "cat '$flagFilePath' 2>/dev/null || echo '0'" + val result = shell.newJob().add(command).to(ArrayList(), null).exec() + + if (result.isSuccess && result.out.isNotEmpty()) { + val timestampStr = result.out.firstOrNull()?.trim() ?: "0" + timestampStr.toLongOrNull() ?: 0L + } else { + 0L + } + } catch (e: Exception) { + Log.e(TAG, "获取验证时间戳时发生异常: $moduleId", e) + 0L + } + } + + fun batchCheckVerificationStatus(moduleIds: List): Map { + if (moduleIds.isEmpty()) return emptyMap() + + return try { + val shell = getRootShell() + val result = mutableMapOf() + + // 确保目录存在 + val createDirCommand = "mkdir -p '$VERIFICATION_FLAGS_DIR'" + shell.newJob().add(createDirCommand).exec() + + // 批量检查所有模块的验证标志文件 + val commands = moduleIds.map { moduleId -> + "test -f '$VERIFICATION_FLAGS_DIR/$moduleId' && echo '$moduleId:true' || echo '$moduleId:false'" + } + + val command = commands.joinToString(" && ") + val shellResult = shell.newJob().add(command).to(ArrayList(), null).exec() + + if (shellResult.isSuccess) { + shellResult.out.forEach { line -> + val parts = line.split(":") + if (parts.size == 2) { + val moduleId = parts[0] + val isVerified = parts[1] == "true" + result[moduleId] = isVerified + } + } + } + + Log.d(TAG, "批量验证检查完成,共检查 ${moduleIds.size} 个模块") + result + } catch (e: Exception) { + Log.e(TAG, "批量检查验证状态时发生异常", e) + // 返回默认值,所有模块都标记为未验证 + moduleIds.associateWith { false } + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt index fa34cc25..8b523174 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.launch import com.sukisu.ultra.ui.util.HanziToPinyin import com.sukisu.ultra.ui.util.listModules import com.sukisu.ultra.ui.util.getRootShell +import com.sukisu.ultra.ui.util.ModuleVerificationManager import kotlinx.coroutines.withContext import org.json.JSONArray import org.json.JSONObject @@ -86,6 +87,8 @@ class ModuleViewModel : ViewModel() { val hasActionScript: Boolean, val dirId: String, // real module id (dir name) var config: ModuleConfig? = null, + var isVerified: Boolean = false, // 添加验证状态字段 + var verificationTimestamp: Long = 0L, // 添加验证时间戳 ) var isRefreshing by mutableStateOf(false) @@ -131,7 +134,7 @@ class ModuleViewModel : ViewModel() { Log.i(TAG, "result: $result") val array = JSONArray(result) - modules = (0 until array.length()) + val moduleInfos = (0 until array.length()) .asSequence() .map { array.getJSONObject(it) } .map { obj -> @@ -151,6 +154,26 @@ class ModuleViewModel : ViewModel() { obj.getString("dir_id") ) }.toList() + + // 批量检查所有模块的验证状态 + val moduleIds = moduleInfos.map { it.dirId } + val verificationStatus = ModuleVerificationManager.batchCheckVerificationStatus(moduleIds) + + // 更新模块验证状态 + modules = moduleInfos.map { moduleInfo -> + val isVerified = verificationStatus[moduleInfo.dirId] ?: false + val verificationTimestamp = if (isVerified) { + ModuleVerificationManager.getVerificationTimestamp(moduleInfo.dirId) + } else { + 0L + } + + moduleInfo.copy( + isVerified = isVerified, + verificationTimestamp = verificationTimestamp + ) + } + launch { modules.forEach { module -> withContext(Dispatchers.IO) { @@ -207,6 +230,14 @@ class ModuleViewModel : ViewModel() { } } + fun createModuleVerificationFlag(moduleId: String): Boolean { + return ModuleVerificationManager.createVerificationFlag(moduleId) + } + + fun removeModuleVerificationFlag(moduleId: String): Boolean { + return ModuleVerificationManager.removeVerificationFlag(moduleId) + } + private fun sanitizeVersionString(version: String): String { return version.replace(Regex("[^a-zA-Z0-9.\\-_]"), "_") } @@ -269,6 +300,31 @@ class ModuleViewModel : ViewModel() { } } +fun ModuleViewModel.ModuleInfo.copy( + id: String = this.id, + name: String = this.name, + author: String = this.author, + version: String = this.version, + versionCode: Int = this.versionCode, + description: String = this.description, + enabled: Boolean = this.enabled, + update: Boolean = this.update, + remove: Boolean = this.remove, + updateJson: String = this.updateJson, + hasWebUi: Boolean = this.hasWebUi, + hasActionScript: Boolean = this.hasActionScript, + dirId: String = this.dirId, + config: ModuleConfig? = this.config, + isVerified: Boolean = this.isVerified, + verificationTimestamp: Long = this.verificationTimestamp +): ModuleViewModel.ModuleInfo { + return ModuleViewModel.ModuleInfo( + id, name, author, version, versionCode, description, + enabled, update, remove, updateJson, hasWebUi, hasActionScript, + dirId, config, isVerified, verificationTimestamp + ) +} + /** * 模块大小缓存管理器 */ diff --git a/manager/app/src/main/res/values-vi/strings.xml b/manager/app/src/main/res/values-vi/strings.xml index a4286b73..56722b5c 100644 --- a/manager/app/src/main/res/values-vi/strings.xml +++ b/manager/app/src/main/res/values-vi/strings.xml @@ -539,8 +539,8 @@ Ngày sao lưu: %s Thiết bị: %s Phiên bản: %s - Trạng thái khóa - Ghi đè thuộc tính trạng thái khóa khởi động ở chế độ dịch vụ late_start + Trạng thái Lock BL + Ghi đè thuộc tính trạng thái lock bootloader ở chế độ dịch vụ late_start Dọn rác Dọn dẹp các file và folder còn sót lại của các module và công cụ (Có thể bị xóa nhầm, dẫn đến mất dữ liệu và không khởi động được) Chỉnh sửa Đường dẫn SuS @@ -607,9 +607,9 @@ Đường dẫn Vòng lặp SuS Cấu hình Đường dẫn Vòng lặp Đường dẫn Vòng lặp được đổi tên thành SUS_PATH mỗi khi một ứng dụng không phải root hoặc dịch vụ cô lập được khởi động. Điều này giúp giải quyết vấn đề đường dẫn đã thêm có thể trở nên không hợp lệ do trạng thái inode được đặt lại hoặc inode được tạo lại trong Kernel - Xác minh chữ ký module - Buộc xác minh chữ ký khi cài đặt module - Xác minh chữ ký module thất bại - Cài đặt sẽ bị chặn do cài đặt bảo mật - Quá trình cài đặt sẽ tiếp tục, vui lòng chú ý xem có tập lệnh mã hóa không xác định nào trong module hay không + Xác minh chữ ký + Buộc xác minh chữ ký khi cài đặt module (Chỉ khả dụng cho arm64-v8a) + Tác giả không xác định + Các module chưa được ký có thể chưa hoàn chỉnh. Để bảo vệ thiết bị của bạn, module này đã bị chặn cài đặt + Các module chưa được ký có thể chưa hoàn chỉnh. Bạn có muốn cài đặt module này từ một tác giả chưa xác định không? 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 3e6d4ada..b711ac43 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -606,6 +606,9 @@ SUS循环路径 循环路径配置 循环路径会在每次非root用户应用或隔离服务启动时重新标记为SUS_PATH。这有助于解决添加的路径可能因inode状态重置或内核中inode重新创建而失效的问题 + + 已验证 + 模块签名已验证 验证签名 模块安装时,强制验证签名。(仅 arm64-v8a 可用) 未知发布者 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 96df47a8..bdcf051e 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -609,6 +609,9 @@ SUS Loop Path Loop Path Configuration Loop paths are re-flagged as SUS_PATH on each non-root user app or isolated service startup. This helps address issues where added paths may have their inode status reset or inode re-created in the kernel. + + Validated + Module signature verified Signature Verification Force signature verification when installing modules. (Only available for arm64-v8a) Unknown publisher