diff --git a/manager/app/src/main/cpp/jni.cc b/manager/app/src/main/cpp/jni.cc
index b0d28698..0893c637 100644
--- a/manager/app/src/main/cpp/jni.cc
+++ b/manager/app/src/main/cpp/jni.cc
@@ -295,4 +295,14 @@ extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
return uid_should_umount(uid);
+}
+extern "C"
+JNIEXPORT jboolean JNICALL
+Java_me_weishu_kernelsu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
+ return is_su_enabled();
+}
+extern "C"
+JNIEXPORT jboolean JNICALL
+Java_me_weishu_kernelsu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
+ return set_su_enabled(enabled);
}
\ No newline at end of file
diff --git a/manager/app/src/main/cpp/ksu.cc b/manager/app/src/main/cpp/ksu.cc
index 1e798189..720c5fd0 100644
--- a/manager/app/src/main/cpp/ksu.cc
+++ b/manager/app/src/main/cpp/ksu.cc
@@ -27,6 +27,8 @@
#define CMD_IS_UID_GRANTED_ROOT 12
#define CMD_IS_UID_SHOULD_UMOUNT 13
+#define CMD_IS_SU_ENABLED 14
+#define CMD_ENABLE_SU 15
static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0;
@@ -84,3 +86,14 @@ bool set_app_profile(const app_profile *profile) {
bool get_app_profile(p_key_t key, app_profile *profile) {
return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr);
}
+
+bool set_su_enabled(bool enabled) {
+ return ksuctl(CMD_ENABLE_SU, (void*) enabled, nullptr);
+}
+
+bool is_su_enabled() {
+ bool enabled = true;
+ // if ksuctl failed, we assume su is enabled, and it cannot be disabled.
+ ksuctl(CMD_IS_SU_ENABLED, &enabled, nullptr);
+ return enabled;
+}
\ No newline at end of file
diff --git a/manager/app/src/main/cpp/ksu.h b/manager/app/src/main/cpp/ksu.h
index 160a9d6f..3854356a 100644
--- a/manager/app/src/main/cpp/ksu.h
+++ b/manager/app/src/main/cpp/ksu.h
@@ -79,4 +79,8 @@ bool set_app_profile(const app_profile *profile);
bool get_app_profile(p_key_t key, app_profile *profile);
+bool set_su_enabled(bool enabled);
+
+bool is_su_enabled();
+
#endif //KERNELSU_KSU_H
diff --git a/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt b/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt
index bb788293..426cf37f 100644
--- a/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt
+++ b/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt
@@ -21,6 +21,9 @@ object Natives {
// 11640: Support query working mode, LKM or GKI
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
+
+ // 12040: Support disable sucompat mode
+ const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
const val ROOT_UID = 0
@@ -55,6 +58,15 @@ object Natives {
external fun getAppProfile(key: String?, uid: Int): Profile
external fun setAppProfile(profile: Profile?): Boolean
+ /**
+ * `su` compat mode can be disabled temporarily.
+ * 0: disabled
+ * 1: enabled
+ * negative : error
+ */
+ external fun isSuEnabled(): Boolean
+ external fun setSuEnabled(enabled: Boolean): Boolean
+
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
private const val NOBODY_UID = 9999
diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt
index fe6b8786..951f12df 100644
--- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt
+++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt
@@ -27,6 +27,7 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DeleteForever
import androidx.compose.material.icons.filled.DeveloperMode
import androidx.compose.material.icons.filled.Fence
+import androidx.compose.material.icons.filled.FolderDelete
import androidx.compose.material.icons.filled.RemoveModerator
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Share
@@ -161,7 +162,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
mutableStateOf(Natives.isDefaultUmountModules())
}
SwitchItem(
- icon = Icons.Filled.RemoveModerator,
+ icon = Icons.Filled.FolderDelete,
title = stringResource(id = R.string.settings_umount_modules_default),
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
checked = umountChecked
@@ -171,6 +172,24 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
}
+ if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
+ var isSuDisabled by rememberSaveable {
+ mutableStateOf(!Natives.isSuEnabled())
+ }
+ SwitchItem(
+ icon = Icons.Filled.RemoveModerator,
+ title = stringResource(id = R.string.settings_disable_su),
+ summary = stringResource(id = R.string.settings_disable_su_summary),
+ checked = isSuDisabled,
+ enabled = !isSuDisabled // we can't re-enable su if it's disabled.
+ ) { checked ->
+ val shouldEnable = !checked
+ if (Natives.setSuEnabled(shouldEnable)) {
+ isSuDisabled = !shouldEnable
+ }
+ }
+ }
+
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
var checkUpdate by rememberSaveable {
mutableStateOf(
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 696dfc45..7c67ad85 100644
--- a/manager/app/src/main/res/values-zh-rCN/strings.xml
+++ b/manager/app/src/main/res/values-zh-rCN/strings.xml
@@ -133,4 +133,6 @@
选择的 LKM:%s
保存日志
日志已保存
+ 关闭 su 兼容
+ 临时禁止任何应用通过 su 命令获取 root 权限(已运行的 root 进程不受影响)
diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml
index 48fff143..cf2cec52 100644
--- a/manager/app/src/main/res/values/strings.xml
+++ b/manager/app/src/main/res/values/strings.xml
@@ -135,4 +135,6 @@
Selected LKM: %s
Save logs
Logs saved
+ Disable su compatibility
+ Temporarily disable any applications from obtaining root privileges via the su command (existing root processes will not be affected).