manager: Refactoring get the status of SUSFS functions

- More precise status cases
- Requires a recompile of your kernel to use it.
This commit is contained in:
ShirkNeko
2025-06-16 20:16:25 +08:00
parent 4ce6ff6286
commit a12b14ef46
5 changed files with 154 additions and 176 deletions

View File

@@ -300,3 +300,51 @@ NativeBridgeNP(getHookType, jstring) {
get_hook_type(hook_type, sizeof(hook_type)); get_hook_type(hook_type, sizeof(hook_type));
return GetEnvironment()->NewStringUTF(env, hook_type); return GetEnvironment()->NewStringUTF(env, hook_type);
} }
NativeBridgeNP(getSusfsFeatureStatus, jobject) {
struct susfs_feature_status status;
bool result = get_susfs_feature_status(&status);
if (!result) {
return NULL;
}
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$SusfsFeatureStatus");
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
// 设置各个字段
jfieldID statusSusPathField = GetEnvironment()->GetFieldID(env, cls, "statusSusPath", "Z");
jfieldID statusSusMountField = GetEnvironment()->GetFieldID(env, cls, "statusSusMount", "Z");
jfieldID statusAutoDefaultMountField = GetEnvironment()->GetFieldID(env, cls, "statusAutoDefaultMount", "Z");
jfieldID statusAutoBindMountField = GetEnvironment()->GetFieldID(env, cls, "statusAutoBindMount", "Z");
jfieldID statusSusKstatField = GetEnvironment()->GetFieldID(env, cls, "statusSusKstat", "Z");
jfieldID statusTryUmountField = GetEnvironment()->GetFieldID(env, cls, "statusTryUmount", "Z");
jfieldID statusAutoTryUmountBindField = GetEnvironment()->GetFieldID(env, cls, "statusAutoTryUmountBind", "Z");
jfieldID statusSpoofUnameField = GetEnvironment()->GetFieldID(env, cls, "statusSpoofUname", "Z");
jfieldID statusEnableLogField = GetEnvironment()->GetFieldID(env, cls, "statusEnableLog", "Z");
jfieldID statusHideSymbolsField = GetEnvironment()->GetFieldID(env, cls, "statusHideSymbols", "Z");
jfieldID statusSpoofCmdlineField = GetEnvironment()->GetFieldID(env, cls, "statusSpoofCmdline", "Z");
jfieldID statusOpenRedirectField = GetEnvironment()->GetFieldID(env, cls, "statusOpenRedirect", "Z");
jfieldID statusMagicMountField = GetEnvironment()->GetFieldID(env, cls, "statusMagicMount", "Z");
jfieldID statusOverlayfsAutoKstatField = GetEnvironment()->GetFieldID(env, cls, "statusOverlayfsAutoKstat", "Z");
jfieldID statusSusSuField = GetEnvironment()->GetFieldID(env, cls, "statusSusSu", "Z");
GetEnvironment()->SetBooleanField(env, obj, statusSusPathField, status.status_sus_path);
GetEnvironment()->SetBooleanField(env, obj, statusSusMountField, status.status_sus_mount);
GetEnvironment()->SetBooleanField(env, obj, statusAutoDefaultMountField, status.status_auto_default_mount);
GetEnvironment()->SetBooleanField(env, obj, statusAutoBindMountField, status.status_auto_bind_mount);
GetEnvironment()->SetBooleanField(env, obj, statusSusKstatField, status.status_sus_kstat);
GetEnvironment()->SetBooleanField(env, obj, statusTryUmountField, status.status_try_umount);
GetEnvironment()->SetBooleanField(env, obj, statusAutoTryUmountBindField, status.status_auto_try_umount_bind);
GetEnvironment()->SetBooleanField(env, obj, statusSpoofUnameField, status.status_spoof_uname);
GetEnvironment()->SetBooleanField(env, obj, statusEnableLogField, status.status_enable_log);
GetEnvironment()->SetBooleanField(env, obj, statusHideSymbolsField, status.status_hide_symbols);
GetEnvironment()->SetBooleanField(env, obj, statusSpoofCmdlineField, status.status_spoof_cmdline);
GetEnvironment()->SetBooleanField(env, obj, statusOpenRedirectField, status.status_open_redirect);
GetEnvironment()->SetBooleanField(env, obj, statusMagicMountField, status.status_magic_mount);
GetEnvironment()->SetBooleanField(env, obj, statusOverlayfsAutoKstatField, status.status_overlayfs_auto_kstat);
GetEnvironment()->SetBooleanField(env, obj, statusSusSuField, status.status_sus_su);
return obj;
}

View File

@@ -32,6 +32,7 @@
#define CMD_ENABLE_SU 15 #define CMD_ENABLE_SU 15
#define CMD_ENABLE_KPM 100 #define CMD_ENABLE_KPM 100
#define CMD_HOOK_TYPE 101 #define CMD_HOOK_TYPE 101
#define CMD_GET_SUSFS_FEATURE_STATUS 102
static bool ksuctl(int cmd, void* arg1, void* arg2) { static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0; int32_t result = 0;
@@ -124,3 +125,11 @@ bool get_hook_type(char* hook_type, size_t size) {
hook_type[size - 1] = '\0'; hook_type[size - 1] = '\0';
return true; return true;
} }
bool get_susfs_feature_status(struct susfs_feature_status* status) {
if (status == NULL) {
return false;
}
return ksuctl(CMD_GET_SUSFS_FEATURE_STATUS, status, NULL);
}

View File

@@ -26,6 +26,25 @@ bool is_lkm_mode();
#define KSU_MAX_GROUPS 32 #define KSU_MAX_GROUPS 32
#define KSU_SELINUX_DOMAIN 64 #define KSU_SELINUX_DOMAIN 64
// SUSFS Functional State Structures
struct susfs_feature_status {
bool status_sus_path;
bool status_sus_mount;
bool status_auto_default_mount;
bool status_auto_bind_mount;
bool status_sus_kstat;
bool status_try_umount;
bool status_auto_try_umount_bind;
bool status_spoof_uname;
bool status_enable_log;
bool status_hide_symbols;
bool status_spoof_cmdline;
bool status_open_redirect;
bool status_magic_mount;
bool status_overlayfs_auto_kstat;
bool status_sus_su;
};
struct root_profile { struct root_profile {
int32_t uid; int32_t uid;
int32_t gid; int32_t gid;
@@ -86,4 +105,6 @@ bool is_KPM_enable();
bool get_hook_type(char* hook_type, size_t size); bool get_hook_type(char* hook_type, size_t size);
bool get_susfs_feature_status(struct susfs_feature_status* status);
#endif //KERNELSU_KSU_H #endif //KERNELSU_KSU_H

View File

@@ -71,6 +71,12 @@ object Natives {
external fun isKPMEnabled(): Boolean external fun isKPMEnabled(): Boolean
external fun getHookType(): String external fun getHookType(): String
/**
* Get SUSFS feature status from kernel
* @return SusfsFeatureStatus object containing all feature states, or null if failed
*/
external fun getSusfsFeatureStatus(): SusfsFeatureStatus?
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$" private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
private const val NOBODY_UID = 9999 private const val NOBODY_UID = 9999
@@ -95,6 +101,48 @@ object Natives {
return version < MINIMAL_SUPPORTED_KERNEL return version < MINIMAL_SUPPORTED_KERNEL
} }
@Immutable
@Parcelize
@Keep
data class SusfsFeatureStatus(
val statusSusPath: Boolean = false,
val statusSusMount: Boolean = false,
val statusAutoDefaultMount: Boolean = false,
val statusAutoBindMount: Boolean = false,
val statusSusKstat: Boolean = false,
val statusTryUmount: Boolean = false,
val statusAutoTryUmountBind: Boolean = false,
val statusSpoofUname: Boolean = false,
val statusEnableLog: Boolean = false,
val statusHideSymbols: Boolean = false,
val statusSpoofCmdline: Boolean = false,
val statusOpenRedirect: Boolean = false,
val statusMagicMount: Boolean = false,
val statusOverlayfsAutoKstat: Boolean = false,
val statusSusSu: Boolean = false
) : Parcelable {
fun toMap(): Map<String, Boolean> {
return mapOf(
"CONFIG_KSU_SUSFS_SUS_PATH" to statusSusPath,
"CONFIG_KSU_SUSFS_SUS_MOUNT" to statusSusMount,
"CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT" to statusAutoDefaultMount,
"CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT" to statusAutoBindMount,
"CONFIG_KSU_SUSFS_SUS_KSTAT" to statusSusKstat,
"CONFIG_KSU_SUSFS_TRY_UMOUNT" to statusTryUmount,
"CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT" to statusAutoTryUmountBind,
"CONFIG_KSU_SUSFS_SPOOF_UNAME" to statusSpoofUname,
"CONFIG_KSU_SUSFS_ENABLE_LOG" to statusEnableLog,
"CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS" to statusHideSymbols,
"CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG" to statusSpoofCmdline,
"CONFIG_KSU_SUSFS_OPEN_REDIRECT" to statusOpenRedirect,
"CONFIG_KSU_SUSFS_HAS_MAGIC_MOUNT" to statusMagicMount,
"CONFIG_KSU_SUSFS_SUS_OVERLAYFS" to statusOverlayfsAutoKstat,
"CONFIG_KSU_SUSFS_SUS_SU" to statusSusSu
)
}
}
@Immutable @Immutable
@Parcelize @Parcelize
@Keep @Keep

View File

@@ -5,6 +5,7 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.widget.Toast import android.widget.Toast
import com.dergoogler.mmrl.platform.Platform.Companion.context import com.dergoogler.mmrl.platform.Platform.Companion.context
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -13,7 +14,6 @@ import kotlinx.coroutines.withContext
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.File import java.io.File
import java.util.zip.GZIPInputStream
/** /**
* SuSFS 配置管理器 * SuSFS 配置管理器
@@ -71,25 +71,6 @@ object SuSFSManager {
val canConfigure: Boolean = false // 是否可配置(通过弹窗) val canConfigure: Boolean = false // 是否可配置(通过弹窗)
) )
/**
* 功能配置映射数据类
*/
private data class FeatureMapping(
val id: String,
val config: String
)
/**
* 执行Shell命令并返回输出
*/
private fun runCmd(shell: Shell, cmd: String): String {
return shell.newJob()
.add(cmd)
.to(mutableListOf<String>(), null)
.exec().out
.joinToString("\n")
}
/** /**
* 获取Root Shell实例 * 获取Root Shell实例
*/ */
@@ -303,56 +284,6 @@ object SuSFSManager {
return getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard" return getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard"
} }
/**
* 读取并解析/proc/config.gz文件
*/
private suspend fun readProcConfig(): Map<String, String> = withContext(Dispatchers.IO) {
val configMap = mutableMapOf<String, String>()
try {
val shell = getRootShell()
// 首先检查/proc/config.gz是否存在
val checkResult = shell.newJob().add("test -f /proc/config.gz").exec()
if (!checkResult.isSuccess) {
return@withContext configMap
}
// 读取并解压/proc/config.gz
val result = shell.newJob().add("zcat /proc/config.gz").exec()
if (result.isSuccess) {
result.out.forEach { line ->
val trimmedLine = line.trim()
when {
// 处理启用的配置项 CONFIG_XXX=y
trimmedLine.contains("=y") -> {
val configName = trimmedLine.substringBefore("=y")
if (configName.startsWith("CONFIG_")) {
configMap[configName] = "y"
}
}
// 处理未启用的配置项 # CONFIG_XXX is not set
trimmedLine.startsWith("# ") && trimmedLine.endsWith(" is not set") -> {
val configName = trimmedLine.removePrefix("# ").removeSuffix(" is not set")
if (configName.startsWith("CONFIG_")) {
configMap[configName] = "not_set"
}
}
// 处理其他类型的配置项 CONFIG_XXX=value
trimmedLine.contains("=") && trimmedLine.startsWith("CONFIG_") -> {
val parts = trimmedLine.split("=", limit = 2)
if (parts.size == 2) {
configMap[parts[0]] = parts[1]
}
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
configMap
}
/** /**
* 从assets复制ksu_susfs文件到/data/adb/ksu/bin/ * 从assets复制ksu_susfs文件到/data/adb/ksu/bin/
*/ */
@@ -785,63 +716,14 @@ object SuSFSManager {
} }
/** /**
* 获取功能配置映射表 * 获取SuSFS启用功能状态
*/
private fun getFeatureMappings(): List<FeatureMapping> {
return listOf(
FeatureMapping("status_sus_path", "CONFIG_KSU_SUSFS_SUS_PATH"),
FeatureMapping("status_sus_mount", "CONFIG_KSU_SUSFS_SUS_MOUNT"),
FeatureMapping("status_auto_default_mount", "CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT"),
FeatureMapping("status_auto_bind_mount", "CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT"),
FeatureMapping("status_sus_kstat", "CONFIG_KSU_SUSFS_SUS_KSTAT"),
FeatureMapping("status_try_umount", "CONFIG_KSU_SUSFS_TRY_UMOUNT"),
FeatureMapping("status_auto_try_umount_bind", "CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT"),
FeatureMapping("status_spoof_uname", "CONFIG_KSU_SUSFS_SPOOF_UNAME"),
FeatureMapping("status_enable_log", "CONFIG_KSU_SUSFS_ENABLE_LOG"),
FeatureMapping("status_hide_symbols", "CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS"),
FeatureMapping("status_spoof_cmdline", "CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG"),
FeatureMapping("status_open_redirect", "CONFIG_KSU_SUSFS_OPEN_REDIRECT"),
FeatureMapping("status_magic_mount", "CONFIG_KSU_SUSFS_HAS_MAGIC_MOUNT"),
FeatureMapping("status_overlayfs_auto_kstat", "CONFIG_KSU_SUSFS_SUS_OVERLAYFS"),
FeatureMapping("status_sus_su", "CONFIG_KSU_SUSFS_SUS_SU")
)
}
/**
* 获取启用功能状态
*/ */
suspend fun getEnabledFeatures(context: Context): List<EnabledFeature> = withContext(Dispatchers.IO) { suspend fun getEnabledFeatures(context: Context): List<EnabledFeature> = withContext(Dispatchers.IO) {
try { try {
// 每次都重新执行命令获取最新状态 val susfsStatus = Natives.getSusfsFeatureStatus()
val shell = getRootShell() if (susfsStatus != null) {
val targetPath = getSuSFSTargetPath() parseEnabledFeaturesFromStatus(context, susfsStatus)
// 首先检查二进制文件是否存在于目标位置
val checkResult = shell.newJob().add("test -f '$targetPath'").exec()
val binaryPath = if (checkResult.isSuccess) {
// 如果目标位置存在,直接使用
targetPath
} else { } else {
// 如果不存在尝试从assets复制
copyBinaryFromAssets(context)
}
if (binaryPath == null) {
return@withContext emptyList()
}
// 使用runCmd执行show enabled_features命令获取实时状态
val command = "$binaryPath show enabled_features"
val output = runCmd(shell, command)
// 读取/proc/config.gz进行二次检查
val procConfig = readProcConfig()
if (output.isNotEmpty()) {
parseEnabledFeatures(context, output, procConfig)
} else {
// 如果命令输出为空,返回空列表
emptyList() emptyList()
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -851,65 +733,35 @@ object SuSFSManager {
} }
/** /**
* 解析启用功能状态输出 * 解析SuSFS启用功能状态
*/ */
private fun parseEnabledFeatures(context: Context, output: String, procConfig: Map<String, String>): List<EnabledFeature> { private fun parseEnabledFeaturesFromStatus(context: Context, status: Natives.SusfsFeatureStatus): List<EnabledFeature> {
val features = mutableListOf<EnabledFeature>() val features = mutableListOf<EnabledFeature>()
// 将输出按行分割并保存到集合中进行快速查找 // 定义功能名称和状态的映射
val outputLines = output.lines().map { it.trim() }.filter { it.isNotEmpty() }.toSet() val featureList = listOf(
Triple("status_sus_path", context.getString(R.string.sus_path_feature_label), status.statusSusPath),
// 获取功能配置映射表 Triple("status_sus_mount", context.getString(R.string.sus_mount_feature_label), status.statusSusMount),
val featureMappings = getFeatureMappings() Triple("status_try_umount", context.getString(R.string.try_umount_feature_label), status.statusTryUmount),
Triple("status_spoof_uname", context.getString(R.string.spoof_uname_feature_label), status.statusSpoofUname),
// 定义功能名称映射id到显示名称 Triple("status_spoof_cmdline", context.getString(R.string.spoof_cmdline_feature_label), status.statusSpoofCmdline),
val featureNameMap = mapOf( Triple("status_open_redirect", context.getString(R.string.open_redirect_feature_label), status.statusOpenRedirect),
"status_sus_path" to context.getString(R.string.sus_path_feature_label), Triple("status_enable_log", context.getString(R.string.enable_log_feature_label), status.statusEnableLog),
"status_sus_mount" to context.getString(R.string.sus_mount_feature_label), Triple("status_auto_default_mount", context.getString(R.string.auto_default_mount_feature_label), status.statusAutoDefaultMount),
"status_try_umount" to context.getString(R.string.try_umount_feature_label), Triple("status_auto_bind_mount", context.getString(R.string.auto_bind_mount_feature_label), status.statusAutoBindMount),
"status_spoof_uname" to context.getString(R.string.spoof_uname_feature_label), Triple("status_auto_try_umount_bind", context.getString(R.string.auto_try_umount_bind_feature_label), status.statusAutoTryUmountBind),
"status_spoof_cmdline" to context.getString(R.string.spoof_cmdline_feature_label), Triple("status_hide_symbols", context.getString(R.string.hide_symbols_feature_label), status.statusHideSymbols),
"status_open_redirect" to context.getString(R.string.open_redirect_feature_label), Triple("status_sus_kstat", context.getString(R.string.sus_kstat_feature_label), status.statusSusKstat),
"status_enable_log" to context.getString(R.string.enable_log_feature_label), Triple("status_magic_mount", context.getString(R.string.magic_mount_feature_label), status.statusMagicMount),
"status_auto_default_mount" to context.getString(R.string.auto_default_mount_feature_label), Triple("status_overlayfs_auto_kstat", context.getString(R.string.overlayfs_auto_kstat_feature_label), status.statusOverlayfsAutoKstat),
"status_auto_bind_mount" to context.getString(R.string.auto_bind_mount_feature_label), Triple("status_sus_su", context.getString(R.string.sus_su_feature_label), status.statusSusSu)
"status_auto_try_umount_bind" to context.getString(R.string.auto_try_umount_bind_feature_label),
"status_hide_symbols" to context.getString(R.string.hide_symbols_feature_label),
"status_sus_kstat" to context.getString(R.string.sus_kstat_feature_label),
"status_magic_mount" to context.getString(R.string.magic_mount_feature_label),
"status_overlayfs_auto_kstat" to context.getString(R.string.overlayfs_auto_kstat_feature_label),
"status_sus_su" to context.getString(R.string.sus_su_feature_label)
) )
// 根据映射表检查每个功能的启用状态 // 根据功能列表创建EnabledFeature对象
featureMappings.forEach { mapping -> featureList.forEach { (id, displayName, isEnabled) ->
val displayName = featureNameMap[mapping.id] ?: mapping.id
// 检查show enabled_features命令输出中是否存在该配置
val existsInOutput = outputLines.contains(mapping.config)
// 检查/proc/config.gz中的配置状态
val procConfigValue = procConfig[mapping.config]
val isEnabled = when {
// 如果show enabled_features命令中存在该CONFIG则认为启用
existsInOutput -> true
// 如果show enabled_features命令中不存在该CONFIG则检查/proc/config.gz中是否存在y
!existsInOutput && procConfigValue == "y" -> true
// 如果/proc/config.gz中对应的为is not set则为未启用
procConfigValue == "not_set" -> false
// 如果/proc/config.gz中没有对应的CONFIG则按照show enabled_features命令为主
procConfigValue == null -> false
else -> false
}
val statusText = if (isEnabled) context.getString(R.string.susfs_feature_enabled) else context.getString(R.string.susfs_feature_disabled) val statusText = if (isEnabled) context.getString(R.string.susfs_feature_enabled) else context.getString(R.string.susfs_feature_disabled)
// 只有 enable_log 功能可以配置 // 只有对应功能可以配置
val canConfigure = mapping.id == "status_enable_log" val canConfigure = id == "status_enable_log"
features.add(EnabledFeature(displayName, isEnabled, statusText, canConfigure)) features.add(EnabledFeature(displayName, isEnabled, statusText, canConfigure))
} }