new supercall impl (#511)

* refactor: replace throne tracker with ksud token

* use snprintf

* refactor: new supercall impl

- Import the sukisu command

* disable seccomp for supercall users

* kernel: fmt clear

* kernel: Enable macro protection for sulog

- Only enabled on kernel versions greater than 5.10.245

* kernel: Refactor kprobe hooks and implement LSM hooks for improved security handling

* debug mode

* kernel: Add functionality to generate and validate authentication tokens for cmd_su

* kernel: Simplified manual SU command processing for code

* kernel: replace renameat hook with fsnotify

* Revert "refactor: replace throne tracker with ksud token"

This reverts commit aa2cbbf9cd.

* kernel: fix compile

* kernel: fix compile below 6.0

* Fix compile err; Add become_manager

* kernel: install fd for manager automaticlly

- extend to import the corresponding command

* manager: new supercall impl

* temp changes for ksud

* ksud: fix compile

* fix wrong opcode

* kernel: fix compile

* kernel: Fixed hook type and KPM status retrieval errors

* kernel: Fixed potential null pointer issue with current->mm in kernel version 5.10

When calling get_full_comm() within system call hooks, current->mm may be null (prctl). A fallback mechanism for current->comm must be added beforehand to prevent null pointer dereferences when accessing mm->arg_start/arg_end.

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>

* ksud: fix cargo check

* manager: Fixed an issue where the KSUD release and user-mode scanning switch failed to function correctly.

- kernel: fix spin lock mutual

kernel: Fixed potential null pointer issue with current->mm in kernel version 5.10

When calling get_full_comm() within system call hooks, current->mm may be null (prctl). A fallback mechanism for current->comm must be added beforehand to prevent null pointer dereferences when accessing mm->arg_start/arg_end.

kernel: try introduce like susfs's method to fix prctl delay

* seccomp: allow reboot

* use u32

* update clang-format

* 4 spaces save the world

* ksud: Fix build on macOS

* manager: bump minimal supported kernel.

- When get_hook_type is empty, display “Unknown”.


* Fix ksud build (#2841)

* try fix ksud

* fix for macos

* remove any

* Fix ksud build, take 3

* try fix allowlist

* bring lsm hook back

* fix: a lot again

* Fix ksud build, take 4 (#2846)

Remove init_driver_fd function for non-linux/android targets

* manager: Return to the native method via KSUd installation

* Merge with susfs-mian format

---------

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
Co-authored-by: Ylarod <me@ylarod.cn>
Co-authored-by: weishu <twsxtd@gmail.com>
Co-authored-by: AlexLiuDev233 <wzylin11@outlook.com>
Co-authored-by: Wang Han <416810799@qq.com>
This commit is contained in:
ShirkNeko
2025-11-01 23:30:30 +08:00
committed by GitHub
parent 0da8ecb071
commit 320e08b8fb
49 changed files with 6061 additions and 5315 deletions

View File

@@ -6,410 +6,412 @@
#include <android/log.h>
#include <string.h>
NativeBridge(becomeManager, jboolean, jstring pkg) {
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, JNI_FALSE);
bool result = become_manager(cpkg);
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
return result;
}
NativeBridgeNP(getVersion, jint) {
return get_version();
uint32_t version = get_version();
if (version > INT32_MAX) {
LogDebug("Version overflow: %u", version);
}
return (jint)version;
}
// get VERSION FULL
NativeBridgeNP(getFullVersion, jstring) {
char buff[255] = { 0 };
get_full_version((char *) &buff);
return GetEnvironment()->NewStringUTF(env, buff);
char buff[255] = { 0 };
get_full_version((char *) &buff);
return GetEnvironment()->NewStringUTF(env, buff);
}
NativeBridgeNP(getAllowList, jintArray) {
int uids[1024];
int size = 0;
bool result = get_allow_list(uids, &size);
struct ksu_get_allow_list_cmd cmd = {};
bool result = get_allow_list(&cmd);
LogDebug("getAllowList: %d, size: %d", result, size);
if (result) {
jsize array_size = (jsize)cmd.count;
if (array_size < 0 || (unsigned int)array_size != cmd.count) {
LogDebug("Invalid array size: %u", cmd.count);
return GetEnvironment()->NewIntArray(env, 0);
}
if (result) {
jintArray array = GetEnvironment()->NewIntArray(env, size);
GetEnvironment()->SetIntArrayRegion(env, array, 0, size, uids);
jintArray array = GetEnvironment()->NewIntArray(env, array_size);
GetEnvironment()->SetIntArrayRegion(env, array, 0, array_size, (const jint *)(cmd.uids));
return array;
}
return array;
}
return GetEnvironment()->NewIntArray(env, 0);
return GetEnvironment()->NewIntArray(env, 0);
}
NativeBridgeNP(isSafeMode, jboolean) {
return is_safe_mode();
return is_safe_mode();
}
NativeBridgeNP(isLkmMode, jboolean) {
return is_lkm_mode();
return is_lkm_mode();
}
NativeBridgeNP(isManager, jboolean) {
return is_manager();
}
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
for (int i = 0; i < count; ++i) {
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, data[i]);
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
}
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
for (int i = 0; i < count; ++i) {
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, data[i]);
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
}
}
static void addIntToList(JNIEnv *env, jobject list, int ele) {
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, ele);
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, ele);
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
}
static uint64_t capListToBits(JNIEnv *env, jobject list) {
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
jint listSize = GetEnvironment()->CallIntMethod(env, list, size);
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
uint64_t result = 0;
for (int i = 0; i < listSize; ++i) {
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
int data = GetEnvironment()->CallIntMethod(env, integer, intValue);
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
jint listSize = GetEnvironment()->CallIntMethod(env, list, size);
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
uint64_t result = 0;
for (int i = 0; i < listSize; ++i) {
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
int data = GetEnvironment()->CallIntMethod(env, integer, intValue);
if (cap_valid(data)) {
result |= (1ULL << data);
}
}
if (cap_valid(data)) {
result |= (1ULL << data);
}
}
return result;
return result;
}
static int getListSize(JNIEnv *env, jobject list) {
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
return GetEnvironment()->CallIntMethod(env, list, size);
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
return GetEnvironment()->CallIntMethod(env, list, size);
}
static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
for (int i = 0; i < count; ++i) {
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
data[i] = GetEnvironment()->CallIntMethod(env, integer, intValue);
}
jclass cls = GetEnvironment()->GetObjectClass(env, list);
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
for (int i = 0; i < count; ++i) {
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
data[i] = GetEnvironment()->CallIntMethod(env, integer, intValue);
}
}
NativeBridge(getAppProfile, jobject, jstring pkg, jint uid) {
if (GetEnvironment()->GetStringLength(env, pkg) > KSU_MAX_PACKAGE_NAME) {
return NULL;
}
if (GetEnvironment()->GetStringLength(env, pkg) > KSU_MAX_PACKAGE_NAME) {
return NULL;
}
char key[KSU_MAX_PACKAGE_NAME] = { 0 };
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, nullptr);
strcpy(key, cpkg);
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
char key[KSU_MAX_PACKAGE_NAME] = { 0 };
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, nullptr);
strcpy(key, cpkg);
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
struct app_profile profile = { 0 };
profile.version = KSU_APP_PROFILE_VER;
struct app_profile profile = { 0 };
profile.version = KSU_APP_PROFILE_VER;
strcpy(profile.key, key);
profile.current_uid = uid;
strcpy(profile.key, key);
profile.current_uid = uid;
bool useDefaultProfile = !get_app_profile(key, &profile);
bool useDefaultProfile = get_app_profile(&profile) != 0;
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
GetEnvironment()->SetObjectField(env, obj, keyField, GetEnvironment()->NewStringUTF(env, profile.key));
GetEnvironment()->SetIntField(env, obj, currentUidField, profile.current_uid);
GetEnvironment()->SetObjectField(env, obj, keyField, GetEnvironment()->NewStringUTF(env, profile.key));
GetEnvironment()->SetIntField(env, obj, currentUidField, profile.current_uid);
if (useDefaultProfile) {
// no profile found, so just use default profile:
// don't allow root and use default profile!
LogDebug("use default profile for: %s, %d", key, uid);
if (useDefaultProfile) {
// no profile found, so just use default profile:
// don't allow root and use default profile!
LogDebug("use default profile for: %s, %d", key, uid);
// allow_su = false
// non root use default = true
GetEnvironment()->SetBooleanField(env, obj, allowSuField, false);
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, true);
// allow_su = false
// non root use default = true
GetEnvironment()->SetBooleanField(env, obj, allowSuField, false);
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, true);
return obj;
}
return obj;
}
bool allowSu = profile.allow_su;
bool allowSu = profile.allow_su;
if (allowSu) {
GetEnvironment()->SetBooleanField(env, obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
if (strlen(profile.rp_config.template_name) > 0) {
GetEnvironment()->SetObjectField(env, obj, rootTemplateField,
GetEnvironment()->NewStringUTF(env, profile.rp_config.template_name));
}
if (allowSu) {
GetEnvironment()->SetBooleanField(env, obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
if (strlen(profile.rp_config.template_name) > 0) {
GetEnvironment()->SetObjectField(env, obj, rootTemplateField,
GetEnvironment()->NewStringUTF(env, profile.rp_config.template_name));
}
GetEnvironment()->SetIntField(env, obj, uidField, profile.rp_config.profile.uid);
GetEnvironment()->SetIntField(env, obj, gidField, profile.rp_config.profile.gid);
GetEnvironment()->SetIntField(env, obj, uidField, profile.rp_config.profile.uid);
GetEnvironment()->SetIntField(env, obj, gidField, profile.rp_config.profile.gid);
jobject groupList = GetEnvironment()->GetObjectField(env, obj, groupsField);
int groupCount = profile.rp_config.profile.groups_count;
if (groupCount > KSU_MAX_GROUPS) {
LogDebug("kernel group count too large: %d???", groupCount);
groupCount = KSU_MAX_GROUPS;
}
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
jobject groupList = GetEnvironment()->GetObjectField(env, obj, groupsField);
int groupCount = profile.rp_config.profile.groups_count;
if (groupCount > KSU_MAX_GROUPS) {
LogDebug("kernel group count too large: %d???", groupCount);
groupCount = KSU_MAX_GROUPS;
}
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
jobject capList = GetEnvironment()->GetObjectField(env, obj, capabilitiesField);
for (int i = 0; i <= CAP_LAST_CAP; i++) {
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
addIntToList(env, capList, i);
}
}
jobject capList = GetEnvironment()->GetObjectField(env, obj, capabilitiesField);
for (int i = 0; i <= CAP_LAST_CAP; i++) {
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
addIntToList(env, capList, i);
}
}
GetEnvironment()->SetObjectField(env, obj, domainField,
GetEnvironment()->NewStringUTF(env, profile.rp_config.profile.selinux_domain));
GetEnvironment()->SetIntField(env, obj, namespacesField, profile.rp_config.profile.namespaces);
GetEnvironment()->SetBooleanField(env, obj, allowSuField, profile.allow_su);
} else {
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, profile.nrp_config.use_default);
GetEnvironment()->SetBooleanField(env, obj, umountModulesField, profile.nrp_config.profile.umount_modules);
}
GetEnvironment()->SetObjectField(env, obj, domainField,
GetEnvironment()->NewStringUTF(env, profile.rp_config.profile.selinux_domain));
GetEnvironment()->SetIntField(env, obj, namespacesField, profile.rp_config.profile.namespaces);
GetEnvironment()->SetBooleanField(env, obj, allowSuField, profile.allow_su);
} else {
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, profile.nrp_config.use_default);
GetEnvironment()->SetBooleanField(env, obj, umountModulesField, profile.nrp_config.profile.umount_modules);
}
return obj;
return obj;
}
NativeBridge(setAppProfile, jboolean, jobject profile) {
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
jobject key = GetEnvironment()->GetObjectField(env, profile, keyField);
if (!key) {
return false;
}
if (GetEnvironment()->GetStringLength(env, (jstring) key) > KSU_MAX_PACKAGE_NAME) {
return false;
}
jobject key = GetEnvironment()->GetObjectField(env, profile, keyField);
if (!key) {
return false;
}
if (GetEnvironment()->GetStringLength(env, (jstring) key) > KSU_MAX_PACKAGE_NAME) {
return false;
}
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, (jstring) key, nullptr);
char p_key[KSU_MAX_PACKAGE_NAME] = { 0 };
strcpy(p_key, cpkg);
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) key, cpkg);
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, (jstring) key, nullptr);
char p_key[KSU_MAX_PACKAGE_NAME] = { 0 };
strcpy(p_key, cpkg);
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) key, cpkg);
jint currentUid = GetEnvironment()->GetIntField(env, profile, currentUidField);
jint currentUid = GetEnvironment()->GetIntField(env, profile, currentUidField);
jint uid = GetEnvironment()->GetIntField(env, profile, uidField);
jint gid = GetEnvironment()->GetIntField(env, profile, gidField);
jobject groups = GetEnvironment()->GetObjectField(env, profile, groupsField);
jobject capabilities = GetEnvironment()->GetObjectField(env, profile, capabilitiesField);
jobject domain = GetEnvironment()->GetObjectField(env, profile, domainField);
jboolean allowSu = GetEnvironment()->GetBooleanField(env, profile, allowSuField);
jboolean umountModules = GetEnvironment()->GetBooleanField(env, profile, umountModulesField);
jint uid = GetEnvironment()->GetIntField(env, profile, uidField);
jint gid = GetEnvironment()->GetIntField(env, profile, gidField);
jobject groups = GetEnvironment()->GetObjectField(env, profile, groupsField);
jobject capabilities = GetEnvironment()->GetObjectField(env, profile, capabilitiesField);
jobject domain = GetEnvironment()->GetObjectField(env, profile, domainField);
jboolean allowSu = GetEnvironment()->GetBooleanField(env, profile, allowSuField);
jboolean umountModules = GetEnvironment()->GetBooleanField(env, profile, umountModulesField);
struct app_profile p = { 0 };
p.version = KSU_APP_PROFILE_VER;
struct app_profile p = { 0 };
p.version = KSU_APP_PROFILE_VER;
strcpy(p.key, p_key);
p.allow_su = allowSu;
p.current_uid = currentUid;
strcpy(p.key, p_key);
p.allow_su = allowSu;
p.current_uid = currentUid;
if (allowSu) {
p.rp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, rootUseDefaultField);
jobject templateName = GetEnvironment()->GetObjectField(env, profile, rootTemplateField);
if (templateName) {
const char* ctemplateName = GetEnvironment()->GetStringUTFChars(env, (jstring) templateName, nullptr);
strcpy(p.rp_config.template_name, ctemplateName);
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) templateName, ctemplateName);
}
if (allowSu) {
p.rp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, rootUseDefaultField);
jobject templateName = GetEnvironment()->GetObjectField(env, profile, rootTemplateField);
if (templateName) {
const char* ctemplateName = GetEnvironment()->GetStringUTFChars(env, (jstring) templateName, nullptr);
strcpy(p.rp_config.template_name, ctemplateName);
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) templateName, ctemplateName);
}
p.rp_config.profile.uid = uid;
p.rp_config.profile.gid = gid;
p.rp_config.profile.uid = uid;
p.rp_config.profile.gid = gid;
int groups_count = getListSize(env, groups);
if (groups_count > KSU_MAX_GROUPS) {
LogDebug("groups count too large: %d", groups_count);
return false;
}
p.rp_config.profile.groups_count = groups_count;
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
int groups_count = getListSize(env, groups);
if (groups_count > KSU_MAX_GROUPS) {
LogDebug("groups count too large: %d", groups_count);
return false;
}
p.rp_config.profile.groups_count = groups_count;
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
const char* cdomain = GetEnvironment()->GetStringUTFChars(env, (jstring) domain, nullptr);
strcpy(p.rp_config.profile.selinux_domain, cdomain);
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) domain, cdomain);
const char* cdomain = GetEnvironment()->GetStringUTFChars(env, (jstring) domain, nullptr);
strcpy(p.rp_config.profile.selinux_domain, cdomain);
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) domain, cdomain);
p.rp_config.profile.namespaces = GetEnvironment()->GetIntField(env, profile, namespacesField);
} else {
p.nrp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, nonRootUseDefaultField);
p.nrp_config.profile.umount_modules = umountModules;
}
p.rp_config.profile.namespaces = GetEnvironment()->GetIntField(env, profile, namespacesField);
} else {
p.nrp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, nonRootUseDefaultField);
p.nrp_config.profile.umount_modules = umountModules;
}
return set_app_profile(&p);
return set_app_profile(&p);
}
NativeBridge(uidShouldUmount, jboolean, jint uid) {
return uid_should_umount(uid);
return uid_should_umount(uid);
}
NativeBridgeNP(isSuEnabled, jboolean) {
return is_su_enabled();
return is_su_enabled();
}
NativeBridge(setSuEnabled, jboolean, jboolean enabled) {
return set_su_enabled(enabled);
return set_su_enabled(enabled);
}
// Check if KPM is enabled
NativeBridgeNP(isKPMEnabled, jboolean) {
return is_KPM_enable();
return is_KPM_enable();
}
// Get HOOK type
NativeBridgeNP(getHookType, jstring) {
char hook_type[16];
get_hook_type(hook_type, sizeof(hook_type));
return GetEnvironment()->NewStringUTF(env, hook_type);
char hook_type[32] = { 0 };
get_hook_type((char *) &hook_type);
return GetEnvironment()->NewStringUTF(env, hook_type);
}
// dynamic manager
NativeBridge(setDynamicManager, jboolean, jint size, jstring hash) {
if (!hash) {
LogDebug("setDynamicManager: hash is null");
return false;
}
if (!hash) {
LogDebug("setDynamicManager: hash is null");
return false;
}
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
bool result = set_dynamic_manager((unsigned int)size, chash);
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
bool result = set_dynamic_manager((unsigned int)size, chash);
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
LogDebug("setDynamicManager: size=0x%x, result=%d", size, result);
return result;
LogDebug("setDynamicManager: size=0x%x, result=%d", size, result);
return result;
}
NativeBridgeNP(getDynamicManager, jobject) {
struct dynamic_manager_user_config config;
bool result = get_dynamic_manager(&config);
struct dynamic_manager_user_config config;
bool result = get_dynamic_manager(&config);
if (!result) {
LogDebug("getDynamicManager: failed to get dynamic manager config");
return NULL;
}
if (!result) {
LogDebug("getDynamicManager: failed to get dynamic manager config");
return NULL;
}
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig");
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig");
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig");
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig");
SET_INT_FIELD(obj, cls, size, (jint)config.size);
SET_STRING_FIELD(obj, cls, hash, config.hash);
SET_INT_FIELD(obj, cls, size, (jint)config.size);
SET_STRING_FIELD(obj, cls, hash, config.hash);
LogDebug("getDynamicManager: size=0x%x, hash=%.16s...", config.size, config.hash);
return obj;
LogDebug("getDynamicManager: size=0x%x, hash=%.16s...", config.size, config.hash);
return obj;
}
NativeBridgeNP(clearDynamicManager, jboolean) {
bool result = clear_dynamic_manager();
LogDebug("clearDynamicManager: result=%d", result);
return result;
bool result = clear_dynamic_manager();
LogDebug("clearDynamicManager: result=%d", result);
return result;
}
// Get a list of active managers
NativeBridgeNP(getManagersList, jobject) {
struct manager_list_info managerListInfo;
bool result = get_managers_list(&managerListInfo);
struct manager_list_info managerListInfo;
bool result = get_managers_list(&managerListInfo);
if (!result) {
LogDebug("getManagersList: failed to get active managers list");
return NULL;
}
if (!result) {
LogDebug("getManagersList: failed to get active managers list");
return NULL;
}
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$ManagersList");
jclass managerListCls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$ManagersList");
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$ManagersList");
jclass managerListCls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$ManagersList");
SET_INT_FIELD(obj, managerListCls, count, (jint)managerListInfo.count);
SET_INT_FIELD(obj, managerListCls, count, (jint)managerListInfo.count);
jobject managersList = CREATE_ARRAYLIST();
jobject managersList = CREATE_ARRAYLIST();
for (int i = 0; i < managerListInfo.count; i++) {
jobject managerInfo = CREATE_JAVA_OBJECT_WITH_PARAMS(
"com/sukisu/ultra/Natives$ManagerInfo",
"(II)V",
(jint)managerListInfo.managers[i].uid,
(jint)managerListInfo.managers[i].signature_index
);
ADD_TO_LIST(managersList, managerInfo);
}
for (int i = 0; i < managerListInfo.count; i++) {
jobject managerInfo = CREATE_JAVA_OBJECT_WITH_PARAMS(
"com/sukisu/ultra/Natives$ManagerInfo",
"(II)V",
(jint)managerListInfo.managers[i].uid,
(jint)managerListInfo.managers[i].signature_index
);
ADD_TO_LIST(managersList, managerInfo);
}
SET_OBJECT_FIELD(obj, managerListCls, managers, managersList);
SET_OBJECT_FIELD(obj, managerListCls, managers, managersList);
LogDebug("getManagersList: count=%d", managerListInfo.count);
return obj;
LogDebug("getManagersList: count=%d", managerListInfo.count);
return obj;
}
NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
if (!modulePath) {
LogDebug("verifyModuleSignature: modulePath is null");
return false;
}
if (!modulePath) {
LogDebug("verifyModuleSignature: modulePath is null");
return false;
}
const char* cModulePath = GetEnvironment()->GetStringUTFChars(env, modulePath, nullptr);
bool result = verify_module_signature(cModulePath);
GetEnvironment()->ReleaseStringUTFChars(env, modulePath, cModulePath);
const char* cModulePath = GetEnvironment()->GetStringUTFChars(env, modulePath, nullptr);
bool result = verify_module_signature(cModulePath);
GetEnvironment()->ReleaseStringUTFChars(env, modulePath, cModulePath);
LogDebug("verifyModuleSignature: path=%s, result=%d", cModulePath, result);
return result;
LogDebug("verifyModuleSignature: path=%s, result=%d", cModulePath, result);
return result;
#else
LogDebug("verifyModuleSignature: not supported on non-ARM architecture");
return false;
LogDebug("verifyModuleSignature: not supported on non-ARM architecture");
return false;
#endif
}
NativeBridgeNP(isUidScannerEnabled, jboolean) {
return is_uid_scanner_enabled();
return is_uid_scanner_enabled();
}
NativeBridge(setUidScannerEnabled, jboolean, jboolean enabled) {
return set_uid_scanner_enabled(enabled);
return set_uid_scanner_enabled(enabled);
}
NativeBridgeNP(clearUidScannerEnvironment, jboolean) {
return clear_uid_scanner_environment();
return clear_uid_scanner_environment();
}

View File

@@ -7,6 +7,11 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <android/log.h>
#include <dirent.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include "prelude.h"
#include "ksu.h"
@@ -21,238 +26,276 @@ extern const char* zako_file_verrcidx2str(uint8_t index);
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
#define KERNEL_SU_OPTION 0xDEADBEEF
static int fd = -1;
#define CMD_GRANT_ROOT 0
static inline int scan_driver_fd() {
const char *kName = "[ksu_driver]";
DIR *fd_dir = opendir("/proc/self/fd");
if (!fd_dir) {
return -1;
}
#define CMD_BECOME_MANAGER 1
#define CMD_GET_VERSION 2
#define CMD_ALLOW_SU 3
#define CMD_DENY_SU 4
#define CMD_GET_SU_LIST 5
#define CMD_GET_DENY_LIST 6
#define CMD_CHECK_SAFEMODE 9
int found = -1;
struct dirent *de;
char path[64];
char target[PATH_MAX];
#define CMD_GET_APP_PROFILE 10
#define CMD_SET_APP_PROFILE 11
while ((de = readdir(fd_dir)) != NULL) {
if (de->d_name[0] == '.') {
continue;
}
#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
char *endptr = nullptr;
long fd_long = strtol(de->d_name, &endptr, 10);
if (!de->d_name[0] || *endptr != '\0' || fd_long < 0 || fd_long > INT_MAX) {
continue;
}
#define CMD_GET_VERSION_FULL 0xC0FFEE1A
snprintf(path, sizeof(path), "/proc/self/fd/%s", de->d_name);
ssize_t n = readlink(path, target, sizeof(target) - 1);
if (n < 0) {
continue;
}
target[n] = '\0';
#define CMD_ENABLE_KPM 100
#define CMD_HOOK_TYPE 101
#define CMD_DYNAMIC_MANAGER 103
#define CMD_GET_MANAGERS 104
#define CMD_ENABLE_UID_SCANNER 105
const char *base = strrchr(target, '/');
base = base ? base + 1 : target;
#define DYNAMIC_MANAGER_OP_SET 0
#define DYNAMIC_MANAGER_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2
if (strstr(base, kName)) {
found = (int)fd_long;
break;
}
}
static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0;
int32_t rtn = prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
return result == KERNEL_SU_OPTION && rtn == -1;
closedir(fd_dir);
return found;
}
bool become_manager(const char* pkg) {
char param[128];
uid_t uid = getuid();
uint32_t userId = uid / 100000;
if (userId == 0) {
sprintf(param, "/data/data/%s", pkg);
} else {
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
}
return ksuctl(CMD_BECOME_MANAGER, param, NULL);
static int ksuctl(unsigned long op, void* arg) {
if (fd < 0) {
fd = scan_driver_fd();
}
return ioctl(fd, op, arg);
}
// cache the result to avoid unnecessary syscall
static bool is_lkm;
int get_version() {
int32_t version = -1;
int32_t flags = 0;
ksuctl(CMD_GET_VERSION, &version, &flags);
if (!is_lkm && (flags & 0x1)) {
is_lkm = true;
}
return version;
static struct ksu_get_info_cmd g_version = {0};
struct ksu_get_info_cmd get_info() {
if (!g_version.version) {
ksuctl(KSU_IOCTL_GET_INFO, &g_version);
}
return g_version;
}
void get_full_version(char* buff) {
ksuctl(CMD_GET_VERSION_FULL, buff, NULL);
uint32_t get_version() {
auto info = get_info();
return info.version;
}
bool get_allow_list(int *uids, int *size) {
return ksuctl(CMD_GET_SU_LIST, uids, size);
bool get_allow_list(struct ksu_get_allow_list_cmd *cmd) {
return ksuctl(KSU_IOCTL_GET_ALLOW_LIST, cmd) == 0;
}
bool is_safe_mode() {
return ksuctl(CMD_CHECK_SAFEMODE, NULL, NULL);
struct ksu_check_safemode_cmd cmd = {};
ksuctl(KSU_IOCTL_CHECK_SAFEMODE, &cmd);
return cmd.in_safe_mode;
}
bool is_lkm_mode() {
// you should call get_version first!
return is_lkm;
auto info = get_info();
return (info.flags & 0x1) != 0;
}
bool is_manager() {
auto info = get_info();
return (info.flags & 0x2) != 0;
}
bool uid_should_umount(int uid) {
int should;
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should;
struct ksu_uid_should_umount_cmd cmd = {};
cmd.uid = uid;
ksuctl(KSU_IOCTL_UID_SHOULD_UMOUNT, &cmd);
return cmd.should_umount;
}
bool set_app_profile(const struct app_profile* profile) {
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL);
bool set_app_profile(const struct app_profile *profile) {
struct ksu_set_app_profile_cmd cmd = {};
cmd.profile = *profile;
return ksuctl(KSU_IOCTL_SET_APP_PROFILE, &cmd) == 0;
}
bool get_app_profile(char* key, struct app_profile* profile) {
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL);
int get_app_profile(struct app_profile *profile) {
struct ksu_get_app_profile_cmd cmd = {.profile = *profile};
int ret = ksuctl(KSU_IOCTL_GET_APP_PROFILE, &cmd);
*profile = cmd.profile;
return ret;
}
bool set_su_enabled(bool enabled) {
return ksuctl(CMD_ENABLE_SU, (void*) enabled, NULL);
struct ksu_enable_su_cmd cmd = {.enable = enabled};
return ksuctl(KSU_IOCTL_ENABLE_SU, &cmd) == 0;
}
bool is_su_enabled() {
int enabled = true;
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL);
return enabled;
struct ksu_is_su_enabled_cmd cmd = {};
return ksuctl(KSU_IOCTL_IS_SU_ENABLED, &cmd) == 0 && cmd.enabled;
}
bool is_KPM_enable() {
int enabled = false;
ksuctl(CMD_ENABLE_KPM, &enabled, NULL);
return enabled;
void get_full_version(char* buff) {
struct ksu_get_full_version_cmd cmd = {0};
if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) {
strncpy(buff, cmd.version_full, KSU_FULL_VERSION_STRING - 1);
buff[KSU_FULL_VERSION_STRING - 1] = '\0';
} else {
buff[0] = '\0';
}
}
bool get_hook_type(char* hook_type, size_t size) {
if (hook_type == NULL || size == 0) {
return false;
}
static char cached_hook_type[16] = {0};
if (cached_hook_type[0] == '\0') {
if (!ksuctl(CMD_HOOK_TYPE, cached_hook_type, NULL)) {
strcpy(cached_hook_type, "Unknown");
}
}
strncpy(hook_type, cached_hook_type, size);
hook_type[size - 1] = '\0';
return true;
bool is_KPM_enable(void)
{
struct ksu_enable_kpm_cmd cmd = {};
return ksuctl(KSU_IOCTL_ENABLE_KPM, &cmd) == 0 && cmd.enabled;
}
bool set_dynamic_manager(unsigned int size, const char* hash) {
if (hash == NULL) {
return false;
}
struct dynamic_manager_user_config config;
config.operation = DYNAMIC_MANAGER_OP_SET;
config.size = size;
strncpy(config.hash, hash, sizeof(config.hash) - 1);
config.hash[sizeof(config.hash) - 1] = '\0';
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
void get_hook_type(char *buff)
{
struct ksu_hook_type_cmd cmd = {0};
if (ksuctl(KSU_IOCTL_HOOK_TYPE, &cmd) == 0) {
strncpy(buff, cmd.hook_type, 32 - 1);
buff[32 - 1] = '\0';
} else {
strcpy(buff, "Unknown");
}
}
bool get_dynamic_manager(struct dynamic_manager_user_config* config) {
if (config == NULL) {
return false;
}
bool set_dynamic_manager(unsigned int size, const char *hash)
{
struct ksu_dynamic_manager_cmd cmd = {0};
cmd.config.operation = DYNAMIC_MANAGER_OP_SET;
cmd.config.size = size;
strlcpy(cmd.config.hash, hash, sizeof(cmd.config.hash));
config->operation = DYNAMIC_MANAGER_OP_GET;
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
}
bool clear_dynamic_manager() {
struct dynamic_manager_user_config config;
config.operation = DYNAMIC_MANAGER_OP_CLEAR;
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
bool get_dynamic_manager(struct dynamic_manager_user_config *cfg)
{
if (!cfg)
return false;
struct ksu_dynamic_manager_cmd cmd = {0};
cmd.config.operation = DYNAMIC_MANAGER_OP_GET;
if (ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) != 0)
return false;
*cfg = cmd.config;
return true;
}
bool get_managers_list(struct manager_list_info* info) {
if (info == NULL) {
return false;
}
return ksuctl(CMD_GET_MANAGERS, info, NULL);
bool clear_dynamic_manager(void)
{
struct ksu_dynamic_manager_cmd cmd = {0};
cmd.config.operation = DYNAMIC_MANAGER_OP_CLEAR;
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
}
bool get_managers_list(struct manager_list_info *info)
{
if (!info)
return false;
struct ksu_get_managers_cmd cmd = {0};
if (ksuctl(KSU_IOCTL_GET_MANAGERS, &cmd) != 0)
return false;
*info = cmd.manager_info;
return true;
}
bool verify_module_signature(const char* input) {
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
if (input == NULL) {
LogDebug("verify_module_signature: input path is null");
return false;
}
if (input == NULL) {
LogDebug("verify_module_signature: input path is null");
return false;
}
int fd = zako_sys_file_open(input);
if (fd < 0) {
LogDebug("verify_module_signature: failed to open file: %s", input);
return false;
}
int file_fd = zako_sys_file_open(input);
if (file_fd < 0) {
LogDebug("verify_module_signature: failed to open file: %s", input);
return false;
}
uint32_t results = zako_file_verify_esig(fd, 0);
uint32_t results = zako_file_verify_esig(file_fd, 0);
if (results != 0) {
/* If important error occured, verification process should
be considered as failed due to unexpected modification
potentially happened. */
if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) {
LogDebug("verify_module_signature: Verification failed! (important error)");
} else {
/* This is for manager that doesn't want to do certificate checks */
LogDebug("verify_module_signature: Verification partially passed");
}
} else {
LogDebug("verify_module_signature: Verification passed!");
goto exit;
}
if (results != 0) {
/* If important error occured, verification process should
be considered as failed due to unexpected modification
potentially happened. */
if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) {
LogDebug("verify_module_signature: Verification failed! (important error)");
} else {
/* This is for manager that doesn't want to do certificate checks */
LogDebug("verify_module_signature: Verification partially passed");
}
} else {
LogDebug("verify_module_signature: Verification passed!");
goto exit;
}
/* Go through all bit fields */
for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) {
if ((results & (1 << i)) == 0) {
continue;
}
/* Go through all bit fields */
for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) {
if ((results & (1 << i)) == 0) {
continue;
}
/* Convert error bit field index into human readable string */
const char* message = zako_file_verrcidx2str((uint8_t)i);
// Error message: message
if (message != NULL) {
LogDebug("verify_module_signature: Error bit %zu: %s", i, message);
} else {
LogDebug("verify_module_signature: Error bit %zu: Unknown error", i);
}
}
/* Convert error bit field index into human readable string */
const char* message = zako_file_verrcidx2str((uint8_t)i);
// Error message: message
if (message != NULL) {
LogDebug("verify_module_signature: Error bit %zu: %s", i, message);
} else {
LogDebug("verify_module_signature: Error bit %zu: Unknown error", i);
}
}
exit:
close(fd);
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
input, results, (results == 0) ? "true" : "false");
return results == 0;
exit:
close(file_fd);
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
input, results, (results == 0) ? "true" : "false");
return results == 0;
#else
LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null");
return false;
LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null");
return false;
#endif
}
bool is_uid_scanner_enabled() {
bool status = false;
ksuctl(CMD_ENABLE_UID_SCANNER, (void*)0, &status);
return status;
bool is_uid_scanner_enabled(void)
{
bool status = false;
struct ksu_enable_uid_scanner_cmd cmd = {
.operation = UID_SCANNER_OP_GET_STATUS,
.status_ptr = (__u64)(uintptr_t)&status
};
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd) == 0 != 0 && status;
}
bool set_uid_scanner_enabled(bool enabled) {
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)1, (void*)enabled);
bool set_uid_scanner_enabled(bool enabled)
{
struct ksu_enable_uid_scanner_cmd cmd = {
.operation = UID_SCANNER_OP_TOGGLE,
.enabled = enabled
};
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
}
bool clear_uid_scanner_environment() {
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)2, NULL);
bool clear_uid_scanner_environment(void)
{
struct ksu_enable_uid_scanner_cmd cmd = {
.operation = UID_SCANNER_OP_CLEAR_ENV
};
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
}

View File

@@ -8,14 +8,11 @@
#include "prelude.h"
#include <linux/capability.h>
#include <sys/types.h>
#include <sys/ioctl.h>
bool become_manager(const char *);
#define KSU_FULL_VERSION_STRING 255
void get_full_version(char* buff);
int get_version();
bool get_allow_list(int *uids, int *size);
uint32_t get_version();
bool uid_should_umount(int uid);
@@ -23,6 +20,10 @@ bool is_safe_mode();
bool is_lkm_mode();
bool is_manager();
void get_full_version(char* buff);
#define KSU_APP_PROFILE_VER 2
#define KSU_MAX_PACKAGE_NAME 256
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
@@ -33,72 +34,76 @@ bool is_lkm_mode();
#define DYNAMIC_MANAGER_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2
#define UID_SCANNER_OP_GET_STATUS 0
#define UID_SCANNER_OP_TOGGLE 1
#define UID_SCANNER_OP_CLEAR_ENV 2
struct dynamic_manager_user_config {
unsigned int operation;
unsigned int size;
char hash[65];
unsigned int operation;
unsigned int size;
char hash[65];
};
struct root_profile {
int32_t uid;
int32_t gid;
int32_t uid;
int32_t gid;
int32_t groups_count;
int32_t groups[KSU_MAX_GROUPS];
int32_t groups_count;
int32_t groups[KSU_MAX_GROUPS];
// kernel_cap_t is u32[2] for capabilities v3
struct {
uint64_t effective;
uint64_t permitted;
uint64_t inheritable;
} capabilities;
// kernel_cap_t is u32[2] for capabilities v3
struct {
uint64_t effective;
uint64_t permitted;
uint64_t inheritable;
} capabilities;
char selinux_domain[KSU_SELINUX_DOMAIN];
char selinux_domain[KSU_SELINUX_DOMAIN];
int32_t namespaces;
int32_t namespaces;
};
struct non_root_profile {
bool umount_modules;
bool umount_modules;
};
struct app_profile {
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
uint32_t version;
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
uint32_t version;
// this is usually the package of the app, but can be other value for special apps
char key[KSU_MAX_PACKAGE_NAME];
int32_t current_uid;
bool allow_su;
// this is usually the package of the app, but can be other value for special apps
char key[KSU_MAX_PACKAGE_NAME];
int32_t current_uid;
bool allow_su;
union {
struct {
bool use_default;
char template_name[KSU_MAX_PACKAGE_NAME];
union {
struct {
bool use_default;
char template_name[KSU_MAX_PACKAGE_NAME];
struct root_profile profile;
} rp_config;
struct root_profile profile;
} rp_config;
struct {
bool use_default;
struct {
bool use_default;
struct non_root_profile profile;
} nrp_config;
};
struct non_root_profile profile;
} nrp_config;
};
};
struct manager_list_info {
int count;
struct {
uid_t uid;
int signature_index;
} managers[2];
int count;
struct {
uid_t uid;
int signature_index;
} managers[2];
};
bool set_app_profile(const struct app_profile* profile);
bool get_app_profile(char* key, struct app_profile* profile);
int get_app_profile(struct app_profile* profile);
bool set_su_enabled(bool enabled);
@@ -106,8 +111,7 @@ bool is_su_enabled();
bool is_KPM_enable();
bool get_hook_type(char* hook_type, size_t size);
void get_hook_type(char* hook_type);
bool set_dynamic_manager(unsigned int size, const char* hash);
@@ -125,4 +129,119 @@ bool set_uid_scanner_enabled(bool enabled);
bool clear_uid_scanner_environment();
struct ksu_become_daemon_cmd {
__u8 token[65]; // Input: daemon token (null-terminated)
};
struct ksu_get_info_cmd {
__u32 version; // Output: KERNEL_SU_VERSION
__u32 flags; // Output: flags (bit 0: MODULE mode)
};
struct ksu_report_event_cmd {
__u32 event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc.
};
struct ksu_set_sepolicy_cmd {
__u64 cmd; // Input: sepolicy command
__aligned_u64 arg; // Input: sepolicy argument pointer
};
struct ksu_check_safemode_cmd {
__u8 in_safe_mode; // Output: true if in safe mode, false otherwise
};
struct ksu_get_allow_list_cmd {
__u32 uids[128]; // Output: array of allowed/denied UIDs
__u32 count; // Output: number of UIDs in array
__u8 allow; // Input: true for allow list, false for deny list
};
struct ksu_uid_granted_root_cmd {
__u32 uid; // Input: target UID to check
__u8 granted; // Output: true if granted, false otherwise
};
struct ksu_uid_should_umount_cmd {
__u32 uid; // Input: target UID to check
__u8 should_umount; // Output: true if should umount, false otherwise
};
struct ksu_get_manager_uid_cmd {
__u32 uid; // Output: manager UID
};
struct ksu_set_manager_uid_cmd {
__u32 uid; // Input: new manager UID
};
struct ksu_get_app_profile_cmd {
struct app_profile profile; // Input/Output: app profile structure
};
struct ksu_set_app_profile_cmd {
struct app_profile profile; // Input: app profile structure
};
struct ksu_is_su_enabled_cmd {
__u8 enabled; // Output: true if su compat enabled
};
struct ksu_enable_su_cmd {
__u8 enable; // Input: true to enable, false to disable
};
// Other command structures
struct ksu_get_full_version_cmd {
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
};
struct ksu_hook_type_cmd {
char hook_type[32]; // Output: hook type string
};
struct ksu_enable_kpm_cmd {
__u8 enabled; // Output: true if KPM is enabled
};
struct ksu_dynamic_manager_cmd {
struct dynamic_manager_user_config config; // Input/Output: dynamic manager config
};
struct ksu_get_managers_cmd {
struct manager_list_info manager_info; // Output: manager list information
};
struct ksu_enable_uid_scanner_cmd {
__u32 operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV)
__u32 enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE)
__u64 status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS)
};
// IOCTL command definitions
#define KSU_IOCTL_GRANT_ROOT _IO('K', 1)
#define KSU_IOCTL_GET_INFO _IOR('K', 2, struct ksu_get_info_cmd)
#define KSU_IOCTL_REPORT_EVENT _IOW('K', 3, struct ksu_report_event_cmd)
#define KSU_IOCTL_SET_SEPOLICY _IOWR('K', 4, struct ksu_set_sepolicy_cmd)
#define KSU_IOCTL_CHECK_SAFEMODE _IOR('K', 5, struct ksu_check_safemode_cmd)
#define KSU_IOCTL_GET_ALLOW_LIST _IOWR('K', 6, struct ksu_get_allow_list_cmd)
#define KSU_IOCTL_GET_DENY_LIST _IOWR('K', 7, struct ksu_get_allow_list_cmd)
#define KSU_IOCTL_UID_GRANTED_ROOT _IOWR('K', 8, struct ksu_uid_granted_root_cmd)
#define KSU_IOCTL_UID_SHOULD_UMOUNT _IOWR('K', 9, struct ksu_uid_should_umount_cmd)
#define KSU_IOCTL_GET_MANAGER_UID _IOR('K', 10, struct ksu_get_manager_uid_cmd)
#define KSU_IOCTL_GET_APP_PROFILE _IOWR('K', 11, struct ksu_get_app_profile_cmd)
#define KSU_IOCTL_SET_APP_PROFILE _IOW('K', 12, struct ksu_set_app_profile_cmd)
#define KSU_IOCTL_IS_SU_ENABLED _IOR('K', 13, struct ksu_is_su_enabled_cmd)
#define KSU_IOCTL_ENABLE_SU _IOW('K', 14, struct ksu_enable_su_cmd)
// Other IOCTL command definitions
#define KSU_IOCTL_GET_FULL_VERSION _IOR('K', 100, struct ksu_get_full_version_cmd)
#define KSU_IOCTL_HOOK_TYPE _IOR('K', 101, struct ksu_hook_type_cmd)
#define KSU_IOCTL_ENABLE_KPM _IOR('K', 102, struct ksu_enable_kpm_cmd)
#define KSU_IOCTL_DYNAMIC_MANAGER _IOWR('K', 103, struct ksu_dynamic_manager_cmd)
#define KSU_IOCTL_GET_MANAGERS _IOWR('K', 104, struct ksu_get_managers_cmd)
#define KSU_IOCTL_ENABLE_UID_SCANNER _IOWR('K', 105, struct ksu_enable_uid_scanner_cmd)
bool get_allow_list(struct ksu_get_allow_list_cmd*);
#endif //KERNELSU_KSU_H

View File

@@ -14,51 +14,51 @@
// Macros to simplify field setup
#define SET_BOOLEAN_FIELD(obj, cls, fieldName, value) do { \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Z"); \
GetEnvironment()->SetBooleanField(env, obj, field, value); \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Z"); \
GetEnvironment()->SetBooleanField(env, obj, field, value); \
} while(0)
#define SET_INT_FIELD(obj, cls, fieldName, value) do { \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "I"); \
GetEnvironment()->SetIntField(env, obj, field, value); \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "I"); \
GetEnvironment()->SetIntField(env, obj, field, value); \
} while(0)
#define SET_STRING_FIELD(obj, cls, fieldName, value) do { \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/lang/String;"); \
GetEnvironment()->SetObjectField(env, obj, field, GetEnvironment()->NewStringUTF(env, value)); \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/lang/String;"); \
GetEnvironment()->SetObjectField(env, obj, field, GetEnvironment()->NewStringUTF(env, value)); \
} while(0)
#define SET_OBJECT_FIELD(obj, cls, fieldName, value) do { \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/util/List;"); \
GetEnvironment()->SetObjectField(env, obj, field, value); \
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/util/List;"); \
GetEnvironment()->SetObjectField(env, obj, field, value); \
} while(0)
// Macros for creating Java objects
#define CREATE_JAVA_OBJECT(className) ({ \
jclass cls = GetEnvironment()->FindClass(env, className); \
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V"); \
GetEnvironment()->NewObject(env, cls, constructor); \
jclass cls = GetEnvironment()->FindClass(env, className); \
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V"); \
GetEnvironment()->NewObject(env, cls, constructor); \
})
// Macros for creating ArrayList
#define CREATE_ARRAYLIST() ({ \
jclass arrayListCls = GetEnvironment()->FindClass(env, "java/util/ArrayList"); \
jmethodID constructor = GetEnvironment()->GetMethodID(env, arrayListCls, "<init>", "()V"); \
GetEnvironment()->NewObject(env, arrayListCls, constructor); \
jclass arrayListCls = GetEnvironment()->FindClass(env, "java/util/ArrayList"); \
jmethodID constructor = GetEnvironment()->GetMethodID(env, arrayListCls, "<init>", "()V"); \
GetEnvironment()->NewObject(env, arrayListCls, constructor); \
})
// Macros for adding elements to an ArrayList
#define ADD_TO_LIST(list, item) do { \
jclass cls = GetEnvironment()->GetObjectClass(env, list); \
jmethodID addMethod = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z"); \
GetEnvironment()->CallBooleanMethod(env, list, addMethod, item); \
jclass cls = GetEnvironment()->GetObjectClass(env, list); \
jmethodID addMethod = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z"); \
GetEnvironment()->CallBooleanMethod(env, list, addMethod, item); \
} while(0)
// Macros for creating Java objects with parameter constructors
#define CREATE_JAVA_OBJECT_WITH_PARAMS(className, signature, ...) ({ \
jclass cls = GetEnvironment()->FindClass(env, className); \
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", signature); \
GetEnvironment()->NewObject(env, cls, constructor, __VA_ARGS__); \
jclass cls = GetEnvironment()->FindClass(env, className); \
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", signature); \
GetEnvironment()->NewObject(env, cls, constructor, __VA_ARGS__); \
})
#ifdef NDEBUG

View File

@@ -16,17 +16,15 @@ object Natives {
// 10946: add capabilities
// 10977: change groups_count and groups to avoid overflow write
// 11071: Fix the issue of failing to set a custom SELinux type.
const val MINIMAL_SUPPORTED_KERNEL = 11071
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.8"
// 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
// 12143: breaking: new supercall impl
const val MINIMAL_SUPPORTED_KERNEL = 12143
// 12040: Support disable sucompat mode
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.8"
const val MINIMAL_SUPPORTED_KPM = 12800
const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215
@@ -66,8 +64,6 @@ object Natives {
System.loadLibrary("kernelsu")
}
// become root manager, return true if success.
external fun becomeManager(pkg: String?): Boolean
val version: Int
external get
@@ -81,6 +77,9 @@ object Natives {
val isLkmMode: Boolean
external get
val isManager: Boolean
external get
external fun uidShouldUmount(uid: Int): Boolean
/**
@@ -99,6 +98,7 @@ object Natives {
*/
external fun isSuEnabled(): Boolean
external fun setSuEnabled(enabled: Boolean): Boolean
external fun grantRoot(): Boolean
external fun isKPMEnabled(): Boolean
external fun getHookType(): String

View File

@@ -74,7 +74,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
val isManager = Natives.becomeManager(packageName)
val isManager = Natives.isManager
if (isManager) {
install()
}

View File

@@ -15,7 +15,6 @@ import com.ramcosta.composedestinations.spec.RouteOrDirection
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.activity.util.*
import com.sukisu.ultra.ui.activity.util.AppData.getKpmVersionUse
@@ -29,7 +28,7 @@ import com.sukisu.ultra.ui.util.*
@Composable
fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator()
val isFullFeatured = AppData.isFullFeatured(ksuApp.packageName)
val isFullFeatured = AppData.isFullFeatured()
val kpmVersion = getKpmVersionUse()
val cardColor = MaterialTheme.colorScheme.surfaceContainer
val activity = LocalContext.current as MainActivity

View File

@@ -175,8 +175,8 @@ object AppData {
/**
* 检查是否是完整功能模式
*/
fun isFullFeatured(packageName: String): Boolean {
val isManager = Natives.becomeManager(packageName)
fun isFullFeatured(): Boolean {
val isManager = Natives.isManager
return isManager && !Natives.requireNewKernel() && rootAvailable()
}
}

View File

@@ -8,7 +8,7 @@ import com.sukisu.ultra.ksuApp
fun KsuIsValid(
content: @Composable () -> Unit
) {
val isManager = Natives.becomeManager(ksuApp.packageName)
val isManager = Natives.isManager
val ksuVersion = if (isManager) Natives.version else null
if (ksuVersion != null) {

View File

@@ -2,6 +2,7 @@ package com.sukisu.ultra.ui.screen
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -48,8 +49,9 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.util.*
import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.LocalDateTime
@@ -179,127 +181,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
)
// UID 扫描开关
if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER) {
var uidAutoScanEnabled by rememberSaveable {
mutableStateOf(prefs.getBoolean("uid_auto_scan", false))
}
var uidMultiUserScanEnabled by rememberSaveable {
mutableStateOf(prefs.getBoolean("uid_multi_user_scan", false))
}
LaunchedEffect(Unit) {
uidAutoScanEnabled = Natives.isUidScannerEnabled()
uidMultiUserScanEnabled = getUidMultiUserScan()
prefs.edit {
putBoolean("uid_auto_scan", uidAutoScanEnabled)
putBoolean("uid_multi_user_scan", uidMultiUserScanEnabled)
}
}
// 用户态扫描应用列表开关
SwitchItem(
icon = Icons.Filled.Scanner,
title = stringResource(R.string.uid_auto_scan_title),
summary = stringResource(R.string.uid_auto_scan_summary),
checked = uidAutoScanEnabled,
onCheckedChange = { enabled ->
scope.launch {
try {
if (setUidAutoScan(enabled)) {
uidAutoScanEnabled = enabled
prefs.edit { putBoolean("uid_auto_scan", enabled) }
if (!enabled) {
uidMultiUserScanEnabled = false
prefs.edit { putBoolean("uid_multi_user_scan", false) }
}
} else {
snackBarHost.showSnackbar(context.getString(R.string.uid_scanner_setting_failed))
}
} catch (e: Exception) {
snackBarHost.showSnackbar(
context.getString(
R.string.uid_scanner_setting_error,
e.message ?: ""
)
)
}
}
}
)
// 多用户应用扫描开关 - 仅在启用用户态扫描时显示
AnimatedVisibility(
visible = uidAutoScanEnabled,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
SwitchItem(
icon = Icons.Filled.Groups,
title = stringResource(R.string.uid_multi_user_scan_title),
summary = stringResource(R.string.uid_multi_user_scan_summary),
checked = uidMultiUserScanEnabled,
onCheckedChange = { enabled ->
scope.launch {
try {
if (setUidMultiUserScan(enabled)) {
uidMultiUserScanEnabled = enabled
prefs.edit { putBoolean("uid_multi_user_scan", enabled) }
} else {
snackBarHost.showSnackbar(context.getString(R.string.uid_scanner_setting_failed))
}
} catch (e: Exception) {
snackBarHost.showSnackbar(
context.getString(
R.string.uid_scanner_setting_error,
e.message ?: ""
)
)
}
}
}
)
}
// 清理运行环境
AnimatedVisibility(
visible = uidAutoScanEnabled,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
val confirmDialog = rememberConfirmDialog()
val scope = rememberCoroutineScope()
SettingItem(
icon = Icons.Filled.CleaningServices,
title = stringResource(R.string.clean_runtime_environment),
summary = stringResource(R.string.clean_runtime_environment_summary),
onClick = {
scope.launch {
val result = confirmDialog.awaitConfirm(
title = context.getString(R.string.clean_runtime_environment),
content = context.getString(R.string.clean_runtime_environment_confirm)
)
if (result == ConfirmResult.Confirmed) {
val cleanResult = cleanRuntimeEnvironment()
if (cleanResult) {
uidAutoScanEnabled = false
prefs.edit { putBoolean("uid_auto_scan", false) }
uidMultiUserScanEnabled = false
prefs.edit { putBoolean("uid_multi_user_scan", false) }
Natives.setUidScannerEnabled(false)
snackBarHost.showSnackbar(context.getString(R.string.clean_runtime_environment_success))
} else {
snackBarHost.showSnackbar(context.getString(R.string.clean_runtime_environment_failed))
}
}
}
}
)
}
UidScannerSection(prefs, snackBarHost, scope, context)
}
}
)
@@ -453,7 +335,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
)
}
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
val lkmMode = Natives.isLkmMode
if (lkmMode) {
UninstallItem(navigator) {
loadingDialog.withLoading(it)
@@ -481,24 +363,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
}
fun cleanRuntimeEnvironment(): Boolean {
val shell = getRootShell()
return try {
try {
ShellUtils.fastCmd(shell, "/data/adb/uid_scanner stop")
} catch (_: Exception) {
}
ShellUtils.fastCmdResult(shell, "rm -rf /data/misc/user_uid")
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/uid_scanner")
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/ksu/bin/user_uid")
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/service.d/uid_scanner.sh")
Natives.clearUidScannerEnvironment()
true
} catch (_: Exception) {
false
}
}
@Composable
private fun SettingsGroupCard(
title: String,
@@ -960,4 +824,125 @@ private fun TopBar(
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
)
}
@Composable
private fun UidScannerSection(
prefs: SharedPreferences,
snackBarHost: SnackbarHostState,
scope: CoroutineScope,
context: Context
) {
if (Natives.version < Natives.MINIMAL_SUPPORTED_UID_SCANNER) return
val realAuto = Natives.isUidScannerEnabled()
val realMulti = getUidMultiUserScan()
var autoOn by remember { mutableStateOf(realAuto) }
var multiOn by remember { mutableStateOf(realMulti) }
LaunchedEffect(Unit) {
autoOn = realAuto
multiOn = realMulti
prefs.edit {
putBoolean("uid_auto_scan", autoOn)
putBoolean("uid_multi_user_scan", multiOn)
}
}
SwitchItem(
icon = Icons.Filled.Scanner,
title = stringResource(R.string.uid_auto_scan_title),
summary = stringResource(R.string.uid_auto_scan_summary),
checked = autoOn,
onCheckedChange = { target ->
autoOn = target
if (!target) multiOn = false
scope.launch(Dispatchers.IO) {
setUidAutoScan(target)
val actual = Natives.isUidScannerEnabled() || readUidScannerFile()
withContext(Dispatchers.Main) {
autoOn = actual
if (!actual) multiOn = false
prefs.edit {
putBoolean("uid_auto_scan", actual)
putBoolean("uid_multi_user_scan", multiOn)
}
if (actual != target) {
snackBarHost.showSnackbar(
context.getString(R.string.uid_scanner_setting_failed)
)
}
}
}
}
)
AnimatedVisibility(
visible = autoOn,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
SwitchItem(
icon = Icons.Filled.Groups,
title = stringResource(R.string.uid_multi_user_scan_title),
summary = stringResource(R.string.uid_multi_user_scan_summary),
checked = multiOn,
onCheckedChange = { target ->
scope.launch(Dispatchers.IO) {
val ok = setUidMultiUserScan(target)
withContext(Dispatchers.Main) {
if (ok) {
multiOn = target
prefs.edit { putBoolean("uid_multi_user_scan", target) }
} else {
snackBarHost.showSnackbar(
context.getString(R.string.uid_scanner_setting_failed)
)
}
}
}
}
)
}
AnimatedVisibility(
visible = autoOn,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
val confirmDialog = rememberConfirmDialog()
SettingItem(
icon = Icons.Filled.CleaningServices,
title = stringResource(R.string.clean_runtime_environment),
summary = stringResource(R.string.clean_runtime_environment_summary),
onClick = {
scope.launch {
if (confirmDialog.awaitConfirm(
title = context.getString(R.string.clean_runtime_environment),
content = context.getString(R.string.clean_runtime_environment_confirm)
) == ConfirmResult.Confirmed
) {
if (cleanRuntimeEnvironment()) {
autoOn = false
multiOn = false
prefs.edit {
putBoolean("uid_auto_scan", false)
putBoolean("uid_multi_user_scan", false)
}
Natives.setUidScannerEnabled(false)
snackBarHost.showSnackbar(
context.getString(R.string.clean_runtime_environment_success)
)
} else {
snackBarHost.showSnackbar(
context.getString(R.string.clean_runtime_environment_failed)
)
}
}
}
}
)
}
}

View File

@@ -21,6 +21,7 @@ import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import org.json.JSONArray
import java.io.File
import java.util.concurrent.CountDownLatch
/**
@@ -34,7 +35,7 @@ private fun getKsuDaemonPath(): String {
}
object KsuCli {
val SHELL: Shell = createRootShell()
var SHELL: Shell = createRootShell()
val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
}
@@ -578,11 +579,10 @@ fun getUidScannerDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libuid_scanner.so"
}
private const val targetPath = "/data/adb/uid_scanner"
fun ensureUidScannerExecutable(): Boolean {
val shell = getRootShell()
val uidScannerPath = getUidScannerDaemonPath()
val targetPath = "/data/adb/uid_scanner"
if (!ShellUtils.fastCmdResult(shell, "test -f $targetPath")) {
val copyResult = ShellUtils.fastCmdResult(shell, "cp $uidScannerPath $targetPath")
if (!copyResult) {
@@ -593,7 +593,6 @@ fun ensureUidScannerExecutable(): Boolean {
val result = ShellUtils.fastCmdResult(shell, "chmod 755 $targetPath")
return result
}
private const val targetPath = "/data/adb/uid_scanner"
fun setUidAutoScan(enabled: Boolean): Boolean {
val shell = getRootShell()
@@ -634,3 +633,30 @@ fun getUidMultiUserScan(): Boolean {
false
}
}
fun cleanRuntimeEnvironment(): Boolean {
val shell = getRootShell()
return try {
try {
ShellUtils.fastCmd(shell, "/data/adb/uid_scanner stop")
} catch (_: Exception) {
}
ShellUtils.fastCmdResult(shell, "rm -rf /data/misc/user_uid")
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/uid_scanner")
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/ksu/bin/user_uid")
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/service.d/uid_scanner.sh")
Natives.clearUidScannerEnvironment()
true
} catch (_: Exception) {
false
}
}
fun readUidScannerFile(): Boolean {
val shell = getRootShell()
return try {
ShellUtils.fastCmd(shell, "cat /data/adb/ksu/.uid_scanner").trim() == "1"
} catch (_: Exception) {
false
}
}

View File

@@ -121,18 +121,12 @@ class HomeViewModel : ViewModel() {
try {
val kernelVersion = getKernelVersion()
val isManager = try {
Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra")
Natives.isManager
} catch (_: Exception) {
false
}
val ksuVersion = if (isManager) {
try {
Natives.version
} catch (_: Exception) {
null
}
} else null
val ksuVersion = if (isManager) Natives.version else null
val fullVersion = try {
Natives.getFullVersion()
@@ -163,13 +157,7 @@ class HomeViewModel : ViewModel() {
}
val lkmMode = ksuVersion?.let {
try {
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) {
Natives.isLkmMode
} else null
} catch (_: Exception) {
null
}
if (kernelVersion.isGKI()) Natives.isLkmMode else null
}
val isRootAvailable = try {
@@ -346,7 +334,7 @@ class HomeViewModel : ViewModel() {
try {
// 检查KSU状态是否发生变化
val currentKsuVersion = try {
if (Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra")) {
if (Natives.isManager) {
Natives.version
} else null
} catch (_: Exception) {

View File

@@ -17,7 +17,7 @@ class KsuLibSuProvider : IProvider {
override fun isAvailable() = true
override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName)
override suspend fun isAuthorized() = Natives.isManager
private val serviceIntent
get() = PlatformIntent(