diff --git a/kernel/core_hook.c b/kernel/core_hook.c index ab71e14e..5c582822 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -545,7 +545,7 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old) } // disallow appuid decrease to any other uid if it is allowed to su if (is_appuid(old_uid)) { - if (new_uid.val < old_uid.val && ksu_is_allow_uid_for_current(old_uid.val)) { + if (new_uid.val < old_uid.val && !ksu_is_allow_uid_for_current(old_uid.val)) { pr_warn("find suspicious EoP: %d %s, from %d to %d\n", current->pid, current->comm, old_uid.val, new_uid.val); send_sig(SIGKILL, current, 0); diff --git a/manager/app/build.gradle.kts b/manager/app/build.gradle.kts index 14c20f26..d51afa9c 100644 --- a/manager/app/build.gradle.kts +++ b/manager/app/build.gradle.kts @@ -2,7 +2,6 @@ import com.android.build.gradle.internal.api.BaseVariantOutputImpl import com.android.build.gradle.tasks.PackageAndroidArtifact -import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.agp.app) @@ -17,6 +16,7 @@ plugins { val managerVersionCode: Int by rootProject.extra val managerVersionName: String by rootProject.extra +val androidCmakeVersion: String by rootProject.extra apksign { storeFileProperty = "KEYSTORE_FILE" @@ -57,10 +57,6 @@ android { prefab = true } - kotlin { - jvmToolchain(21) - } - packaging { jniLibs { useLegacyPackaging = true @@ -78,7 +74,8 @@ android { externalNativeBuild { cmake { - path("src/main/cpp/CMakeLists.txt") + path = file("src/main/cpp/CMakeLists.txt") + version = androidCmakeVersion } } @@ -126,6 +123,7 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.foundation) implementation(libs.androidx.documentfile) + implementation(libs.androidx.compose.foundation) debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.tooling) diff --git a/manager/app/src/main/cpp/jni.c b/manager/app/src/main/cpp/jni.c index b879b4e0..c28ea642 100644 --- a/manager/app/src/main/cpp/jni.c +++ b/manager/app/src/main/cpp/jni.c @@ -310,6 +310,14 @@ NativeBridge(setKernelUmountEnabled, jboolean, jboolean enabled) { return set_kernel_umount_enabled(enabled); } +NativeBridgeNP(isEnhancedSecurityEnabled, jboolean) { + return is_enhanced_security_enabled(); +} + +NativeBridge(setEnhancedSecurityEnabled, jboolean, jboolean enabled) { + return set_enhanced_security_enabled(enabled); +} + // Check if KPM is enabled NativeBridgeNP(isKPMEnabled, jboolean) { return is_KPM_enable(); diff --git a/manager/app/src/main/cpp/ksu.c b/manager/app/src/main/cpp/ksu.c index a47fd245..f3d1c133 100644 --- a/manager/app/src/main/cpp/ksu.c +++ b/manager/app/src/main/cpp/ksu.c @@ -215,6 +215,22 @@ bool is_kernel_umount_enabled() { return value != 0; } +bool set_enhanced_security_enabled(bool enabled) { + return set_feature(KSU_FEATURE_ENHANCED_SECURITY, enabled ? 1 : 0); +} + +bool is_enhanced_security_enabled() { + uint64_t value = 0; + bool supported = false; + if (!get_feature(KSU_FEATURE_ENHANCED_SECURITY, &value, &supported)) { + return false; + } + if (!supported) { + return false; + } + return value != 0; +} + void get_full_version(char* buff) { struct ksu_get_full_version_cmd cmd = {0}; if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) { diff --git a/manager/app/src/main/cpp/ksu.h b/manager/app/src/main/cpp/ksu.h index d1a786da..dd46e04c 100644 --- a/manager/app/src/main/cpp/ksu.h +++ b/manager/app/src/main/cpp/ksu.h @@ -131,6 +131,7 @@ bool clear_uid_scanner_environment(); enum ksu_feature_id { KSU_FEATURE_SU_COMPAT = 0, KSU_FEATURE_KERNEL_UMOUNT = 1, + KSU_FEATURE_ENHANCED_SECURITY = 2, }; // Generic feature API @@ -208,6 +209,11 @@ bool is_su_enabled(); bool set_kernel_umount_enabled(bool enabled); bool is_kernel_umount_enabled(); +// Enhanced security +bool set_enhanced_security_enabled(bool enabled); + +bool is_enhanced_security_enabled(); + // Other command structures struct ksu_get_full_version_cmd { char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string 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 7d787728..157350dc 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/Natives.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/Natives.kt @@ -109,6 +109,15 @@ object Natives { external fun isKernelUmountEnabled(): Boolean external fun setKernelUmountEnabled(enabled: Boolean): Boolean + /** + * Enhanced security can be enabled/disabled. + * 0: disabled + * 1: enabled + * negative : error + */ + external fun isEnhancedSecurityEnabled(): Boolean + external fun setEnhancedSecurityEnabled(enabled: Boolean): Boolean + external fun isKPMEnabled(): Boolean external fun getHookType(): String 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 a975ead5..65ac037d 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 @@ -10,14 +10,20 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.rounded.EnhancedEncryption import androidx.compose.material.icons.rounded.FolderDelete +import androidx.compose.material.icons.rounded.RemoveCircle +import androidx.compose.material.icons.rounded.RemoveModerator import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -130,60 +136,147 @@ fun SettingScreen(navigator: DestinationsNavigator) { } ) - // 卸载模块开关 - var umountChecked by rememberSaveable { - mutableStateOf(Natives.isDefaultUmountModules()) - } - - SwitchItem( - icon = Icons.Filled.FolderDelete, - title = stringResource(R.string.settings_umount_modules_default), - summary = stringResource(R.string.settings_umount_modules_default_summary), - checked = umountChecked, - onCheckedChange = { enabled -> - if (Natives.setDefaultUmountModules(enabled)) { - umountChecked = enabled - } - } + val modeItems = listOf( + stringResource(id = R.string.settings_mode_default), + stringResource(id = R.string.settings_mode_temp_enable), + stringResource(id = R.string.settings_mode_always_enable), ) - - // SU 禁用开关 - var isSuDisabled by rememberSaveable { - mutableStateOf(!Natives.isSuEnabled()) - } - - SwitchItem( - icon = Icons.Filled.RemoveModerator, - title = stringResource(R.string.settings_disable_su), - summary = stringResource(R.string.settings_disable_su_summary), - checked = isSuDisabled, - onCheckedChange = { enabled -> - val shouldEnable = !enabled - if (Natives.setSuEnabled(shouldEnable)) { - isSuDisabled = enabled - } - } - ) - - // 禁用内核卸载开关 - if (Natives.version >= Natives.MINIMAL_NEW_IOCTL_KERNEL) { - var isKernelUmountDisabled by rememberSaveable { - mutableStateOf(!Natives.isKernelUmountEnabled()) - } - - SwitchItem( - icon = Icons.Rounded.FolderDelete, - title = stringResource(id = R.string.settings_disable_kernel_umount), - summary = stringResource(id = R.string.settings_disable_kernel_umount_summary), - checked = isKernelUmountDisabled, - onCheckedChange = { checked: Boolean -> - val shouldEnable = !checked - if (Natives.setKernelUmountEnabled(shouldEnable)) { - isKernelUmountDisabled = !shouldEnable - } - } + var enhancedSecurityMode by rememberSaveable { + mutableIntStateOf( + prefs.getInt( + "enhanced_security_mode", if (Natives.isEnhancedSecurityEnabled()) 1 else 0 + ) ) } + SettingDropdown( + icon = Icons.Rounded.EnhancedEncryption, + title = stringResource(id = R.string.settings_enable_enhanced_security), + summary = stringResource(id = R.string.settings_enable_enhanced_security_summary), + items = modeItems, + selectedIndex = enhancedSecurityMode, + onSelectedIndexChange = { index -> + when (index) { + // Default: disable and save to persist + 0 -> if (Natives.setEnhancedSecurityEnabled(false)) { + execKsud("feature save", true) + prefs.edit { putInt("enhanced_security_mode", 0) } + enhancedSecurityMode = 0 + } + + // Temporarily enable: save disabled state first, then enable + 1 -> if (Natives.setEnhancedSecurityEnabled(false)) { + execKsud("feature save", true) + if (Natives.setEnhancedSecurityEnabled(true)) { + prefs.edit { putInt("enhanced_security_mode", 1) } + enhancedSecurityMode = 1 + } + } + + // Permanently enable: enable and save + 2 -> if (Natives.setEnhancedSecurityEnabled(true)) { + execKsud("feature save", true) + prefs.edit { putInt("enhanced_security_mode", 2) } + enhancedSecurityMode = 2 + } + } + } + ) + + var suCompatMode by rememberSaveable { + mutableIntStateOf( + prefs.getInt( + "su_compat_mode", if (!Natives.isSuEnabled()) 1 else 0 + ) + ) + } + SettingDropdown( + icon = Icons.Rounded.RemoveModerator, + title = stringResource(id = R.string.settings_disable_su), + summary = stringResource(id = R.string.settings_disable_su_summary), + items = modeItems, + selectedIndex = suCompatMode, + onSelectedIndexChange = { index -> + when (index) { + // Default: enable and save to persist + 0 -> if (Natives.setSuEnabled(true)) { + execKsud("feature save", true) + prefs.edit { putInt("su_compat_mode", 0) } + suCompatMode = 0 + } + + // Temporarily disable: save enabled state first, then disable + 1 -> if (Natives.setSuEnabled(true)) { + execKsud("feature save", true) + if (Natives.setSuEnabled(false)) { + prefs.edit { putInt("su_compat_mode", 1) } + suCompatMode = 1 + } + } + + // Permanently disable: disable and save + 2 -> if (Natives.setSuEnabled(false)) { + execKsud("feature save", true) + prefs.edit { putInt("su_compat_mode", 2) } + suCompatMode = 2 + } + } + } + ) + + var kernelUmountMode by rememberSaveable { + mutableIntStateOf( + prefs.getInt( + "kernel_umount_mode", if (!Natives.isKernelUmountEnabled()) 1 else 0 + ) + ) + } + SettingDropdown( + icon = Icons.Rounded.RemoveCircle, + title = stringResource(id = R.string.settings_disable_kernel_umount), + summary = stringResource(id = R.string.settings_disable_kernel_umount_summary), + items = modeItems, + selectedIndex = kernelUmountMode, + onSelectedIndexChange = { index -> + when (index) { + // Default: enable and save to persist + 0 -> if (Natives.setKernelUmountEnabled(true)) { + execKsud("feature save", true) + prefs.edit { putInt("kernel_umount_mode", 0) } + kernelUmountMode = 0 + } + + // Temporarily disable: save enabled state first, then disable + 1 -> if (Natives.setKernelUmountEnabled(true)) { + execKsud("feature save", true) + if (Natives.setKernelUmountEnabled(false)) { + prefs.edit { putInt("kernel_umount_mode", 1) } + kernelUmountMode = 1 + } + } + + // Permanently disable: disable and save + 2 -> if (Natives.setKernelUmountEnabled(false)) { + execKsud("feature save", true) + prefs.edit { putInt("kernel_umount_mode", 2) } + kernelUmountMode = 2 + } + } + } + ) + + // 卸载模块开关 + var umountChecked by rememberSaveable { mutableStateOf(Natives.isDefaultUmountModules()) } + SwitchItem( + icon = Icons.Rounded.FolderDelete, + title = stringResource(id = R.string.settings_umount_modules_default), + summary = stringResource(id = R.string.settings_umount_modules_default_summary), + checked = umountChecked, + onCheckedChange = { + if (Natives.setDefaultUmountModules(it)) { + umountChecked = it + } + } + ) // 强制签名验证开关 @@ -966,4 +1059,104 @@ private fun UidScannerSection( } ) } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingDropdown( + icon: ImageVector, + title: String, + summary: String, + items: List, + selectedIndex: Int, + leftAction: (@Composable () -> Unit)? = null, + onSelectedIndexChange: (Int) -> Unit +) { + var showDialog by remember { mutableStateOf(false) } + val selectedItemText = items.getOrNull(selectedIndex) ?: "" + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { showDialog = true } + .padding(horizontal = SPACING_LARGE, vertical = 12.dp), + verticalAlignment = Alignment.Top + ) { + if (leftAction != null) { + leftAction() + } else { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .padding(end = SPACING_LARGE) + .size(24.dp) + ) + } + + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium + ) + Spacer(modifier = Modifier.height(SPACING_SMALL)) + Text( + text = summary, + style = MaterialTheme.typography.bodyMedium + ) + Spacer(modifier = Modifier.height(SPACING_SMALL)) + Text( + text = selectedItemText, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Icon( + imageVector = Icons.Filled.ArrowForward, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) + ) + } + + if (showDialog) { + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { Text(title) }, + text = { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(items) { item -> + val index = items.indexOf(item) + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onSelectedIndexChange(index) + showDialog = false + } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedIndex == index, + onClick = null + ) + Spacer(modifier = Modifier.width(16.dp)) + Text(text = item) + } + } + } + }, + confirmButton = { + TextButton(onClick = { showDialog = false }) { + Text(stringResource(android.R.string.cancel)) + } + } + ) + } } \ No newline at end of file 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 18e6b11c..e52ee6ca 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -165,9 +165,14 @@ 设备 不允许授予 %s 超级用户权限 禁用 su 兼容性 - 临时禁止任何应用程序通过 su 命令获取 Root 权限(现有的 Root 进程不受影响) + 禁止任何应用通过 su 命令获取 root 权限(已运行的 root 进程不受影响)。 关闭内核 umount - 临时关闭 KernelSU 控制的内核级 umount 行为。 + 关闭 KernelSU 控制的内核级 umount 行为。 + 启用增强安全 + 启用更严格的安全策略。 + 默认 + 临时启用 + 始终启用 确定要安装以下 %1$d 个模块吗?\n\n%2$s 更多设置 SELinux diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 6ec1f0aa..fcc7b88f 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -167,9 +167,14 @@ Device model Granting superuser to %s is not allowed Disable su compatibility - Temporarily disable any applications from obtaining root privileges via the ⁠su command (Existing root processes will not be affected) + Disable the ability of any app to gain root privileges via the ⁠su command (existing root processes won\'t be affected). Disable kernel umount - Temporarily disable kernel-level umount behavior controlled by KernelSU. + Disable kernel-level umount behavior controlled by KernelSU. + Enable enhanced security + Enable stricter security policies. + Default + Temporarily enable + Permanently enable Sure you want to install the following %1$d modules? \n\n%2$s More settings SELinux diff --git a/manager/build.gradle.kts b/manager/build.gradle.kts index 309ae70b..396caec3 100644 --- a/manager/build.gradle.kts +++ b/manager/build.gradle.kts @@ -29,7 +29,9 @@ cmaker { val androidMinSdkVersion = 26 val androidTargetSdkVersion = 36 val androidCompileSdkVersion = 36 -val androidCompileNdkVersion = "29.0.14206865" +val androidBuildToolsVersion = "36.1.0" +val androidCompileNdkVersion by extra(libs.versions.ndk.get()) +val androidCmakeVersion by extra("3.22.0+") val androidSourceCompatibility = JavaVersion.VERSION_21 val androidTargetCompatibility = JavaVersion.VERSION_21 val managerVersionCode by extra(4 * 10000 + getGitCommitCount() - 2815) @@ -52,6 +54,7 @@ subprojects { extensions.configure(CommonExtension::class.java) { compileSdk = androidCompileSdkVersion ndkVersion = androidCompileNdkVersion + buildToolsVersion = androidBuildToolsVersion defaultConfig { minSdk = androidMinSdkVersion diff --git a/manager/gradle/libs.versions.toml b/manager/gradle/libs.versions.toml index e2f419c2..fd065596 100644 --- a/manager/gradle/libs.versions.toml +++ b/manager/gradle/libs.versions.toml @@ -2,15 +2,15 @@ accompanist-drawablepainter = "0.37.3" agp = "8.13.0" gson = "2.13.2" -kotlin = "2.2.20" -ksp = "2.2.20-2.0.2" -compose-bom = "2025.09.01" +kotlin = "2.2.21" +ksp = "2.2.21-2.0.4" +compose-bom = "2025.11.00" lifecycle = "2.9.4" -navigation = "2.9.5" +navigation = "2.9.6" activity-compose = "1.11.0" kotlinx-coroutines = "1.10.2" coil-compose = "2.7.0" -compose-destination = "2.2.0" +compose-destination = "2.3.0" sheets-compose-dialogs = "1.3.0" markdown = "4.6.2" webkit = "1.14.0" @@ -19,11 +19,13 @@ parcelablelist = "2.0.1" libsu = "6.0.0" apksign = "1.4" cmaker = "1.2" -compose-material = "1.9.2" +compose-material = "1.9.4" compose-material3 = "1.4.0" -compose-ui = "1.9.2" +compose-ui = "1.9.4" documentfile = "1.1.0" mmrl = "2bb00b3c2b" +ndk = "29.0.13599879-beta2" +foundation = "1.9.4" [plugins] agp-app = { id = "com.android.application", version.ref = "agp" } @@ -81,11 +83,12 @@ sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs" markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" } -lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "29.0.13599879-beta2" } +lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version.ref = "ndk" } androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", version.ref = "mmrl" } mmrl-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" } mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", version.ref = "mmrl" } -mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" } \ No newline at end of file +mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" } +androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" } \ No newline at end of file diff --git a/manager/gradle/wrapper/gradle-wrapper.properties b/manager/gradle/wrapper/gradle-wrapper.properties index 2a84e188..bad7c246 100644 --- a/manager/gradle/wrapper/gradle-wrapper.properties +++ b/manager/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/manager/gradlew b/manager/gradlew index 23d15a93..c27469b1 100755 --- a/manager/gradlew +++ b/manager/gradlew @@ -114,8 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" - # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then @@ -172,7 +170,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +209,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/manager/gradlew.bat b/manager/gradlew.bat index db3a6ac2..90a3f3c5 100644 --- a/manager/gradlew.bat +++ b/manager/gradlew.bat @@ -70,11 +70,9 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= - @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/userspace/ksud/src/feature.rs b/userspace/ksud/src/feature.rs index ca0b9a48..f6903f1f 100644 --- a/userspace/ksud/src/feature.rs +++ b/userspace/ksud/src/feature.rs @@ -16,6 +16,7 @@ const FEATURE_VERSION: u32 = 1; pub enum FeatureId { SuCompat = 0, KernelUmount = 1, + EnhancedSecurity = 2, } impl FeatureId { @@ -23,6 +24,7 @@ impl FeatureId { match id { 0 => Some(FeatureId::SuCompat), 1 => Some(FeatureId::KernelUmount), + 2 => Some(FeatureId::EnhancedSecurity), _ => None, } } @@ -31,6 +33,7 @@ impl FeatureId { match self { FeatureId::SuCompat => "su_compat", FeatureId::KernelUmount => "kernel_umount", + FeatureId::EnhancedSecurity => "enhanced_security", } } @@ -42,6 +45,9 @@ impl FeatureId { FeatureId::KernelUmount => { "Kernel Umount - controls whether kernel automatically unmounts modules when not needed" } + FeatureId::EnhancedSecurity => { + "Enhanced Security - disable non‑KSU root elevation and unauthorized UID downgrades" + } } } } @@ -50,6 +56,7 @@ fn parse_feature_id(name: &str) -> Result { match name { "su_compat" | "0" => Ok(FeatureId::SuCompat), "kernel_umount" | "1" => Ok(FeatureId::KernelUmount), + "enhanced_security" | "2" => Ok(FeatureId::EnhancedSecurity), _ => bail!("Unknown feature: {}", name), } } @@ -223,7 +230,11 @@ pub fn list_features() -> Result<()> { } } - let all_features = [FeatureId::SuCompat, FeatureId::KernelUmount]; + let all_features = [ + FeatureId::SuCompat, + FeatureId::KernelUmount, + FeatureId::EnhancedSecurity, + ]; for feature_id in all_features.iter() { let id = *feature_id as u32; @@ -282,7 +293,11 @@ pub fn load_config_and_apply() -> Result<()> { pub fn save_config() -> Result<()> { let mut features = HashMap::new(); - let all_features = [FeatureId::SuCompat, FeatureId::KernelUmount]; + let all_features = [ + FeatureId::SuCompat, + FeatureId::KernelUmount, + FeatureId::EnhancedSecurity, + ]; for feature_id in all_features.iter() { let id = *feature_id as u32;