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

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.idea .idea
.vscode .vscode
CLAUDE.md
.DS_Store .DS_Store

View File

@@ -56,8 +56,8 @@ ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:' CommentPragmas: '^ IWYU pragma:'
#CompactNamespaces: false # Unknown to clang-format-4.0 #CompactNamespaces: false # Unknown to clang-format-4.0
ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8 ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 8 ContinuationIndentWidth: 4
Cpp11BracedListStyle: false Cpp11BracedListStyle: false
DerivePointerAlignment: false DerivePointerAlignment: false
DisableFormat: false DisableFormat: false
@@ -501,7 +501,7 @@ IncludeCategories:
IncludeIsMainRegex: '(Test)?$' IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false IndentCaseLabels: false
#IndentPPDirectives: None # Unknown to clang-format-5.0 #IndentPPDirectives: None # Unknown to clang-format-5.0
IndentWidth: 8 IndentWidth: 4
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave JavaScriptQuotes: Leave
JavaScriptWrapImports: true JavaScriptWrapImports: true
@@ -511,7 +511,7 @@ MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1 MaxEmptyLinesToKeep: 1
NamespaceIndentation: None NamespaceIndentation: None
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 #ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
ObjCBlockIndentWidth: 8 ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true ObjCSpaceBeforeProtocolList: true
@@ -543,6 +543,6 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false SpacesInParentheses: false
SpacesInSquareBrackets: false SpacesInSquareBrackets: false
Standard: Cpp03 Standard: Cpp03
TabWidth: 8 TabWidth: 4
UseTab: Always UseTab: Never
... ...

View File

@@ -1,38 +1,38 @@
menu "KernelSU" menu "KernelSU"
config KSU config KSU
tristate "KernelSU function support" tristate "KernelSU function support"
depends on OVERLAY_FS depends on OVERLAY_FS
default y default y
help help
Enable kernel-level root privileges on Android System. Enable kernel-level root privileges on Android System.
To compile as a module, choose M here: the To compile as a module, choose M here: the
module will be called kernelsu. module will be called kernelsu.
config KSU_DEBUG config KSU_DEBUG
bool "KernelSU debug mode" bool "KernelSU debug mode"
depends on KSU depends on KSU
default n default n
help help
Enable KernelSU debug mode. Enable KernelSU debug mode.
config KSU_MANUAL_SU config KSU_MANUAL_SU
bool "Use manual su" bool "Use manual su"
depends on KSU depends on KSU
default y default y
help help
Use manual su and authorize the corresponding command line and application via prctl Use manual su and authorize the corresponding command line and application via prctl
config KPM config KPM
bool "Enable SukiSU KPM" bool "Enable SukiSU KPM"
depends on KSU && 64BIT depends on KSU && 64BIT
default n default n
help help
Enabling this option will activate the KPM feature of SukiSU. Enabling this option will activate the KPM feature of SukiSU.
This option is suitable for scenarios where you need to force KPM to be enabled. This option is suitable for scenarios where you need to force KPM to be enabled.
but it may affect system stability. but it may affect system stability.
select KALLSYMS select KALLSYMS
select KALLSYMS_ALL select KALLSYMS_ALL
choice choice
prompt "KernelSU hook type" prompt "KernelSU hook type"
@@ -43,21 +43,21 @@ choice
config KSU_KPROBES_HOOK config KSU_KPROBES_HOOK
bool "Hook KernelSU with Kprobes" bool "Hook KernelSU with Kprobes"
depends on KPROBES depends on KPROBES
help help
If enabled, Hook required KernelSU syscalls with Kernel-probe. If enabled, Hook required KernelSU syscalls with Kernel-probe.
config KSU_TRACEPOINT_HOOK config KSU_TRACEPOINT_HOOK
bool "Hook KernelSU with Tracepoint" bool "Hook KernelSU with Tracepoint"
depends on TRACEPOINTS depends on TRACEPOINTS
help help
If enabled, Hook required KernelSU syscalls with Tracepoint. If enabled, Hook required KernelSU syscalls with Tracepoint.
config KSU_MANUAL_HOOK config KSU_MANUAL_HOOK
bool "Hook KernelSU manually" bool "Hook KernelSU manually"
depends on KSU != m depends on KSU != m
help help
If enabled, Hook required KernelSU syscalls with manually-patched function. If enabled, Hook required KernelSU syscalls with manually-patched function.
endchoice endchoice

View File

@@ -3,8 +3,10 @@ kernelsu-objs += allowlist.o
kernelsu-objs += dynamic_manager.o kernelsu-objs += dynamic_manager.o
kernelsu-objs += apk_sign.o kernelsu-objs += apk_sign.o
kernelsu-objs += sucompat.o kernelsu-objs += sucompat.o
kernelsu-objs += pkg_observer.o
kernelsu-objs += throne_tracker.o kernelsu-objs += throne_tracker.o
kernelsu-objs += core_hook.o kernelsu-objs += core_hook.o
kernelsu-objs += supercalls.o
kernelsu-objs += ksud.o kernelsu-objs += ksud.o
kernelsu-objs += embed_ksud.o kernelsu-objs += embed_ksud.o
kernelsu-objs += kernel_compat.o kernelsu-objs += kernel_compat.o
@@ -134,6 +136,11 @@ else
$(info -- KPM is disabled) $(info -- KPM is disabled)
endif endif
# Check new vfs_getattr()
ifeq ($(shell grep -A1 "^int vfs_getattr" $(srctree)/fs/stat.c | grep -q "query_flags" ; echo $$?),0)
ccflags-y += -DKSU_HAS_NEW_VFS_GETATTR
endif
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function ccflags-y += -Wno-declaration-after-statement -Wno-unused-function

View File

@@ -8,7 +8,9 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/version.h> #include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
#include <linux/compiler_types.h> #include <linux/compiler_types.h>
#endif
#include "ksu.h" #include "ksu.h"
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
@@ -34,53 +36,53 @@ static int allow_list_pointer __read_mostly = 0;
static void remove_uid_from_arr(uid_t uid) static void remove_uid_from_arr(uid_t uid)
{ {
int *temp_arr; int *temp_arr;
int i, j; int i, j;
if (allow_list_pointer == 0) if (allow_list_pointer == 0)
return; return;
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL); temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
if (temp_arr == NULL) { if (temp_arr == NULL) {
pr_err("%s: unable to allocate memory\n", __func__); pr_err("%s: unable to allocate memory\n", __func__);
return; return;
} }
for (i = j = 0; i < allow_list_pointer; i++) { for (i = j = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid) if (allow_list_arr[i] == uid)
continue; continue;
temp_arr[j++] = allow_list_arr[i]; temp_arr[j++] = allow_list_arr[i];
} }
allow_list_pointer = j; allow_list_pointer = j;
for (; j < ARRAY_SIZE(allow_list_arr); j++) for (; j < ARRAY_SIZE(allow_list_arr); j++)
temp_arr[j] = -1; temp_arr[j] = -1;
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE); memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
kfree(temp_arr); kfree(temp_arr);
} }
static void init_default_profiles() static void init_default_profiles(void)
{ {
kernel_cap_t full_cap = CAP_FULL_SET; kernel_cap_t full_cap = CAP_FULL_SET;
default_root_profile.uid = 0; default_root_profile.uid = 0;
default_root_profile.gid = 0; default_root_profile.gid = 0;
default_root_profile.groups_count = 1; default_root_profile.groups_count = 1;
default_root_profile.groups[0] = 0; default_root_profile.groups[0] = 0;
memcpy(&default_root_profile.capabilities.effective, &full_cap, memcpy(&default_root_profile.capabilities.effective, &full_cap,
sizeof(default_root_profile.capabilities.effective)); sizeof(default_root_profile.capabilities.effective));
default_root_profile.namespaces = 0; default_root_profile.namespaces = 0;
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN); strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
// This means that we will umount modules by default! // This means that we will umount modules by default!
default_non_root_profile.umount_modules = true; default_non_root_profile.umount_modules = true;
} }
struct perm_data { struct perm_data {
struct list_head list; struct list_head list;
struct app_profile profile; struct app_profile profile;
}; };
static struct list_head allow_list; static struct list_head allow_list;
@@ -93,436 +95,436 @@ static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE);
static struct work_struct ksu_save_work; static struct work_struct ksu_save_work;
static struct work_struct ksu_load_work; static struct work_struct ksu_load_work;
bool persistent_allow_list(void); static bool persistent_allow_list(void);
void ksu_show_allow_list(void) void ksu_show_allow_list(void)
{ {
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
pr_info("ksu_show_allow_list\n"); pr_info("ksu_show_allow_list\n");
list_for_each (pos, &allow_list) { list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list); p = list_entry(pos, struct perm_data, list);
pr_info("uid :%d, allow: %d\n", p->profile.current_uid, pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
p->profile.allow_su); p->profile.allow_su);
} }
} }
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
static void ksu_grant_root_to_shell() static void ksu_grant_root_to_shell(void)
{ {
struct app_profile profile = { struct app_profile profile = {
.version = KSU_APP_PROFILE_VER, .version = KSU_APP_PROFILE_VER,
.allow_su = true, .allow_su = true,
.current_uid = 2000, .current_uid = 2000,
}; };
strcpy(profile.key, "com.android.shell"); strcpy(profile.key, "com.android.shell");
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN); strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
ksu_set_app_profile(&profile, false); ksu_set_app_profile(&profile, false);
} }
#endif #endif
bool ksu_get_app_profile(struct app_profile *profile) bool ksu_get_app_profile(struct app_profile *profile)
{ {
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
bool found = false; bool found = false;
list_for_each (pos, &allow_list) { list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list); p = list_entry(pos, struct perm_data, list);
bool uid_match = profile->current_uid == p->profile.current_uid; bool uid_match = profile->current_uid == p->profile.current_uid;
if (uid_match) { if (uid_match) {
// found it, override it with ours // found it, override it with ours
memcpy(profile, &p->profile, sizeof(*profile)); memcpy(profile, &p->profile, sizeof(*profile));
found = true; found = true;
goto exit; goto exit;
} }
} }
exit: exit:
return found; return found;
} }
static inline bool forbid_system_uid(uid_t uid) { static inline bool forbid_system_uid(uid_t uid) {
#define SHELL_UID 2000 #define SHELL_UID 2000
#define SYSTEM_UID 1000 #define SYSTEM_UID 1000
return uid < SHELL_UID && uid != SYSTEM_UID; return uid < SHELL_UID && uid != SYSTEM_UID;
} }
static bool profile_valid(struct app_profile *profile) static bool profile_valid(struct app_profile *profile)
{ {
if (!profile) { if (!profile) {
return false; return false;
} }
if (profile->version < KSU_APP_PROFILE_VER) { if (profile->version < KSU_APP_PROFILE_VER) {
pr_info("Unsupported profile version: %d\n", profile->version); pr_info("Unsupported profile version: %d\n", profile->version);
return false; return false;
} }
if (profile->allow_su) { if (profile->allow_su) {
if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) { if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) {
return false; return false;
} }
if (strlen(profile->rp_config.profile.selinux_domain) == 0) { if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
return false; return false;
} }
} }
return true; return true;
} }
bool ksu_set_app_profile(struct app_profile *profile, bool persist) bool ksu_set_app_profile(struct app_profile *profile, bool persist)
{ {
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
bool result = false; bool result = false;
if (!profile_valid(profile)) { if (!profile_valid(profile)) {
pr_err("Failed to set app profile: invalid profile!\n"); pr_err("Failed to set app profile: invalid profile!\n");
return false; return false;
} }
list_for_each (pos, &allow_list) { list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list); p = list_entry(pos, struct perm_data, list);
// both uid and package must match, otherwise it will break multiple package with different user id // both uid and package must match, otherwise it will break multiple package with different user id
if (profile->current_uid == p->profile.current_uid && if (profile->current_uid == p->profile.current_uid &&
!strcmp(profile->key, p->profile.key)) { !strcmp(profile->key, p->profile.key)) {
// found it, just override it all! // found it, just override it all!
memcpy(&p->profile, profile, sizeof(*profile)); memcpy(&p->profile, profile, sizeof(*profile));
result = true; result = true;
goto out; goto out;
} }
} }
// not found, alloc a new node! // not found, alloc a new node!
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL); p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
if (!p) { if (!p) {
pr_err("ksu_set_app_profile alloc failed\n"); pr_err("ksu_set_app_profile alloc failed\n");
return false; return false;
} }
memcpy(&p->profile, profile, sizeof(*profile)); memcpy(&p->profile, profile, sizeof(*profile));
if (profile->allow_su) { if (profile->allow_su) {
pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n", pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n",
profile->key, profile->current_uid, profile->key, profile->current_uid,
profile->rp_config.profile.gid, profile->rp_config.profile.gid,
profile->rp_config.profile.selinux_domain); profile->rp_config.profile.selinux_domain);
} else { } else {
pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n", pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n",
profile->key, profile->current_uid, profile->key, profile->current_uid,
profile->nrp_config.profile.umount_modules); profile->nrp_config.profile.umount_modules);
} }
list_add_tail(&p->list, &allow_list); list_add_tail(&p->list, &allow_list);
out: out:
if (profile->current_uid <= BITMAP_UID_MAX) { if (profile->current_uid <= BITMAP_UID_MAX) {
if (profile->allow_su) if (profile->allow_su)
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE); allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
else else
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE)); allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
} else { } else {
if (profile->allow_su) { if (profile->allow_su) {
/* /*
* 1024 apps with uid higher than BITMAP_UID_MAX * 1024 apps with uid higher than BITMAP_UID_MAX
* registered to request superuser? * registered to request superuser?
*/ */
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) { if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
pr_err("too many apps registered\n"); pr_err("too many apps registered\n");
WARN_ON(1); WARN_ON(1);
return false; return false;
} }
allow_list_arr[allow_list_pointer++] = profile->current_uid; allow_list_arr[allow_list_pointer++] = profile->current_uid;
} else { } else {
remove_uid_from_arr(profile->current_uid); remove_uid_from_arr(profile->current_uid);
} }
} }
result = true; result = true;
// check if the default profiles is changed, cache it to a single struct to accelerate access. // check if the default profiles is changed, cache it to a single struct to accelerate access.
if (unlikely(!strcmp(profile->key, "$"))) { if (unlikely(!strcmp(profile->key, "$"))) {
// set default non root profile // set default non root profile
memcpy(&default_non_root_profile, &profile->nrp_config.profile, memcpy(&default_non_root_profile, &profile->nrp_config.profile,
sizeof(default_non_root_profile)); sizeof(default_non_root_profile));
} }
if (unlikely(!strcmp(profile->key, "#"))) { if (unlikely(!strcmp(profile->key, "#"))) {
// set default root profile // set default root profile
memcpy(&default_root_profile, &profile->rp_config.profile, memcpy(&default_root_profile, &profile->rp_config.profile,
sizeof(default_root_profile)); sizeof(default_root_profile));
} }
if (persist) if (persist)
persistent_allow_list(); persistent_allow_list();
return result; return result;
} }
bool __ksu_is_allow_uid(uid_t uid) bool __ksu_is_allow_uid(uid_t uid)
{ {
int i; int i;
if (unlikely(uid == 0)) { if (unlikely(uid == 0)) {
// already root, but only allow our domain. // already root, but only allow our domain.
return is_ksu_domain(); return is_ksu_domain();
} }
if (forbid_system_uid(uid)) { if (forbid_system_uid(uid)) {
// do not bother going through the list if it's system // do not bother going through the list if it's system
return false; return false;
} }
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) { if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
// manager is always allowed! // manager is always allowed!
return true; return true;
} }
if (likely(uid <= BITMAP_UID_MAX)) { if (likely(uid <= BITMAP_UID_MAX)) {
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE))); return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
} else { } else {
for (i = 0; i < allow_list_pointer; i++) { for (i = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid) if (allow_list_arr[i] == uid)
return true; return true;
} }
} }
return false; return false;
} }
bool ksu_uid_should_umount(uid_t uid) bool ksu_uid_should_umount(uid_t uid)
{ {
struct app_profile profile = { .current_uid = uid }; struct app_profile profile = { .current_uid = uid };
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) { if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
// we should not umount on manager! // we should not umount on manager!
return false; return false;
} }
bool found = ksu_get_app_profile(&profile); bool found = ksu_get_app_profile(&profile);
if (!found) { if (!found) {
// no app profile found, it must be non root app // no app profile found, it must be non root app
return default_non_root_profile.umount_modules; return default_non_root_profile.umount_modules;
} }
if (profile.allow_su) { if (profile.allow_su) {
// if found and it is granted to su, we shouldn't umount for it // if found and it is granted to su, we shouldn't umount for it
return false; return false;
} else { } else {
// found an app profile // found an app profile
if (profile.nrp_config.use_default) { if (profile.nrp_config.use_default) {
return default_non_root_profile.umount_modules; return default_non_root_profile.umount_modules;
} else { } else {
return profile.nrp_config.profile.umount_modules; return profile.nrp_config.profile.umount_modules;
} }
} }
} }
struct root_profile *ksu_get_root_profile(uid_t uid) struct root_profile *ksu_get_root_profile(uid_t uid)
{ {
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
list_for_each (pos, &allow_list) { list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list); p = list_entry(pos, struct perm_data, list);
if (uid == p->profile.current_uid && p->profile.allow_su) { if (uid == p->profile.current_uid && p->profile.allow_su) {
if (!p->profile.rp_config.use_default) { if (!p->profile.rp_config.use_default) {
return &p->profile.rp_config.profile; return &p->profile.rp_config.profile;
} }
} }
} }
// use default profile // use default profile
return &default_root_profile; return &default_root_profile;
} }
bool ksu_get_allow_list(int *array, int *length, bool allow) bool ksu_get_allow_list(int *array, int *length, bool allow)
{ {
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
int i = 0; int i = 0;
list_for_each (pos, &allow_list) { list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list); p = list_entry(pos, struct perm_data, list);
// pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow); // pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
if (p->profile.allow_su == allow) { if (p->profile.allow_su == allow) {
array[i++] = p->profile.current_uid; array[i++] = p->profile.current_uid;
} }
} }
*length = i; *length = i;
return true; return true;
} }
void do_save_allow_list(struct work_struct *work) static void do_save_allow_list(struct work_struct *work)
{ {
u32 magic = FILE_MAGIC; u32 magic = FILE_MAGIC;
u32 version = FILE_FORMAT_VERSION; u32 version = FILE_FORMAT_VERSION;
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
loff_t off = 0; loff_t off = 0;
struct file *fp = struct file *fp =
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644); ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp)); pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
return; return;
} }
// store magic and version // store magic and version
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) !=
sizeof(magic)) { sizeof(magic)) {
pr_err("save_allow_list write magic failed.\n"); pr_err("save_allow_list write magic failed.\n");
goto exit; goto exit;
} }
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
sizeof(version)) { sizeof(version)) {
pr_err("save_allow_list write version failed.\n"); pr_err("save_allow_list write version failed.\n");
goto exit; goto exit;
} }
list_for_each (pos, &allow_list) { list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list); p = list_entry(pos, struct perm_data, list);
pr_info("save allow list, name: %s uid :%d, allow: %d\n", pr_info("save allow list, name: %s uid: %d, allow: %d\n",
p->profile.key, p->profile.current_uid, p->profile.key, p->profile.current_uid,
p->profile.allow_su); p->profile.allow_su);
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile), ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
&off); &off);
} }
exit: exit:
filp_close(fp, 0); filp_close(fp, 0);
} }
void do_load_allow_list(struct work_struct *work) static void do_load_allow_list(struct work_struct *work)
{ {
loff_t off = 0; loff_t off = 0;
ssize_t ret = 0; ssize_t ret = 0;
struct file *fp = NULL; struct file *fp = NULL;
u32 magic; u32 magic;
u32 version; u32 version;
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
// always allow adb shell by default // always allow adb shell by default
ksu_grant_root_to_shell(); ksu_grant_root_to_shell();
#endif #endif
// load allowlist now! // load allowlist now!
fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0); fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp)); pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
return; return;
} }
// verify magic // verify magic
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) !=
sizeof(magic) || sizeof(magic) ||
magic != FILE_MAGIC) { magic != FILE_MAGIC) {
pr_err("allowlist file invalid: %d!\n", magic); pr_err("allowlist file invalid: %d!\n", magic);
goto exit; goto exit;
} }
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) !=
sizeof(version)) { sizeof(version)) {
pr_err("allowlist read version: %d failed\n", version); pr_err("allowlist read version: %d failed\n", version);
goto exit; goto exit;
} }
pr_info("allowlist version: %d\n", version); pr_info("allowlist version: %d\n", version);
while (true) { while (true) {
struct app_profile profile; struct app_profile profile;
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile), ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
&off); &off);
if (ret <= 0) { if (ret <= 0) {
pr_info("load_allow_list read err: %zd\n", ret); pr_info("load_allow_list read err: %zd\n", ret);
break; break;
} }
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n",
profile.key, profile.current_uid, profile.allow_su); profile.key, profile.current_uid, profile.allow_su);
ksu_set_app_profile(&profile, false); ksu_set_app_profile(&profile, false);
} }
exit: exit:
ksu_show_allow_list(); ksu_show_allow_list();
filp_close(fp, 0); filp_close(fp, 0);
} }
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data) void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data)
{ {
struct perm_data *np = NULL; struct perm_data *np = NULL;
struct perm_data *n = NULL; struct perm_data *n = NULL;
bool modified = false; bool modified = false;
// TODO: use RCU! // TODO: use RCU!
mutex_lock(&allowlist_mutex); mutex_lock(&allowlist_mutex);
list_for_each_entry_safe (np, n, &allow_list, list) { list_for_each_entry_safe (np, n, &allow_list, list) {
uid_t uid = np->profile.current_uid; uid_t uid = np->profile.current_uid;
char *package = np->profile.key; char *package = np->profile.key;
// we use this uid for special cases, don't prune it! // we use this uid for special cases, don't prune it!
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID; bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
if (!is_preserved_uid && !is_uid_valid(uid, package, data)) { if (!is_preserved_uid && !is_uid_valid(uid, package, data)) {
modified = true; modified = true;
pr_info("prune uid: %d, package: %s\n", uid, package); pr_info("prune uid: %d, package: %s\n", uid, package);
list_del(&np->list); list_del(&np->list);
if (likely(uid <= BITMAP_UID_MAX)) { if (likely(uid <= BITMAP_UID_MAX)) {
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE)); allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
} }
remove_uid_from_arr(uid); remove_uid_from_arr(uid);
smp_mb(); smp_mb();
kfree(np); kfree(np);
} }
} }
mutex_unlock(&allowlist_mutex); mutex_unlock(&allowlist_mutex);
if (modified) { if (modified) {
persistent_allow_list(); persistent_allow_list();
} }
} }
// make sure allow list works cross boot // make sure allow list works cross boot
bool persistent_allow_list(void) static bool persistent_allow_list(void)
{ {
return ksu_queue_work(&ksu_save_work); return ksu_queue_work(&ksu_save_work);
} }
bool ksu_load_allow_list(void) bool ksu_load_allow_list(void)
{ {
return ksu_queue_work(&ksu_load_work); return ksu_queue_work(&ksu_load_work);
} }
void ksu_allowlist_init(void) void ksu_allowlist_init(void)
{ {
int i; int i;
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE); BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE); BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++) for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
allow_list_arr[i] = -1; allow_list_arr[i] = -1;
INIT_LIST_HEAD(&allow_list); INIT_LIST_HEAD(&allow_list);
INIT_WORK(&ksu_save_work, do_save_allow_list); INIT_WORK(&ksu_save_work, do_save_allow_list);
INIT_WORK(&ksu_load_work, do_load_allow_list); INIT_WORK(&ksu_load_work, do_load_allow_list);
init_default_profiles(); init_default_profiles();
} }
void ksu_allowlist_exit(void) void ksu_allowlist_exit(void)
{ {
struct perm_data *np = NULL; struct perm_data *np = NULL;
struct perm_data *n = NULL; struct perm_data *n = NULL;
do_save_allow_list(NULL); do_save_allow_list(NULL);
// free allowlist // free allowlist
mutex_lock(&allowlist_mutex); mutex_lock(&allowlist_mutex);
list_for_each_entry_safe (np, n, &allow_list, list) { list_for_each_entry_safe (np, n, &allow_list, list) {
list_del(&np->list); list_del(&np->list);
kfree(np); kfree(np);
} }
mutex_unlock(&allowlist_mutex); mutex_unlock(&allowlist_mutex);
} }
#ifdef CONFIG_KSU_MANUAL_SU #ifdef CONFIG_KSU_MANUAL_SU
@@ -553,7 +555,7 @@ bool ksu_temp_grant_root_once(uid_t uid)
strcpy(profile.key, default_key); strcpy(profile.key, default_key);
} }
profile.rp_config.profile.uid = default_root_profile.uid; profile.rp_config.profile.uid = default_root_profile.uid;
profile.rp_config.profile.gid = default_root_profile.gid; profile.rp_config.profile.gid = default_root_profile.gid;
profile.rp_config.profile.groups_count = default_root_profile.groups_count; profile.rp_config.profile.groups_count = default_root_profile.groups_count;
memcpy(profile.rp_config.profile.groups, default_root_profile.groups, sizeof(default_root_profile.groups)); memcpy(profile.rp_config.profile.groups, default_root_profile.groups, sizeof(default_root_profile.groups));
@@ -601,4 +603,4 @@ void ksu_temp_revoke_root_once(uid_t uid)
persistent_allow_list(); persistent_allow_list();
pr_info("pending_root: UID=%d removed and persist updated\n", uid); pr_info("pending_root: UID=%d removed and persist updated\n", uid);
} }
#endif #endif

View File

@@ -21,62 +21,62 @@
#include "manager_sign.h" #include "manager_sign.h"
struct sdesc { struct sdesc {
struct shash_desc shash; struct shash_desc shash;
char ctx[]; char ctx[];
}; };
static apk_sign_key_t apk_sign_keys[] = { static apk_sign_key_t apk_sign_keys[] = {
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU {EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
#ifdef EXPECTED_SIZE #ifdef EXPECTED_SIZE
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom {EXPECTED_SIZE, EXPECTED_HASH}, // Custom
#endif #endif
}; };
static struct sdesc *init_sdesc(struct crypto_shash *alg) static struct sdesc *init_sdesc(struct crypto_shash *alg)
{ {
struct sdesc *sdesc; struct sdesc *sdesc;
int size; int size;
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
sdesc = kmalloc(size, GFP_KERNEL); sdesc = kmalloc(size, GFP_KERNEL);
if (!sdesc) if (!sdesc)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
sdesc->shash.tfm = alg; sdesc->shash.tfm = alg;
return sdesc; return sdesc;
} }
static int calc_hash(struct crypto_shash *alg, const unsigned char *data, static int calc_hash(struct crypto_shash *alg, const unsigned char *data,
unsigned int datalen, unsigned char *digest) unsigned int datalen, unsigned char *digest)
{ {
struct sdesc *sdesc; struct sdesc *sdesc;
int ret; int ret;
sdesc = init_sdesc(alg); sdesc = init_sdesc(alg);
if (IS_ERR(sdesc)) { if (IS_ERR(sdesc)) {
pr_info("can't alloc sdesc\n"); pr_info("can't alloc sdesc\n");
return PTR_ERR(sdesc); return PTR_ERR(sdesc);
} }
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
kfree(sdesc); kfree(sdesc);
return ret; return ret;
} }
static int ksu_sha256(const unsigned char *data, unsigned int datalen, static int ksu_sha256(const unsigned char *data, unsigned int datalen,
unsigned char *digest) unsigned char *digest)
{ {
struct crypto_shash *alg; struct crypto_shash *alg;
char *hash_alg_name = "sha256"; char *hash_alg_name = "sha256";
int ret; int ret;
alg = crypto_alloc_shash(hash_alg_name, 0, 0); alg = crypto_alloc_shash(hash_alg_name, 0, 0);
if (IS_ERR(alg)) { if (IS_ERR(alg)) {
pr_info("can't alloc alg %s\n", hash_alg_name); pr_info("can't alloc alg %s\n", hash_alg_name);
return PTR_ERR(alg); return PTR_ERR(alg);
} }
ret = calc_hash(alg, data, datalen, digest); ret = calc_hash(alg, data, datalen, digest);
crypto_free_shash(alg); crypto_free_shash(alg);
return ret; return ret;
} }
@@ -84,304 +84,307 @@ static struct dynamic_sign_key dynamic_sign = DYNAMIC_SIGN_DEFAULT_CONFIG;
static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *matched_index) static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *matched_index)
{ {
struct dynamic_sign_key current_dynamic_key = dynamic_sign; struct dynamic_sign_key current_dynamic_key = dynamic_sign;
if (ksu_get_dynamic_manager_config(&current_dynamic_key.size, &current_dynamic_key.hash)) { if (ksu_get_dynamic_manager_config(&current_dynamic_key.size, &current_dynamic_key.hash)) {
pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n", pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n",
current_dynamic_key.size, current_dynamic_key.hash); current_dynamic_key.size, current_dynamic_key.hash);
} }
if (size4 != current_dynamic_key.size) { if (size4 != current_dynamic_key.size) {
return false; return false;
} }
#define CERT_MAX_LENGTH 1024 #define CERT_MAX_LENGTH 1024
char cert[CERT_MAX_LENGTH]; char cert[CERT_MAX_LENGTH];
if (size4 > CERT_MAX_LENGTH) { if (size4 > CERT_MAX_LENGTH) {
pr_info("cert length overlimit\n"); pr_info("cert length overlimit\n");
return false; return false;
} }
ksu_kernel_read_compat(fp, cert, size4, pos); ksu_kernel_read_compat(fp, cert, size4, pos);
unsigned char digest[SHA256_DIGEST_SIZE]; unsigned char digest[SHA256_DIGEST_SIZE];
if (ksu_sha256(cert, size4, digest) < 0) { if (ksu_sha256(cert, size4, digest) < 0) {
pr_info("sha256 error\n"); pr_info("sha256 error\n");
return false; return false;
} }
char hash_str[SHA256_DIGEST_SIZE * 2 + 1]; char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
hash_str[SHA256_DIGEST_SIZE * 2] = '\0'; hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE); bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
pr_info("sha256: %s, expected: %s, index: dynamic\n", hash_str, current_dynamic_key.hash); pr_info("sha256: %s, expected: %s, index: dynamic\n", hash_str, current_dynamic_key.hash);
if (strcmp(current_dynamic_key.hash, hash_str) == 0) { if (strcmp(current_dynamic_key.hash, hash_str) == 0) {
if (matched_index) { if (matched_index) {
*matched_index = DYNAMIC_SIGN_INDEX; *matched_index = DYNAMIC_SIGN_INDEX;
} }
return true; return true;
} }
return false; return false;
} }
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, int *matched_index) static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, int *matched_index)
{ {
int i; int i;
apk_sign_key_t sign_key; apk_sign_key_t sign_key;
bool signature_valid = false; bool signature_valid = false;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length
*offset += 0x4 * 3; *offset += 0x4 * 3;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length
*pos += *size4; *pos += *size4;
*offset += 0x4 + *size4; *offset += 0x4 + *size4;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
*offset += 0x4 * 2; *offset += 0x4 * 2;
if (ksu_is_dynamic_manager_enabled()) { if (ksu_is_dynamic_manager_enabled()) {
loff_t temp_pos = *pos; loff_t temp_pos = *pos;
if (check_dynamic_sign(fp, *size4, &temp_pos, matched_index)) { if (check_dynamic_sign(fp, *size4, &temp_pos, matched_index)) {
*pos = temp_pos; *pos = temp_pos;
*offset += *size4; *offset += *size4;
return true; return true;
} }
} }
for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) { for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
sign_key = apk_sign_keys[i]; sign_key = apk_sign_keys[i];
if (*size4 != sign_key.size) if (*size4 != sign_key.size)
continue; continue;
*offset += *size4; *offset += *size4;
#define CERT_MAX_LENGTH 1024 #define CERT_MAX_LENGTH 1024
char cert[CERT_MAX_LENGTH]; char cert[CERT_MAX_LENGTH];
if (*size4 > CERT_MAX_LENGTH) { if (*size4 > CERT_MAX_LENGTH) {
pr_info("cert length overlimit\n"); pr_info("cert length overlimit\n");
return false; return false;
} }
ksu_kernel_read_compat(fp, cert, *size4, pos); ksu_kernel_read_compat(fp, cert, *size4, pos);
unsigned char digest[SHA256_DIGEST_SIZE]; unsigned char digest[SHA256_DIGEST_SIZE];
if (IS_ERR(ksu_sha256(cert, *size4, digest))) { if (ksu_sha256(cert, *size4, digest) < 0 ) {
pr_info("sha256 error\n"); pr_info("sha256 error\n");
return false; return false;
} }
char hash_str[SHA256_DIGEST_SIZE * 2 + 1]; char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
hash_str[SHA256_DIGEST_SIZE * 2] = '\0'; hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE); bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
pr_info("sha256: %s, expected: %s, index: %d\n", hash_str, sign_key.sha256, i); pr_info("sha256: %s, expected: %s, index: %d\n", hash_str, sign_key.sha256, i);
if (strcmp(sign_key.sha256, hash_str) == 0) { if (strcmp(sign_key.sha256, hash_str) == 0) {
signature_valid = true; signature_valid = true;
if (matched_index) { if (matched_index) {
*matched_index = i; *matched_index = i;
} }
break; break;
} }
} }
return signature_valid; return signature_valid;
} }
struct zip_entry_header { struct zip_entry_header {
uint32_t signature; uint32_t signature;
uint16_t version; uint16_t version;
uint16_t flags; uint16_t flags;
uint16_t compression; uint16_t compression;
uint16_t mod_time; uint16_t mod_time;
uint16_t mod_date; uint16_t mod_date;
uint32_t crc32; uint32_t crc32;
uint32_t compressed_size; uint32_t compressed_size;
uint32_t uncompressed_size; uint32_t uncompressed_size;
uint16_t file_name_length; uint16_t file_name_length;
uint16_t extra_field_length; uint16_t extra_field_length;
} __attribute__((packed)); } __attribute__((packed));
// This is a necessary but not sufficient condition, but it is enough for us // This is a necessary but not sufficient condition, but it is enough for us
static bool has_v1_signature_file(struct file *fp) static bool has_v1_signature_file(struct file *fp)
{ {
struct zip_entry_header header; struct zip_entry_header header;
const char MANIFEST[] = "META-INF/MANIFEST.MF"; const char MANIFEST[] = "META-INF/MANIFEST.MF";
loff_t pos = 0; loff_t pos = 0;
while (ksu_kernel_read_compat(fp, &header, while (ksu_kernel_read_compat(fp, &header,
sizeof(struct zip_entry_header), &pos) == sizeof(struct zip_entry_header), &pos) ==
sizeof(struct zip_entry_header)) { sizeof(struct zip_entry_header)) {
if (header.signature != 0x04034b50) { if (header.signature != 0x04034b50) {
// ZIP magic: 'PK' // ZIP magic: 'PK'
return false; return false;
} }
// Read the entry file name // Read the entry file name
if (header.file_name_length == sizeof(MANIFEST) - 1) { if (header.file_name_length == sizeof(MANIFEST) - 1) {
char fileName[sizeof(MANIFEST)]; char fileName[sizeof(MANIFEST)];
ksu_kernel_read_compat(fp, fileName, ksu_kernel_read_compat(fp, fileName,
header.file_name_length, &pos); header.file_name_length, &pos);
fileName[header.file_name_length] = '\0'; fileName[header.file_name_length] = '\0';
// Check if the entry matches META-INF/MANIFEST.MF // Check if the entry matches META-INF/MANIFEST.MF
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == 0) { if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) ==
return true; 0) {
} return true;
} else { }
// Skip the entry file name } else {
pos += header.file_name_length; // Skip the entry file name
} pos += header.file_name_length;
}
// Skip to the next entry // Skip to the next entry
pos += header.extra_field_length + header.compressed_size; pos += header.extra_field_length + header.compressed_size;
} }
return false; return false;
} }
static __always_inline bool check_v2_signature(char *path, bool check_multi_manager, int *signature_index) static __always_inline bool check_v2_signature(char *path, bool check_multi_manager, int *signature_index)
{ {
unsigned char buffer[0x11] = { 0 }; unsigned char buffer[0x11] = { 0 };
u32 size4; u32 size4;
u64 size8, size_of_block; u64 size8, size_of_block;
loff_t pos;
bool v2_signing_valid = false;
int v2_signing_blocks = 0;
bool v3_signing_exist = false;
bool v3_1_signing_exist = false;
int matched_index = -1;
int i;
struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("open %s error.\n", path);
return false;
}
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip loff_t pos;
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
filp_close(fp, 0);
return 0;
}
// disable inotify for this file bool v2_signing_valid = false;
fp->f_mode |= FMODE_NONOTIFY; int v2_signing_blocks = 0;
bool v3_signing_exist = false;
bool v3_1_signing_exist = false;
int matched_index = -1;
int i;
struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("open %s error.\n", path);
return false;
}
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD) // If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip
for (i = 0;; ++i) { if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
unsigned short n; filp_close(fp, 0);
pos = generic_file_llseek(fp, -i - 2, SEEK_END); return 0;
ksu_kernel_read_compat(fp, &n, 2, &pos); }
if (n == i) {
pos -= 22;
ksu_kernel_read_compat(fp, &size4, 4, &pos);
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
break;
}
}
if (i == 0xffff) {
pr_info("error: cannot find eocd\n");
goto clean;
}
}
pos += 12; // disable inotify for this file
// offset fp->f_mode |= FMODE_NONOTIFY;
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
pos = size4 - 0x18;
ksu_kernel_read_compat(fp, &size8, 0x8, &pos); // https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
ksu_kernel_read_compat(fp, buffer, 0x10, &pos); for (i = 0;; ++i) {
if (strcmp((char *)buffer, "APK Sig Block 42")) { unsigned short n;
goto clean; pos = generic_file_llseek(fp, -i - 2, SEEK_END);
} ksu_kernel_read_compat(fp, &n, 2, &pos);
if (n == i) {
pos -= 22;
ksu_kernel_read_compat(fp, &size4, 4, &pos);
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
break;
}
}
if (i == 0xffff) {
pr_info("error: cannot find eocd\n");
goto clean;
}
}
pos = size4 - (size8 + 0x8); pos += 12;
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos); // offset
if (size_of_block != size8) { ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
goto clean; pos = size4 - 0x18;
}
int loop_count = 0; ksu_kernel_read_compat(fp, &size8, 0x8, &pos);
while (loop_count++ < 10) { ksu_kernel_read_compat(fp, buffer, 0x10, &pos);
uint32_t id; if (strcmp((char *)buffer, "APK Sig Block 42")) {
uint32_t offset; goto clean;
ksu_kernel_read_compat(fp, &size8, 0x8, }
&pos); // sequence length
if (size8 == size_of_block) { pos = size4 - (size8 + 0x8);
break; ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
} if (size_of_block != size8) {
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id goto clean;
offset = 4; }
if (id == 0x7109871au) {
v2_signing_blocks++; int loop_count = 0;
bool result = check_block(fp, &size4, &pos, &offset, &matched_index); while (loop_count++ < 10) {
if (result) { uint32_t id;
v2_signing_valid = true; uint32_t offset;
} ksu_kernel_read_compat(fp, &size8, 0x8,
} else if (id == 0xf05368c0u) { &pos); // sequence length
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73 if (size8 == size_of_block) {
v3_signing_exist = true; break;
} else if (id == 0x1b93ad61u) { }
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#74 ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
v3_1_signing_exist = true; offset = 4;
} else { if (id == 0x7109871au) {
v2_signing_blocks++;
bool result = check_block(fp, &size4, &pos, &offset, &matched_index);
if (result) {
v2_signing_valid = true;
}
} else if (id == 0xf05368c0u) {
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73
v3_signing_exist = true;
} else if (id == 0x1b93ad61u) {
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#74
v3_1_signing_exist = true;
} else {
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
pr_info("Unknown id: 0x%08x\n", id); pr_info("Unknown id: 0x%08x\n", id);
#endif #endif
} }
pos += (size8 - offset); pos += (size8 - offset);
} }
if (v2_signing_blocks != 1) { if (v2_signing_blocks != 1) {
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
pr_err("Unexpected v2 signature count: %d\n", pr_err("Unexpected v2 signature count: %d\n",
v2_signing_blocks); v2_signing_blocks);
#endif #endif
v2_signing_valid = false; v2_signing_valid = false;
} }
if (v2_signing_valid) { if (v2_signing_valid) {
int has_v1_signing = has_v1_signature_file(fp); int has_v1_signing = has_v1_signature_file(fp);
if (has_v1_signing) { if (has_v1_signing) {
pr_err("Unexpected v1 signature scheme found!\n"); pr_err("Unexpected v1 signature scheme found!\n");
filp_close(fp, 0); filp_close(fp, 0);
return false; return false;
} }
} }
clean: clean:
filp_close(fp, 0); filp_close(fp, 0);
if (v3_signing_exist || v3_1_signing_exist) { if (v3_signing_exist || v3_1_signing_exist) {
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
pr_err("Unexpected v3 signature scheme found!\n"); pr_err("Unexpected v3 signature scheme found!\n");
#endif #endif
return false; return false;
} }
if (v2_signing_valid) { if (v2_signing_valid) {
if (signature_index) { if (signature_index) {
*signature_index = matched_index; *signature_index = matched_index;
} }
if (check_multi_manager) { if (check_multi_manager) {
// 0: ShirkNeko/SukiSU, DYNAMIC_SIGN_INDEX : Dynamic Sign // 0: ShirkNeko/SukiSU, DYNAMIC_SIGN_INDEX : Dynamic Sign
if (matched_index == 0 || matched_index == DYNAMIC_SIGN_INDEX) { if (matched_index == 0 || matched_index == DYNAMIC_SIGN_INDEX) {
pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index); pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index);
return true; return true;
} }
return false; return false;
} else { } else {
// Common manager check: any valid signature will do // Common manager check: any valid signature will do
return true; return true;
} }
} }
return false; return false;
} }
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
@@ -392,19 +395,19 @@ int ksu_debug_manager_uid = -1;
static int set_expected_size(const char *val, const struct kernel_param *kp) static int set_expected_size(const char *val, const struct kernel_param *kp)
{ {
int rv = param_set_uint(val, kp); int rv = param_set_uint(val, kp);
ksu_set_manager_uid(ksu_debug_manager_uid); ksu_set_manager_uid(ksu_debug_manager_uid);
pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid); pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid);
return rv; return rv;
} }
static struct kernel_param_ops expected_size_ops = { static struct kernel_param_ops expected_size_ops = {
.set = set_expected_size, .set = set_expected_size,
.get = param_get_uint, .get = param_get_uint,
}; };
module_param_cb(ksu_debug_manager_uid, &expected_size_ops, module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
&ksu_debug_manager_uid, S_IRUSR | S_IWUSR); &ksu_debug_manager_uid, S_IRUSR | S_IWUSR);
#endif #endif

View File

@@ -19,10 +19,17 @@
#define __PT_IP_REG pc #define __PT_IP_REG pc
#define PRCTL_SYMBOL "__arm64_sys_prctl" #define PRCTL_SYMBOL "__arm64_sys_prctl"
#define REBOOT_SYMBOL "__arm64_sys_reboot"
#define SYS_READ_SYMBOL "__arm64_sys_read" #define SYS_READ_SYMBOL "__arm64_sys_read"
#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat" #define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat"
#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat" #define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat"
#define SYS_EXECVE_SYMBOL "__arm64_sys_execve" #define SYS_EXECVE_SYMBOL "__arm64_sys_execve"
/*LSM HOOK*/
#define SECURITY_TASK_FIX_SETUID_SYMBOL "security_task_fix_setuid"
#define PRCTL_SYMBOL "__arm64_sys_prctl"
#define INODE_PERMISSION_SYMBOL "security_inode_permission"
#define BPRM_CHECK_SECURITY_SYMBOL "security_bprm_check"
#define TASK_ALLOC_SYMBOL "security_task_alloc"
#elif defined(__x86_64__) #elif defined(__x86_64__)
@@ -40,10 +47,17 @@
#define __PT_SP_REG sp #define __PT_SP_REG sp
#define __PT_IP_REG ip #define __PT_IP_REG ip
#define PRCTL_SYMBOL "__x64_sys_prctl" #define PRCTL_SYMBOL "__x64_sys_prctl"
#define REBOOT_SYMBOL "__x64_sys_reboot"
#define SYS_READ_SYMBOL "__x64_sys_read" #define SYS_READ_SYMBOL "__x64_sys_read"
#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat" #define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat"
#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat" #define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat"
#define SYS_EXECVE_SYMBOL "__x64_sys_execve" #define SYS_EXECVE_SYMBOL "__x64_sys_execve"
/*LSM HOOK*/
#define SECURITY_TASK_FIX_SETUID_SYMBOL "security_task_fix_setuid"
#define PRCTL_SYMBOL "__x64_sys_prctl"
#define INODE_PERMISSION_SYMBOL "security_inode_permission"
#define BPRM_CHECK_SECURITY_SYMBOL "security_bprm_check"
#define TASK_ALLOC_SYMBOL "security_task_alloc"
#else #else
#error "Unsupported arch" #error "Unsupported arch"

File diff suppressed because it is too large Load Diff

View File

@@ -11,11 +11,11 @@ void ksu_core_exit(void);
#define KSU_PROC_UMOUNT 50 #define KSU_PROC_UMOUNT 50
static inline bool ksu_is_current_proc_umounted(void) { static inline bool ksu_is_current_proc_umounted(void) {
return test_ti_thread_flag(&current->thread_info, KSU_PROC_UMOUNT); return test_ti_thread_flag(&current->thread_info, KSU_PROC_UMOUNT);
} }
static inline void ksu_set_current_proc_umounted(void) { static inline void ksu_set_current_proc_umounted(void) {
set_ti_thread_flag(&current->thread_info, KSU_PROC_UMOUNT); set_ti_thread_flag(&current->thread_info, KSU_PROC_UMOUNT);
} }
#endif #endif

View File

@@ -7,22 +7,22 @@
// For sucompat // For sucompat
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
int *flags); int *flags);
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags); int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
// For ksud // For ksud
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
size_t *count_ptr, loff_t **pos); size_t *count_ptr, loff_t **pos);
// For ksud and sucompat // For ksud and sucompat
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
void *envp, int *flags); void *envp, int *flags);
// For volume button // For volume button
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
int *value); int *value);
#endif #endif

View File

@@ -3,6 +3,8 @@
#include <linux/nsproxy.h> #include <linux/nsproxy.h>
#include <linux/sched/task.h> #include <linux/sched/task.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h" #include "kernel_compat.h"
@@ -10,20 +12,20 @@ extern struct task_struct init_task;
// mnt_ns context switch for environment that android_init->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns, such as WSA // mnt_ns context switch for environment that android_init->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns, such as WSA
struct ksu_ns_fs_saved { struct ksu_ns_fs_saved {
struct nsproxy *ns; struct nsproxy *ns;
struct fs_struct *fs; struct fs_struct *fs;
}; };
static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved) static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
{ {
ns_fs_saved->ns = current->nsproxy; ns_fs_saved->ns = current->nsproxy;
ns_fs_saved->fs = current->fs; ns_fs_saved->fs = current->fs;
} }
static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved) static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
{ {
current->nsproxy = ns_fs_saved->ns; current->nsproxy = ns_fs_saved->ns;
current->fs = ns_fs_saved->fs; current->fs = ns_fs_saved->fs;
} }
static bool android_context_saved_checked = false; static bool android_context_saved_checked = false;
@@ -32,64 +34,119 @@ static struct ksu_ns_fs_saved android_context_saved;
void ksu_android_ns_fs_check() void ksu_android_ns_fs_check()
{ {
if (android_context_saved_checked) if (android_context_saved_checked)
return; return;
android_context_saved_checked = true; android_context_saved_checked = true;
task_lock(current); task_lock(current);
if (current->nsproxy && current->fs && if (current->nsproxy && current->fs &&
current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) { current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) {
android_context_saved_enabled = true; android_context_saved_enabled = true;
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n", pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n",
current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns); current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns);
#endif #endif
ksu_save_ns_fs(&android_context_saved); ksu_save_ns_fs(&android_context_saved);
} else { } else {
pr_info("android context saved disabled\n"); pr_info("android context saved disabled\n");
} }
task_unlock(current); task_unlock(current);
} }
struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode) struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode)
{ {
// switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns // switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns
struct ksu_ns_fs_saved saved; struct ksu_ns_fs_saved saved;
if (android_context_saved_enabled) { if (android_context_saved_enabled) {
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
pr_info("start switch current nsproxy and fs to android context\n"); pr_info("start switch current nsproxy and fs to android context\n");
#endif #endif
task_lock(current); task_lock(current);
ksu_save_ns_fs(&saved); ksu_save_ns_fs(&saved);
ksu_load_ns_fs(&android_context_saved); ksu_load_ns_fs(&android_context_saved);
task_unlock(current); task_unlock(current);
} }
struct file *fp = filp_open(filename, flags, mode); struct file *fp = filp_open(filename, flags, mode);
if (android_context_saved_enabled) { if (android_context_saved_enabled) {
task_lock(current); task_lock(current);
ksu_load_ns_fs(&saved); ksu_load_ns_fs(&saved);
task_unlock(current); task_unlock(current);
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
pr_info("switch current nsproxy and fs back to saved successfully\n"); pr_info("switch current nsproxy and fs back to saved successfully\n");
#endif #endif
} }
return fp; return fp;
} }
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
loff_t *pos) loff_t *pos)
{ {
return kernel_read(p, buf, count, pos); return kernel_read(p, buf, count, pos);
} }
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
loff_t *pos) loff_t *pos)
{ {
return kernel_write(p, buf, count, pos); return kernel_write(p, buf, count, pos);
} }
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr, long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
long count) long count)
{ {
return strncpy_from_user_nofault(dst, unsafe_addr, count); return strncpy_from_user_nofault(dst, unsafe_addr, count);
} }
struct action_cache {
DECLARE_BITMAP(allow_native, SECCOMP_ARCH_NATIVE_NR);
#ifdef SECCOMP_ARCH_COMPAT
DECLARE_BITMAP(allow_compat, SECCOMP_ARCH_COMPAT_NR);
#endif
};
struct seccomp_filter {
refcount_t refs;
refcount_t users;
bool log;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
bool wait_killable_recv;
#endif
struct action_cache cache;
struct seccomp_filter *prev;
struct bpf_prog *prog;
struct notification *notif;
struct mutex notify_lock;
wait_queue_head_t wqh;
};
void ksu_seccomp_clear_cache(struct seccomp_filter *filter, int nr)
{
if (!filter) {
return;
}
if (nr >= 0 && nr < SECCOMP_ARCH_NATIVE_NR) {
clear_bit(nr, filter->cache.allow_native);
}
#ifdef SECCOMP_ARCH_COMPAT
if (nr >= 0 && nr < SECCOMP_ARCH_COMPAT_NR) {
clear_bit(nr, filter->cache.allow_compat);
}
#endif
}
void ksu_seccomp_allow_cache(struct seccomp_filter *filter, int nr)
{
if (!filter) {
return;
}
if (nr >= 0 && nr < SECCOMP_ARCH_NATIVE_NR) {
set_bit(nr, filter->cache.allow_native);
}
#ifdef SECCOMP_ARCH_COMPAT
if (nr >= 0 && nr < SECCOMP_ARCH_COMPAT_NR) {
set_bit(nr, filter->cache.allow_compat);
}
#endif
}

View File

@@ -48,7 +48,7 @@ static inline __maybe_unused size_t list_count_nodes(const struct list_head *hea
list_for_each(pos, head) list_for_each(pos, head)
count++; count++;
return count; return count;
} }
#endif #endif
@@ -58,25 +58,25 @@ static inline __maybe_unused size_t list_count_nodes(const struct list_head *hea
* From ss/ebitmap.h * From ss/ebitmap.h
*/ */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) && \ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) && \
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) || \ (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) || \
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) && \ (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) && \
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)) (LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0))
#ifdef HISI_SELINUX_EBITMAP_RO #ifdef HISI_SELINUX_EBITMAP_RO
#define CONFIG_IS_HW_HISI #define CONFIG_IS_HW_HISI
#endif #endif
#endif #endif
extern long ksu_strncpy_from_user_nofault(char *dst, extern long ksu_strncpy_from_user_nofault(char *dst,
const void __user *unsafe_addr, const void __user *unsafe_addr,
long count); long count);
extern void ksu_android_ns_fs_check(); extern void ksu_android_ns_fs_check();
extern struct file *ksu_filp_open_compat(const char *filename, int flags, extern struct file *ksu_filp_open_compat(const char *filename, int flags,
umode_t mode); umode_t mode);
extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
loff_t *pos); loff_t *pos);
extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf,
size_t count, loff_t *pos); size_t count, loff_t *pos);
/* /*
* ksu_copy_from_user_retry * ksu_copy_from_user_retry
* try nofault copy first, if it fails, try with plain * try nofault copy first, if it fails, try with plain
@@ -84,14 +84,17 @@ extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf,
* 0 = success * 0 = success
*/ */
static long ksu_copy_from_user_retry(void *to, static long ksu_copy_from_user_retry(void *to,
const void __user *from, unsigned long count) const void __user *from, unsigned long count)
{ {
long ret = copy_from_user_nofault(to, from, count); long ret = copy_from_user_nofault(to, from, count);
if (likely(!ret)) if (likely(!ret))
return ret; return ret;
// we faulted! fallback to slow path // we faulted! fallback to slow path
return copy_from_user(to, from, count); return copy_from_user(to, from, count);
} }
extern void ksu_seccomp_clear_cache(struct seccomp_filter *filter, int nr);
extern void ksu_seccomp_allow_cache(struct seccomp_filter *filter, int nr);
#endif #endif

View File

@@ -16,11 +16,11 @@ static struct workqueue_struct *ksu_workqueue;
bool ksu_queue_work(struct work_struct *work) bool ksu_queue_work(struct work_struct *work)
{ {
return queue_work(ksu_workqueue, work); return queue_work(ksu_workqueue, work);
} }
extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
void *argv, void *envp, int *flags); void *argv, void *envp, int *flags);
extern void ksu_sucompat_init(); extern void ksu_sucompat_init();
extern void ksu_sucompat_exit(); extern void ksu_sucompat_exit();
@@ -34,27 +34,27 @@ extern void ksu_trace_unregister();
int __init kernelsu_init(void) int __init kernelsu_init(void)
{ {
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
pr_alert("*************************************************************"); pr_alert("*************************************************************");
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
pr_alert("** **"); pr_alert("** **");
pr_alert("** You are running KernelSU in DEBUG mode **"); pr_alert("** You are running KernelSU in DEBUG mode **");
pr_alert("** **"); pr_alert("** **");
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
pr_alert("*************************************************************"); pr_alert("*************************************************************");
#endif #endif
ksu_core_init(); ksu_core_init();
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0); ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
ksu_allowlist_init(); ksu_allowlist_init();
ksu_throne_tracker_init(); ksu_throne_tracker_init();
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef CONFIG_KSU_KPROBES_HOOK
ksu_sucompat_init(); ksu_sucompat_init();
ksu_ksud_init(); ksu_ksud_init();
#else #else
pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html"); pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html");
#endif #endif
#ifdef CONFIG_KSU_TRACEPOINT_HOOK #ifdef CONFIG_KSU_TRACEPOINT_HOOK
@@ -63,30 +63,32 @@ int __init kernelsu_init(void)
#ifdef MODULE #ifdef MODULE
#ifndef CONFIG_KSU_DEBUG #ifndef CONFIG_KSU_DEBUG
kobject_del(&THIS_MODULE->mkobj.kobj); kobject_del(&THIS_MODULE->mkobj.kobj);
#endif #endif
#endif #endif
return 0; return 0;
} }
void kernelsu_exit(void) void kernelsu_exit(void)
{ {
ksu_allowlist_exit(); ksu_allowlist_exit();
ksu_throne_tracker_exit(); ksu_observer_exit();
destroy_workqueue(ksu_workqueue); ksu_throne_tracker_exit();
destroy_workqueue(ksu_workqueue);
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef CONFIG_KSU_KPROBES_HOOK
ksu_ksud_exit(); ksu_ksud_exit();
ksu_sucompat_exit(); ksu_sucompat_exit();
#endif #endif
#ifdef CONFIG_KSU_TRACEPOINT_HOOK #ifdef CONFIG_KSU_TRACEPOINT_HOOK
ksu_trace_unregister(); ksu_trace_unregister();
#endif #endif
ksu_core_exit(); ksu_core_exit();
} }
module_init(kernelsu_init); module_init(kernelsu_init);

View File

@@ -5,37 +5,14 @@
#include <linux/workqueue.h> #include <linux/workqueue.h>
#define KERNEL_SU_VERSION KSU_VERSION #define KERNEL_SU_VERSION KSU_VERSION
#define KERNEL_SU_OPTION 0xDEADBEEF #define KERNEL_SU_OPTION 0xBADC0DE
#define CMD_GRANT_ROOT 0 extern bool ksu_uid_scanner_enabled;
#define CMD_BECOME_MANAGER 1
#define CMD_GET_VERSION 2
#define CMD_ALLOW_SU 3
#define CMD_DENY_SU 4
#define CMD_GET_ALLOW_LIST 5
#define CMD_GET_DENY_LIST 6
#define CMD_REPORT_EVENT 7
#define CMD_SET_SEPOLICY 8
#define CMD_CHECK_SAFEMODE 9
#define CMD_GET_APP_PROFILE 10
#define CMD_SET_APP_PROFILE 11
#define CMD_UID_GRANTED_ROOT 12
#define CMD_UID_SHOULD_UMOUNT 13
#define CMD_IS_SU_ENABLED 14
#define CMD_ENABLE_SU 15
#ifdef CONFIG_KSU_MANUAL_SU #ifdef CONFIG_KSU_MANUAL_SU
#define CMD_MANUAL_SU_REQUEST 50 #define CMD_MANUAL_SU_REQUEST 50
#endif #endif
#define CMD_GET_FULL_VERSION 0xC0FFEE1A
#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
#define EVENT_POST_FS_DATA 1 #define EVENT_POST_FS_DATA 1
#define EVENT_BOOT_COMPLETED 2 #define EVENT_BOOT_COMPLETED 2
#define EVENT_MODULE_MOUNTED 3 #define EVENT_MODULE_MOUNTED 3
@@ -56,6 +33,10 @@
#define DYNAMIC_MANAGER_OP_GET 1 #define DYNAMIC_MANAGER_OP_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2 #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 { struct dynamic_manager_user_config {
unsigned int operation; unsigned int operation;
unsigned int size; unsigned int size;
@@ -71,67 +52,67 @@ struct manager_list_info {
}; };
struct root_profile { struct root_profile {
int32_t uid; int32_t uid;
int32_t gid; int32_t gid;
int32_t groups_count; int32_t groups_count;
int32_t groups[KSU_MAX_GROUPS]; int32_t groups[KSU_MAX_GROUPS];
// kernel_cap_t is u32[2] for capabilities v3 // kernel_cap_t is u32[2] for capabilities v3
struct { struct {
u64 effective; u64 effective;
u64 permitted; u64 permitted;
u64 inheritable; u64 inheritable;
} capabilities; } capabilities;
char selinux_domain[KSU_SELINUX_DOMAIN]; char selinux_domain[KSU_SELINUX_DOMAIN];
int32_t namespaces; int32_t namespaces;
}; };
struct non_root_profile { struct non_root_profile {
bool umount_modules; bool umount_modules;
}; };
struct app_profile { struct app_profile {
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this. // It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
u32 version; u32 version;
// this is usually the package of the app, but can be other value for special apps // this is usually the package of the app, but can be other value for special apps
char key[KSU_MAX_PACKAGE_NAME]; char key[KSU_MAX_PACKAGE_NAME];
int32_t current_uid; int32_t current_uid;
bool allow_su; bool allow_su;
union { union {
struct { struct {
bool use_default; bool use_default;
char template_name[KSU_MAX_PACKAGE_NAME]; char template_name[KSU_MAX_PACKAGE_NAME];
struct root_profile profile; struct root_profile profile;
} rp_config; } rp_config;
struct { struct {
bool use_default; bool use_default;
struct non_root_profile profile; struct non_root_profile profile;
} nrp_config; } nrp_config;
}; };
}; };
bool ksu_queue_work(struct work_struct *work); bool ksu_queue_work(struct work_struct *work);
static inline int startswith(char *s, char *prefix) static inline int startswith(char *s, char *prefix)
{ {
return strncmp(s, prefix, strlen(prefix)); return strncmp(s, prefix, strlen(prefix));
} }
static inline int endswith(const char *s, const char *t) static inline int endswith(const char *s, const char *t)
{ {
size_t slen = strlen(s); size_t slen = strlen(s);
size_t tlen = strlen(t); size_t tlen = strlen(t);
if (tlen > slen) if (tlen > slen)
return 1; return 1;
return strcmp(s + slen - tlen, t); return strcmp(s + slen - tlen, t);
} }
#endif #endif

View File

@@ -16,7 +16,7 @@ extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
void ksu_trace_execveat_hook_callback(void *data, int *fd, struct filename **filename_ptr, void ksu_trace_execveat_hook_callback(void *data, int *fd, struct filename **filename_ptr,
void *argv, void *envp, int *flags) void *argv, void *envp, int *flags)
{ {
ksu_handle_execveat(fd, filename_ptr, argv, envp, flags); ksu_handle_execveat(fd, filename_ptr, argv, envp, flags);
} }
void ksu_trace_faccessat_hook_callback(void *data, int *dfd, const char __user **filename_user, void ksu_trace_faccessat_hook_callback(void *data, int *dfd, const char __user **filename_user,
@@ -28,8 +28,8 @@ void ksu_trace_faccessat_hook_callback(void *data, int *dfd, const char __user *
void ksu_trace_sys_read_hook_callback(void *data, unsigned int fd, char __user **buf_ptr, void ksu_trace_sys_read_hook_callback(void *data, unsigned int fd, char __user **buf_ptr,
size_t *count_ptr) size_t *count_ptr)
{ {
if (unlikely(ksu_vfs_read_hook)) if (unlikely(ksu_vfs_read_hook))
ksu_handle_sys_read(fd, buf_ptr, count_ptr); ksu_handle_sys_read(fd, buf_ptr, count_ptr);
} }
void ksu_trace_stat_hook_callback(void *data, int *dfd, const char __user **filename_user, void ksu_trace_stat_hook_callback(void *data, int *dfd, const char __user **filename_user,
@@ -41,8 +41,8 @@ void ksu_trace_stat_hook_callback(void *data, int *dfd, const char __user **file
void ksu_trace_input_hook_callback(void *data, unsigned int *type, unsigned int *code, void ksu_trace_input_hook_callback(void *data, unsigned int *type, unsigned int *code,
int *value) int *value)
{ {
if (unlikely(ksu_input_hook)) if (unlikely(ksu_input_hook))
ksu_handle_input_handle_event(type, code, value); ksu_handle_input_handle_event(type, code, value);
} }
// end tracepoint callback functions // end tracepoint callback functions

View File

@@ -8,24 +8,24 @@
#include <linux/tracepoint.h> #include <linux/tracepoint.h>
DECLARE_TRACE(ksu_trace_execveat_hook, DECLARE_TRACE(ksu_trace_execveat_hook,
TP_PROTO(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags), TP_PROTO(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags),
TP_ARGS(fd, filename_ptr, argv, envp, flags)); TP_ARGS(fd, filename_ptr, argv, envp, flags));
DECLARE_TRACE(ksu_trace_faccessat_hook, DECLARE_TRACE(ksu_trace_faccessat_hook,
TP_PROTO(int *dfd, const char __user **filename_user, int *mode, int *flags), TP_PROTO(int *dfd, const char __user **filename_user, int *mode, int *flags),
TP_ARGS(dfd, filename_user, mode, flags)); TP_ARGS(dfd, filename_user, mode, flags));
DECLARE_TRACE(ksu_trace_sys_read_hook, DECLARE_TRACE(ksu_trace_sys_read_hook,
TP_PROTO(unsigned int fd, char __user **buf_ptr, size_t *count_ptr), TP_PROTO(unsigned int fd, char __user **buf_ptr, size_t *count_ptr),
TP_ARGS(fd, buf_ptr, count_ptr)); TP_ARGS(fd, buf_ptr, count_ptr));
DECLARE_TRACE(ksu_trace_stat_hook, DECLARE_TRACE(ksu_trace_stat_hook,
TP_PROTO(int *dfd, const char __user **filename_user, int *flags), TP_PROTO(int *dfd, const char __user **filename_user, int *flags),
TP_ARGS(dfd, filename_user, flags)); TP_ARGS(dfd, filename_user, flags));
DECLARE_TRACE(ksu_trace_input_hook, DECLARE_TRACE(ksu_trace_input_hook,
TP_PROTO(unsigned int *type, unsigned int *code, int *value), TP_PROTO(unsigned int *type, unsigned int *code, int *value),
TP_ARGS(type, code, value)); TP_ARGS(type, code, value));
#endif /* _KSU_TRACE_H */ #endif /* _KSU_TRACE_H */

File diff suppressed because it is too large Load Diff

View File

@@ -13,30 +13,33 @@ extern void ksu_add_manager(uid_t uid, int signature_index);
extern void ksu_remove_manager(uid_t uid); extern void ksu_remove_manager(uid_t uid);
extern int ksu_get_manager_signature_index(uid_t uid); extern int ksu_get_manager_signature_index(uid_t uid);
static inline bool ksu_is_manager_uid_valid() static inline bool ksu_is_manager_uid_valid(void)
{ {
return ksu_manager_uid != KSU_INVALID_UID; return ksu_manager_uid != KSU_INVALID_UID;
} }
static inline bool is_manager() static inline bool is_manager(void)
{ {
return unlikely(ksu_is_any_manager(current_uid().val) || return unlikely(ksu_is_any_manager(current_uid().val) ||
(ksu_manager_uid != KSU_INVALID_UID && ksu_manager_uid == current_uid().val)); (ksu_manager_uid != KSU_INVALID_UID && ksu_manager_uid == current_uid().val));
} }
static inline uid_t ksu_get_manager_uid() static inline uid_t ksu_get_manager_uid(void)
{ {
return ksu_manager_uid; return ksu_manager_uid;
} }
static inline void ksu_set_manager_uid(uid_t uid) static inline void ksu_set_manager_uid(uid_t uid)
{ {
ksu_manager_uid = uid; ksu_manager_uid = uid;
} }
static inline void ksu_invalidate_manager_uid() static inline void ksu_invalidate_manager_uid(void)
{ {
ksu_manager_uid = KSU_INVALID_UID; ksu_manager_uid = KSU_INVALID_UID;
} }
int ksu_observer_init(void);
void ksu_observer_exit(void);
#endif #endif

View File

@@ -10,8 +10,8 @@
#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000" #define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000"
typedef struct { typedef struct {
unsigned size; unsigned size;
const char *sha256; const char *sha256;
} apk_sign_key_t; } apk_sign_key_t;
#endif /* MANAGER_SIGN_H */ #endif /* MANAGER_SIGN_H */

View File

@@ -120,9 +120,9 @@ static char* ksu_generate_auth_token(void)
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1); strscpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
#else #else
strlcpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1); strlcpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
#endif #endif
auth_tokens[token_count].expire_time = jiffies + KSU_TOKEN_EXPIRE_TIME * HZ; auth_tokens[token_count].expire_time = jiffies + KSU_TOKEN_EXPIRE_TIME * HZ;
auth_tokens[token_count].used = false; auth_tokens[token_count].used = false;
@@ -203,9 +203,9 @@ static int handle_token_generation(struct manual_su_request *request)
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1); strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
#else #else
strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1); strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
#endif #endif
pr_info("manual_su: auth token generated successfully\n"); pr_info("manual_su: auth token generated successfully\n");
@@ -344,4 +344,4 @@ static void add_pending_root(uid_t uid)
pending_uids[pending_cnt++] = (struct pending_uid){uid, 0}; pending_uids[pending_cnt++] = (struct pending_uid){uid, 0};
ksu_temp_grant_root_once(uid); ksu_temp_grant_root_once(uid);
pr_info("pending_root: cached UID %d\n", uid); pr_info("pending_root: cached UID %d\n", uid);
} }

View File

@@ -3,6 +3,11 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
#define mmap_lock mmap_sem
#endif
#define ksu_task_is_dead(t) ((t)->exit_state != 0) #define ksu_task_is_dead(t) ((t)->exit_state != 0)

133
kernel/pkg_observer.c Normal file
View File

@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/fsnotify_backend.h>
#include <linux/slab.h>
#include <linux/rculist.h>
#include <linux/version.h>
#include "klog.h" // IWYU pragma: keep
#include "ksu.h"
#include "throne_tracker.h"
#include "throne_comm.h"
#define MASK_SYSTEM (FS_CREATE | FS_MOVE | FS_EVENT_ON_CHILD)
struct watch_dir {
const char *path;
u32 mask;
struct path kpath;
struct inode *inode;
struct fsnotify_mark *mark;
};
static struct fsnotify_group *g;
static int ksu_handle_inode_event(struct fsnotify_mark *mark, u32 mask,
struct inode *inode, struct inode *dir,
const struct qstr *file_name, u32 cookie)
{
if (!file_name)
return 0;
if (mask & FS_ISDIR)
return 0;
if (file_name->len == 13 &&
!memcmp(file_name->name, "packages.list", 13)) {
pr_info("packages.list detected: %d\n", mask);
if (ksu_uid_scanner_enabled) {
ksu_request_userspace_scan();
}
track_throne();
}
return 0;
}
static const struct fsnotify_ops ksu_ops = {
.handle_inode_event = ksu_handle_inode_event,
};
static int add_mark_on_inode(struct inode *inode, u32 mask,
struct fsnotify_mark **out)
{
struct fsnotify_mark *m;
m = kzalloc(sizeof(*m), GFP_KERNEL);
if (!m)
return -ENOMEM;
fsnotify_init_mark(m, g);
m->mask = mask;
if (fsnotify_add_inode_mark(m, inode, 0)) {
fsnotify_put_mark(m);
return -EINVAL;
}
*out = m;
return 0;
}
static int watch_one_dir(struct watch_dir *wd)
{
int ret = kern_path(wd->path, LOOKUP_FOLLOW, &wd->kpath);
if (ret) {
pr_info("path not ready: %s (%d)\n", wd->path, ret);
return ret;
}
wd->inode = d_inode(wd->kpath.dentry);
ihold(wd->inode);
ret = add_mark_on_inode(wd->inode, wd->mask, &wd->mark);
if (ret) {
pr_err("Add mark failed for %s (%d)\n", wd->path, ret);
path_put(&wd->kpath);
iput(wd->inode);
wd->inode = NULL;
return ret;
}
pr_info("watching %s\n", wd->path);
return 0;
}
static void unwatch_one_dir(struct watch_dir *wd)
{
if (wd->mark) {
fsnotify_destroy_mark(wd->mark, g);
fsnotify_put_mark(wd->mark);
wd->mark = NULL;
}
if (wd->inode) {
iput(wd->inode);
wd->inode = NULL;
}
if (wd->kpath.dentry) {
path_put(&wd->kpath);
memset(&wd->kpath, 0, sizeof(wd->kpath));
}
}
static struct watch_dir g_watch = { .path = "/data/system",
.mask = MASK_SYSTEM };
int ksu_observer_init(void)
{
int ret = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
g = fsnotify_alloc_group(&ksu_ops, 0);
#else
g = fsnotify_alloc_group(&ksu_ops);
#endif
if (IS_ERR(g))
return PTR_ERR(g);
ret = watch_one_dir(&g_watch);
pr_info("observer init done\n");
return 0;
}
void ksu_observer_exit(void)
{
unwatch_one_dir(&g_watch);
fsnotify_put_group(g);
pr_info("observer exit done\n");
}

View File

@@ -18,119 +18,119 @@
static struct policydb *get_policydb(void) static struct policydb *get_policydb(void)
{ {
struct policydb *db; struct policydb *db;
struct selinux_policy *policy = selinux_state.policy; struct selinux_policy *policy = selinux_state.policy;
db = &policy->policydb; db = &policy->policydb;
return db; return db;
} }
static DEFINE_MUTEX(ksu_rules); static DEFINE_MUTEX(ksu_rules);
void apply_kernelsu_rules() void apply_kernelsu_rules()
{ {
struct policydb *db; struct policydb *db;
if (!getenforce()) { if (!getenforce()) {
pr_info("SELinux permissive or disabled, apply rules!\n"); pr_info("SELinux permissive or disabled, apply rules!\n");
} }
mutex_lock(&ksu_rules); mutex_lock(&ksu_rules);
db = get_policydb(); db = get_policydb();
ksu_permissive(db, KERNEL_SU_DOMAIN); ksu_permissive(db, KERNEL_SU_DOMAIN);
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject"); ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "netdomain"); ksu_typeattribute(db, KERNEL_SU_DOMAIN, "netdomain");
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "bluetoothdomain"); ksu_typeattribute(db, KERNEL_SU_DOMAIN, "bluetoothdomain");
// Create unconstrained file type // Create unconstrained file type
ksu_type(db, KERNEL_SU_FILE, "file_type"); ksu_type(db, KERNEL_SU_FILE, "file_type");
ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject"); ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject");
ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL); ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL);
// allow all! // allow all!
ksu_allow(db, KERNEL_SU_DOMAIN, ALL, ALL, ALL); ksu_allow(db, KERNEL_SU_DOMAIN, ALL, ALL, ALL);
// allow us do any ioctl // allow us do any ioctl
if (db->policyvers >= POLICYDB_VERSION_XPERMS_IOCTL) { if (db->policyvers >= POLICYDB_VERSION_XPERMS_IOCTL) {
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "blk_file", ALL); ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "blk_file", ALL);
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "fifo_file", ALL); ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "fifo_file", ALL);
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL); ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL);
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "file", ALL); ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "file", ALL);
} }
// we need to save allowlist in /data/adb/ksu // we need to save allowlist in /data/adb/ksu
ksu_allow(db, "kernel", "adb_data_file", "dir", ALL); ksu_allow(db, "kernel", "adb_data_file", "dir", ALL);
ksu_allow(db, "kernel", "adb_data_file", "file", ALL); ksu_allow(db, "kernel", "adb_data_file", "file", ALL);
// we need to search /data/app // we need to search /data/app
ksu_allow(db, "kernel", "apk_data_file", "file", "open"); ksu_allow(db, "kernel", "apk_data_file", "file", "open");
ksu_allow(db, "kernel", "apk_data_file", "dir", "open"); ksu_allow(db, "kernel", "apk_data_file", "dir", "open");
ksu_allow(db, "kernel", "apk_data_file", "dir", "read"); ksu_allow(db, "kernel", "apk_data_file", "dir", "read");
ksu_allow(db, "kernel", "apk_data_file", "dir", "search"); ksu_allow(db, "kernel", "apk_data_file", "dir", "search");
// we may need to do mount on shell // we may need to do mount on shell
ksu_allow(db, "kernel", "shell_data_file", "file", ALL); ksu_allow(db, "kernel", "shell_data_file", "file", ALL);
// we need to read /data/system/packages.list // we need to read /data/system/packages.list
ksu_allow(db, "kernel", "kernel", "capability", "dac_override"); ksu_allow(db, "kernel", "kernel", "capability", "dac_override");
// Android 10+: // Android 10+:
// http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512 // http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512
ksu_allow(db, "kernel", "packages_list_file", "file", ALL); ksu_allow(db, "kernel", "packages_list_file", "file", ALL);
// Kernel 4.4 // Kernel 4.4
ksu_allow(db, "kernel", "packages_list_file", "dir", ALL); ksu_allow(db, "kernel", "packages_list_file", "dir", ALL);
// Android 9-: // Android 9-:
// http://aospxref.com/android-9.0.0_r61/xref/system/sepolicy/private/file_contexts#360 // http://aospxref.com/android-9.0.0_r61/xref/system/sepolicy/private/file_contexts#360
ksu_allow(db, "kernel", "system_data_file", "file", ALL); ksu_allow(db, "kernel", "system_data_file", "file", ALL);
ksu_allow(db, "kernel", "system_data_file", "dir", ALL); ksu_allow(db, "kernel", "system_data_file", "dir", ALL);
// our ksud triggered by init // our ksud triggered by init
ksu_allow(db, "init", "adb_data_file", "file", ALL); ksu_allow(db, "init", "adb_data_file", "file", ALL);
ksu_allow(db, "init", "adb_data_file", "dir", ALL); // #1289 ksu_allow(db, "init", "adb_data_file", "dir", ALL); // #1289
ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL); ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL);
// we need to umount modules in zygote // we need to umount modules in zygote
ksu_allow(db, "zygote", "adb_data_file", "dir", "search"); ksu_allow(db, "zygote", "adb_data_file", "dir", "search");
// copied from Magisk rules // copied from Magisk rules
// suRights // suRights
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "search"); ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "search");
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "read"); ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "dir", "read");
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "open"); ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "open");
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "read"); ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "read");
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "process", "getattr"); ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "process", "getattr");
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "process", "sigchld"); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "process", "sigchld");
// allowLog // allowLog
ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "dir", "search"); ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "dir", "search");
ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "read"); ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "read");
ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "open"); ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "open");
ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "getattr"); ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "getattr");
// dumpsys // dumpsys
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fd", "use"); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fd", "use");
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "write"); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "write");
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "read"); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "read");
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "open"); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "open");
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "getattr"); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "getattr");
// bootctl // bootctl
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "dir", "search"); ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "dir", "search");
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "read"); ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "read");
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "open"); ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "file", "open");
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process", ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process",
"getattr"); "getattr");
// For mounting loop devices, mirrors, tmpfs // For mounting loop devices, mirrors, tmpfs
ksu_allow(db, "kernel", ALL, "file", "read"); ksu_allow(db, "kernel", ALL, "file", "read");
ksu_allow(db, "kernel", ALL, "file", "write"); ksu_allow(db, "kernel", ALL, "file", "write");
// Allow all binder transactions // Allow all binder transactions
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
// Allow system server kill su process // Allow system server kill su process
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid"); ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid");
ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill"); ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill");
// https://android-review.googlesource.com/c/platform/system/logging/+/3725346 // https://android-review.googlesource.com/c/platform/system/logging/+/3725346
ksu_dontaudit(db, "untrusted_app", KERNEL_SU_DOMAIN, "dir", "getattr"); ksu_dontaudit(db, "untrusted_app", KERNEL_SU_DOMAIN, "dir", "getattr");
mutex_unlock(&ksu_rules); mutex_unlock(&ksu_rules);
} }
#define MAX_SEPOL_LEN 128 #define MAX_SEPOL_LEN 128
@@ -147,399 +147,399 @@ void apply_kernelsu_rules()
#ifdef CONFIG_64BIT #ifdef CONFIG_64BIT
struct sepol_data { struct sepol_data {
u32 cmd; u32 cmd;
u32 subcmd; u32 subcmd;
u64 field_sepol1; u64 field_sepol1;
u64 field_sepol2; u64 field_sepol2;
u64 field_sepol3; u64 field_sepol3;
u64 field_sepol4; u64 field_sepol4;
u64 field_sepol5; u64 field_sepol5;
u64 field_sepol6; u64 field_sepol6;
u64 field_sepol7; u64 field_sepol7;
}; };
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
extern bool ksu_is_compat __read_mostly; extern bool ksu_is_compat __read_mostly;
struct sepol_compat_data { struct sepol_compat_data {
u32 cmd; u32 cmd;
u32 subcmd; u32 subcmd;
u32 field_sepol1; u32 field_sepol1;
u32 field_sepol2; u32 field_sepol2;
u32 field_sepol3; u32 field_sepol3;
u32 field_sepol4; u32 field_sepol4;
u32 field_sepol5; u32 field_sepol5;
u32 field_sepol6; u32 field_sepol6;
u32 field_sepol7; u32 field_sepol7;
}; };
#endif // CONFIG_COMPAT #endif // CONFIG_COMPAT
#else #else
struct sepol_data { struct sepol_data {
u32 cmd; u32 cmd;
u32 subcmd; u32 subcmd;
u32 field_sepol1; u32 field_sepol1;
u32 field_sepol2; u32 field_sepol2;
u32 field_sepol3; u32 field_sepol3;
u32 field_sepol4; u32 field_sepol4;
u32 field_sepol5; u32 field_sepol5;
u32 field_sepol6; u32 field_sepol6;
u32 field_sepol7; u32 field_sepol7;
}; };
#endif // CONFIG_64BIT #endif // CONFIG_64BIT
static int get_object(char *buf, char __user *user_object, size_t buf_sz, static int get_object(char *buf, char __user *user_object, size_t buf_sz,
char **object) char **object)
{ {
if (!user_object) { if (!user_object) {
*object = ALL; *object = ALL;
return 0; return 0;
} }
if (strncpy_from_user(buf, user_object, buf_sz) < 0) { if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
return -1; return -1;
} }
*object = buf; *object = buf;
return 0; return 0;
} }
// reset avc cache table, otherwise the new rules will not take effect if already denied // reset avc cache table, otherwise the new rules will not take effect if already denied
static void reset_avc_cache() static void reset_avc_cache()
{ {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))
avc_ss_reset(0); avc_ss_reset(0);
selnl_notify_policyload(0); selnl_notify_policyload(0);
selinux_status_update_policyload(0); selinux_status_update_policyload(0);
#else #else
struct selinux_avc *avc = selinux_state.avc; struct selinux_avc *avc = selinux_state.avc;
avc_ss_reset(avc, 0); avc_ss_reset(avc, 0);
selnl_notify_policyload(0); selnl_notify_policyload(0);
selinux_status_update_policyload(&selinux_state, 0); selinux_status_update_policyload(&selinux_state, 0);
#endif #endif
selinux_xfrm_notify_policyload(); selinux_xfrm_notify_policyload();
} }
int handle_sepolicy(unsigned long arg3, void __user *arg4) int handle_sepolicy(unsigned long arg3, void __user *arg4)
{ {
struct policydb *db; struct policydb *db;
if (!arg4) { if (!arg4) {
return -1; return -1;
} }
if (!getenforce()) { if (!getenforce()) {
pr_info("SELinux permissive or disabled when handle policy!\n"); pr_info("SELinux permissive or disabled when handle policy!\n");
} }
u32 cmd, subcmd; u32 cmd, subcmd;
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7; char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT) #if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
if (unlikely(ksu_is_compat)) { if (unlikely(ksu_is_compat)) {
struct sepol_compat_data compat_data; struct sepol_compat_data compat_data;
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) { if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
pr_err("sepol: copy sepol_data failed.\n"); pr_err("sepol: copy sepol_data failed.\n");
return -1; return -1;
} }
sepol1 = compat_ptr(compat_data.field_sepol1); sepol1 = compat_ptr(compat_data.field_sepol1);
sepol2 = compat_ptr(compat_data.field_sepol2); sepol2 = compat_ptr(compat_data.field_sepol2);
sepol3 = compat_ptr(compat_data.field_sepol3); sepol3 = compat_ptr(compat_data.field_sepol3);
sepol4 = compat_ptr(compat_data.field_sepol4); sepol4 = compat_ptr(compat_data.field_sepol4);
sepol5 = compat_ptr(compat_data.field_sepol5); sepol5 = compat_ptr(compat_data.field_sepol5);
sepol6 = compat_ptr(compat_data.field_sepol6); sepol6 = compat_ptr(compat_data.field_sepol6);
sepol7 = compat_ptr(compat_data.field_sepol7); sepol7 = compat_ptr(compat_data.field_sepol7);
cmd = compat_data.cmd; cmd = compat_data.cmd;
subcmd = compat_data.subcmd; subcmd = compat_data.subcmd;
} else { } else {
struct sepol_data data; struct sepol_data data;
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) { if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
pr_err("sepol: copy sepol_data failed.\n"); pr_err("sepol: copy sepol_data failed.\n");
return -1; return -1;
} }
sepol1 = data.field_sepol1; sepol1 = data.field_sepol1;
sepol2 = data.field_sepol2; sepol2 = data.field_sepol2;
sepol3 = data.field_sepol3; sepol3 = data.field_sepol3;
sepol4 = data.field_sepol4; sepol4 = data.field_sepol4;
sepol5 = data.field_sepol5; sepol5 = data.field_sepol5;
sepol6 = data.field_sepol6; sepol6 = data.field_sepol6;
sepol7 = data.field_sepol7; sepol7 = data.field_sepol7;
cmd = data.cmd; cmd = data.cmd;
subcmd = data.subcmd; subcmd = data.subcmd;
} }
#else #else
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n) // basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
struct sepol_data data; struct sepol_data data;
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) { if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
pr_err("sepol: copy sepol_data failed.\n"); pr_err("sepol: copy sepol_data failed.\n");
return -1; return -1;
} }
sepol1 = data.field_sepol1; sepol1 = data.field_sepol1;
sepol2 = data.field_sepol2; sepol2 = data.field_sepol2;
sepol3 = data.field_sepol3; sepol3 = data.field_sepol3;
sepol4 = data.field_sepol4; sepol4 = data.field_sepol4;
sepol5 = data.field_sepol5; sepol5 = data.field_sepol5;
sepol6 = data.field_sepol6; sepol6 = data.field_sepol6;
sepol7 = data.field_sepol7; sepol7 = data.field_sepol7;
cmd = data.cmd; cmd = data.cmd;
subcmd = data.subcmd; subcmd = data.subcmd;
#endif #endif
mutex_lock(&ksu_rules); mutex_lock(&ksu_rules);
db = get_policydb(); db = get_policydb();
int ret = -1; int ret = -1;
if (cmd == CMD_NORMAL_PERM) { if (cmd == CMD_NORMAL_PERM) {
char src_buf[MAX_SEPOL_LEN]; char src_buf[MAX_SEPOL_LEN];
char tgt_buf[MAX_SEPOL_LEN]; char tgt_buf[MAX_SEPOL_LEN];
char cls_buf[MAX_SEPOL_LEN]; char cls_buf[MAX_SEPOL_LEN];
char perm_buf[MAX_SEPOL_LEN]; char perm_buf[MAX_SEPOL_LEN];
char *s, *t, *c, *p; char *s, *t, *c, *p;
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) { if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) { if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) { if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) < if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
0) { 0) {
pr_err("sepol: copy perm failed.\n"); pr_err("sepol: copy perm failed.\n");
goto exit; goto exit;
} }
bool success = false; bool success = false;
if (subcmd == 1) { if (subcmd == 1) {
success = ksu_allow(db, s, t, c, p); success = ksu_allow(db, s, t, c, p);
} else if (subcmd == 2) { } else if (subcmd == 2) {
success = ksu_deny(db, s, t, c, p); success = ksu_deny(db, s, t, c, p);
} else if (subcmd == 3) { } else if (subcmd == 3) {
success = ksu_auditallow(db, s, t, c, p); success = ksu_auditallow(db, s, t, c, p);
} else if (subcmd == 4) { } else if (subcmd == 4) {
success = ksu_dontaudit(db, s, t, c, p); success = ksu_dontaudit(db, s, t, c, p);
} else { } else {
pr_err("sepol: unknown subcmd: %d\n", subcmd); pr_err("sepol: unknown subcmd: %d\n", subcmd);
} }
ret = success ? 0 : -1; ret = success ? 0 : -1;
} else if (cmd == CMD_XPERM) { } else if (cmd == CMD_XPERM) {
char src_buf[MAX_SEPOL_LEN]; char src_buf[MAX_SEPOL_LEN];
char tgt_buf[MAX_SEPOL_LEN]; char tgt_buf[MAX_SEPOL_LEN];
char cls_buf[MAX_SEPOL_LEN]; char cls_buf[MAX_SEPOL_LEN];
char __maybe_unused char __maybe_unused
operation[MAX_SEPOL_LEN]; // it is always ioctl now! operation[MAX_SEPOL_LEN]; // it is always ioctl now!
char perm_set[MAX_SEPOL_LEN]; char perm_set[MAX_SEPOL_LEN];
char *s, *t, *c; char *s, *t, *c;
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) { if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) { if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) { if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(operation, sepol4, if (strncpy_from_user(operation, sepol4,
sizeof(operation)) < 0) { sizeof(operation)) < 0) {
pr_err("sepol: copy operation failed.\n"); pr_err("sepol: copy operation failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) < if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
0) { 0) {
pr_err("sepol: copy perm_set failed.\n"); pr_err("sepol: copy perm_set failed.\n");
goto exit; goto exit;
} }
bool success = false; bool success = false;
if (subcmd == 1) { if (subcmd == 1) {
success = ksu_allowxperm(db, s, t, c, perm_set); success = ksu_allowxperm(db, s, t, c, perm_set);
} else if (subcmd == 2) { } else if (subcmd == 2) {
success = ksu_auditallowxperm(db, s, t, c, perm_set); success = ksu_auditallowxperm(db, s, t, c, perm_set);
} else if (subcmd == 3) { } else if (subcmd == 3) {
success = ksu_dontauditxperm(db, s, t, c, perm_set); success = ksu_dontauditxperm(db, s, t, c, perm_set);
} else { } else {
pr_err("sepol: unknown subcmd: %d\n", subcmd); pr_err("sepol: unknown subcmd: %d\n", subcmd);
} }
ret = success ? 0 : -1; ret = success ? 0 : -1;
} else if (cmd == CMD_TYPE_STATE) { } else if (cmd == CMD_TYPE_STATE) {
char src[MAX_SEPOL_LEN]; char src[MAX_SEPOL_LEN];
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
bool success = false; bool success = false;
if (subcmd == 1) { if (subcmd == 1) {
success = ksu_permissive(db, src); success = ksu_permissive(db, src);
} else if (subcmd == 2) { } else if (subcmd == 2) {
success = ksu_enforce(db, src); success = ksu_enforce(db, src);
} else { } else {
pr_err("sepol: unknown subcmd: %d\n", subcmd); pr_err("sepol: unknown subcmd: %d\n", subcmd);
} }
if (success) if (success)
ret = 0; ret = 0;
} else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) { } else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) {
char type[MAX_SEPOL_LEN]; char type[MAX_SEPOL_LEN];
char attr[MAX_SEPOL_LEN]; char attr[MAX_SEPOL_LEN];
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) { if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
pr_err("sepol: copy type failed.\n"); pr_err("sepol: copy type failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) { if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
pr_err("sepol: copy attr failed.\n"); pr_err("sepol: copy attr failed.\n");
goto exit; goto exit;
} }
bool success = false; bool success = false;
if (cmd == CMD_TYPE) { if (cmd == CMD_TYPE) {
success = ksu_type(db, type, attr); success = ksu_type(db, type, attr);
} else { } else {
success = ksu_typeattribute(db, type, attr); success = ksu_typeattribute(db, type, attr);
} }
if (!success) { if (!success) {
pr_err("sepol: %d failed.\n", cmd); pr_err("sepol: %d failed.\n", cmd);
goto exit; goto exit;
} }
ret = 0; ret = 0;
} else if (cmd == CMD_ATTR) { } else if (cmd == CMD_ATTR) {
char attr[MAX_SEPOL_LEN]; char attr[MAX_SEPOL_LEN];
if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) { if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
pr_err("sepol: copy attr failed.\n"); pr_err("sepol: copy attr failed.\n");
goto exit; goto exit;
} }
if (!ksu_attribute(db, attr)) { if (!ksu_attribute(db, attr)) {
pr_err("sepol: %d failed.\n", cmd); pr_err("sepol: %d failed.\n", cmd);
goto exit; goto exit;
} }
ret = 0; ret = 0;
} else if (cmd == CMD_TYPE_TRANSITION) { } else if (cmd == CMD_TYPE_TRANSITION) {
char src[MAX_SEPOL_LEN]; char src[MAX_SEPOL_LEN];
char tgt[MAX_SEPOL_LEN]; char tgt[MAX_SEPOL_LEN];
char cls[MAX_SEPOL_LEN]; char cls[MAX_SEPOL_LEN];
char default_type[MAX_SEPOL_LEN]; char default_type[MAX_SEPOL_LEN];
char object[MAX_SEPOL_LEN]; char object[MAX_SEPOL_LEN];
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) { if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) { if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(default_type, sepol4, if (strncpy_from_user(default_type, sepol4,
sizeof(default_type)) < 0) { sizeof(default_type)) < 0) {
pr_err("sepol: copy default_type failed.\n"); pr_err("sepol: copy default_type failed.\n");
goto exit; goto exit;
} }
char *real_object; char *real_object;
if (sepol5 == NULL) { if (sepol5 == NULL) {
real_object = NULL; real_object = NULL;
} else { } else {
if (strncpy_from_user(object, sepol5, if (strncpy_from_user(object, sepol5,
sizeof(object)) < 0) { sizeof(object)) < 0) {
pr_err("sepol: copy object failed.\n"); pr_err("sepol: copy object failed.\n");
goto exit; goto exit;
} }
real_object = object; real_object = object;
} }
bool success = ksu_type_transition(db, src, tgt, cls, bool success = ksu_type_transition(db, src, tgt, cls,
default_type, real_object); default_type, real_object);
if (success) if (success)
ret = 0; ret = 0;
} else if (cmd == CMD_TYPE_CHANGE) { } else if (cmd == CMD_TYPE_CHANGE) {
char src[MAX_SEPOL_LEN]; char src[MAX_SEPOL_LEN];
char tgt[MAX_SEPOL_LEN]; char tgt[MAX_SEPOL_LEN];
char cls[MAX_SEPOL_LEN]; char cls[MAX_SEPOL_LEN];
char default_type[MAX_SEPOL_LEN]; char default_type[MAX_SEPOL_LEN];
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) { if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
pr_err("sepol: copy tgt failed.\n"); pr_err("sepol: copy tgt failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) { if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
pr_err("sepol: copy cls failed.\n"); pr_err("sepol: copy cls failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(default_type, sepol4, if (strncpy_from_user(default_type, sepol4,
sizeof(default_type)) < 0) { sizeof(default_type)) < 0) {
pr_err("sepol: copy default_type failed.\n"); pr_err("sepol: copy default_type failed.\n");
goto exit; goto exit;
} }
bool success = false; bool success = false;
if (subcmd == 1) { if (subcmd == 1) {
success = ksu_type_change(db, src, tgt, cls, success = ksu_type_change(db, src, tgt, cls,
default_type); default_type);
} else if (subcmd == 2) { } else if (subcmd == 2) {
success = ksu_type_member(db, src, tgt, cls, success = ksu_type_member(db, src, tgt, cls,
default_type); default_type);
} else { } else {
pr_err("sepol: unknown subcmd: %d\n", subcmd); pr_err("sepol: unknown subcmd: %d\n", subcmd);
} }
if (success) if (success)
ret = 0; ret = 0;
} else if (cmd == CMD_GENFSCON) { } else if (cmd == CMD_GENFSCON) {
char name[MAX_SEPOL_LEN]; char name[MAX_SEPOL_LEN];
char path[MAX_SEPOL_LEN]; char path[MAX_SEPOL_LEN];
char context[MAX_SEPOL_LEN]; char context[MAX_SEPOL_LEN];
if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) { if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
pr_err("sepol: copy name failed.\n"); pr_err("sepol: copy name failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) { if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
pr_err("sepol: copy path failed.\n"); pr_err("sepol: copy path failed.\n");
goto exit; goto exit;
} }
if (strncpy_from_user(context, sepol3, sizeof(context)) < if (strncpy_from_user(context, sepol3, sizeof(context)) <
0) { 0) {
pr_err("sepol: copy context failed.\n"); pr_err("sepol: copy context failed.\n");
goto exit; goto exit;
} }
if (!ksu_genfscon(db, name, path, context)) { if (!ksu_genfscon(db, name, path, context)) {
pr_err("sepol: %d failed.\n", cmd); pr_err("sepol: %d failed.\n", cmd);
goto exit; goto exit;
} }
ret = 0; ret = 0;
} else { } else {
pr_err("sepol: unknown cmd: %d\n", cmd); pr_err("sepol: unknown cmd: %d\n", cmd);
} }
exit: exit:
mutex_unlock(&ksu_rules); mutex_unlock(&ksu_rules);
// only allow and xallow needs to reset avc cache, but we cannot do that because // only allow and xallow needs to reset avc cache, but we cannot do that because
// we are in atomic context. so we just reset it every time. // we are in atomic context. so we just reset it every time.
reset_avc_cache(); reset_avc_cache();
return ret; return ret;
} }

View File

@@ -7,41 +7,41 @@
static int transive_to_domain(const char *domain) static int transive_to_domain(const char *domain)
{ {
struct cred *cred; struct cred *cred;
struct task_security_struct *tsec; struct task_security_struct *tsec;
u32 sid; u32 sid;
int error; int error;
cred = (struct cred *)__task_cred(current); cred = (struct cred *)__task_cred(current);
tsec = cred->security; tsec = cred->security;
if (!tsec) { if (!tsec) {
pr_err("tsec == NULL!\n"); pr_err("tsec == NULL!\n");
return -1; return -1;
} }
error = security_secctx_to_secid(domain, strlen(domain), &sid); error = security_secctx_to_secid(domain, strlen(domain), &sid);
if (error) { if (error) {
pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n", pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n",
domain, sid, error); domain, sid, error);
} }
if (!error) { if (!error) {
tsec->sid = sid; tsec->sid = sid;
tsec->create_sid = 0; tsec->create_sid = 0;
tsec->keycreate_sid = 0; tsec->keycreate_sid = 0;
tsec->sockcreate_sid = 0; tsec->sockcreate_sid = 0;
} }
return error; return error;
} }
void setup_selinux(const char *domain) void setup_selinux(const char *domain)
{ {
if (transive_to_domain(domain)) { if (transive_to_domain(domain)) {
pr_err("transive domain failed.\n"); pr_err("transive domain failed.\n");
return; return;
} }
/* we didn't need this now, we have change selinux rules when boot! /* we didn't need this now, we have change selinux rules when boot!
if (!is_domain_permissive) { if (!is_domain_permissive) {
if (set_domain_permissive() == 0) { if (set_domain_permissive() == 0) {
is_domain_permissive = true; is_domain_permissive = true;
@@ -52,109 +52,109 @@ if (!is_domain_permissive) {
void setenforce(bool enforce) void setenforce(bool enforce)
{ {
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP #ifdef CONFIG_SECURITY_SELINUX_DEVELOP
selinux_state.enforcing = enforce; selinux_state.enforcing = enforce;
#endif #endif
} }
bool getenforce() bool getenforce()
{ {
#ifdef CONFIG_SECURITY_SELINUX_DISABLE #ifdef CONFIG_SECURITY_SELINUX_DISABLE
if (selinux_state.disabled) { if (selinux_state.disabled) {
return false; return false;
} }
#endif #endif
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP #ifdef CONFIG_SECURITY_SELINUX_DEVELOP
return selinux_state.enforcing; return selinux_state.enforcing;
#else #else
return true; return true;
#endif #endif
} }
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \ #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \
!defined(KSU_COMPAT_HAS_CURRENT_SID) !defined(KSU_COMPAT_HAS_CURRENT_SID)
/* /*
* get the subjective security ID of the current task * get the subjective security ID of the current task
*/ */
static inline u32 current_sid(void) static inline u32 current_sid(void)
{ {
const struct task_security_struct *tsec = current_security(); const struct task_security_struct *tsec = current_security();
return tsec->sid; return tsec->sid;
} }
#endif #endif
bool is_ksu_domain() bool is_ksu_domain()
{ {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
struct lsm_context ctx; struct lsm_context ctx;
#else #else
char *domain; char *domain;
u32 seclen; u32 seclen;
#endif #endif
bool result; bool result;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
int err = security_secid_to_secctx(current_sid(), &ctx); int err = security_secid_to_secctx(current_sid(), &ctx);
#else #else
int err = security_secid_to_secctx(current_sid(), &domain, &seclen); int err = security_secid_to_secctx(current_sid(), &domain, &seclen);
#endif #endif
if (err) { if (err) {
return false; return false;
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
result = strncmp(KERNEL_SU_DOMAIN, ctx.context, ctx.len) == 0; result = strncmp(KERNEL_SU_DOMAIN, ctx.context, ctx.len) == 0;
security_release_secctx(&ctx); security_release_secctx(&ctx);
#else #else
result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0; result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0;
security_release_secctx(domain, seclen); security_release_secctx(domain, seclen);
#endif #endif
return result; return result;
} }
bool is_zygote(void *sec) bool is_zygote(void *sec)
{ {
struct task_security_struct *tsec = (struct task_security_struct *)sec; struct task_security_struct *tsec = (struct task_security_struct *)sec;
if (!tsec) { if (!tsec) {
return false; return false;
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
struct lsm_context ctx; struct lsm_context ctx;
#else #else
char *domain; char *domain;
u32 seclen; u32 seclen;
#endif #endif
bool result; bool result;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
int err = security_secid_to_secctx(tsec->sid, &ctx); int err = security_secid_to_secctx(tsec->sid, &ctx);
#else #else
int err = security_secid_to_secctx(tsec->sid, &domain, &seclen); int err = security_secid_to_secctx(tsec->sid, &domain, &seclen);
#endif #endif
if (err) { if (err) {
return false; return false;
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
result = strncmp("u:r:zygote:s0", ctx.context, ctx.len) == 0; result = strncmp("u:r:zygote:s0", ctx.context, ctx.len) == 0;
security_release_secctx(&ctx); security_release_secctx(&ctx);
#else #else
result = strncmp("u:r:zygote:s0", domain, seclen) == 0; result = strncmp("u:r:zygote:s0", domain, seclen) == 0;
security_release_secctx(domain, seclen); security_release_secctx(domain, seclen);
#endif #endif
return result; return result;
} }
#define DEVPTS_DOMAIN "u:object_r:ksu_file:s0" #define DEVPTS_DOMAIN "u:object_r:ksu_file:s0"
u32 ksu_get_devpts_sid() u32 ksu_get_devpts_sid()
{ {
u32 devpts_sid = 0; u32 devpts_sid = 0;
int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN), int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN),
&devpts_sid); &devpts_sid);
if (err) { if (err) {
pr_info("get devpts sid err %d\n", err); pr_info("get devpts sid err %d\n", err);
} }
return devpts_sid; return devpts_sid;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -15,32 +15,32 @@ bool ksu_exists(struct policydb *db, const char *type);
// Access vector rules // Access vector rules
bool ksu_allow(struct policydb *db, const char *src, const char *tgt, bool ksu_allow(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *perm); const char *cls, const char *perm);
bool ksu_deny(struct policydb *db, const char *src, const char *tgt, bool ksu_deny(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *perm); const char *cls, const char *perm);
bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt, bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *perm); const char *cls, const char *perm);
bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt, bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *perm); const char *cls, const char *perm);
// Extended permissions access vector rules // Extended permissions access vector rules
bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt, bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *range); const char *cls, const char *range);
bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt, bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *range); const char *cls, const char *range);
bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt, bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *range); const char *cls, const char *range);
// Type rules // Type rules
bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt, bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *def, const char *obj); const char *cls, const char *def, const char *obj);
bool ksu_type_change(struct policydb *db, const char *src, const char *tgt, bool ksu_type_change(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *def); const char *cls, const char *def);
bool ksu_type_member(struct policydb *db, const char *src, const char *tgt, bool ksu_type_member(struct policydb *db, const char *src, const char *tgt,
const char *cls, const char *def); const char *cls, const char *def);
// File system labeling // File system labeling
bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path, bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path,
const char *ctx); const char *ctx);
#endif #endif

View File

@@ -29,312 +29,312 @@ static bool ksu_sucompat_hook_state __read_mostly = true;
static void __user *userspace_stack_buffer(const void *d, size_t len) static void __user *userspace_stack_buffer(const void *d, size_t len)
{ {
/* To avoid having to mmap a page in userspace, just write below the stack /* To avoid having to mmap a page in userspace, just write below the stack
* pointer. */ * pointer. */
char __user *p = (void __user *)current_user_stack_pointer() - len; char __user *p = (void __user *)current_user_stack_pointer() - len;
return copy_to_user(p, d, len) ? NULL : p; return copy_to_user(p, d, len) ? NULL : p;
} }
static char __user *sh_user_path(void) static char __user *sh_user_path(void)
{ {
static const char sh_path[] = "/system/bin/sh"; static const char sh_path[] = "/system/bin/sh";
return userspace_stack_buffer(sh_path, sizeof(sh_path)); return userspace_stack_buffer(sh_path, sizeof(sh_path));
} }
static char __user *ksud_user_path(void) static char __user *ksud_user_path(void)
{ {
static const char ksud_path[] = KSUD_PATH; static const char ksud_path[] = KSUD_PATH;
return userspace_stack_buffer(ksud_path, sizeof(ksud_path)); return userspace_stack_buffer(ksud_path, sizeof(ksud_path));
} }
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
int *__unused_flags) int *__unused_flags)
{ {
const char su[] = SU_PATH; const char su[] = SU_PATH;
#ifndef CONFIG_KSU_KPROBES_HOOK #ifndef CONFIG_KSU_KPROBES_HOOK
if (!ksu_sucompat_hook_state) { if (!ksu_sucompat_hook_state) {
return 0; return 0;
} }
#endif #endif
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
char path[sizeof(su) + 1]; char path[sizeof(su) + 1];
memset(path, 0, sizeof(path)); memset(path, 0, sizeof(path));
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
if (unlikely(!memcmp(path, su, sizeof(su)))) { if (unlikely(!memcmp(path, su, sizeof(su)))) {
#if __SULOG_GATE #if __SULOG_GATE
ksu_sulog_report_syscall(current_uid().val, NULL, "faccessat", path); ksu_sulog_report_syscall(current_uid().val, NULL, "faccessat", path);
#endif #endif
pr_info("faccessat su->sh!\n"); pr_info("faccessat su->sh!\n");
*filename_user = sh_user_path(); *filename_user = sh_user_path();
} }
return 0; return 0;
} }
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
{ {
// const char sh[] = SH_PATH; // const char sh[] = SH_PATH;
const char su[] = SU_PATH; const char su[] = SU_PATH;
#ifndef CONFIG_KSU_KPROBES_HOOK #ifndef CONFIG_KSU_KPROBES_HOOK
if (!ksu_sucompat_hook_state) { if (!ksu_sucompat_hook_state) {
return 0; return 0;
} }
#endif #endif
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
if (unlikely(!filename_user)) { if (unlikely(!filename_user)) {
return 0; return 0;
} }
char path[sizeof(su) + 1]; char path[sizeof(su) + 1];
memset(path, 0, sizeof(path)); memset(path, 0, sizeof(path));
// Remove this later!! we use syscall hook, so this will never happen!!!!! // Remove this later!! we use syscall hook, so this will never happen!!!!!
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && 0 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && 0
// it becomes a `struct filename *` after 5.18 // it becomes a `struct filename *` after 5.18
// https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216 // https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216
const char sh[] = SH_PATH; const char sh[] = SH_PATH;
struct filename *filename = *((struct filename **)filename_user); struct filename *filename = *((struct filename **)filename_user);
if (IS_ERR(filename)) { if (IS_ERR(filename)) {
return 0; return 0;
} }
if (likely(memcmp(filename->name, su, sizeof(su)))) if (likely(memcmp(filename->name, su, sizeof(su))))
return 0; return 0;
pr_info("vfs_statx su->sh!\n"); pr_info("vfs_statx su->sh!\n");
memcpy((void *)filename->name, sh, sizeof(sh)); memcpy((void *)filename->name, sh, sizeof(sh));
#else #else
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
if (unlikely(!memcmp(path, su, sizeof(su)))) { if (unlikely(!memcmp(path, su, sizeof(su)))) {
#if __SULOG_GATE #if __SULOG_GATE
ksu_sulog_report_syscall(current_uid().val, NULL, "newfstatat", path); ksu_sulog_report_syscall(current_uid().val, NULL, "newfstatat", path);
#endif #endif
pr_info("newfstatat su->sh!\n"); pr_info("newfstatat su->sh!\n");
*filename_user = sh_user_path(); *filename_user = sh_user_path();
} }
#endif #endif
return 0; return 0;
} }
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
void *envp, int *flags) void *envp, int *flags)
{ {
return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, flags); return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, flags);
} }
// the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code // the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code
int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
void *__never_use_argv, void *__never_use_envp, void *__never_use_argv, void *__never_use_envp,
int *__never_use_flags) int *__never_use_flags)
{ {
struct filename *filename; struct filename *filename;
const char sh[] = KSUD_PATH; const char sh[] = KSUD_PATH;
const char su[] = SU_PATH; const char su[] = SU_PATH;
#ifndef CONFIG_KSU_KPROBES_HOOK #ifndef CONFIG_KSU_KPROBES_HOOK
if (!ksu_sucompat_hook_state) { if (!ksu_sucompat_hook_state) {
return 0; return 0;
} }
#endif #endif
if (unlikely(!filename_ptr)) if (unlikely(!filename_ptr))
return 0; return 0;
filename = *filename_ptr; filename = *filename_ptr;
if (IS_ERR(filename)) { if (IS_ERR(filename)) {
return 0; return 0;
} }
if (likely(memcmp(filename->name, su, sizeof(su)))) if (likely(memcmp(filename->name, su, sizeof(su))))
return 0; return 0;
#if __SULOG_GATE #if __SULOG_GATE
bool is_allowed = ksu_is_allow_uid(current_uid().val); bool is_allowed = ksu_is_allow_uid(current_uid().val);
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", filename->name); ksu_sulog_report_syscall(current_uid().val, NULL, "execve", filename->name);
if (!is_allowed) { if (!is_allowed) {
return 0; return 0;
} }
ksu_sulog_report_su_attempt(current_uid().val, NULL, filename->name, is_allowed); ksu_sulog_report_su_attempt(current_uid().val, NULL, filename->name, is_allowed);
#else #else
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
#endif #endif
pr_info("do_execveat_common su found\n"); pr_info("do_execveat_common su found\n");
memcpy((void *)filename->name, sh, sizeof(sh)); memcpy((void *)filename->name, sh, sizeof(sh));
escape_to_root(); escape_to_root();
return 0; return 0;
} }
int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user, int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
void *__never_use_argv, void *__never_use_envp, void *__never_use_argv, void *__never_use_envp,
int *__never_use_flags) int *__never_use_flags)
{ {
const char su[] = SU_PATH; const char su[] = SU_PATH;
char path[sizeof(su) + 1]; char path[sizeof(su) + 1];
#ifndef CONFIG_KSU_KPROBES_HOOK #ifndef CONFIG_KSU_KPROBES_HOOK
if (!ksu_sucompat_hook_state){ if (!ksu_sucompat_hook_state){
return 0; return 0;
} }
#endif #endif
if (unlikely(!filename_user)) if (unlikely(!filename_user))
return 0; return 0;
memset(path, 0, sizeof(path)); memset(path, 0, sizeof(path));
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
if (likely(memcmp(path, su, sizeof(su)))) if (likely(memcmp(path, su, sizeof(su))))
return 0; return 0;
#if __SULOG_GATE #if __SULOG_GATE
bool is_allowed = ksu_is_allow_uid(current_uid().val); bool is_allowed = ksu_is_allow_uid(current_uid().val);
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", path); ksu_sulog_report_syscall(current_uid().val, NULL, "execve", path);
if (!is_allowed) if (!is_allowed)
return 0; return 0;
ksu_sulog_report_su_attempt(current_uid().val, NULL, path, is_allowed); ksu_sulog_report_su_attempt(current_uid().val, NULL, path, is_allowed);
#else #else
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
#endif #endif
pr_info("sys_execve su found\n"); pr_info("sys_execve su found\n");
*filename_user = ksud_user_path(); *filename_user = ksud_user_path();
escape_to_root(); escape_to_root();
return 0; return 0;
} }
// dummified // dummified
int ksu_handle_devpts(struct inode *inode) int ksu_handle_devpts(struct inode *inode)
{ {
return 0; return 0;
} }
int __ksu_handle_devpts(struct inode *inode) int __ksu_handle_devpts(struct inode *inode)
{ {
#ifndef CONFIG_KSU_KPROBES_HOOK #ifndef CONFIG_KSU_KPROBES_HOOK
if (!ksu_sucompat_hook_state) if (!ksu_sucompat_hook_state)
return 0; return 0;
#endif #endif
if (!current->mm) { if (!current->mm) {
return 0; return 0;
} }
uid_t uid = current_uid().val; uid_t uid = current_uid().val;
if (uid % 100000 < 10000) { if (uid % 100000 < 10000) {
// not untrusted_app, ignore it // not untrusted_app, ignore it
return 0; return 0;
} }
if (likely(!ksu_is_allow_uid(uid))) if (likely(!ksu_is_allow_uid(uid)))
return 0; return 0;
struct inode_security_struct *sec = selinux_inode(inode); struct inode_security_struct *sec = selinux_inode(inode);
if (ksu_devpts_sid && sec) if (ksu_devpts_sid && sec)
sec->sid = ksu_devpts_sid; sec->sid = ksu_devpts_sid;
return 0; return 0;
} }
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef CONFIG_KSU_KPROBES_HOOK
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs) static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
{ {
struct pt_regs *real_regs = PT_REAL_REGS(regs); struct pt_regs *real_regs = PT_REAL_REGS(regs);
int *dfd = (int *)&PT_REGS_PARM1(real_regs); int *dfd = (int *)&PT_REGS_PARM1(real_regs);
const char __user **filename_user = const char __user **filename_user =
(const char **)&PT_REGS_PARM2(real_regs); (const char **)&PT_REGS_PARM2(real_regs);
int *mode = (int *)&PT_REGS_PARM3(real_regs); int *mode = (int *)&PT_REGS_PARM3(real_regs);
return ksu_handle_faccessat(dfd, filename_user, mode, NULL); return ksu_handle_faccessat(dfd, filename_user, mode, NULL);
} }
static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs) static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
{ {
struct pt_regs *real_regs = PT_REAL_REGS(regs); struct pt_regs *real_regs = PT_REAL_REGS(regs);
int *dfd = (int *)&PT_REGS_PARM1(real_regs); int *dfd = (int *)&PT_REGS_PARM1(real_regs);
const char __user **filename_user = const char __user **filename_user =
(const char **)&PT_REGS_PARM2(real_regs); (const char **)&PT_REGS_PARM2(real_regs);
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(real_regs); int *flags = (int *)&PT_REGS_SYSCALL_PARM4(real_regs);
return ksu_handle_stat(dfd, filename_user, flags); return ksu_handle_stat(dfd, filename_user, flags);
} }
static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs) static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
{ {
struct pt_regs *real_regs = PT_REAL_REGS(regs); struct pt_regs *real_regs = PT_REAL_REGS(regs);
const char __user **filename_user = const char __user **filename_user =
(const char **)&PT_REGS_PARM1(real_regs); (const char **)&PT_REGS_PARM1(real_regs);
return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL, return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL,
NULL); NULL);
} }
static struct kprobe *su_kps[4]; static struct kprobe *su_kps[4];
static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs) static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs)
{ {
struct inode *inode; struct inode *inode;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)
struct file *file = (struct file *)PT_REGS_PARM2(regs); struct file *file = (struct file *)PT_REGS_PARM2(regs);
inode = file->f_path.dentry->d_inode; inode = file->f_path.dentry->d_inode;
#else #else
inode = (struct inode *)PT_REGS_PARM2(regs); inode = (struct inode *)PT_REGS_PARM2(regs);
#endif #endif
return ksu_handle_devpts(inode); return ksu_handle_devpts(inode);
} }
static struct kprobe *init_kprobe(const char *name, static struct kprobe *init_kprobe(const char *name,
kprobe_pre_handler_t handler) kprobe_pre_handler_t handler)
{ {
struct kprobe *kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL); struct kprobe *kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL);
if (!kp) if (!kp)
return NULL; return NULL;
kp->symbol_name = name; kp->symbol_name = name;
kp->pre_handler = handler; kp->pre_handler = handler;
int ret = register_kprobe(kp); int ret = register_kprobe(kp);
pr_info("sucompat: register_%s kprobe: %d\n", name, ret); pr_info("sucompat: register_%s kprobe: %d\n", name, ret);
if (ret) { if (ret) {
kfree(kp); kfree(kp);
return NULL; return NULL;
} }
return kp; return kp;
} }
static void destroy_kprobe(struct kprobe **kp_ptr) static void destroy_kprobe(struct kprobe **kp_ptr)
{ {
struct kprobe *kp = *kp_ptr; struct kprobe *kp = *kp_ptr;
if (!kp) if (!kp)
return; return;
unregister_kprobe(kp); unregister_kprobe(kp);
synchronize_rcu(); synchronize_rcu();
kfree(kp); kfree(kp);
*kp_ptr = NULL; *kp_ptr = NULL;
} }
#endif #endif
@@ -343,25 +343,25 @@ static void destroy_kprobe(struct kprobe **kp_ptr)
void ksu_sucompat_init() void ksu_sucompat_init()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef CONFIG_KSU_KPROBES_HOOK
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre); su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre); su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre); su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre); su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre);
#else #else
ksu_sucompat_hook_state = true; ksu_sucompat_hook_state = true;
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n"); pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
#endif #endif
} }
void ksu_sucompat_exit() void ksu_sucompat_exit()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef CONFIG_KSU_KPROBES_HOOK
int i; int i;
for (i = 0; i < ARRAY_SIZE(su_kps); i++) { for (i = 0; i < ARRAY_SIZE(su_kps); i++) {
destroy_kprobe(&su_kps[i]); destroy_kprobe(&su_kps[i]);
} }
#else #else
ksu_sucompat_hook_state = false; ksu_sucompat_hook_state = false;
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n"); pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
#endif #endif
} }

View File

@@ -30,380 +30,380 @@ static bool sulog_enabled = true;
static void get_timestamp(char *buf, size_t len) static void get_timestamp(char *buf, size_t len)
{ {
struct timespec64 ts; struct timespec64 ts;
struct tm tm; struct tm tm;
ktime_get_real_ts64(&ts); ktime_get_real_ts64(&ts);
time64_to_tm(ts.tv_sec - sys_tz.tz_minuteswest * 60, 0, &tm); time64_to_tm(ts.tv_sec - sys_tz.tz_minuteswest * 60, 0, &tm);
snprintf(buf, len, snprintf(buf, len,
"%04ld-%02d-%02d %02d:%02d:%02d", "%04ld-%02d-%02d %02d:%02d:%02d",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec); tm.tm_hour, tm.tm_min, tm.tm_sec);
} }
static void ksu_get_cmdline(char *full_comm, const char *comm, size_t buf_len) static void ksu_get_cmdline(char *full_comm, const char *comm, size_t buf_len)
{ {
char *kbuf; char *kbuf;
if (!full_comm || buf_len <= 0) if (!full_comm || buf_len <= 0)
return; return;
if (comm && strlen(comm) > 0) { if (comm && strlen(comm) > 0) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(full_comm, comm, buf_len); strscpy(full_comm, comm, buf_len);
#else #else
strlcpy(full_comm, comm, buf_len); strlcpy(full_comm, comm, buf_len);
#endif #endif
} else { } else {
kbuf = kmalloc(buf_len, GFP_ATOMIC); kbuf = kmalloc(buf_len, GFP_ATOMIC);
if (!kbuf) { if (!kbuf) {
pr_err("sulog: failed to allocate memory for kbuf\n"); pr_err("sulog: failed to allocate memory for kbuf\n");
return; return;
} }
int n = get_cmdline(current, kbuf, buf_len); int n = get_cmdline(current, kbuf, buf_len);
if (n <= 0) { if (n <= 0) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(full_comm, current->comm, buf_len); strscpy(full_comm, current->comm, buf_len);
#else #else
strlcpy(full_comm, current->comm, buf_len); strlcpy(full_comm, current->comm, buf_len);
#endif #endif
} else { } else {
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
if (kbuf[i] == '\0') kbuf[i] = ' '; if (kbuf[i] == '\0') kbuf[i] = ' ';
} }
kbuf[n < buf_len ? n : buf_len - 1] = '\0'; kbuf[n < buf_len ? n : buf_len - 1] = '\0';
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(full_comm, kbuf, buf_len); strscpy(full_comm, kbuf, buf_len);
#else #else
strlcpy(full_comm, kbuf, buf_len); strlcpy(full_comm, kbuf, buf_len);
#endif #endif
} }
kfree(kbuf); kfree(kbuf);
} }
} }
static bool dedup_should_print(uid_t uid, u8 type, static bool dedup_should_print(uid_t uid, u8 type,
const char *content, size_t len) const char *content, size_t len)
{ {
struct dedup_key key = { struct dedup_key key = {
.crc = dedup_calc_hash(content, len), .crc = dedup_calc_hash(content, len),
.uid = uid, .uid = uid,
.type = type, .type = type,
}; };
u64 now = ktime_get_ns(); u64 now = ktime_get_ns();
u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC; u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC;
u32 idx = key.crc & (SULOG_COMM_LEN - 1); u32 idx = key.crc & (SULOG_COMM_LEN - 1);
spin_lock(&dedup_lock); spin_lock(&dedup_lock);
struct dedup_entry *e = &dedup_tbl[idx]; struct dedup_entry *e = &dedup_tbl[idx];
if (e->key.crc == key.crc && if (e->key.crc == key.crc &&
e->key.uid == key.uid && e->key.uid == key.uid &&
e->key.type == key.type && e->key.type == key.type &&
(now - e->ts_ns) < delta_ns) { (now - e->ts_ns) < delta_ns) {
spin_unlock(&dedup_lock); spin_unlock(&dedup_lock);
return false; return false;
} }
e->key = key; e->key = key;
e->ts_ns = now; e->ts_ns = now;
spin_unlock(&dedup_lock); spin_unlock(&dedup_lock);
return true; return true;
} }
static void sulog_work_handler(struct work_struct *work) static void sulog_work_handler(struct work_struct *work)
{ {
struct file *fp; struct file *fp;
struct sulog_entry *entry, *tmp; struct sulog_entry *entry, *tmp;
LIST_HEAD(local_queue); LIST_HEAD(local_queue);
loff_t pos = 0; loff_t pos = 0;
mutex_lock(&sulog_mutex); mutex_lock(&sulog_mutex);
list_splice_init(&sulog_queue, &local_queue); list_splice_init(&sulog_queue, &local_queue);
mutex_unlock(&sulog_mutex); mutex_unlock(&sulog_mutex);
if (list_empty(&local_queue)) if (list_empty(&local_queue))
return; return;
fp = ksu_filp_open_compat(SULOG_PATH, O_WRONLY | O_CREAT | O_APPEND, 0640); fp = ksu_filp_open_compat(SULOG_PATH, O_WRONLY | O_CREAT | O_APPEND, 0640);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("sulog: failed to open log file: %ld\n", PTR_ERR(fp)); pr_err("sulog: failed to open log file: %ld\n", PTR_ERR(fp));
goto cleanup; goto cleanup;
} }
if (fp->f_inode->i_size > SULOG_MAX_SIZE) { if (fp->f_inode->i_size > SULOG_MAX_SIZE) {
pr_info("sulog: log file exceeds maximum size, clearing...\n"); pr_info("sulog: log file exceeds maximum size, clearing...\n");
if (vfs_truncate(&fp->f_path, 0)) { if (vfs_truncate(&fp->f_path, 0)) {
pr_err("sulog: failed to truncate log file\n"); pr_err("sulog: failed to truncate log file\n");
} }
pos = 0; pos = 0;
} else { } else {
pos = fp->f_inode->i_size; pos = fp->f_inode->i_size;
} }
list_for_each_entry(entry, &local_queue, list) { list_for_each_entry(entry, &local_queue, list) {
ksu_kernel_write_compat(fp, entry->content, strlen(entry->content), &pos); ksu_kernel_write_compat(fp, entry->content, strlen(entry->content), &pos);
} }
vfs_fsync(fp, 0); vfs_fsync(fp, 0);
filp_close(fp, 0); filp_close(fp, 0);
cleanup: cleanup:
list_for_each_entry_safe(entry, tmp, &local_queue, list) { list_for_each_entry_safe(entry, tmp, &local_queue, list) {
list_del(&entry->list); list_del(&entry->list);
kfree(entry); kfree(entry);
} }
} }
static void sulog_add_entry(const char *content) static void sulog_add_entry(const char *content)
{ {
struct sulog_entry *entry; struct sulog_entry *entry;
if (!sulog_enabled || !content) if (!sulog_enabled || !content)
return; return;
entry = kmalloc(sizeof(*entry), GFP_ATOMIC); entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
if (!entry) { if (!entry) {
pr_err("sulog: failed to allocate memory for log entry\n"); pr_err("sulog: failed to allocate memory for log entry\n");
return; return;
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(entry->content, content, SULOG_ENTRY_MAX_LEN - 1); strscpy(entry->content, content, SULOG_ENTRY_MAX_LEN - 1);
#else #else
strlcpy(entry->content, content, SULOG_ENTRY_MAX_LEN - 1); strlcpy(entry->content, content, SULOG_ENTRY_MAX_LEN - 1);
#endif #endif
mutex_lock(&sulog_mutex); mutex_lock(&sulog_mutex);
list_add_tail(&entry->list, &sulog_queue); list_add_tail(&entry->list, &sulog_queue);
mutex_unlock(&sulog_mutex); mutex_unlock(&sulog_mutex);
if (sulog_workqueue) if (sulog_workqueue)
queue_work(sulog_workqueue, &sulog_work); queue_work(sulog_workqueue, &sulog_work);
} }
void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method) void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method)
{ {
char *timestamp, *full_comm, *log_buf; char *timestamp, *full_comm, *log_buf;
if (!sulog_enabled) if (!sulog_enabled)
return; return;
timestamp = kmalloc(32, GFP_ATOMIC); timestamp = kmalloc(32, GFP_ATOMIC);
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
if (!timestamp || !full_comm || !log_buf) { if (!timestamp || !full_comm || !log_buf) {
pr_err("sulog: failed to allocate memory for su_grant log\n"); pr_err("sulog: failed to allocate memory for su_grant log\n");
goto cleanup; goto cleanup;
} }
get_timestamp(timestamp, 32); get_timestamp(timestamp, 32);
ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN); ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN);
snprintf(log_buf, SULOG_ENTRY_MAX_LEN, snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
"[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n", "[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n",
timestamp, uid, full_comm, timestamp, uid, full_comm,
method ? method : "unknown", current->pid); method ? method : "unknown", current->pid);
if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf))) if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
goto cleanup; goto cleanup;
sulog_add_entry(log_buf); sulog_add_entry(log_buf);
cleanup: cleanup:
if (timestamp) kfree(timestamp); if (timestamp) kfree(timestamp);
if (full_comm) kfree(full_comm); if (full_comm) kfree(full_comm);
if (log_buf) kfree(log_buf); if (log_buf) kfree(log_buf);
} }
void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success) void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success)
{ {
char *timestamp, *full_comm, *log_buf; char *timestamp, *full_comm, *log_buf;
if (!sulog_enabled) if (!sulog_enabled)
return; return;
timestamp = kmalloc(32, GFP_ATOMIC); timestamp = kmalloc(32, GFP_ATOMIC);
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
if (!timestamp || !full_comm || !log_buf) { if (!timestamp || !full_comm || !log_buf) {
pr_err("sulog: failed to allocate memory for su_attempt log\n"); pr_err("sulog: failed to allocate memory for su_attempt log\n");
goto cleanup; goto cleanup;
} }
get_timestamp(timestamp, 32); get_timestamp(timestamp, 32);
ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN); ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN);
snprintf(log_buf, SULOG_ENTRY_MAX_LEN, snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
"[%s] SU_EXEC: UID=%d COMM=%s TARGET=%s RESULT=%s PID=%d\n", "[%s] SU_EXEC: UID=%d COMM=%s TARGET=%s RESULT=%s PID=%d\n",
timestamp, uid, full_comm, timestamp, uid, full_comm,
target_path ? target_path : "unknown", target_path ? target_path : "unknown",
success ? "SUCCESS" : "DENIED", current->pid); success ? "SUCCESS" : "DENIED", current->pid);
if (!dedup_should_print(uid, DEDUP_SU_ATTEMPT, log_buf, strlen(log_buf))) if (!dedup_should_print(uid, DEDUP_SU_ATTEMPT, log_buf, strlen(log_buf)))
goto cleanup; goto cleanup;
sulog_add_entry(log_buf); sulog_add_entry(log_buf);
cleanup: cleanup:
if (timestamp) kfree(timestamp); if (timestamp) kfree(timestamp);
if (full_comm) kfree(full_comm); if (full_comm) kfree(full_comm);
if (log_buf) kfree(log_buf); if (log_buf) kfree(log_buf);
} }
void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed) void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed)
{ {
char *timestamp, *full_comm, *log_buf; char *timestamp, *full_comm, *log_buf;
if (!sulog_enabled) if (!sulog_enabled)
return; return;
timestamp = kmalloc(32, GFP_ATOMIC); timestamp = kmalloc(32, GFP_ATOMIC);
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
if (!timestamp || !full_comm || !log_buf) { if (!timestamp || !full_comm || !log_buf) {
pr_err("sulog: failed to allocate memory for permission_check log\n"); pr_err("sulog: failed to allocate memory for permission_check log\n");
goto cleanup; goto cleanup;
} }
get_timestamp(timestamp, 32); get_timestamp(timestamp, 32);
ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN); ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN);
snprintf(log_buf, SULOG_ENTRY_MAX_LEN, snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
"[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n", "[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n",
timestamp, uid, full_comm, timestamp, uid, full_comm,
allowed ? "ALLOWED" : "DENIED", current->pid); allowed ? "ALLOWED" : "DENIED", current->pid);
if (!dedup_should_print(uid, DEDUP_PERM_CHECK, log_buf, strlen(log_buf))) if (!dedup_should_print(uid, DEDUP_PERM_CHECK, log_buf, strlen(log_buf)))
goto cleanup; goto cleanup;
sulog_add_entry(log_buf); sulog_add_entry(log_buf);
cleanup: cleanup:
if (timestamp) kfree(timestamp); if (timestamp) kfree(timestamp);
if (full_comm) kfree(full_comm); if (full_comm) kfree(full_comm);
if (log_buf) kfree(log_buf); if (log_buf) kfree(log_buf);
} }
void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid) void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid)
{ {
char *timestamp, *full_comm, *log_buf; char *timestamp, *full_comm, *log_buf;
if (!sulog_enabled) if (!sulog_enabled)
return; return;
timestamp = kmalloc(32, GFP_ATOMIC); timestamp = kmalloc(32, GFP_ATOMIC);
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
if (!timestamp || !full_comm || !log_buf) { if (!timestamp || !full_comm || !log_buf) {
pr_err("sulog: failed to allocate memory for manager_operation log\n"); pr_err("sulog: failed to allocate memory for manager_operation log\n");
goto cleanup; goto cleanup;
} }
get_timestamp(timestamp, 32); get_timestamp(timestamp, 32);
ksu_get_cmdline(full_comm, NULL, SULOG_COMM_LEN); ksu_get_cmdline(full_comm, NULL, SULOG_COMM_LEN);
snprintf(log_buf, SULOG_ENTRY_MAX_LEN, snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
"[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d COMM=%s PID=%d\n", "[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d COMM=%s PID=%d\n",
timestamp, operation ? operation : "unknown", timestamp, operation ? operation : "unknown",
manager_uid, target_uid, full_comm, current->pid); manager_uid, target_uid, full_comm, current->pid);
if (!dedup_should_print(manager_uid, DEDUP_MANAGER_OP, log_buf, strlen(log_buf))) if (!dedup_should_print(manager_uid, DEDUP_MANAGER_OP, log_buf, strlen(log_buf)))
goto cleanup; goto cleanup;
sulog_add_entry(log_buf); sulog_add_entry(log_buf);
cleanup: cleanup:
if (timestamp) kfree(timestamp); if (timestamp) kfree(timestamp);
if (full_comm) kfree(full_comm); if (full_comm) kfree(full_comm);
if (log_buf) kfree(log_buf); if (log_buf) kfree(log_buf);
} }
void ksu_sulog_report_syscall(uid_t uid, const char *comm, void ksu_sulog_report_syscall(uid_t uid, const char *comm,
const char *syscall, const char *args) const char *syscall, const char *args)
{ {
char *timestamp, *full_comm, *log_buf; char *timestamp, *full_comm, *log_buf;
if (!sulog_enabled) if (!sulog_enabled)
return; return;
timestamp = kmalloc(32, GFP_ATOMIC); timestamp = kmalloc(32, GFP_ATOMIC);
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC); full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC); log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
if (!timestamp || !full_comm || !log_buf) { if (!timestamp || !full_comm || !log_buf) {
pr_err("sulog: failed to allocate memory for syscall log\n"); pr_err("sulog: failed to allocate memory for syscall log\n");
goto cleanup; goto cleanup;
} }
get_timestamp(timestamp, 32); get_timestamp(timestamp, 32);
ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN); ksu_get_cmdline(full_comm, comm, SULOG_COMM_LEN);
snprintf(log_buf, SULOG_ENTRY_MAX_LEN, snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
"[%s] SYSCALL: UID=%d COMM=%s SYSCALL=%s ARGS=%s PID=%d\n", "[%s] SYSCALL: UID=%d COMM=%s SYSCALL=%s ARGS=%s PID=%d\n",
timestamp, uid, full_comm, timestamp, uid, full_comm,
syscall ? syscall : "unknown", syscall ? syscall : "unknown",
args ? args : "none", args ? args : "none",
current->pid); current->pid);
if (!dedup_should_print(uid, DEDUP_SYSCALL, log_buf, strlen(log_buf))) if (!dedup_should_print(uid, DEDUP_SYSCALL, log_buf, strlen(log_buf)))
goto cleanup; goto cleanup;
sulog_add_entry(log_buf); sulog_add_entry(log_buf);
cleanup: cleanup:
if (timestamp) kfree(timestamp); if (timestamp) kfree(timestamp);
if (full_comm) kfree(full_comm); if (full_comm) kfree(full_comm);
if (log_buf) kfree(log_buf); if (log_buf) kfree(log_buf);
} }
int ksu_sulog_init(void) int ksu_sulog_init(void)
{ {
sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1); sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1);
if (!sulog_workqueue) { if (!sulog_workqueue) {
pr_err("sulog: failed to create workqueue\n"); pr_err("sulog: failed to create workqueue\n");
return -ENOMEM; return -ENOMEM;
} }
INIT_WORK(&sulog_work, sulog_work_handler); INIT_WORK(&sulog_work, sulog_work_handler);
pr_info("sulog: initialized successfully\n"); pr_info("sulog: initialized successfully\n");
return 0; return 0;
} }
void ksu_sulog_exit(void) void ksu_sulog_exit(void)
{ {
struct sulog_entry *entry, *tmp; struct sulog_entry *entry, *tmp;
sulog_enabled = false; sulog_enabled = false;
if (sulog_workqueue) { if (sulog_workqueue) {
flush_workqueue(sulog_workqueue); flush_workqueue(sulog_workqueue);
destroy_workqueue(sulog_workqueue); destroy_workqueue(sulog_workqueue);
sulog_workqueue = NULL; sulog_workqueue = NULL;
} }
mutex_lock(&sulog_mutex); mutex_lock(&sulog_mutex);
list_for_each_entry_safe(entry, tmp, &sulog_queue, list) { list_for_each_entry_safe(entry, tmp, &sulog_queue, list) {
list_del(&entry->list); list_del(&entry->list);
kfree(entry); kfree(entry);
} }
mutex_unlock(&sulog_mutex); mutex_unlock(&sulog_mutex);
pr_info("sulog: cleaned up successfully\n"); pr_info("sulog: cleaned up successfully\n");
} }
#endif // __SULOG_GATE #endif // __SULOG_GATE

View File

@@ -15,6 +15,23 @@ extern struct timezone sys_tz;
#define SULOG_COMM_LEN 256 #define SULOG_COMM_LEN 256
#define DEDUP_SECS 10 #define DEDUP_SECS 10
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
#include <linux/rtc.h>
static inline void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
{
struct rtc_time rtc_tm;
rtc_time64_to_tm(totalsecs, &rtc_tm);
result->tm_sec = rtc_tm.tm_sec;
result->tm_min = rtc_tm.tm_min;
result->tm_hour = rtc_tm.tm_hour;
result->tm_mday = rtc_tm.tm_mday;
result->tm_mon = rtc_tm.tm_mon;
result->tm_year = rtc_tm.tm_year;
}
#endif
struct dedup_key { struct dedup_key {
u32 crc; u32 crc;
uid_t uid; uid_t uid;
@@ -41,8 +58,8 @@ static inline u32 dedup_calc_hash(const char *content, size_t len)
} }
struct sulog_entry { struct sulog_entry {
struct list_head list; struct list_head list;
char content[SULOG_ENTRY_MAX_LEN]; char content[SULOG_ENTRY_MAX_LEN];
}; };
void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method); void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method);

609
kernel/supercalls.c Normal file
View File

@@ -0,0 +1,609 @@
#include "supercalls.h"
#include <linux/anon_inodes.h>
#include <linux/capability.h>
#include <linux/cred.h>
#include <linux/err.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include "allowlist.h"
#include "klog.h" // IWYU pragma: keep
#include "ksud.h"
#include "manager.h"
#include "sulog.h"
#include "selinux/selinux.h"
#include "kernel_compat.h"
#include "throne_comm.h"
#include "dynamic_manager.h"
#ifdef CONFIG_KSU_MANUAL_SU
#include "manual_su.h"
#endif
// Forward declarations from core_hook.c
extern void escape_to_root(void);
extern void nuke_ext4_sysfs(void);
extern bool ksu_module_mounted;
extern int handle_sepolicy(unsigned long arg3, void __user *arg4);
extern void ksu_sucompat_init(void);
extern void ksu_sucompat_exit(void);
// Forward declaration for anon_ksu_fops
static const struct file_operations anon_ksu_fops;
static bool ksu_su_compat_enabled = true;
bool ksu_uid_scanner_enabled = false;
// Permission check functions
bool perm_check_manager(void)
{
return is_manager();
}
bool perm_check_root(void)
{
return current_uid().val == 0;
}
bool perm_check_basic(void)
{
return current_uid().val == 0 || is_manager();
}
bool perm_check_all(void)
{
return true; // No permission check
}
static void init_uid_scanner(void)
{
ksu_uid_init();
do_load_throne_state(NULL);
if (ksu_uid_scanner_enabled) {
int ret = ksu_throne_comm_init();
if (ret != 0) {
pr_err("Failed to initialize throne communication: %d\n", ret);
}
}
}
static int do_grant_root(void __user *arg)
{
// Check if current UID is allowed
bool is_allowed = is_manager() || ksu_is_allow_uid(current_uid().val);
if (!is_allowed) {
return -EPERM;
}
pr_info("allow root for: %d\n", current_uid().val);
escape_to_root();
return 0;
}
static int do_get_info(void __user *arg)
{
struct ksu_get_info_cmd cmd = {.version = KERNEL_SU_VERSION, .flags = 0};
#ifdef MODULE
cmd.flags |= 0x1;
#endif
if (is_manager()) {
cmd.flags |= 0x2;
}
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_version: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_report_event(void __user *arg)
{
struct ksu_report_event_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
return -EFAULT;
}
switch (cmd.event) {
case EVENT_POST_FS_DATA: {
static bool post_fs_data_lock = false;
if (!post_fs_data_lock) {
post_fs_data_lock = true;
pr_info("post-fs-data triggered\n");
on_post_fs_data();
init_uid_scanner();
#if __SULOG_GATE
ksu_sulog_init();
#endif
ksu_dynamic_manager_init();
}
break;
}
case EVENT_BOOT_COMPLETED: {
static bool boot_complete_lock = false;
if (!boot_complete_lock) {
boot_complete_lock = true;
pr_info("boot_complete triggered\n");
}
break;
}
case EVENT_MODULE_MOUNTED: {
ksu_module_mounted = true;
pr_info("module mounted!\n");
nuke_ext4_sysfs();
break;
}
default:
break;
}
return 0;
}
static int do_set_sepolicy(void __user *arg)
{
struct ksu_set_sepolicy_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
return -EFAULT;
}
return handle_sepolicy(cmd.cmd, (void __user *)cmd.arg);
}
static int do_check_safemode(void __user *arg)
{
struct ksu_check_safemode_cmd cmd;
cmd.in_safe_mode = ksu_is_safe_mode();
if (cmd.in_safe_mode) {
pr_warn("safemode enabled!\n");
}
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("check_safemode: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_get_allow_list(void __user *arg)
{
struct ksu_get_allow_list_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
return -EFAULT;
}
bool success = ksu_get_allow_list((int *)cmd.uids, (int *)&cmd.count, true);
if (!success) {
return -EFAULT;
}
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_allow_list: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_get_deny_list(void __user *arg)
{
struct ksu_get_allow_list_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
return -EFAULT;
}
bool success = ksu_get_allow_list((int *)cmd.uids, (int *)&cmd.count, false);
if (!success) {
return -EFAULT;
}
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_deny_list: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_uid_granted_root(void __user *arg)
{
struct ksu_uid_granted_root_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
return -EFAULT;
}
cmd.granted = ksu_is_allow_uid(cmd.uid);
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("uid_granted_root: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_uid_should_umount(void __user *arg)
{
struct ksu_uid_should_umount_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
return -EFAULT;
}
cmd.should_umount = ksu_uid_should_umount(cmd.uid);
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("uid_should_umount: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_get_manager_uid(void __user *arg)
{
struct ksu_get_manager_uid_cmd cmd;
cmd.uid = ksu_get_manager_uid();
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_manager_uid: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_get_app_profile(void __user *arg)
{
struct ksu_get_app_profile_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
pr_err("get_app_profile: copy_from_user failed\n");
return -EFAULT;
}
if (!ksu_get_app_profile(&cmd.profile)) {
return -ENOENT;
}
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_app_profile: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_set_app_profile(void __user *arg)
{
struct ksu_set_app_profile_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
pr_err("set_app_profile: copy_from_user failed\n");
return -EFAULT;
}
if (!ksu_set_app_profile(&cmd.profile, true)) {
#if __SULOG_GATE
ksu_sulog_report_manager_operation("SET_APP_PROFILE",
current_uid().val, cmd.profile.current_uid);
#endif
return -EFAULT;
}
return 0;
}
static int do_is_su_enabled(void __user *arg)
{
struct ksu_is_su_enabled_cmd cmd;
cmd.enabled = ksu_su_compat_enabled;
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("is_su_enabled: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_enable_su(void __user *arg)
{
struct ksu_enable_su_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
pr_err("enable_su: copy_from_user failed\n");
return -EFAULT;
}
if (cmd.enable == ksu_su_compat_enabled) {
pr_info("enable_su: no need to change\n");
return 0;
}
if (cmd.enable) {
ksu_sucompat_init();
} else {
ksu_sucompat_exit();
}
ksu_su_compat_enabled = cmd.enable;
return 0;
}
// 100. GET_FULL_VERSION - Get full version string
static int do_get_full_version(void __user *arg)
{
struct ksu_get_full_version_cmd cmd = {0};
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(cmd.version_full, KSU_VERSION_FULL, sizeof(cmd.version_full));
#else
strlcpy(cmd.version_full, KSU_VERSION_FULL, sizeof(cmd.version_full));
#endif
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_full_version: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
// 101. HOOK_TYPE - Get hook type
static int do_get_hook_type(void __user *arg)
{
struct ksu_hook_type_cmd cmd = {0};
const char *type = "Kprobes";
#if defined(CONFIG_KSU_TRACEPOINT_HOOK)
type = "Tracepoint";
#elif defined(CONFIG_KSU_MANUAL_HOOK)
type = "Manual";
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(cmd.hook_type, type, sizeof(cmd.hook_type));
#else
strlcpy(cmd.hook_type, type, sizeof(cmd.hook_type));
#endif
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_hook_type: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
// 102. ENABLE_KPM - Check if KPM is enabled
static int do_enable_kpm(void __user *arg)
{
struct ksu_enable_kpm_cmd cmd;
cmd.enabled = IS_ENABLED(CONFIG_KPM);
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("enable_kpm: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_dynamic_manager(void __user *arg)
{
struct ksu_dynamic_manager_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
pr_err("dynamic_manager: copy_from_user failed\n");
return -EFAULT;
}
int ret = ksu_handle_dynamic_manager(&cmd.config);
if (ret)
return ret;
if (cmd.config.operation == DYNAMIC_MANAGER_OP_GET &&
copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("dynamic_manager: copy_to_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_get_managers(void __user *arg)
{
struct ksu_get_managers_cmd cmd;
int ret = ksu_get_active_managers(&cmd.manager_info);
if (ret)
return ret;
if (copy_to_user(arg, &cmd, sizeof(cmd))) {
pr_err("get_managers: copy_from_user failed\n");
return -EFAULT;
}
return 0;
}
static int do_enable_uid_scanner(void __user *arg)
{
struct ksu_enable_uid_scanner_cmd cmd;
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
pr_err("enable_uid_scanner: copy_from_user failed\n");
return -EFAULT;
}
switch (cmd.operation) {
case UID_SCANNER_OP_GET_STATUS: {
bool status = ksu_uid_scanner_enabled;
if (copy_to_user((void __user *)cmd.status_ptr, &status, sizeof(status))) {
pr_err("enable_uid_scanner: copy status failed\n");
return -EFAULT;
}
break;
}
case UID_SCANNER_OP_TOGGLE: {
bool enabled = cmd.enabled;
if (enabled == ksu_uid_scanner_enabled) {
pr_info("enable_uid_scanner: no need to change, already %s\n",
enabled ? "enabled" : "disabled");
break;
}
if (enabled) {
// Enable UID scanner
int ret = ksu_throne_comm_init();
if (ret != 0) {
pr_err("enable_uid_scanner: failed to initialize: %d\n", ret);
return -EFAULT;
}
pr_info("enable_uid_scanner: enabled\n");
} else {
// Disable UID scanner
ksu_throne_comm_exit();
pr_info("enable_uid_scanner: disabled\n");
}
ksu_uid_scanner_enabled = enabled;
ksu_throne_comm_save_state();
break;
}
case UID_SCANNER_OP_CLEAR_ENV: {
// Clear environment (force exit)
ksu_throne_comm_exit();
ksu_uid_scanner_enabled = false;
ksu_throne_comm_save_state();
pr_info("enable_uid_scanner: environment cleared\n");
break;
}
default:
pr_err("enable_uid_scanner: invalid operation\n");
return -EINVAL;
}
return 0;
}
// IOCTL handlers mapping table
static const struct ksu_ioctl_cmd_map ksu_ioctl_handlers[] = {
{ .cmd = KSU_IOCTL_GRANT_ROOT, .handler = do_grant_root, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_GET_INFO, .handler = do_get_info, .perm_check = perm_check_all},
{ .cmd = KSU_IOCTL_REPORT_EVENT, .handler = do_report_event, .perm_check = perm_check_root},
{ .cmd = KSU_IOCTL_SET_SEPOLICY, .handler = do_set_sepolicy, .perm_check = perm_check_root},
{ .cmd = KSU_IOCTL_CHECK_SAFEMODE, .handler = do_check_safemode, .perm_check = perm_check_all},
{ .cmd = KSU_IOCTL_GET_ALLOW_LIST, .handler = do_get_allow_list, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_GET_DENY_LIST, .handler = do_get_deny_list, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_UID_GRANTED_ROOT, .handler = do_uid_granted_root, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_UID_SHOULD_UMOUNT, .handler = do_uid_should_umount, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_GET_MANAGER_UID, .handler = do_get_manager_uid, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_GET_APP_PROFILE, .handler = do_get_app_profile, .perm_check = perm_check_manager},
{ .cmd = KSU_IOCTL_SET_APP_PROFILE, .handler = do_set_app_profile, .perm_check = perm_check_manager},
{ .cmd = KSU_IOCTL_IS_SU_ENABLED, .handler = do_is_su_enabled, .perm_check = perm_check_manager},
{ .cmd = KSU_IOCTL_ENABLE_SU, .handler = do_enable_su, .perm_check = perm_check_manager},
{ .cmd = KSU_IOCTL_GET_FULL_VERSION, .handler = do_get_full_version, .perm_check = perm_check_manager},
{ .cmd = KSU_IOCTL_HOOK_TYPE, .handler = do_get_hook_type, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_ENABLE_KPM, .handler = do_enable_kpm, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_DYNAMIC_MANAGER, .handler = do_dynamic_manager, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_GET_MANAGERS, .handler = do_get_managers, .perm_check = perm_check_basic},
{ .cmd = KSU_IOCTL_ENABLE_UID_SCANNER, .handler = do_enable_uid_scanner, .perm_check = perm_check_basic},
{ .cmd = 0, .handler = NULL, .perm_check = NULL} // Sentinel
};
// IOCTL dispatcher
static long anon_ksu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int i;
#ifdef CONFIG_KSU_DEBUG
pr_info("ksu ioctl: cmd=0x%x from uid=%d\n", cmd, current_uid().val);
#endif
for (i = 0; ksu_ioctl_handlers[i].handler; i++) {
if (cmd == ksu_ioctl_handlers[i].cmd) {
// Check permission first
if (ksu_ioctl_handlers[i].perm_check &&
!ksu_ioctl_handlers[i].perm_check()) {
pr_warn("ksu ioctl: permission denied for cmd=0x%x uid=%d\n",
cmd, current_uid().val);
return -EPERM;
}
// Execute handler
return ksu_ioctl_handlers[i].handler(argp);
}
}
pr_warn("ksu ioctl: unsupported command 0x%x\n", cmd);
return -ENOTTY;
}
// File release handler
static int anon_ksu_release(struct inode *inode, struct file *filp)
{
pr_info("ksu fd released\n");
return 0;
}
// File operations structure
static const struct file_operations anon_ksu_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = anon_ksu_ioctl,
.compat_ioctl = anon_ksu_ioctl,
.release = anon_ksu_release,
};
// Install KSU fd to current process
int ksu_install_fd(void)
{
struct file *filp;
int fd;
// Get unused fd
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0) {
pr_err("ksu_install_fd: failed to get unused fd\n");
return fd;
}
// Create anonymous inode file
filp = anon_inode_getfile("[ksu_driver]", &anon_ksu_fops, NULL, O_RDWR | O_CLOEXEC);
if (IS_ERR(filp)) {
pr_err("ksu_install_fd: failed to create anon inode file\n");
put_unused_fd(fd);
return PTR_ERR(filp);
}
// Install fd
fd_install(fd, filp);
pr_info("ksu fd installed: %d for pid %d\n", fd, current->pid);
return fd;
}

142
kernel/supercalls.h Normal file
View File

@@ -0,0 +1,142 @@
#ifndef __KSU_H_SUPERCALLS
#define __KSU_H_SUPERCALLS
#include <linux/types.h>
#include <linux/ioctl.h>
#include "ksu.h"
// Magic numbers for reboot hook to install fd
#define KSU_INSTALL_MAGIC1 0xDEADBEEF
#define KSU_INSTALL_MAGIC2 0xCAFEBABE
// Command structures for ioctl
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_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)
void __user *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)
// IOCTL handler types
typedef int (*ksu_ioctl_handler_t)(void __user *arg);
typedef bool (*ksu_perm_check_t)(void);
// Permission check functions
bool perm_check_manager(void);
bool perm_check_root(void);
bool perm_check_basic(void);
bool perm_check_all(void);
// IOCTL command mapping
struct ksu_ioctl_cmd_map {
unsigned int cmd;
ksu_ioctl_handler_t handler;
ksu_perm_check_t perm_check; // Permission check function
};
// Install KSU fd to current process
int ksu_install_fd(void);
#endif // __KSU_H_SUPERCALLS

View File

@@ -8,6 +8,7 @@
#include "klog.h" #include "klog.h"
#include "throne_comm.h" #include "throne_comm.h"
#include "kernel_compat.h" #include "kernel_compat.h"
#include "ksu.h"
#define PROC_UID_SCANNER "ksu_uid_scanner" #define PROC_UID_SCANNER "ksu_uid_scanner"
#define UID_SCANNER_STATE_FILE "/data/adb/ksu/.uid_scanner" #define UID_SCANNER_STATE_FILE "/data/adb/ksu/.uid_scanner"
@@ -18,187 +19,197 @@ static struct work_struct scan_work;
static struct work_struct ksu_state_save_work; static struct work_struct ksu_state_save_work;
static struct work_struct ksu_state_load_work; static struct work_struct ksu_state_load_work;
extern bool ksu_uid_scanner_enabled;
// Signal userspace to rescan // Signal userspace to rescan
static bool need_rescan = false; static bool need_rescan = false;
static void rescan_work_fn(struct work_struct *work) static void rescan_work_fn(struct work_struct *work)
{ {
// Signal userspace through proc interface // Signal userspace through proc interface
need_rescan = true; need_rescan = true;
pr_info("requested userspace uid rescan\n"); pr_info("requested userspace uid rescan\n");
} }
void ksu_request_userspace_scan(void) void ksu_request_userspace_scan(void)
{ {
if (scanner_wq) { if (scanner_wq) {
queue_work(scanner_wq, &scan_work); queue_work(scanner_wq, &scan_work);
} }
} }
void ksu_handle_userspace_update(void) void ksu_handle_userspace_update(void)
{ {
// Called when userspace notifies update complete // Called when userspace notifies update complete
need_rescan = false; need_rescan = false;
pr_info("userspace uid list updated\n"); pr_info("userspace uid list updated\n");
} }
static void do_save_throne_state(struct work_struct *work) static void do_save_throne_state(struct work_struct *work)
{ {
struct file *fp; struct file *fp;
char state_char = ksu_uid_scanner_enabled ? '1' : '0'; char state_char = ksu_uid_scanner_enabled ? '1' : '0';
loff_t off = 0; loff_t off = 0;
fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("save_throne_state create file failed: %ld\n", PTR_ERR(fp)); pr_err("save_throne_state create file failed: %ld\n", PTR_ERR(fp));
return; return;
} }
if (ksu_kernel_write_compat(fp, &state_char, sizeof(state_char), &off) != sizeof(state_char)) { if (ksu_kernel_write_compat(fp, &state_char, sizeof(state_char), &off) != sizeof(state_char)) {
pr_err("save_throne_state write failed\n"); pr_err("save_throne_state write failed\n");
goto exit; goto exit;
} }
pr_info("throne state saved: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled"); pr_info("throne state saved: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
exit: exit:
filp_close(fp, 0); filp_close(fp, 0);
} }
void do_load_throne_state(struct work_struct *work) void do_load_throne_state(struct work_struct *work)
{ {
struct file *fp; struct file *fp;
char state_char; char state_char;
loff_t off = 0; loff_t off = 0;
ssize_t ret; ssize_t ret;
fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_RDONLY, 0); fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_RDONLY, 0);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_info("throne state file not found, using default: disabled\n"); pr_info("throne state file not found, using default: disabled\n");
ksu_uid_scanner_enabled = false; ksu_uid_scanner_enabled = false;
return; return;
} }
ret = ksu_kernel_read_compat(fp, &state_char, sizeof(state_char), &off); ret = ksu_kernel_read_compat(fp, &state_char, sizeof(state_char), &off);
if (ret != sizeof(state_char)) { if (ret != sizeof(state_char)) {
pr_err("load_throne_state read err: %zd\n", ret); pr_err("load_throne_state read err: %zd\n", ret);
ksu_uid_scanner_enabled = false; ksu_uid_scanner_enabled = false;
goto exit; goto exit;
} }
ksu_uid_scanner_enabled = (state_char == '1'); ksu_uid_scanner_enabled = (state_char == '1');
pr_info("throne state loaded: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled"); pr_info("throne state loaded: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
exit: exit:
filp_close(fp, 0); filp_close(fp, 0);
} }
bool ksu_throne_comm_load_state(void) bool ksu_throne_comm_load_state(void)
{ {
return ksu_queue_work(&ksu_state_load_work); return ksu_queue_work(&ksu_state_load_work);
} }
void ksu_throne_comm_save_state(void) void ksu_throne_comm_save_state(void)
{ {
ksu_queue_work(&ksu_state_save_work); ksu_queue_work(&ksu_state_save_work);
} }
static int uid_scanner_show(struct seq_file *m, void *v) static int uid_scanner_show(struct seq_file *m, void *v)
{ {
if (need_rescan) { if (need_rescan) {
seq_puts(m, "RESCAN\n"); seq_puts(m, "RESCAN\n");
} else { } else {
seq_puts(m, "OK\n"); seq_puts(m, "OK\n");
} }
return 0; return 0;
} }
static int uid_scanner_open(struct inode *inode, struct file *file) static int uid_scanner_open(struct inode *inode, struct file *file)
{ {
return single_open(file, uid_scanner_show, NULL); return single_open(file, uid_scanner_show, NULL);
} }
static ssize_t uid_scanner_write(struct file *file, const char __user *buffer, static ssize_t uid_scanner_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos) size_t count, loff_t *pos)
{ {
char cmd[16]; char cmd[16];
if (count >= sizeof(cmd)) if (count >= sizeof(cmd))
return -EINVAL; return -EINVAL;
if (copy_from_user(cmd, buffer, count)) if (copy_from_user(cmd, buffer, count))
return -EFAULT; return -EFAULT;
cmd[count] = '\0'; cmd[count] = '\0';
// Remove newline if present // Remove newline if present
if (count > 0 && cmd[count-1] == '\n') if (count > 0 && cmd[count-1] == '\n')
cmd[count-1] = '\0'; cmd[count-1] = '\0';
if (strcmp(cmd, "UPDATED") == 0) { if (strcmp(cmd, "UPDATED") == 0) {
ksu_handle_userspace_update(); ksu_handle_userspace_update();
pr_info("received userspace update notification\n"); pr_info("received userspace update notification\n");
} }
return count; return count;
} }
#ifdef KSU_COMPAT_HAS_PROC_OPS
static const struct proc_ops uid_scanner_proc_ops = { static const struct proc_ops uid_scanner_proc_ops = {
.proc_open = uid_scanner_open, .proc_open = uid_scanner_open,
.proc_read = seq_read, .proc_read = seq_read,
.proc_write = uid_scanner_write, .proc_write = uid_scanner_write,
.proc_lseek = seq_lseek, .proc_lseek = seq_lseek,
.proc_release = single_release, .proc_release = single_release,
}; };
#else
static const struct file_operations uid_scanner_proc_ops = {
.owner = THIS_MODULE,
.open = uid_scanner_open,
.read = seq_read,
.write = uid_scanner_write,
.llseek = seq_lseek,
.release = single_release,
};
#endif
int ksu_throne_comm_init(void) int ksu_throne_comm_init(void)
{ {
// Create workqueue // Create workqueue
scanner_wq = alloc_workqueue("ksu_scanner", WQ_UNBOUND, 1); scanner_wq = alloc_workqueue("ksu_scanner", WQ_UNBOUND, 1);
if (!scanner_wq) { if (!scanner_wq) {
pr_err("failed to create scanner workqueue\n"); pr_err("failed to create scanner workqueue\n");
return -ENOMEM; return -ENOMEM;
} }
INIT_WORK(&scan_work, rescan_work_fn); INIT_WORK(&scan_work, rescan_work_fn);
// Create proc entry // Create proc entry
proc_entry = proc_create(PROC_UID_SCANNER, 0600, NULL, &uid_scanner_proc_ops); proc_entry = proc_create(PROC_UID_SCANNER, 0600, NULL, &uid_scanner_proc_ops);
if (!proc_entry) { if (!proc_entry) {
pr_err("failed to create proc entry\n"); pr_err("failed to create proc entry\n");
destroy_workqueue(scanner_wq); destroy_workqueue(scanner_wq);
return -ENOMEM; return -ENOMEM;
} }
pr_info("throne communication initialized\n"); pr_info("throne communication initialized\n");
return 0; return 0;
} }
void ksu_throne_comm_exit(void) void ksu_throne_comm_exit(void)
{ {
if (proc_entry) { if (proc_entry) {
proc_remove(proc_entry); proc_remove(proc_entry);
proc_entry = NULL; proc_entry = NULL;
} }
if (scanner_wq) { if (scanner_wq) {
destroy_workqueue(scanner_wq); destroy_workqueue(scanner_wq);
scanner_wq = NULL; scanner_wq = NULL;
} }
pr_info("throne communication cleaned up\n"); pr_info("throne communication cleaned up\n");
} }
int ksu_uid_init(void) int ksu_uid_init(void)
{ {
INIT_WORK(&ksu_state_save_work, do_save_throne_state); INIT_WORK(&ksu_state_save_work, do_save_throne_state);
INIT_WORK(&ksu_state_load_work, do_load_throne_state); INIT_WORK(&ksu_state_load_work, do_load_throne_state);
return 0; return 0;
} }
void ksu_uid_exit(void) void ksu_uid_exit(void)
{ {
do_save_throne_state(NULL); do_save_throne_state(NULL);
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -7,6 +7,11 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <android/log.h>
#include <dirent.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include "prelude.h" #include "prelude.h"
#include "ksu.h" #include "ksu.h"
@@ -21,238 +26,276 @@ extern const char* zako_file_verrcidx2str(uint8_t index);
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM #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 int found = -1;
#define CMD_GET_VERSION 2 struct dirent *de;
#define CMD_ALLOW_SU 3 char path[64];
#define CMD_DENY_SU 4 char target[PATH_MAX];
#define CMD_GET_SU_LIST 5
#define CMD_GET_DENY_LIST 6
#define CMD_CHECK_SAFEMODE 9
#define CMD_GET_APP_PROFILE 10 while ((de = readdir(fd_dir)) != NULL) {
#define CMD_SET_APP_PROFILE 11 if (de->d_name[0] == '.') {
continue;
}
#define CMD_IS_UID_GRANTED_ROOT 12 char *endptr = nullptr;
#define CMD_IS_UID_SHOULD_UMOUNT 13 long fd_long = strtol(de->d_name, &endptr, 10);
#define CMD_IS_SU_ENABLED 14 if (!de->d_name[0] || *endptr != '\0' || fd_long < 0 || fd_long > INT_MAX) {
#define CMD_ENABLE_SU 15 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 const char *base = strrchr(target, '/');
#define CMD_HOOK_TYPE 101 base = base ? base + 1 : target;
#define CMD_DYNAMIC_MANAGER 103
#define CMD_GET_MANAGERS 104
#define CMD_ENABLE_UID_SCANNER 105
#define DYNAMIC_MANAGER_OP_SET 0 if (strstr(base, kName)) {
#define DYNAMIC_MANAGER_OP_GET 1 found = (int)fd_long;
#define DYNAMIC_MANAGER_OP_CLEAR 2 break;
}
}
static bool ksuctl(int cmd, void* arg1, void* arg2) { closedir(fd_dir);
int32_t result = 0; return found;
int32_t rtn = prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
return result == KERNEL_SU_OPTION && rtn == -1;
} }
bool become_manager(const char* pkg) { static int ksuctl(unsigned long op, void* arg) {
char param[128]; if (fd < 0) {
uid_t uid = getuid(); fd = scan_driver_fd();
uint32_t userId = uid / 100000; }
if (userId == 0) { return ioctl(fd, op, arg);
sprintf(param, "/data/data/%s", pkg);
} else {
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
}
return ksuctl(CMD_BECOME_MANAGER, param, NULL);
} }
// cache the result to avoid unnecessary syscall static struct ksu_get_info_cmd g_version = {0};
static bool is_lkm;
int get_version() { struct ksu_get_info_cmd get_info() {
int32_t version = -1; if (!g_version.version) {
int32_t flags = 0; ksuctl(KSU_IOCTL_GET_INFO, &g_version);
ksuctl(CMD_GET_VERSION, &version, &flags); }
if (!is_lkm && (flags & 0x1)) { return g_version;
is_lkm = true;
}
return version;
} }
void get_full_version(char* buff) { uint32_t get_version() {
ksuctl(CMD_GET_VERSION_FULL, buff, NULL); auto info = get_info();
return info.version;
} }
bool get_allow_list(int *uids, int *size) { bool get_allow_list(struct ksu_get_allow_list_cmd *cmd) {
return ksuctl(CMD_GET_SU_LIST, uids, size); return ksuctl(KSU_IOCTL_GET_ALLOW_LIST, cmd) == 0;
} }
bool is_safe_mode() { 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() { bool is_lkm_mode() {
// you should call get_version first! auto info = get_info();
return is_lkm; return (info.flags & 0x1) != 0;
}
bool is_manager() {
auto info = get_info();
return (info.flags & 0x2) != 0;
} }
bool uid_should_umount(int uid) { bool uid_should_umount(int uid) {
int should; struct ksu_uid_should_umount_cmd cmd = {};
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should; cmd.uid = uid;
ksuctl(KSU_IOCTL_UID_SHOULD_UMOUNT, &cmd);
return cmd.should_umount;
} }
bool set_app_profile(const struct app_profile* profile) { bool set_app_profile(const struct app_profile *profile) {
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL); 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) { int get_app_profile(struct app_profile *profile) {
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL); 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) { 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() { bool is_su_enabled() {
int enabled = true; struct ksu_is_su_enabled_cmd cmd = {};
// if ksuctl failed, we assume su is enabled, and it cannot be disabled. return ksuctl(KSU_IOCTL_IS_SU_ENABLED, &cmd) == 0 && cmd.enabled;
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL);
return enabled;
} }
bool is_KPM_enable() { void get_full_version(char* buff) {
int enabled = false; struct ksu_get_full_version_cmd cmd = {0};
ksuctl(CMD_ENABLE_KPM, &enabled, NULL); if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) {
return enabled; 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) { bool is_KPM_enable(void)
if (hook_type == NULL || size == 0) { {
return false; struct ksu_enable_kpm_cmd cmd = {};
} return ksuctl(KSU_IOCTL_ENABLE_KPM, &cmd) == 0 && cmd.enabled;
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 set_dynamic_manager(unsigned int size, const char* hash) { void get_hook_type(char *buff)
if (hash == NULL) { {
return false; struct ksu_hook_type_cmd cmd = {0};
} if (ksuctl(KSU_IOCTL_HOOK_TYPE, &cmd) == 0) {
strncpy(buff, cmd.hook_type, 32 - 1);
struct dynamic_manager_user_config config; buff[32 - 1] = '\0';
config.operation = DYNAMIC_MANAGER_OP_SET; } else {
config.size = size; strcpy(buff, "Unknown");
strncpy(config.hash, hash, sizeof(config.hash) - 1); }
config.hash[sizeof(config.hash) - 1] = '\0';
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
} }
bool get_dynamic_manager(struct dynamic_manager_user_config* config) { bool set_dynamic_manager(unsigned int size, const char *hash)
if (config == NULL) { {
return false; 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(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
} }
bool clear_dynamic_manager() { bool get_dynamic_manager(struct dynamic_manager_user_config *cfg)
struct dynamic_manager_user_config config; {
config.operation = DYNAMIC_MANAGER_OP_CLEAR; if (!cfg)
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL); 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) { bool clear_dynamic_manager(void)
if (info == NULL) { {
return false; struct ksu_dynamic_manager_cmd cmd = {0};
} cmd.config.operation = DYNAMIC_MANAGER_OP_CLEAR;
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
return ksuctl(CMD_GET_MANAGERS, info, NULL);
} }
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) { bool verify_module_signature(const char* input) {
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM) #if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
if (input == NULL) { if (input == NULL) {
LogDebug("verify_module_signature: input path is null"); LogDebug("verify_module_signature: input path is null");
return false; return false;
} }
int fd = zako_sys_file_open(input); int file_fd = zako_sys_file_open(input);
if (fd < 0) { if (file_fd < 0) {
LogDebug("verify_module_signature: failed to open file: %s", input); LogDebug("verify_module_signature: failed to open file: %s", input);
return false; 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 (results != 0) {
/* If important error occured, verification process should /* If important error occured, verification process should
be considered as failed due to unexpected modification be considered as failed due to unexpected modification
potentially happened. */ potentially happened. */
if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) { if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) {
LogDebug("verify_module_signature: Verification failed! (important error)"); LogDebug("verify_module_signature: Verification failed! (important error)");
} else { } else {
/* This is for manager that doesn't want to do certificate checks */ /* This is for manager that doesn't want to do certificate checks */
LogDebug("verify_module_signature: Verification partially passed"); LogDebug("verify_module_signature: Verification partially passed");
} }
} else { } else {
LogDebug("verify_module_signature: Verification passed!"); LogDebug("verify_module_signature: Verification passed!");
goto exit; goto exit;
} }
/* Go through all bit fields */ /* Go through all bit fields */
for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) { for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) {
if ((results & (1 << i)) == 0) { if ((results & (1 << i)) == 0) {
continue; continue;
} }
/* Convert error bit field index into human readable string */ /* Convert error bit field index into human readable string */
const char* message = zako_file_verrcidx2str((uint8_t)i); const char* message = zako_file_verrcidx2str((uint8_t)i);
// Error message: message // Error message: message
if (message != NULL) { if (message != NULL) {
LogDebug("verify_module_signature: Error bit %zu: %s", i, message); LogDebug("verify_module_signature: Error bit %zu: %s", i, message);
} else { } else {
LogDebug("verify_module_signature: Error bit %zu: Unknown error", i); LogDebug("verify_module_signature: Error bit %zu: Unknown error", i);
} }
} }
exit: exit:
close(fd); close(file_fd);
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s", LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
input, results, (results == 0) ? "true" : "false"); input, results, (results == 0) ? "true" : "false");
return results == 0; return results == 0;
#else #else
LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null"); LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null");
return false; return false;
#endif #endif
} }
bool is_uid_scanner_enabled() { bool is_uid_scanner_enabled(void)
bool status = false; {
ksuctl(CMD_ENABLE_UID_SCANNER, (void*)0, &status); bool status = false;
return status;
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) { bool set_uid_scanner_enabled(bool enabled)
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)1, (void*)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() { bool clear_uid_scanner_environment(void)
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)2, NULL); {
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 "prelude.h"
#include <linux/capability.h> #include <linux/capability.h>
#include <sys/types.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); uint32_t get_version();
int get_version();
bool get_allow_list(int *uids, int *size);
bool uid_should_umount(int uid); bool uid_should_umount(int uid);
@@ -23,6 +20,10 @@ bool is_safe_mode();
bool is_lkm_mode(); bool is_lkm_mode();
bool is_manager();
void get_full_version(char* buff);
#define KSU_APP_PROFILE_VER 2 #define KSU_APP_PROFILE_VER 2
#define KSU_MAX_PACKAGE_NAME 256 #define KSU_MAX_PACKAGE_NAME 256
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. // 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_GET 1
#define DYNAMIC_MANAGER_OP_CLEAR 2 #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 { struct dynamic_manager_user_config {
unsigned int operation; unsigned int operation;
unsigned int size; unsigned int size;
char hash[65]; char hash[65];
}; };
struct root_profile { struct root_profile {
int32_t uid; int32_t uid;
int32_t gid; int32_t gid;
int32_t groups_count; int32_t groups_count;
int32_t groups[KSU_MAX_GROUPS]; int32_t groups[KSU_MAX_GROUPS];
// kernel_cap_t is u32[2] for capabilities v3 // kernel_cap_t is u32[2] for capabilities v3
struct { struct {
uint64_t effective; uint64_t effective;
uint64_t permitted; uint64_t permitted;
uint64_t inheritable; uint64_t inheritable;
} capabilities; } capabilities;
char selinux_domain[KSU_SELINUX_DOMAIN]; char selinux_domain[KSU_SELINUX_DOMAIN];
int32_t namespaces; int32_t namespaces;
}; };
struct non_root_profile { struct non_root_profile {
bool umount_modules; bool umount_modules;
}; };
struct app_profile { struct app_profile {
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this. // It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
uint32_t version; uint32_t version;
// this is usually the package of the app, but can be other value for special apps // this is usually the package of the app, but can be other value for special apps
char key[KSU_MAX_PACKAGE_NAME]; char key[KSU_MAX_PACKAGE_NAME];
int32_t current_uid; int32_t current_uid;
bool allow_su; bool allow_su;
union { union {
struct { struct {
bool use_default; bool use_default;
char template_name[KSU_MAX_PACKAGE_NAME]; char template_name[KSU_MAX_PACKAGE_NAME];
struct root_profile profile; struct root_profile profile;
} rp_config; } rp_config;
struct { struct {
bool use_default; bool use_default;
struct non_root_profile profile; struct non_root_profile profile;
} nrp_config; } nrp_config;
}; };
}; };
struct manager_list_info { struct manager_list_info {
int count; int count;
struct { struct {
uid_t uid; uid_t uid;
int signature_index; int signature_index;
} managers[2]; } managers[2];
}; };
bool set_app_profile(const struct app_profile* profile); 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); bool set_su_enabled(bool enabled);
@@ -106,8 +111,7 @@ bool is_su_enabled();
bool is_KPM_enable(); 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); 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(); 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 #endif //KERNELSU_KSU_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package com.sukisu.ultra.ui.screen
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult 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.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.util.* import com.sukisu.ultra.ui.util.*
import com.topjohnwu.superuser.ShellUtils import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -179,127 +181,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
) )
// UID 扫描开关 // UID 扫描开关
if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER) { if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER) {
var uidAutoScanEnabled by rememberSaveable { UidScannerSection(prefs, snackBarHost, scope, context)
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))
}
}
}
}
)
}
} }
} }
) )
@@ -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) { if (lkmMode) {
UninstallItem(navigator) { UninstallItem(navigator) {
loadingDialog.withLoading(it) 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 @Composable
private fun SettingsGroupCard( private fun SettingsGroupCard(
title: String, title: String,
@@ -960,4 +824,125 @@ private fun TopBar(
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior 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 com.sukisu.ultra.ksuApp
import org.json.JSONArray import org.json.JSONArray
import java.io.File import java.io.File
import java.util.concurrent.CountDownLatch
/** /**
@@ -34,7 +35,7 @@ private fun getKsuDaemonPath(): String {
} }
object KsuCli { object KsuCli {
val SHELL: Shell = createRootShell() var SHELL: Shell = createRootShell()
val GLOBAL_MNT_SHELL: Shell = createRootShell(true) val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
} }
@@ -578,11 +579,10 @@ fun getUidScannerDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libuid_scanner.so" return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libuid_scanner.so"
} }
private const val targetPath = "/data/adb/uid_scanner"
fun ensureUidScannerExecutable(): Boolean { fun ensureUidScannerExecutable(): Boolean {
val shell = getRootShell() val shell = getRootShell()
val uidScannerPath = getUidScannerDaemonPath() val uidScannerPath = getUidScannerDaemonPath()
val targetPath = "/data/adb/uid_scanner"
if (!ShellUtils.fastCmdResult(shell, "test -f $targetPath")) { if (!ShellUtils.fastCmdResult(shell, "test -f $targetPath")) {
val copyResult = ShellUtils.fastCmdResult(shell, "cp $uidScannerPath $targetPath") val copyResult = ShellUtils.fastCmdResult(shell, "cp $uidScannerPath $targetPath")
if (!copyResult) { if (!copyResult) {
@@ -593,7 +593,6 @@ fun ensureUidScannerExecutable(): Boolean {
val result = ShellUtils.fastCmdResult(shell, "chmod 755 $targetPath") val result = ShellUtils.fastCmdResult(shell, "chmod 755 $targetPath")
return result return result
} }
private const val targetPath = "/data/adb/uid_scanner"
fun setUidAutoScan(enabled: Boolean): Boolean { fun setUidAutoScan(enabled: Boolean): Boolean {
val shell = getRootShell() val shell = getRootShell()
@@ -634,3 +633,30 @@ fun getUidMultiUserScan(): Boolean {
false 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 { try {
val kernelVersion = getKernelVersion() val kernelVersion = getKernelVersion()
val isManager = try { val isManager = try {
Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra") Natives.isManager
} catch (_: Exception) { } catch (_: Exception) {
false false
} }
val ksuVersion = if (isManager) { val ksuVersion = if (isManager) Natives.version else null
try {
Natives.version
} catch (_: Exception) {
null
}
} else null
val fullVersion = try { val fullVersion = try {
Natives.getFullVersion() Natives.getFullVersion()
@@ -163,13 +157,7 @@ class HomeViewModel : ViewModel() {
} }
val lkmMode = ksuVersion?.let { val lkmMode = ksuVersion?.let {
try { if (kernelVersion.isGKI()) Natives.isLkmMode else null
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) {
Natives.isLkmMode
} else null
} catch (_: Exception) {
null
}
} }
val isRootAvailable = try { val isRootAvailable = try {
@@ -346,7 +334,7 @@ class HomeViewModel : ViewModel() {
try { try {
// 检查KSU状态是否发生变化 // 检查KSU状态是否发生变化
val currentKsuVersion = try { val currentKsuVersion = try {
if (Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra")) { if (Natives.isManager) {
Natives.version Natives.version
} else null } else null
} catch (_: Exception) { } catch (_: Exception) {

View File

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

View File

@@ -1,10 +1,133 @@
const EVENT_POST_FS_DATA: u64 = 1; use std::fs;
const EVENT_BOOT_COMPLETED: u64 = 2; #[cfg(any(target_os = "linux", target_os = "android"))]
const EVENT_MODULE_MOUNTED: u64 = 3; use std::os::fd::RawFd;
use std::sync::OnceLock;
// Event constants
const EVENT_POST_FS_DATA: u32 = 1;
const EVENT_BOOT_COMPLETED: u32 = 2;
const EVENT_MODULE_MOUNTED: u32 = 3;
const KSU_IOCTL_GRANT_ROOT: u32 = 0x4B01; // _IO('K', 1)
const KSU_IOCTL_GET_INFO: u32 = 0x80084b02; // _IOR('K', 2, struct ksu_get_info_cmd)
const KSU_IOCTL_REPORT_EVENT: u32 = 0x40044b03; // _IOW('K', 3, struct ksu_report_event_cmd)
const KSU_IOCTL_SET_SEPOLICY: u32 = 0xc0104b04; // _IOWR('K', 4, struct ksu_set_sepolicy_cmd)
const KSU_IOCTL_CHECK_SAFEMODE: u32 = 0x80014b05; // _IOR('K', 5, struct ksu_check_safemode_cmd)
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct GetInfoCmd {
version: u32,
flags: u32,
}
#[repr(C)]
struct ReportEventCmd {
event: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct SetSepolicyCmd {
pub cmd: u64,
pub arg: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct CheckSafemodeCmd {
in_safe_mode: u8,
}
// Global driver fd cache
#[cfg(any(target_os = "linux", target_os = "android"))]
static DRIVER_FD: OnceLock<RawFd> = OnceLock::new();
#[cfg(any(target_os = "linux", target_os = "android"))]
static INFO_CACHE: OnceLock<GetInfoCmd> = OnceLock::new();
const KSU_INSTALL_MAGIC1: u32 = 0xDEADBEEF;
const KSU_INSTALL_MAGIC2: u32 = 0xCAFEBABE;
#[cfg(any(target_os = "linux", target_os = "android"))]
fn scan_driver_fd() -> Option<RawFd> {
let fd_dir = fs::read_dir("/proc/self/fd").ok()?;
for entry in fd_dir.flatten() {
if let Ok(fd_num) = entry.file_name().to_string_lossy().parse::<i32>() {
let link_path = format!("/proc/self/fd/{}", fd_num);
if let Ok(target) = fs::read_link(&link_path) {
let target_str = target.to_string_lossy();
if target_str.contains("[ksu_driver]") {
return Some(fd_num);
}
}
}
}
None
}
// Get cached driver fd
#[cfg(any(target_os = "linux", target_os = "android"))]
fn init_driver_fd() -> Option<RawFd> {
let fd = scan_driver_fd();
if fd.is_none() {
let mut fd = -1;
unsafe {
libc::syscall(
libc::SYS_reboot,
KSU_INSTALL_MAGIC1,
KSU_INSTALL_MAGIC2,
0,
&mut fd,
);
};
if fd >= 0 { Some(fd) } else { None }
} else {
fd
}
}
// ioctl wrapper using libc
#[cfg(any(target_os = "linux", target_os = "android"))]
fn ksuctl<T>(request: u32, arg: *mut T) -> std::io::Result<i32> {
use std::io;
let fd = *DRIVER_FD.get_or_init(|| init_driver_fd().unwrap_or(-1));
unsafe {
#[cfg(not(target_env = "gnu"))]
let ret = libc::ioctl(fd as libc::c_int, request as i32, arg);
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(fd as libc::c_int, request as u64, arg);
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn ksuctl<T>(_request: u32, _arg: *mut T) -> std::io::Result<i32> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
// API implementations
#[cfg(any(target_os = "linux", target_os = "android"))]
fn get_info() -> GetInfoCmd {
*INFO_CACHE.get_or_init(|| {
let mut cmd = GetInfoCmd {
version: 0,
flags: 0,
};
let _ = ksuctl(KSU_IOCTL_GET_INFO, &mut cmd as *mut _);
cmd
})
}
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_version() -> i32 { pub fn get_version() -> i32 {
rustix::process::ksu_get_version() get_info().version as i32
} }
#[cfg(not(any(target_os = "linux", target_os = "android")))] #[cfg(not(any(target_os = "linux", target_os = "android")))]
@@ -13,22 +136,24 @@ pub fn get_version() -> i32 {
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
fn report_event(event: u64) { pub fn grant_root() -> std::io::Result<()> {
rustix::process::ksu_report_event(event) ksuctl(KSU_IOCTL_GRANT_ROOT, std::ptr::null_mut::<u8>())?;
Ok(())
} }
#[cfg(not(any(target_os = "linux", target_os = "android")))] #[cfg(not(any(target_os = "linux", target_os = "android")))]
fn report_event(_event: u64) {} pub fn grant_root() -> std::io::Result<()> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
pub fn check_kernel_safemode() -> bool { fn report_event(event: u32) {
rustix::process::ksu_check_kernel_safemode() let mut cmd = ReportEventCmd { event };
let _ = ksuctl(KSU_IOCTL_REPORT_EVENT, &mut cmd as *mut _);
} }
#[cfg(not(any(target_os = "linux", target_os = "android")))] #[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn check_kernel_safemode() -> bool { fn report_event(_event: u32) {}
false
}
pub fn report_post_fs_data() { pub fn report_post_fs_data() {
report_event(EVENT_POST_FS_DATA); report_event(EVENT_POST_FS_DATA);
@@ -41,3 +166,21 @@ pub fn report_boot_complete() {
pub fn report_module_mounted() { pub fn report_module_mounted() {
report_event(EVENT_MODULE_MOUNTED); report_event(EVENT_MODULE_MOUNTED);
} }
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn check_kernel_safemode() -> bool {
let mut cmd = CheckSafemodeCmd { in_safe_mode: 0 };
let _ = ksuctl(KSU_IOCTL_CHECK_SAFEMODE, &mut cmd as *mut _);
cmd.in_safe_mode != 0
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn check_kernel_safemode() -> bool {
false
}
pub fn set_sepolicy(cmd: &SetSepolicyCmd) -> std::io::Result<()> {
let mut ioctl_cmd = *cmd;
ksuctl(KSU_IOCTL_SET_SEPOLICY, &mut ioctl_cmd as *mut _)?;
Ok(())
}

View File

@@ -20,7 +20,7 @@ fn parse_single_word(input: &str) -> IResult<&str, &str> {
take_while1(is_sepolicy_char).parse(input) take_while1(is_sepolicy_char).parse(input)
} }
fn parse_bracket_objs<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> { fn parse_bracket_objs(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, (_, words, _)) = ( let (input, (_, words, _)) = (
tag("{"), tag("{"),
take_while_m_n(1, 100, |c: char| is_sepolicy_char(c) || c.is_whitespace()), take_while_m_n(1, 100, |c: char| is_sepolicy_char(c) || c.is_whitespace()),
@@ -30,12 +30,12 @@ fn parse_bracket_objs<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
Ok((input, words.split_whitespace().collect())) Ok((input, words.split_whitespace().collect()))
} }
fn parse_single_obj<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> { fn parse_single_obj(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, word) = take_while1(is_sepolicy_char).parse(input)?; let (input, word) = take_while1(is_sepolicy_char).parse(input)?;
Ok((input, vec![word])) Ok((input, vec![word]))
} }
fn parse_star<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> { fn parse_star(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, _) = tag("*").parse(input)?; let (input, _) = tag("*").parse(input)?;
Ok((input, vec!["*"])) Ok((input, vec!["*"]))
} }
@@ -43,12 +43,12 @@ fn parse_star<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
// 1. a single sepolicy word // 1. a single sepolicy word
// 2. { obj1 obj2 obj3 ...} // 2. { obj1 obj2 obj3 ...}
// 3. * // 3. *
fn parse_seobj<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> { fn parse_seobj(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, strs) = alt((parse_single_obj, parse_bracket_objs, parse_star)).parse(input)?; let (input, strs) = alt((parse_single_obj, parse_bracket_objs, parse_star)).parse(input)?;
Ok((input, strs)) Ok((input, strs))
} }
fn parse_seobj_no_star<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> { fn parse_seobj_no_star(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, strs) = alt((parse_single_obj, parse_bracket_objs)).parse(input)?; let (input, strs) = alt((parse_single_obj, parse_bracket_objs)).parse(input)?;
Ok((input, strs)) Ok((input, strs))
} }
@@ -697,7 +697,12 @@ fn apply_one_rule<'a>(statement: &'a PolicyStatement<'a>, strict: bool) -> Resul
let policies: Vec<AtomicStatement> = statement.try_into()?; let policies: Vec<AtomicStatement> = statement.try_into()?;
for policy in policies { for policy in policies {
if !rustix::process::ksu_set_policy(&FfiPolicy::from(policy)) { let ffi_policy = FfiPolicy::from(policy);
let cmd = crate::ksucalls::SetSepolicyCmd {
cmd: 0,
arg: &ffi_policy as *const _ as u64,
};
if crate::ksucalls::set_sepolicy(&cmd).is_err() {
log::warn!("apply rule: {statement:?} failed."); log::warn!("apply rule: {statement:?} failed.");
if strict { if strict {
return Err(anyhow::anyhow!("apply rule {:?} failed.", statement)); return Err(anyhow::anyhow!("apply rule {:?} failed.", statement));

View File

@@ -17,7 +17,7 @@ use crate::{
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
pub fn grant_root(global_mnt: bool) -> Result<()> { pub fn grant_root(global_mnt: bool) -> Result<()> {
rustix::process::ksu_grant_root()?; crate::ksucalls::grant_root()?;
let mut command = Command::new("sh"); let mut command = Command::new("sh");
let command = unsafe { let command = unsafe {