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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.idea
|
||||
.vscode
|
||||
CLAUDE.md
|
||||
.DS_Store
|
||||
|
||||
@@ -56,8 +56,8 @@ ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
#CompactNamespaces: false # Unknown to clang-format-4.0
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
@@ -501,7 +501,7 @@ IncludeCategories:
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
#IndentPPDirectives: None # Unknown to clang-format-5.0
|
||||
IndentWidth: 8
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
@@ -511,7 +511,7 @@ MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
|
||||
ObjCBlockIndentWidth: 8
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
@@ -543,6 +543,6 @@ SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp03
|
||||
TabWidth: 8
|
||||
UseTab: Always
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
menu "KernelSU"
|
||||
|
||||
config KSU
|
||||
tristate "KernelSU function support"
|
||||
depends on OVERLAY_FS
|
||||
default y
|
||||
help
|
||||
Enable kernel-level root privileges on Android System.
|
||||
To compile as a module, choose M here: the
|
||||
module will be called kernelsu.
|
||||
tristate "KernelSU function support"
|
||||
depends on OVERLAY_FS
|
||||
default y
|
||||
help
|
||||
Enable kernel-level root privileges on Android System.
|
||||
To compile as a module, choose M here: the
|
||||
module will be called kernelsu.
|
||||
|
||||
config KSU_DEBUG
|
||||
bool "KernelSU debug mode"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
Enable KernelSU debug mode.
|
||||
bool "KernelSU debug mode"
|
||||
depends on KSU
|
||||
default n
|
||||
help
|
||||
Enable KernelSU debug mode.
|
||||
|
||||
config KSU_MANUAL_SU
|
||||
bool "Use manual su"
|
||||
depends on KSU
|
||||
default y
|
||||
help
|
||||
Use manual su and authorize the corresponding command line and application via prctl
|
||||
bool "Use manual su"
|
||||
depends on KSU
|
||||
default y
|
||||
help
|
||||
Use manual su and authorize the corresponding command line and application via prctl
|
||||
|
||||
config KPM
|
||||
bool "Enable SukiSU KPM"
|
||||
depends on KSU && 64BIT
|
||||
default n
|
||||
help
|
||||
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.
|
||||
but it may affect system stability.
|
||||
select KALLSYMS
|
||||
select KALLSYMS_ALL
|
||||
bool "Enable SukiSU KPM"
|
||||
depends on KSU && 64BIT
|
||||
default n
|
||||
help
|
||||
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.
|
||||
but it may affect system stability.
|
||||
select KALLSYMS
|
||||
select KALLSYMS_ALL
|
||||
|
||||
choice
|
||||
prompt "KernelSU hook type"
|
||||
@@ -43,21 +43,21 @@ choice
|
||||
|
||||
config KSU_KPROBES_HOOK
|
||||
bool "Hook KernelSU with Kprobes"
|
||||
depends on KPROBES
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with Kernel-probe.
|
||||
depends on KPROBES
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with Kernel-probe.
|
||||
|
||||
config KSU_TRACEPOINT_HOOK
|
||||
bool "Hook KernelSU with Tracepoint"
|
||||
depends on TRACEPOINTS
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with Tracepoint.
|
||||
depends on TRACEPOINTS
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with Tracepoint.
|
||||
|
||||
config KSU_MANUAL_HOOK
|
||||
bool "Hook KernelSU manually"
|
||||
depends on KSU != m
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
||||
depends on KSU != m
|
||||
help
|
||||
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
||||
|
||||
endchoice
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ kernelsu-objs += allowlist.o
|
||||
kernelsu-objs += dynamic_manager.o
|
||||
kernelsu-objs += apk_sign.o
|
||||
kernelsu-objs += sucompat.o
|
||||
kernelsu-objs += pkg_observer.o
|
||||
kernelsu-objs += throne_tracker.o
|
||||
kernelsu-objs += core_hook.o
|
||||
kernelsu-objs += supercalls.o
|
||||
kernelsu-objs += ksud.o
|
||||
kernelsu-objs += embed_ksud.o
|
||||
kernelsu-objs += kernel_compat.o
|
||||
@@ -134,6 +136,11 @@ else
|
||||
$(info -- KPM is disabled)
|
||||
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-declaration-after-statement -Wno-unused-function
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/version.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
||||
#include <linux/compiler_types.h>
|
||||
#endif
|
||||
|
||||
#include "ksu.h"
|
||||
#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)
|
||||
{
|
||||
int *temp_arr;
|
||||
int i, j;
|
||||
int *temp_arr;
|
||||
int i, j;
|
||||
|
||||
if (allow_list_pointer == 0)
|
||||
return;
|
||||
if (allow_list_pointer == 0)
|
||||
return;
|
||||
|
||||
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
|
||||
if (temp_arr == NULL) {
|
||||
pr_err("%s: unable to allocate memory\n", __func__);
|
||||
return;
|
||||
}
|
||||
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
|
||||
if (temp_arr == NULL) {
|
||||
pr_err("%s: unable to allocate memory\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = j = 0; i < allow_list_pointer; i++) {
|
||||
if (allow_list_arr[i] == uid)
|
||||
continue;
|
||||
temp_arr[j++] = allow_list_arr[i];
|
||||
}
|
||||
for (i = j = 0; i < allow_list_pointer; i++) {
|
||||
if (allow_list_arr[i] == uid)
|
||||
continue;
|
||||
temp_arr[j++] = allow_list_arr[i];
|
||||
}
|
||||
|
||||
allow_list_pointer = j;
|
||||
allow_list_pointer = j;
|
||||
|
||||
for (; j < ARRAY_SIZE(allow_list_arr); j++)
|
||||
temp_arr[j] = -1;
|
||||
for (; j < ARRAY_SIZE(allow_list_arr); j++)
|
||||
temp_arr[j] = -1;
|
||||
|
||||
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
|
||||
kfree(temp_arr);
|
||||
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
|
||||
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.gid = 0;
|
||||
default_root_profile.groups_count = 1;
|
||||
default_root_profile.groups[0] = 0;
|
||||
memcpy(&default_root_profile.capabilities.effective, &full_cap,
|
||||
sizeof(default_root_profile.capabilities.effective));
|
||||
default_root_profile.namespaces = 0;
|
||||
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
default_root_profile.uid = 0;
|
||||
default_root_profile.gid = 0;
|
||||
default_root_profile.groups_count = 1;
|
||||
default_root_profile.groups[0] = 0;
|
||||
memcpy(&default_root_profile.capabilities.effective, &full_cap,
|
||||
sizeof(default_root_profile.capabilities.effective));
|
||||
default_root_profile.namespaces = 0;
|
||||
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
|
||||
// This means that we will umount modules by default!
|
||||
default_non_root_profile.umount_modules = true;
|
||||
// This means that we will umount modules by default!
|
||||
default_non_root_profile.umount_modules = true;
|
||||
}
|
||||
|
||||
struct perm_data {
|
||||
struct list_head list;
|
||||
struct app_profile profile;
|
||||
struct list_head list;
|
||||
struct app_profile profile;
|
||||
};
|
||||
|
||||
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_load_work;
|
||||
|
||||
bool persistent_allow_list(void);
|
||||
static bool persistent_allow_list(void);
|
||||
|
||||
void ksu_show_allow_list(void)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
pr_info("ksu_show_allow_list\n");
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
|
||||
p->profile.allow_su);
|
||||
}
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
pr_info("ksu_show_allow_list\n");
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
|
||||
p->profile.allow_su);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
static void ksu_grant_root_to_shell()
|
||||
static void ksu_grant_root_to_shell(void)
|
||||
{
|
||||
struct app_profile profile = {
|
||||
.version = KSU_APP_PROFILE_VER,
|
||||
.allow_su = true,
|
||||
.current_uid = 2000,
|
||||
};
|
||||
strcpy(profile.key, "com.android.shell");
|
||||
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
ksu_set_app_profile(&profile, false);
|
||||
struct app_profile profile = {
|
||||
.version = KSU_APP_PROFILE_VER,
|
||||
.allow_su = true,
|
||||
.current_uid = 2000,
|
||||
};
|
||||
strcpy(profile.key, "com.android.shell");
|
||||
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||
ksu_set_app_profile(&profile, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ksu_get_app_profile(struct app_profile *profile)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
bool found = false;
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
bool found = false;
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
bool uid_match = profile->current_uid == p->profile.current_uid;
|
||||
if (uid_match) {
|
||||
// found it, override it with ours
|
||||
memcpy(profile, &p->profile, sizeof(*profile));
|
||||
found = true;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
bool uid_match = profile->current_uid == p->profile.current_uid;
|
||||
if (uid_match) {
|
||||
// found it, override it with ours
|
||||
memcpy(profile, &p->profile, sizeof(*profile));
|
||||
found = true;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return found;
|
||||
return found;
|
||||
}
|
||||
|
||||
static inline bool forbid_system_uid(uid_t uid) {
|
||||
#define SHELL_UID 2000
|
||||
#define SYSTEM_UID 1000
|
||||
return uid < SHELL_UID && uid != SYSTEM_UID;
|
||||
#define SHELL_UID 2000
|
||||
#define SYSTEM_UID 1000
|
||||
return uid < SHELL_UID && uid != SYSTEM_UID;
|
||||
}
|
||||
|
||||
static bool profile_valid(struct app_profile *profile)
|
||||
{
|
||||
if (!profile) {
|
||||
return false;
|
||||
}
|
||||
if (!profile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile->version < KSU_APP_PROFILE_VER) {
|
||||
pr_info("Unsupported profile version: %d\n", profile->version);
|
||||
return false;
|
||||
}
|
||||
if (profile->version < KSU_APP_PROFILE_VER) {
|
||||
pr_info("Unsupported profile version: %d\n", profile->version);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile->allow_su) {
|
||||
if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) {
|
||||
return false;
|
||||
}
|
||||
if (profile->allow_su) {
|
||||
if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ksu_set_app_profile(struct app_profile *profile, bool persist)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
bool result = false;
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
bool result = false;
|
||||
|
||||
if (!profile_valid(profile)) {
|
||||
pr_err("Failed to set app profile: invalid profile!\n");
|
||||
return false;
|
||||
}
|
||||
if (!profile_valid(profile)) {
|
||||
pr_err("Failed to set app profile: invalid profile!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
list_for_each (pos, &allow_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
|
||||
if (profile->current_uid == p->profile.current_uid &&
|
||||
!strcmp(profile->key, p->profile.key)) {
|
||||
// found it, just override it all!
|
||||
memcpy(&p->profile, profile, sizeof(*profile));
|
||||
result = true;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
list_for_each (pos, &allow_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
|
||||
if (profile->current_uid == p->profile.current_uid &&
|
||||
!strcmp(profile->key, p->profile.key)) {
|
||||
// found it, just override it all!
|
||||
memcpy(&p->profile, profile, sizeof(*profile));
|
||||
result = true;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
// not found, alloc a new node!
|
||||
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
|
||||
if (!p) {
|
||||
pr_err("ksu_set_app_profile alloc failed\n");
|
||||
return false;
|
||||
}
|
||||
// not found, alloc a new node!
|
||||
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
|
||||
if (!p) {
|
||||
pr_err("ksu_set_app_profile alloc failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&p->profile, profile, sizeof(*profile));
|
||||
if (profile->allow_su) {
|
||||
pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n",
|
||||
profile->key, profile->current_uid,
|
||||
profile->rp_config.profile.gid,
|
||||
profile->rp_config.profile.selinux_domain);
|
||||
} else {
|
||||
pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n",
|
||||
profile->key, profile->current_uid,
|
||||
profile->nrp_config.profile.umount_modules);
|
||||
}
|
||||
list_add_tail(&p->list, &allow_list);
|
||||
memcpy(&p->profile, profile, sizeof(*profile));
|
||||
if (profile->allow_su) {
|
||||
pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n",
|
||||
profile->key, profile->current_uid,
|
||||
profile->rp_config.profile.gid,
|
||||
profile->rp_config.profile.selinux_domain);
|
||||
} else {
|
||||
pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n",
|
||||
profile->key, profile->current_uid,
|
||||
profile->nrp_config.profile.umount_modules);
|
||||
}
|
||||
list_add_tail(&p->list, &allow_list);
|
||||
|
||||
out:
|
||||
if (profile->current_uid <= BITMAP_UID_MAX) {
|
||||
if (profile->allow_su)
|
||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
|
||||
else
|
||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
|
||||
} else {
|
||||
if (profile->allow_su) {
|
||||
/*
|
||||
* 1024 apps with uid higher than BITMAP_UID_MAX
|
||||
* registered to request superuser?
|
||||
*/
|
||||
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
|
||||
pr_err("too many apps registered\n");
|
||||
WARN_ON(1);
|
||||
return false;
|
||||
}
|
||||
allow_list_arr[allow_list_pointer++] = profile->current_uid;
|
||||
} else {
|
||||
remove_uid_from_arr(profile->current_uid);
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
if (profile->current_uid <= BITMAP_UID_MAX) {
|
||||
if (profile->allow_su)
|
||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
|
||||
else
|
||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
|
||||
} else {
|
||||
if (profile->allow_su) {
|
||||
/*
|
||||
* 1024 apps with uid higher than BITMAP_UID_MAX
|
||||
* registered to request superuser?
|
||||
*/
|
||||
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
|
||||
pr_err("too many apps registered\n");
|
||||
WARN_ON(1);
|
||||
return false;
|
||||
}
|
||||
allow_list_arr[allow_list_pointer++] = profile->current_uid;
|
||||
} else {
|
||||
remove_uid_from_arr(profile->current_uid);
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
|
||||
// check if the default profiles is changed, cache it to a single struct to accelerate access.
|
||||
if (unlikely(!strcmp(profile->key, "$"))) {
|
||||
// set default non root profile
|
||||
memcpy(&default_non_root_profile, &profile->nrp_config.profile,
|
||||
sizeof(default_non_root_profile));
|
||||
}
|
||||
// check if the default profiles is changed, cache it to a single struct to accelerate access.
|
||||
if (unlikely(!strcmp(profile->key, "$"))) {
|
||||
// set default non root profile
|
||||
memcpy(&default_non_root_profile, &profile->nrp_config.profile,
|
||||
sizeof(default_non_root_profile));
|
||||
}
|
||||
|
||||
if (unlikely(!strcmp(profile->key, "#"))) {
|
||||
// set default root profile
|
||||
memcpy(&default_root_profile, &profile->rp_config.profile,
|
||||
sizeof(default_root_profile));
|
||||
}
|
||||
if (unlikely(!strcmp(profile->key, "#"))) {
|
||||
// set default root profile
|
||||
memcpy(&default_root_profile, &profile->rp_config.profile,
|
||||
sizeof(default_root_profile));
|
||||
}
|
||||
|
||||
if (persist)
|
||||
persistent_allow_list();
|
||||
if (persist)
|
||||
persistent_allow_list();
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool __ksu_is_allow_uid(uid_t uid)
|
||||
{
|
||||
int i;
|
||||
int i;
|
||||
|
||||
if (unlikely(uid == 0)) {
|
||||
// already root, but only allow our domain.
|
||||
return is_ksu_domain();
|
||||
}
|
||||
if (unlikely(uid == 0)) {
|
||||
// already root, but only allow our domain.
|
||||
return is_ksu_domain();
|
||||
}
|
||||
|
||||
if (forbid_system_uid(uid)) {
|
||||
// do not bother going through the list if it's system
|
||||
return false;
|
||||
}
|
||||
if (forbid_system_uid(uid)) {
|
||||
// do not bother going through the list if it's system
|
||||
return false;
|
||||
}
|
||||
|
||||
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
|
||||
// manager is always allowed!
|
||||
return true;
|
||||
}
|
||||
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
|
||||
// manager is always allowed!
|
||||
return true;
|
||||
}
|
||||
|
||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
|
||||
} else {
|
||||
for (i = 0; i < allow_list_pointer; i++) {
|
||||
if (allow_list_arr[i] == uid)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
|
||||
} else {
|
||||
for (i = 0; i < allow_list_pointer; i++) {
|
||||
if (allow_list_arr[i] == uid)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ksu_uid_should_umount(uid_t uid)
|
||||
{
|
||||
struct app_profile profile = { .current_uid = uid };
|
||||
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
|
||||
// we should not umount on manager!
|
||||
return false;
|
||||
}
|
||||
bool found = ksu_get_app_profile(&profile);
|
||||
if (!found) {
|
||||
// no app profile found, it must be non root app
|
||||
return default_non_root_profile.umount_modules;
|
||||
}
|
||||
if (profile.allow_su) {
|
||||
// if found and it is granted to su, we shouldn't umount for it
|
||||
return false;
|
||||
} else {
|
||||
// found an app profile
|
||||
if (profile.nrp_config.use_default) {
|
||||
return default_non_root_profile.umount_modules;
|
||||
} else {
|
||||
return profile.nrp_config.profile.umount_modules;
|
||||
}
|
||||
}
|
||||
struct app_profile profile = { .current_uid = uid };
|
||||
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
|
||||
// we should not umount on manager!
|
||||
return false;
|
||||
}
|
||||
bool found = ksu_get_app_profile(&profile);
|
||||
if (!found) {
|
||||
// no app profile found, it must be non root app
|
||||
return default_non_root_profile.umount_modules;
|
||||
}
|
||||
if (profile.allow_su) {
|
||||
// if found and it is granted to su, we shouldn't umount for it
|
||||
return false;
|
||||
} else {
|
||||
// found an app profile
|
||||
if (profile.nrp_config.use_default) {
|
||||
return default_non_root_profile.umount_modules;
|
||||
} else {
|
||||
return profile.nrp_config.profile.umount_modules;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct root_profile *ksu_get_root_profile(uid_t uid)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
if (uid == p->profile.current_uid && p->profile.allow_su) {
|
||||
if (!p->profile.rp_config.use_default) {
|
||||
return &p->profile.rp_config.profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
if (uid == p->profile.current_uid && p->profile.allow_su) {
|
||||
if (!p->profile.rp_config.use_default) {
|
||||
return &p->profile.rp_config.profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use default profile
|
||||
return &default_root_profile;
|
||||
// use default profile
|
||||
return &default_root_profile;
|
||||
}
|
||||
|
||||
bool ksu_get_allow_list(int *array, int *length, bool allow)
|
||||
{
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
int i = 0;
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
// pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
|
||||
if (p->profile.allow_su == allow) {
|
||||
array[i++] = p->profile.current_uid;
|
||||
}
|
||||
}
|
||||
*length = i;
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
int i = 0;
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
// pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
|
||||
if (p->profile.allow_su == allow) {
|
||||
array[i++] = p->profile.current_uid;
|
||||
}
|
||||
}
|
||||
*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 version = FILE_FORMAT_VERSION;
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
loff_t off = 0;
|
||||
u32 magic = FILE_MAGIC;
|
||||
u32 version = FILE_FORMAT_VERSION;
|
||||
struct perm_data *p = NULL;
|
||||
struct list_head *pos = NULL;
|
||||
loff_t off = 0;
|
||||
|
||||
struct file *fp =
|
||||
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
struct file *fp =
|
||||
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
// store magic and version
|
||||
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) !=
|
||||
sizeof(magic)) {
|
||||
pr_err("save_allow_list write magic failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
// store magic and version
|
||||
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) !=
|
||||
sizeof(magic)) {
|
||||
pr_err("save_allow_list write magic failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("save_allow_list write version failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("save_allow_list write version failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("save allow list, name: %s uid :%d, allow: %d\n",
|
||||
p->profile.key, p->profile.current_uid,
|
||||
p->profile.allow_su);
|
||||
list_for_each (pos, &allow_list) {
|
||||
p = list_entry(pos, struct perm_data, list);
|
||||
pr_info("save allow list, name: %s uid: %d, allow: %d\n",
|
||||
p->profile.key, p->profile.current_uid,
|
||||
p->profile.allow_su);
|
||||
|
||||
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
|
||||
&off);
|
||||
}
|
||||
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
|
||||
&off);
|
||||
}
|
||||
|
||||
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;
|
||||
ssize_t ret = 0;
|
||||
struct file *fp = NULL;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
loff_t off = 0;
|
||||
ssize_t ret = 0;
|
||||
struct file *fp = NULL;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
// always allow adb shell by default
|
||||
ksu_grant_root_to_shell();
|
||||
// always allow adb shell by default
|
||||
ksu_grant_root_to_shell();
|
||||
#endif
|
||||
|
||||
// load allowlist now!
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
// load allowlist now!
|
||||
fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
// verify magic
|
||||
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) !=
|
||||
sizeof(magic) ||
|
||||
magic != FILE_MAGIC) {
|
||||
pr_err("allowlist file invalid: %d!\n", magic);
|
||||
goto exit;
|
||||
}
|
||||
// verify magic
|
||||
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) !=
|
||||
sizeof(magic) ||
|
||||
magic != FILE_MAGIC) {
|
||||
pr_err("allowlist file invalid: %d!\n", magic);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("allowlist read version: %d failed\n", version);
|
||||
goto exit;
|
||||
}
|
||||
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) !=
|
||||
sizeof(version)) {
|
||||
pr_err("allowlist read version: %d failed\n", version);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pr_info("allowlist version: %d\n", version);
|
||||
pr_info("allowlist version: %d\n", version);
|
||||
|
||||
while (true) {
|
||||
struct app_profile profile;
|
||||
while (true) {
|
||||
struct app_profile profile;
|
||||
|
||||
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
|
||||
&off);
|
||||
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
|
||||
&off);
|
||||
|
||||
if (ret <= 0) {
|
||||
pr_info("load_allow_list read err: %zd\n", ret);
|
||||
break;
|
||||
}
|
||||
if (ret <= 0) {
|
||||
pr_info("load_allow_list read err: %zd\n", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n",
|
||||
profile.key, profile.current_uid, profile.allow_su);
|
||||
ksu_set_app_profile(&profile, false);
|
||||
}
|
||||
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n",
|
||||
profile.key, profile.current_uid, profile.allow_su);
|
||||
ksu_set_app_profile(&profile, false);
|
||||
}
|
||||
|
||||
exit:
|
||||
ksu_show_allow_list();
|
||||
filp_close(fp, 0);
|
||||
ksu_show_allow_list();
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data)
|
||||
{
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
|
||||
bool modified = false;
|
||||
// TODO: use RCU!
|
||||
mutex_lock(&allowlist_mutex);
|
||||
list_for_each_entry_safe (np, n, &allow_list, list) {
|
||||
uid_t uid = np->profile.current_uid;
|
||||
char *package = np->profile.key;
|
||||
// we use this uid for special cases, don't prune it!
|
||||
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
|
||||
if (!is_preserved_uid && !is_uid_valid(uid, package, data)) {
|
||||
modified = true;
|
||||
pr_info("prune uid: %d, package: %s\n", uid, package);
|
||||
list_del(&np->list);
|
||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
|
||||
}
|
||||
remove_uid_from_arr(uid);
|
||||
smp_mb();
|
||||
kfree(np);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&allowlist_mutex);
|
||||
bool modified = false;
|
||||
// TODO: use RCU!
|
||||
mutex_lock(&allowlist_mutex);
|
||||
list_for_each_entry_safe (np, n, &allow_list, list) {
|
||||
uid_t uid = np->profile.current_uid;
|
||||
char *package = np->profile.key;
|
||||
// we use this uid for special cases, don't prune it!
|
||||
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
|
||||
if (!is_preserved_uid && !is_uid_valid(uid, package, data)) {
|
||||
modified = true;
|
||||
pr_info("prune uid: %d, package: %s\n", uid, package);
|
||||
list_del(&np->list);
|
||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
|
||||
}
|
||||
remove_uid_from_arr(uid);
|
||||
smp_mb();
|
||||
kfree(np);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&allowlist_mutex);
|
||||
|
||||
if (modified) {
|
||||
persistent_allow_list();
|
||||
}
|
||||
if (modified) {
|
||||
persistent_allow_list();
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return ksu_queue_work(&ksu_load_work);
|
||||
return ksu_queue_work(&ksu_load_work);
|
||||
}
|
||||
|
||||
void ksu_allowlist_init(void)
|
||||
{
|
||||
int i;
|
||||
int i;
|
||||
|
||||
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
|
||||
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
|
||||
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
|
||||
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
|
||||
allow_list_arr[i] = -1;
|
||||
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
|
||||
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_load_work, do_load_allow_list);
|
||||
INIT_WORK(&ksu_save_work, do_save_allow_list);
|
||||
INIT_WORK(&ksu_load_work, do_load_allow_list);
|
||||
|
||||
init_default_profiles();
|
||||
init_default_profiles();
|
||||
}
|
||||
|
||||
void ksu_allowlist_exit(void)
|
||||
{
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
struct perm_data *np = NULL;
|
||||
struct perm_data *n = NULL;
|
||||
|
||||
do_save_allow_list(NULL);
|
||||
do_save_allow_list(NULL);
|
||||
|
||||
// free allowlist
|
||||
mutex_lock(&allowlist_mutex);
|
||||
list_for_each_entry_safe (np, n, &allow_list, list) {
|
||||
list_del(&np->list);
|
||||
kfree(np);
|
||||
}
|
||||
mutex_unlock(&allowlist_mutex);
|
||||
// free allowlist
|
||||
mutex_lock(&allowlist_mutex);
|
||||
list_for_each_entry_safe (np, n, &allow_list, list) {
|
||||
list_del(&np->list);
|
||||
kfree(np);
|
||||
}
|
||||
mutex_unlock(&allowlist_mutex);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
@@ -553,7 +555,7 @@ bool ksu_temp_grant_root_once(uid_t uid)
|
||||
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.groups_count = default_root_profile.groups_count;
|
||||
memcpy(profile.rp_config.profile.groups, default_root_profile.groups, sizeof(default_root_profile.groups));
|
||||
|
||||
@@ -21,62 +21,62 @@
|
||||
#include "manager_sign.h"
|
||||
|
||||
struct sdesc {
|
||||
struct shash_desc shash;
|
||||
char ctx[];
|
||||
struct shash_desc shash;
|
||||
char ctx[];
|
||||
};
|
||||
|
||||
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
|
||||
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
|
||||
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct sdesc *init_sdesc(struct crypto_shash *alg)
|
||||
{
|
||||
struct sdesc *sdesc;
|
||||
int size;
|
||||
struct sdesc *sdesc;
|
||||
int size;
|
||||
|
||||
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
|
||||
sdesc = kmalloc(size, GFP_KERNEL);
|
||||
if (!sdesc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
sdesc->shash.tfm = alg;
|
||||
return sdesc;
|
||||
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
|
||||
sdesc = kmalloc(size, GFP_KERNEL);
|
||||
if (!sdesc)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
sdesc->shash.tfm = alg;
|
||||
return sdesc;
|
||||
}
|
||||
|
||||
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;
|
||||
int ret;
|
||||
struct sdesc *sdesc;
|
||||
int ret;
|
||||
|
||||
sdesc = init_sdesc(alg);
|
||||
if (IS_ERR(sdesc)) {
|
||||
pr_info("can't alloc sdesc\n");
|
||||
return PTR_ERR(sdesc);
|
||||
}
|
||||
sdesc = init_sdesc(alg);
|
||||
if (IS_ERR(sdesc)) {
|
||||
pr_info("can't alloc sdesc\n");
|
||||
return PTR_ERR(sdesc);
|
||||
}
|
||||
|
||||
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
|
||||
kfree(sdesc);
|
||||
return ret;
|
||||
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
|
||||
kfree(sdesc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ksu_sha256(const unsigned char *data, unsigned int datalen,
|
||||
unsigned char *digest)
|
||||
unsigned char *digest)
|
||||
{
|
||||
struct crypto_shash *alg;
|
||||
char *hash_alg_name = "sha256";
|
||||
int ret;
|
||||
struct crypto_shash *alg;
|
||||
char *hash_alg_name = "sha256";
|
||||
int ret;
|
||||
|
||||
alg = crypto_alloc_shash(hash_alg_name, 0, 0);
|
||||
if (IS_ERR(alg)) {
|
||||
pr_info("can't alloc alg %s\n", hash_alg_name);
|
||||
return PTR_ERR(alg);
|
||||
}
|
||||
ret = calc_hash(alg, data, datalen, digest);
|
||||
crypto_free_shash(alg);
|
||||
return ret;
|
||||
alg = crypto_alloc_shash(hash_alg_name, 0, 0);
|
||||
if (IS_ERR(alg)) {
|
||||
pr_info("can't alloc alg %s\n", hash_alg_name);
|
||||
return PTR_ERR(alg);
|
||||
}
|
||||
ret = calc_hash(alg, data, datalen, digest);
|
||||
crypto_free_shash(alg);
|
||||
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)
|
||||
{
|
||||
struct dynamic_sign_key current_dynamic_key = dynamic_sign;
|
||||
struct dynamic_sign_key current_dynamic_key = dynamic_sign;
|
||||
|
||||
if (ksu_get_dynamic_manager_config(¤t_dynamic_key.size, ¤t_dynamic_key.hash)) {
|
||||
pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n",
|
||||
current_dynamic_key.size, current_dynamic_key.hash);
|
||||
}
|
||||
if (ksu_get_dynamic_manager_config(¤t_dynamic_key.size, ¤t_dynamic_key.hash)) {
|
||||
pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n",
|
||||
current_dynamic_key.size, current_dynamic_key.hash);
|
||||
}
|
||||
|
||||
if (size4 != current_dynamic_key.size) {
|
||||
return false;
|
||||
}
|
||||
if (size4 != current_dynamic_key.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CERT_MAX_LENGTH 1024
|
||||
char cert[CERT_MAX_LENGTH];
|
||||
if (size4 > CERT_MAX_LENGTH) {
|
||||
pr_info("cert length overlimit\n");
|
||||
return false;
|
||||
}
|
||||
char cert[CERT_MAX_LENGTH];
|
||||
if (size4 > CERT_MAX_LENGTH) {
|
||||
pr_info("cert length overlimit\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ksu_kernel_read_compat(fp, cert, size4, pos);
|
||||
ksu_kernel_read_compat(fp, cert, size4, pos);
|
||||
|
||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||
if (ksu_sha256(cert, size4, digest) < 0) {
|
||||
pr_info("sha256 error\n");
|
||||
return false;
|
||||
}
|
||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||
if (ksu_sha256(cert, size4, digest) < 0) {
|
||||
pr_info("sha256 error\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
|
||||
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
|
||||
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
|
||||
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
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 (matched_index) {
|
||||
*matched_index = DYNAMIC_SIGN_INDEX;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (strcmp(current_dynamic_key.hash, hash_str) == 0) {
|
||||
if (matched_index) {
|
||||
*matched_index = DYNAMIC_SIGN_INDEX;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, int *matched_index)
|
||||
{
|
||||
int i;
|
||||
apk_sign_key_t sign_key;
|
||||
bool signature_valid = false;
|
||||
int i;
|
||||
apk_sign_key_t sign_key;
|
||||
bool signature_valid = false;
|
||||
|
||||
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); // signed data 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); // 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;
|
||||
*offset += 0x4 + *size4;
|
||||
*pos += *size4;
|
||||
*offset += 0x4 + *size4;
|
||||
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
|
||||
*offset += 0x4 * 2;
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
|
||||
*offset += 0x4 * 2;
|
||||
|
||||
if (ksu_is_dynamic_manager_enabled()) {
|
||||
loff_t temp_pos = *pos;
|
||||
if (check_dynamic_sign(fp, *size4, &temp_pos, matched_index)) {
|
||||
*pos = temp_pos;
|
||||
*offset += *size4;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (ksu_is_dynamic_manager_enabled()) {
|
||||
loff_t temp_pos = *pos;
|
||||
if (check_dynamic_sign(fp, *size4, &temp_pos, matched_index)) {
|
||||
*pos = temp_pos;
|
||||
*offset += *size4;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
|
||||
sign_key = apk_sign_keys[i];
|
||||
for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
|
||||
sign_key = apk_sign_keys[i];
|
||||
|
||||
if (*size4 != sign_key.size)
|
||||
continue;
|
||||
*offset += *size4;
|
||||
if (*size4 != sign_key.size)
|
||||
continue;
|
||||
*offset += *size4;
|
||||
|
||||
#define CERT_MAX_LENGTH 1024
|
||||
char cert[CERT_MAX_LENGTH];
|
||||
if (*size4 > CERT_MAX_LENGTH) {
|
||||
pr_info("cert length overlimit\n");
|
||||
return false;
|
||||
}
|
||||
ksu_kernel_read_compat(fp, cert, *size4, pos);
|
||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||
if (IS_ERR(ksu_sha256(cert, *size4, digest))) {
|
||||
pr_info("sha256 error\n");
|
||||
return false;
|
||||
}
|
||||
char cert[CERT_MAX_LENGTH];
|
||||
if (*size4 > CERT_MAX_LENGTH) {
|
||||
pr_info("cert length overlimit\n");
|
||||
return false;
|
||||
}
|
||||
ksu_kernel_read_compat(fp, cert, *size4, pos);
|
||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||
if (ksu_sha256(cert, *size4, digest) < 0 ) {
|
||||
pr_info("sha256 error\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
|
||||
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
char hash_str[SHA256_DIGEST_SIZE * 2 + 1];
|
||||
hash_str[SHA256_DIGEST_SIZE * 2] = '\0';
|
||||
|
||||
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
|
||||
pr_info("sha256: %s, expected: %s, index: %d\n", hash_str, sign_key.sha256, i);
|
||||
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
|
||||
pr_info("sha256: %s, expected: %s, index: %d\n", hash_str, sign_key.sha256, i);
|
||||
|
||||
if (strcmp(sign_key.sha256, hash_str) == 0) {
|
||||
signature_valid = true;
|
||||
if (matched_index) {
|
||||
*matched_index = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return signature_valid;
|
||||
if (strcmp(sign_key.sha256, hash_str) == 0) {
|
||||
signature_valid = true;
|
||||
if (matched_index) {
|
||||
*matched_index = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return signature_valid;
|
||||
}
|
||||
|
||||
struct zip_entry_header {
|
||||
uint32_t signature;
|
||||
uint16_t version;
|
||||
uint16_t flags;
|
||||
uint16_t compression;
|
||||
uint16_t mod_time;
|
||||
uint16_t mod_date;
|
||||
uint32_t crc32;
|
||||
uint32_t compressed_size;
|
||||
uint32_t uncompressed_size;
|
||||
uint16_t file_name_length;
|
||||
uint16_t extra_field_length;
|
||||
uint32_t signature;
|
||||
uint16_t version;
|
||||
uint16_t flags;
|
||||
uint16_t compression;
|
||||
uint16_t mod_time;
|
||||
uint16_t mod_date;
|
||||
uint32_t crc32;
|
||||
uint32_t compressed_size;
|
||||
uint32_t uncompressed_size;
|
||||
uint16_t file_name_length;
|
||||
uint16_t extra_field_length;
|
||||
} __attribute__((packed));
|
||||
|
||||
// This is a necessary but not sufficient condition, but it is enough for us
|
||||
static bool has_v1_signature_file(struct file *fp)
|
||||
{
|
||||
struct zip_entry_header header;
|
||||
const char MANIFEST[] = "META-INF/MANIFEST.MF";
|
||||
struct zip_entry_header header;
|
||||
const char MANIFEST[] = "META-INF/MANIFEST.MF";
|
||||
|
||||
loff_t pos = 0;
|
||||
loff_t pos = 0;
|
||||
|
||||
while (ksu_kernel_read_compat(fp, &header,
|
||||
sizeof(struct zip_entry_header), &pos) ==
|
||||
sizeof(struct zip_entry_header)) {
|
||||
if (header.signature != 0x04034b50) {
|
||||
// ZIP magic: 'PK'
|
||||
return false;
|
||||
}
|
||||
// Read the entry file name
|
||||
if (header.file_name_length == sizeof(MANIFEST) - 1) {
|
||||
char fileName[sizeof(MANIFEST)];
|
||||
ksu_kernel_read_compat(fp, fileName,
|
||||
header.file_name_length, &pos);
|
||||
fileName[header.file_name_length] = '\0';
|
||||
while (ksu_kernel_read_compat(fp, &header,
|
||||
sizeof(struct zip_entry_header), &pos) ==
|
||||
sizeof(struct zip_entry_header)) {
|
||||
if (header.signature != 0x04034b50) {
|
||||
// ZIP magic: 'PK'
|
||||
return false;
|
||||
}
|
||||
// Read the entry file name
|
||||
if (header.file_name_length == sizeof(MANIFEST) - 1) {
|
||||
char fileName[sizeof(MANIFEST)];
|
||||
ksu_kernel_read_compat(fp, fileName,
|
||||
header.file_name_length, &pos);
|
||||
fileName[header.file_name_length] = '\0';
|
||||
|
||||
// Check if the entry matches META-INF/MANIFEST.MF
|
||||
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Skip the entry file name
|
||||
pos += header.file_name_length;
|
||||
}
|
||||
// Check if the entry matches META-INF/MANIFEST.MF
|
||||
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) ==
|
||||
0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Skip the entry file name
|
||||
pos += header.file_name_length;
|
||||
}
|
||||
|
||||
// Skip to the next entry
|
||||
pos += header.extra_field_length + header.compressed_size;
|
||||
}
|
||||
// Skip to the next entry
|
||||
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)
|
||||
{
|
||||
unsigned char buffer[0x11] = { 0 };
|
||||
u32 size4;
|
||||
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;
|
||||
}
|
||||
unsigned char buffer[0x11] = { 0 };
|
||||
u32 size4;
|
||||
u64 size8, size_of_block;
|
||||
|
||||
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip
|
||||
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
|
||||
filp_close(fp, 0);
|
||||
return 0;
|
||||
}
|
||||
loff_t pos;
|
||||
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
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;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
|
||||
for (i = 0;; ++i) {
|
||||
unsigned short n;
|
||||
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;
|
||||
}
|
||||
}
|
||||
// If you want to check for multi-manager APK signing, but dynamic managering is not enabled, skip
|
||||
if (check_multi_manager && !ksu_is_dynamic_manager_enabled()) {
|
||||
filp_close(fp, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pos += 12;
|
||||
// offset
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8, &pos);
|
||||
ksu_kernel_read_compat(fp, buffer, 0x10, &pos);
|
||||
if (strcmp((char *)buffer, "APK Sig Block 42")) {
|
||||
goto clean;
|
||||
}
|
||||
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
|
||||
for (i = 0;; ++i) {
|
||||
unsigned short n;
|
||||
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);
|
||||
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
|
||||
if (size_of_block != size8) {
|
||||
goto clean;
|
||||
}
|
||||
pos += 12;
|
||||
// offset
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
|
||||
int loop_count = 0;
|
||||
while (loop_count++ < 10) {
|
||||
uint32_t id;
|
||||
uint32_t offset;
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8,
|
||||
&pos); // sequence length
|
||||
if (size8 == size_of_block) {
|
||||
break;
|
||||
}
|
||||
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
|
||||
offset = 4;
|
||||
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 {
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8, &pos);
|
||||
ksu_kernel_read_compat(fp, buffer, 0x10, &pos);
|
||||
if (strcmp((char *)buffer, "APK Sig Block 42")) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
pos = size4 - (size8 + 0x8);
|
||||
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
|
||||
if (size_of_block != size8) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
int loop_count = 0;
|
||||
while (loop_count++ < 10) {
|
||||
uint32_t id;
|
||||
uint32_t offset;
|
||||
ksu_kernel_read_compat(fp, &size8, 0x8,
|
||||
&pos); // sequence length
|
||||
if (size8 == size_of_block) {
|
||||
break;
|
||||
}
|
||||
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
|
||||
offset = 4;
|
||||
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
|
||||
pr_info("Unknown id: 0x%08x\n", id);
|
||||
pr_info("Unknown id: 0x%08x\n", id);
|
||||
#endif
|
||||
}
|
||||
pos += (size8 - offset);
|
||||
}
|
||||
}
|
||||
pos += (size8 - offset);
|
||||
}
|
||||
|
||||
if (v2_signing_blocks != 1) {
|
||||
if (v2_signing_blocks != 1) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_err("Unexpected v2 signature count: %d\n",
|
||||
v2_signing_blocks);
|
||||
pr_err("Unexpected v2 signature count: %d\n",
|
||||
v2_signing_blocks);
|
||||
#endif
|
||||
v2_signing_valid = false;
|
||||
}
|
||||
v2_signing_valid = false;
|
||||
}
|
||||
|
||||
if (v2_signing_valid) {
|
||||
int has_v1_signing = has_v1_signature_file(fp);
|
||||
if (has_v1_signing) {
|
||||
pr_err("Unexpected v1 signature scheme found!\n");
|
||||
filp_close(fp, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (v2_signing_valid) {
|
||||
int has_v1_signing = has_v1_signature_file(fp);
|
||||
if (has_v1_signing) {
|
||||
pr_err("Unexpected v1 signature scheme found!\n");
|
||||
filp_close(fp, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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
|
||||
pr_err("Unexpected v3 signature scheme found!\n");
|
||||
pr_err("Unexpected v3 signature scheme found!\n");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (v2_signing_valid) {
|
||||
if (signature_index) {
|
||||
*signature_index = matched_index;
|
||||
}
|
||||
if (v2_signing_valid) {
|
||||
if (signature_index) {
|
||||
*signature_index = matched_index;
|
||||
}
|
||||
|
||||
if (check_multi_manager) {
|
||||
// 0: ShirkNeko/SukiSU, DYNAMIC_SIGN_INDEX : Dynamic Sign
|
||||
if (matched_index == 0 || matched_index == DYNAMIC_SIGN_INDEX) {
|
||||
pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// Common manager check: any valid signature will do
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (check_multi_manager) {
|
||||
// 0: ShirkNeko/SukiSU, DYNAMIC_SIGN_INDEX : Dynamic Sign
|
||||
if (matched_index == 0 || matched_index == DYNAMIC_SIGN_INDEX) {
|
||||
pr_info("Multi-manager APK detected (dynamic_manager enabled): signature_index=%d\n", matched_index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// Common manager check: any valid signature will do
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
int rv = param_set_uint(val, kp);
|
||||
ksu_set_manager_uid(ksu_debug_manager_uid);
|
||||
pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid);
|
||||
return rv;
|
||||
int rv = param_set_uint(val, kp);
|
||||
ksu_set_manager_uid(ksu_debug_manager_uid);
|
||||
pr_info("ksu_manager_uid set to %d\n", ksu_debug_manager_uid);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static struct kernel_param_ops expected_size_ops = {
|
||||
.set = set_expected_size,
|
||||
.get = param_get_uint,
|
||||
.set = set_expected_size,
|
||||
.get = param_get_uint,
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -19,10 +19,17 @@
|
||||
#define __PT_IP_REG pc
|
||||
|
||||
#define PRCTL_SYMBOL "__arm64_sys_prctl"
|
||||
#define REBOOT_SYMBOL "__arm64_sys_reboot"
|
||||
#define SYS_READ_SYMBOL "__arm64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat"
|
||||
#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__)
|
||||
|
||||
@@ -40,10 +47,17 @@
|
||||
#define __PT_SP_REG sp
|
||||
#define __PT_IP_REG ip
|
||||
#define PRCTL_SYMBOL "__x64_sys_prctl"
|
||||
#define REBOOT_SYMBOL "__x64_sys_reboot"
|
||||
#define SYS_READ_SYMBOL "__x64_sys_read"
|
||||
#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat"
|
||||
#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat"
|
||||
#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
|
||||
#error "Unsupported arch"
|
||||
|
||||
1821
kernel/core_hook.c
1821
kernel/core_hook.c
File diff suppressed because it is too large
Load Diff
@@ -11,11 +11,11 @@ void ksu_core_exit(void);
|
||||
#define KSU_PROC_UMOUNT 50
|
||||
|
||||
static inline bool ksu_is_current_proc_umounted(void) {
|
||||
return test_ti_thread_flag(¤t->thread_info, KSU_PROC_UMOUNT);
|
||||
return test_ti_thread_flag(¤t->thread_info, KSU_PROC_UMOUNT);
|
||||
}
|
||||
|
||||
static inline void ksu_set_current_proc_umounted(void) {
|
||||
set_ti_thread_flag(¤t->thread_info, KSU_PROC_UMOUNT);
|
||||
set_ti_thread_flag(¤t->thread_info, KSU_PROC_UMOUNT);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,22 +7,22 @@
|
||||
// For sucompat
|
||||
|
||||
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);
|
||||
|
||||
// For ksud
|
||||
|
||||
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
|
||||
|
||||
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
|
||||
void *envp, int *flags);
|
||||
void *envp, int *flags);
|
||||
|
||||
// For volume button
|
||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||
int *value);
|
||||
int *value);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/seccomp.h>
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#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
|
||||
struct ksu_ns_fs_saved {
|
||||
struct nsproxy *ns;
|
||||
struct fs_struct *fs;
|
||||
struct nsproxy *ns;
|
||||
struct fs_struct *fs;
|
||||
};
|
||||
|
||||
static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||
{
|
||||
ns_fs_saved->ns = current->nsproxy;
|
||||
ns_fs_saved->fs = current->fs;
|
||||
ns_fs_saved->ns = current->nsproxy;
|
||||
ns_fs_saved->fs = current->fs;
|
||||
}
|
||||
|
||||
static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||
{
|
||||
current->nsproxy = ns_fs_saved->ns;
|
||||
current->fs = ns_fs_saved->fs;
|
||||
current->nsproxy = ns_fs_saved->ns;
|
||||
current->fs = ns_fs_saved->fs;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (android_context_saved_checked)
|
||||
return;
|
||||
android_context_saved_checked = true;
|
||||
task_lock(current);
|
||||
if (current->nsproxy && current->fs &&
|
||||
current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) {
|
||||
android_context_saved_enabled = true;
|
||||
if (android_context_saved_checked)
|
||||
return;
|
||||
android_context_saved_checked = true;
|
||||
task_lock(current);
|
||||
if (current->nsproxy && current->fs &&
|
||||
current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) {
|
||||
android_context_saved_enabled = true;
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
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);
|
||||
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);
|
||||
#endif
|
||||
ksu_save_ns_fs(&android_context_saved);
|
||||
} else {
|
||||
pr_info("android context saved disabled\n");
|
||||
}
|
||||
task_unlock(current);
|
||||
ksu_save_ns_fs(&android_context_saved);
|
||||
} else {
|
||||
pr_info("android context saved disabled\n");
|
||||
}
|
||||
task_unlock(current);
|
||||
}
|
||||
|
||||
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
|
||||
struct ksu_ns_fs_saved saved;
|
||||
if (android_context_saved_enabled) {
|
||||
// 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;
|
||||
if (android_context_saved_enabled) {
|
||||
#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
|
||||
task_lock(current);
|
||||
ksu_save_ns_fs(&saved);
|
||||
ksu_load_ns_fs(&android_context_saved);
|
||||
task_unlock(current);
|
||||
}
|
||||
struct file *fp = filp_open(filename, flags, mode);
|
||||
if (android_context_saved_enabled) {
|
||||
task_lock(current);
|
||||
ksu_load_ns_fs(&saved);
|
||||
task_unlock(current);
|
||||
task_lock(current);
|
||||
ksu_save_ns_fs(&saved);
|
||||
ksu_load_ns_fs(&android_context_saved);
|
||||
task_unlock(current);
|
||||
}
|
||||
struct file *fp = filp_open(filename, flags, mode);
|
||||
if (android_context_saved_enabled) {
|
||||
task_lock(current);
|
||||
ksu_load_ns_fs(&saved);
|
||||
task_unlock(current);
|
||||
#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
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
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,
|
||||
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 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
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ static inline __maybe_unused size_t list_count_nodes(const struct list_head *hea
|
||||
list_for_each(pos, head)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -58,25 +58,25 @@ static inline __maybe_unused size_t list_count_nodes(const struct list_head *hea
|
||||
* From ss/ebitmap.h
|
||||
*/
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) && \
|
||||
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) || \
|
||||
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) && \
|
||||
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0))
|
||||
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) || \
|
||||
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) && \
|
||||
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0))
|
||||
#ifdef HISI_SELINUX_EBITMAP_RO
|
||||
#define CONFIG_IS_HW_HISI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern long ksu_strncpy_from_user_nofault(char *dst,
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
const void __user *unsafe_addr,
|
||||
long count);
|
||||
|
||||
extern void ksu_android_ns_fs_check();
|
||||
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,
|
||||
loff_t *pos);
|
||||
loff_t *pos);
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
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);
|
||||
if (likely(!ret))
|
||||
return ret;
|
||||
long ret = copy_from_user_nofault(to, from, count);
|
||||
if (likely(!ret))
|
||||
return ret;
|
||||
|
||||
// we faulted! fallback to slow path
|
||||
return copy_from_user(to, from, count);
|
||||
// we faulted! fallback to slow path
|
||||
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
|
||||
|
||||
50
kernel/ksu.c
50
kernel/ksu.c
@@ -16,11 +16,11 @@ static struct workqueue_struct *ksu_workqueue;
|
||||
|
||||
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,
|
||||
void *argv, void *envp, int *flags);
|
||||
void *argv, void *envp, int *flags);
|
||||
|
||||
extern void ksu_sucompat_init();
|
||||
extern void ksu_sucompat_exit();
|
||||
@@ -34,27 +34,27 @@ extern void ksu_trace_unregister();
|
||||
int __init kernelsu_init(void)
|
||||
{
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_alert("*************************************************************");
|
||||
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
|
||||
pr_alert("** **");
|
||||
pr_alert("** You are running KernelSU in DEBUG mode **");
|
||||
pr_alert("** **");
|
||||
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
|
||||
pr_alert("*************************************************************");
|
||||
pr_alert("*************************************************************");
|
||||
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
|
||||
pr_alert("** **");
|
||||
pr_alert("** You are running KernelSU in DEBUG mode **");
|
||||
pr_alert("** **");
|
||||
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
|
||||
pr_alert("*************************************************************");
|
||||
#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
|
||||
ksu_sucompat_init();
|
||||
ksu_ksud_init();
|
||||
ksu_sucompat_init();
|
||||
ksu_ksud_init();
|
||||
#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
|
||||
|
||||
#ifdef CONFIG_KSU_TRACEPOINT_HOOK
|
||||
@@ -63,30 +63,32 @@ int __init kernelsu_init(void)
|
||||
|
||||
#ifdef MODULE
|
||||
#ifndef CONFIG_KSU_DEBUG
|
||||
kobject_del(&THIS_MODULE->mkobj.kobj);
|
||||
kobject_del(&THIS_MODULE->mkobj.kobj);
|
||||
#endif
|
||||
#endif
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
ksu_ksud_exit();
|
||||
ksu_sucompat_exit();
|
||||
ksu_ksud_exit();
|
||||
ksu_sucompat_exit();
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KSU_TRACEPOINT_HOOK
|
||||
ksu_trace_unregister();
|
||||
#endif
|
||||
|
||||
ksu_core_exit();
|
||||
ksu_core_exit();
|
||||
}
|
||||
|
||||
module_init(kernelsu_init);
|
||||
|
||||
103
kernel/ksu.h
103
kernel/ksu.h
@@ -5,37 +5,14 @@
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define KERNEL_SU_VERSION KSU_VERSION
|
||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||
#define KERNEL_SU_OPTION 0xBADC0DE
|
||||
|
||||
#define CMD_GRANT_ROOT 0
|
||||
#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
|
||||
extern bool ksu_uid_scanner_enabled;
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#define CMD_MANUAL_SU_REQUEST 50
|
||||
#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_BOOT_COMPLETED 2
|
||||
#define EVENT_MODULE_MOUNTED 3
|
||||
@@ -56,6 +33,10 @@
|
||||
#define DYNAMIC_MANAGER_OP_GET 1
|
||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||
|
||||
#define UID_SCANNER_OP_GET_STATUS 0
|
||||
#define UID_SCANNER_OP_TOGGLE 1
|
||||
#define UID_SCANNER_OP_CLEAR_ENV 2
|
||||
|
||||
struct dynamic_manager_user_config {
|
||||
unsigned int operation;
|
||||
unsigned int size;
|
||||
@@ -71,67 +52,67 @@ struct manager_list_info {
|
||||
};
|
||||
|
||||
struct root_profile {
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
u64 effective;
|
||||
u64 permitted;
|
||||
u64 inheritable;
|
||||
} capabilities;
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
u64 effective;
|
||||
u64 permitted;
|
||||
u64 inheritable;
|
||||
} capabilities;
|
||||
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
|
||||
int32_t namespaces;
|
||||
int32_t namespaces;
|
||||
};
|
||||
|
||||
struct non_root_profile {
|
||||
bool umount_modules;
|
||||
bool umount_modules;
|
||||
};
|
||||
|
||||
struct app_profile {
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
u32 version;
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
u32 version;
|
||||
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
|
||||
struct {
|
||||
bool use_default;
|
||||
struct {
|
||||
bool use_default;
|
||||
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
};
|
||||
|
||||
bool ksu_queue_work(struct work_struct *work);
|
||||
|
||||
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)
|
||||
{
|
||||
size_t slen = strlen(s);
|
||||
size_t tlen = strlen(t);
|
||||
if (tlen > slen)
|
||||
return 1;
|
||||
return strcmp(s + slen - tlen, t);
|
||||
size_t slen = strlen(s);
|
||||
size_t tlen = strlen(t);
|
||||
if (tlen > slen)
|
||||
return 1;
|
||||
return strcmp(s + slen - tlen, t);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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 *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,
|
||||
@@ -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,
|
||||
size_t *count_ptr)
|
||||
{
|
||||
if (unlikely(ksu_vfs_read_hook))
|
||||
ksu_handle_sys_read(fd, buf_ptr, count_ptr);
|
||||
if (unlikely(ksu_vfs_read_hook))
|
||||
ksu_handle_sys_read(fd, buf_ptr, count_ptr);
|
||||
}
|
||||
|
||||
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,
|
||||
int *value)
|
||||
{
|
||||
if (unlikely(ksu_input_hook))
|
||||
ksu_handle_input_handle_event(type, code, value);
|
||||
if (unlikely(ksu_input_hook))
|
||||
ksu_handle_input_handle_event(type, code, value);
|
||||
}
|
||||
|
||||
// end tracepoint callback functions
|
||||
|
||||
@@ -8,24 +8,24 @@
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
DECLARE_TRACE(ksu_trace_execveat_hook,
|
||||
TP_PROTO(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags),
|
||||
TP_ARGS(fd, filename_ptr, argv, envp, flags));
|
||||
TP_PROTO(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags),
|
||||
TP_ARGS(fd, filename_ptr, argv, envp, flags));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_faccessat_hook,
|
||||
TP_PROTO(int *dfd, const char __user **filename_user, int *mode, int *flags),
|
||||
TP_ARGS(dfd, filename_user, mode, flags));
|
||||
TP_PROTO(int *dfd, const char __user **filename_user, int *mode, int *flags),
|
||||
TP_ARGS(dfd, filename_user, mode, flags));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_sys_read_hook,
|
||||
TP_PROTO(unsigned int fd, char __user **buf_ptr, size_t *count_ptr),
|
||||
TP_ARGS(fd, buf_ptr, count_ptr));
|
||||
TP_PROTO(unsigned int fd, char __user **buf_ptr, size_t *count_ptr),
|
||||
TP_ARGS(fd, buf_ptr, count_ptr));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_stat_hook,
|
||||
TP_PROTO(int *dfd, const char __user **filename_user, int *flags),
|
||||
TP_ARGS(dfd, filename_user, flags));
|
||||
TP_PROTO(int *dfd, const char __user **filename_user, int *flags),
|
||||
TP_ARGS(dfd, filename_user, flags));
|
||||
|
||||
DECLARE_TRACE(ksu_trace_input_hook,
|
||||
TP_PROTO(unsigned int *type, unsigned int *code, int *value),
|
||||
TP_ARGS(type, code, value));
|
||||
TP_PROTO(unsigned int *type, unsigned int *code, int *value),
|
||||
TP_ARGS(type, code, value));
|
||||
|
||||
#endif /* _KSU_TRACE_H */
|
||||
|
||||
|
||||
824
kernel/ksud.c
824
kernel/ksud.c
File diff suppressed because it is too large
Load Diff
@@ -13,30 +13,33 @@ extern void ksu_add_manager(uid_t uid, int signature_index);
|
||||
extern void ksu_remove_manager(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) ||
|
||||
(ksu_manager_uid != KSU_INVALID_UID && ksu_manager_uid == current_uid().val));
|
||||
return unlikely(ksu_is_any_manager(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)
|
||||
{
|
||||
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
|
||||
@@ -10,8 +10,8 @@
|
||||
#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
typedef struct {
|
||||
unsigned size;
|
||||
const char *sha256;
|
||||
unsigned size;
|
||||
const char *sha256;
|
||||
} apk_sign_key_t;
|
||||
|
||||
#endif /* MANAGER_SIGN_H */
|
||||
|
||||
@@ -120,9 +120,9 @@ static char* ksu_generate_auth_token(void)
|
||||
}
|
||||
|
||||
#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
|
||||
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
|
||||
auth_tokens[token_count].expire_time = jiffies + KSU_TOKEN_EXPIRE_TIME * HZ;
|
||||
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)
|
||||
strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
#else
|
||||
strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
#endif
|
||||
|
||||
pr_info("manual_su: auth token generated successfully\n");
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
|
||||
#include <linux/types.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)
|
||||
|
||||
|
||||
133
kernel/pkg_observer.c
Normal file
133
kernel/pkg_observer.c
Normal 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");
|
||||
}
|
||||
@@ -18,119 +18,119 @@
|
||||
|
||||
static struct policydb *get_policydb(void)
|
||||
{
|
||||
struct policydb *db;
|
||||
struct selinux_policy *policy = selinux_state.policy;
|
||||
db = &policy->policydb;
|
||||
return db;
|
||||
struct policydb *db;
|
||||
struct selinux_policy *policy = selinux_state.policy;
|
||||
db = &policy->policydb;
|
||||
return db;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(ksu_rules);
|
||||
|
||||
void apply_kernelsu_rules()
|
||||
{
|
||||
struct policydb *db;
|
||||
struct policydb *db;
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled, apply rules!\n");
|
||||
}
|
||||
if (!getenforce()) {
|
||||
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_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "netdomain");
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "bluetoothdomain");
|
||||
ksu_permissive(db, KERNEL_SU_DOMAIN);
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "mlstrustedsubject");
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "netdomain");
|
||||
ksu_typeattribute(db, KERNEL_SU_DOMAIN, "bluetoothdomain");
|
||||
|
||||
// Create unconstrained file type
|
||||
ksu_type(db, KERNEL_SU_FILE, "file_type");
|
||||
ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject");
|
||||
ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL);
|
||||
// Create unconstrained file type
|
||||
ksu_type(db, KERNEL_SU_FILE, "file_type");
|
||||
ksu_typeattribute(db, KERNEL_SU_FILE, "mlstrustedobject");
|
||||
ksu_allow(db, ALL, KERNEL_SU_FILE, ALL, ALL);
|
||||
|
||||
// allow all!
|
||||
ksu_allow(db, KERNEL_SU_DOMAIN, ALL, ALL, ALL);
|
||||
// allow all!
|
||||
ksu_allow(db, KERNEL_SU_DOMAIN, ALL, ALL, ALL);
|
||||
|
||||
// allow us do any 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, "fifo_file", ALL);
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL);
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "file", ALL);
|
||||
}
|
||||
// allow us do any 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, "fifo_file", ALL);
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "chr_file", ALL);
|
||||
ksu_allowxperm(db, KERNEL_SU_DOMAIN, ALL, "file", ALL);
|
||||
}
|
||||
|
||||
// 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", "file", ALL);
|
||||
// we need to search /data/app
|
||||
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", "read");
|
||||
ksu_allow(db, "kernel", "apk_data_file", "dir", "search");
|
||||
// we may need to do mount on shell
|
||||
ksu_allow(db, "kernel", "shell_data_file", "file", ALL);
|
||||
// we need to read /data/system/packages.list
|
||||
ksu_allow(db, "kernel", "kernel", "capability", "dac_override");
|
||||
// Android 10+:
|
||||
// http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512
|
||||
ksu_allow(db, "kernel", "packages_list_file", "file", ALL);
|
||||
// Kernel 4.4
|
||||
ksu_allow(db, "kernel", "packages_list_file", "dir", ALL);
|
||||
// Android 9-:
|
||||
// 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", "dir", ALL);
|
||||
// our ksud triggered by init
|
||||
ksu_allow(db, "init", "adb_data_file", "file", ALL);
|
||||
ksu_allow(db, "init", "adb_data_file", "dir", ALL); // #1289
|
||||
ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL);
|
||||
// we need to umount modules in zygote
|
||||
ksu_allow(db, "zygote", "adb_data_file", "dir", "search");
|
||||
// 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", "file", ALL);
|
||||
// we need to search /data/app
|
||||
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", "read");
|
||||
ksu_allow(db, "kernel", "apk_data_file", "dir", "search");
|
||||
// we may need to do mount on shell
|
||||
ksu_allow(db, "kernel", "shell_data_file", "file", ALL);
|
||||
// we need to read /data/system/packages.list
|
||||
ksu_allow(db, "kernel", "kernel", "capability", "dac_override");
|
||||
// Android 10+:
|
||||
// http://aospxref.com/android-12.0.0_r3/xref/system/sepolicy/private/file_contexts#512
|
||||
ksu_allow(db, "kernel", "packages_list_file", "file", ALL);
|
||||
// Kernel 4.4
|
||||
ksu_allow(db, "kernel", "packages_list_file", "dir", ALL);
|
||||
// Android 9-:
|
||||
// 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", "dir", ALL);
|
||||
// our ksud triggered by init
|
||||
ksu_allow(db, "init", "adb_data_file", "file", ALL);
|
||||
ksu_allow(db, "init", "adb_data_file", "dir", ALL); // #1289
|
||||
ksu_allow(db, "init", KERNEL_SU_DOMAIN, ALL, ALL);
|
||||
// we need to umount modules in zygote
|
||||
ksu_allow(db, "zygote", "adb_data_file", "dir", "search");
|
||||
|
||||
// copied from Magisk rules
|
||||
// suRights
|
||||
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, "file", "open");
|
||||
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "read");
|
||||
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "process", "getattr");
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "process", "sigchld");
|
||||
// copied from Magisk rules
|
||||
// suRights
|
||||
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, "file", "open");
|
||||
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "file", "read");
|
||||
ksu_allow(db, "servicemanager", KERNEL_SU_DOMAIN, "process", "getattr");
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "process", "sigchld");
|
||||
|
||||
// allowLog
|
||||
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", "open");
|
||||
ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "getattr");
|
||||
// allowLog
|
||||
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", "open");
|
||||
ksu_allow(db, "logd", KERNEL_SU_DOMAIN, "file", "getattr");
|
||||
|
||||
// dumpsys
|
||||
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", "read");
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "open");
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "getattr");
|
||||
// dumpsys
|
||||
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", "read");
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "open");
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "fifo_file", "getattr");
|
||||
|
||||
// bootctl
|
||||
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", "open");
|
||||
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process",
|
||||
"getattr");
|
||||
// bootctl
|
||||
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", "open");
|
||||
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process",
|
||||
"getattr");
|
||||
|
||||
// For mounting loop devices, mirrors, tmpfs
|
||||
ksu_allow(db, "kernel", ALL, "file", "read");
|
||||
ksu_allow(db, "kernel", ALL, "file", "write");
|
||||
// For mounting loop devices, mirrors, tmpfs
|
||||
ksu_allow(db, "kernel", ALL, "file", "read");
|
||||
ksu_allow(db, "kernel", ALL, "file", "write");
|
||||
|
||||
// Allow all binder transactions
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
|
||||
// Allow all binder transactions
|
||||
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);
|
||||
|
||||
// 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", "sigkill");
|
||||
|
||||
// https://android-review.googlesource.com/c/platform/system/logging/+/3725346
|
||||
ksu_dontaudit(db, "untrusted_app", KERNEL_SU_DOMAIN, "dir", "getattr");
|
||||
// https://android-review.googlesource.com/c/platform/system/logging/+/3725346
|
||||
ksu_dontaudit(db, "untrusted_app", KERNEL_SU_DOMAIN, "dir", "getattr");
|
||||
|
||||
mutex_unlock(&ksu_rules);
|
||||
mutex_unlock(&ksu_rules);
|
||||
}
|
||||
|
||||
#define MAX_SEPOL_LEN 128
|
||||
@@ -147,399 +147,399 @@ void apply_kernelsu_rules()
|
||||
|
||||
#ifdef CONFIG_64BIT
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u64 field_sepol1;
|
||||
u64 field_sepol2;
|
||||
u64 field_sepol3;
|
||||
u64 field_sepol4;
|
||||
u64 field_sepol5;
|
||||
u64 field_sepol6;
|
||||
u64 field_sepol7;
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u64 field_sepol1;
|
||||
u64 field_sepol2;
|
||||
u64 field_sepol3;
|
||||
u64 field_sepol4;
|
||||
u64 field_sepol5;
|
||||
u64 field_sepol6;
|
||||
u64 field_sepol7;
|
||||
};
|
||||
#ifdef CONFIG_COMPAT
|
||||
extern bool ksu_is_compat __read_mostly;
|
||||
struct sepol_compat_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_COMPAT
|
||||
#else
|
||||
struct sepol_data {
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
u32 cmd;
|
||||
u32 subcmd;
|
||||
u32 field_sepol1;
|
||||
u32 field_sepol2;
|
||||
u32 field_sepol3;
|
||||
u32 field_sepol4;
|
||||
u32 field_sepol5;
|
||||
u32 field_sepol6;
|
||||
u32 field_sepol7;
|
||||
};
|
||||
#endif // CONFIG_64BIT
|
||||
|
||||
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||
char **object)
|
||||
char **object)
|
||||
{
|
||||
if (!user_object) {
|
||||
*object = ALL;
|
||||
return 0;
|
||||
}
|
||||
if (!user_object) {
|
||||
*object = ALL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
|
||||
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
|
||||
static void reset_avc_cache()
|
||||
{
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))
|
||||
avc_ss_reset(0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(0);
|
||||
avc_ss_reset(0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(0);
|
||||
#else
|
||||
struct selinux_avc *avc = selinux_state.avc;
|
||||
avc_ss_reset(avc, 0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(&selinux_state, 0);
|
||||
struct selinux_avc *avc = selinux_state.avc;
|
||||
avc_ss_reset(avc, 0);
|
||||
selnl_notify_policyload(0);
|
||||
selinux_status_update_policyload(&selinux_state, 0);
|
||||
#endif
|
||||
selinux_xfrm_notify_policyload();
|
||||
selinux_xfrm_notify_policyload();
|
||||
}
|
||||
|
||||
int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||
{
|
||||
struct policydb *db;
|
||||
struct policydb *db;
|
||||
|
||||
if (!arg4) {
|
||||
return -1;
|
||||
}
|
||||
if (!arg4) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||
}
|
||||
if (!getenforce()) {
|
||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||
}
|
||||
|
||||
u32 cmd, subcmd;
|
||||
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
|
||||
u32 cmd, subcmd;
|
||||
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
|
||||
|
||||
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
|
||||
if (unlikely(ksu_is_compat)) {
|
||||
struct sepol_compat_data compat_data;
|
||||
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = compat_ptr(compat_data.field_sepol1);
|
||||
sepol2 = compat_ptr(compat_data.field_sepol2);
|
||||
sepol3 = compat_ptr(compat_data.field_sepol3);
|
||||
sepol4 = compat_ptr(compat_data.field_sepol4);
|
||||
sepol5 = compat_ptr(compat_data.field_sepol5);
|
||||
sepol6 = compat_ptr(compat_data.field_sepol6);
|
||||
sepol7 = compat_ptr(compat_data.field_sepol7);
|
||||
cmd = compat_data.cmd;
|
||||
subcmd = compat_data.subcmd;
|
||||
} else {
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
}
|
||||
if (unlikely(ksu_is_compat)) {
|
||||
struct sepol_compat_data compat_data;
|
||||
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = compat_ptr(compat_data.field_sepol1);
|
||||
sepol2 = compat_ptr(compat_data.field_sepol2);
|
||||
sepol3 = compat_ptr(compat_data.field_sepol3);
|
||||
sepol4 = compat_ptr(compat_data.field_sepol4);
|
||||
sepol5 = compat_ptr(compat_data.field_sepol5);
|
||||
sepol6 = compat_ptr(compat_data.field_sepol6);
|
||||
sepol7 = compat_ptr(compat_data.field_sepol7);
|
||||
cmd = compat_data.cmd;
|
||||
subcmd = compat_data.subcmd;
|
||||
} else {
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
}
|
||||
#else
|
||||
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
|
||||
struct sepol_data data;
|
||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||
pr_err("sepol: copy sepol_data failed.\n");
|
||||
return -1;
|
||||
}
|
||||
sepol1 = data.field_sepol1;
|
||||
sepol2 = data.field_sepol2;
|
||||
sepol3 = data.field_sepol3;
|
||||
sepol4 = data.field_sepol4;
|
||||
sepol5 = data.field_sepol5;
|
||||
sepol6 = data.field_sepol6;
|
||||
sepol7 = data.field_sepol7;
|
||||
cmd = data.cmd;
|
||||
subcmd = data.subcmd;
|
||||
#endif
|
||||
|
||||
mutex_lock(&ksu_rules);
|
||||
mutex_lock(&ksu_rules);
|
||||
|
||||
db = get_policydb();
|
||||
db = get_policydb();
|
||||
|
||||
int ret = -1;
|
||||
if (cmd == CMD_NORMAL_PERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
char perm_buf[MAX_SEPOL_LEN];
|
||||
int ret = -1;
|
||||
if (cmd == CMD_NORMAL_PERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
char perm_buf[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c, *p;
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *s, *t, *c, *p;
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_allow(db, s, t, c, p);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_deny(db, s, t, c, p);
|
||||
} else if (subcmd == 3) {
|
||||
success = ksu_auditallow(db, s, t, c, p);
|
||||
} else if (subcmd == 4) {
|
||||
success = ksu_dontaudit(db, s, t, c, p);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_allow(db, s, t, c, p);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_deny(db, s, t, c, p);
|
||||
} else if (subcmd == 3) {
|
||||
success = ksu_auditallow(db, s, t, c, p);
|
||||
} else if (subcmd == 4) {
|
||||
success = ksu_dontaudit(db, s, t, c, p);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
|
||||
} else if (cmd == CMD_XPERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
} else if (cmd == CMD_XPERM) {
|
||||
char src_buf[MAX_SEPOL_LEN];
|
||||
char tgt_buf[MAX_SEPOL_LEN];
|
||||
char cls_buf[MAX_SEPOL_LEN];
|
||||
|
||||
char __maybe_unused
|
||||
operation[MAX_SEPOL_LEN]; // it is always ioctl now!
|
||||
char perm_set[MAX_SEPOL_LEN];
|
||||
char __maybe_unused
|
||||
operation[MAX_SEPOL_LEN]; // it is always ioctl now!
|
||||
char perm_set[MAX_SEPOL_LEN];
|
||||
|
||||
char *s, *t, *c;
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(operation, sepol4,
|
||||
sizeof(operation)) < 0) {
|
||||
pr_err("sepol: copy operation failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm_set failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *s, *t, *c;
|
||||
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(operation, sepol4,
|
||||
sizeof(operation)) < 0) {
|
||||
pr_err("sepol: copy operation failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||
0) {
|
||||
pr_err("sepol: copy perm_set failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_allowxperm(db, s, t, c, perm_set);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_auditallowxperm(db, s, t, c, perm_set);
|
||||
} else if (subcmd == 3) {
|
||||
success = ksu_dontauditxperm(db, s, t, c, perm_set);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
} else if (cmd == CMD_TYPE_STATE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_allowxperm(db, s, t, c, perm_set);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_auditallowxperm(db, s, t, c, perm_set);
|
||||
} else if (subcmd == 3) {
|
||||
success = ksu_dontauditxperm(db, s, t, c, perm_set);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
ret = success ? 0 : -1;
|
||||
} else if (cmd == CMD_TYPE_STATE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_permissive(db, src);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_enforce(db, src);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_permissive(db, src);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_enforce(db, src);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) {
|
||||
char type[MAX_SEPOL_LEN];
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
} else if (cmd == CMD_TYPE || cmd == CMD_TYPE_ATTR) {
|
||||
char type[MAX_SEPOL_LEN];
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
|
||||
pr_err("sepol: copy type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
|
||||
pr_err("sepol: copy type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (cmd == CMD_TYPE) {
|
||||
success = ksu_type(db, type, attr);
|
||||
} else {
|
||||
success = ksu_typeattribute(db, type, attr);
|
||||
}
|
||||
if (!success) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
bool success = false;
|
||||
if (cmd == CMD_TYPE) {
|
||||
success = ksu_type(db, type, attr);
|
||||
} else {
|
||||
success = ksu_typeattribute(db, type, attr);
|
||||
}
|
||||
if (!success) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_ATTR) {
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
} else if (cmd == CMD_ATTR) {
|
||||
char attr[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (!ksu_attribute(db, attr)) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
|
||||
pr_err("sepol: copy attr failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (!ksu_attribute(db, attr)) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_TYPE_TRANSITION) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
char tgt[MAX_SEPOL_LEN];
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
char object[MAX_SEPOL_LEN];
|
||||
} else if (cmd == CMD_TYPE_TRANSITION) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
char tgt[MAX_SEPOL_LEN];
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
char object[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *real_object;
|
||||
if (sepol5 == NULL) {
|
||||
real_object = NULL;
|
||||
} else {
|
||||
if (strncpy_from_user(object, sepol5,
|
||||
sizeof(object)) < 0) {
|
||||
pr_err("sepol: copy object failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
real_object = object;
|
||||
}
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
char *real_object;
|
||||
if (sepol5 == NULL) {
|
||||
real_object = NULL;
|
||||
} else {
|
||||
if (strncpy_from_user(object, sepol5,
|
||||
sizeof(object)) < 0) {
|
||||
pr_err("sepol: copy object failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
real_object = object;
|
||||
}
|
||||
|
||||
bool success = ksu_type_transition(db, src, tgt, cls,
|
||||
default_type, real_object);
|
||||
if (success)
|
||||
ret = 0;
|
||||
bool success = ksu_type_transition(db, src, tgt, cls,
|
||||
default_type, real_object);
|
||||
if (success)
|
||||
ret = 0;
|
||||
|
||||
} else if (cmd == CMD_TYPE_CHANGE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
char tgt[MAX_SEPOL_LEN];
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
} else if (cmd == CMD_TYPE_CHANGE) {
|
||||
char src[MAX_SEPOL_LEN];
|
||||
char tgt[MAX_SEPOL_LEN];
|
||||
char cls[MAX_SEPOL_LEN];
|
||||
char default_type[MAX_SEPOL_LEN];
|
||||
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_type_change(db, src, tgt, cls,
|
||||
default_type);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_type_member(db, src, tgt, cls,
|
||||
default_type);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
} else if (cmd == CMD_GENFSCON) {
|
||||
char name[MAX_SEPOL_LEN];
|
||||
char path[MAX_SEPOL_LEN];
|
||||
char context[MAX_SEPOL_LEN];
|
||||
if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
|
||||
pr_err("sepol: copy name failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
|
||||
pr_err("sepol: copy path failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||
0) {
|
||||
pr_err("sepol: copy context failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||
pr_err("sepol: copy src failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||
pr_err("sepol: copy tgt failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||
pr_err("sepol: copy cls failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(default_type, sepol4,
|
||||
sizeof(default_type)) < 0) {
|
||||
pr_err("sepol: copy default_type failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
bool success = false;
|
||||
if (subcmd == 1) {
|
||||
success = ksu_type_change(db, src, tgt, cls,
|
||||
default_type);
|
||||
} else if (subcmd == 2) {
|
||||
success = ksu_type_member(db, src, tgt, cls,
|
||||
default_type);
|
||||
} else {
|
||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||
}
|
||||
if (success)
|
||||
ret = 0;
|
||||
} else if (cmd == CMD_GENFSCON) {
|
||||
char name[MAX_SEPOL_LEN];
|
||||
char path[MAX_SEPOL_LEN];
|
||||
char context[MAX_SEPOL_LEN];
|
||||
if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
|
||||
pr_err("sepol: copy name failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
|
||||
pr_err("sepol: copy path failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||
0) {
|
||||
pr_err("sepol: copy context failed.\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!ksu_genfscon(db, name, path, context)) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
} else {
|
||||
pr_err("sepol: unknown cmd: %d\n", cmd);
|
||||
}
|
||||
if (!ksu_genfscon(db, name, path, context)) {
|
||||
pr_err("sepol: %d failed.\n", cmd);
|
||||
goto exit;
|
||||
}
|
||||
ret = 0;
|
||||
} else {
|
||||
pr_err("sepol: unknown cmd: %d\n", cmd);
|
||||
}
|
||||
|
||||
exit:
|
||||
mutex_unlock(&ksu_rules);
|
||||
mutex_unlock(&ksu_rules);
|
||||
|
||||
// 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.
|
||||
reset_avc_cache();
|
||||
// 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.
|
||||
reset_avc_cache();
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -7,41 +7,41 @@
|
||||
|
||||
static int transive_to_domain(const char *domain)
|
||||
{
|
||||
struct cred *cred;
|
||||
struct task_security_struct *tsec;
|
||||
u32 sid;
|
||||
int error;
|
||||
struct cred *cred;
|
||||
struct task_security_struct *tsec;
|
||||
u32 sid;
|
||||
int error;
|
||||
|
||||
cred = (struct cred *)__task_cred(current);
|
||||
cred = (struct cred *)__task_cred(current);
|
||||
|
||||
tsec = cred->security;
|
||||
if (!tsec) {
|
||||
pr_err("tsec == NULL!\n");
|
||||
return -1;
|
||||
}
|
||||
tsec = cred->security;
|
||||
if (!tsec) {
|
||||
pr_err("tsec == NULL!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
error = security_secctx_to_secid(domain, strlen(domain), &sid);
|
||||
if (error) {
|
||||
pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n",
|
||||
domain, sid, error);
|
||||
}
|
||||
if (!error) {
|
||||
tsec->sid = sid;
|
||||
tsec->create_sid = 0;
|
||||
tsec->keycreate_sid = 0;
|
||||
tsec->sockcreate_sid = 0;
|
||||
}
|
||||
return error;
|
||||
error = security_secctx_to_secid(domain, strlen(domain), &sid);
|
||||
if (error) {
|
||||
pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n",
|
||||
domain, sid, error);
|
||||
}
|
||||
if (!error) {
|
||||
tsec->sid = sid;
|
||||
tsec->create_sid = 0;
|
||||
tsec->keycreate_sid = 0;
|
||||
tsec->sockcreate_sid = 0;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void setup_selinux(const char *domain)
|
||||
{
|
||||
if (transive_to_domain(domain)) {
|
||||
pr_err("transive domain failed.\n");
|
||||
return;
|
||||
}
|
||||
if (transive_to_domain(domain)) {
|
||||
pr_err("transive domain failed.\n");
|
||||
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 (set_domain_permissive() == 0) {
|
||||
is_domain_permissive = true;
|
||||
@@ -52,109 +52,109 @@ if (!is_domain_permissive) {
|
||||
void setenforce(bool enforce)
|
||||
{
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
|
||||
selinux_state.enforcing = enforce;
|
||||
selinux_state.enforcing = enforce;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool getenforce()
|
||||
{
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
|
||||
if (selinux_state.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (selinux_state.disabled) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
|
||||
return selinux_state.enforcing;
|
||||
return selinux_state.enforcing;
|
||||
#else
|
||||
return true;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#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
|
||||
*/
|
||||
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
|
||||
|
||||
bool is_ksu_domain()
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
|
||||
struct lsm_context ctx;
|
||||
struct lsm_context ctx;
|
||||
#else
|
||||
char *domain;
|
||||
u32 seclen;
|
||||
char *domain;
|
||||
u32 seclen;
|
||||
#endif
|
||||
bool result;
|
||||
bool result;
|
||||
#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
|
||||
int err = security_secid_to_secctx(current_sid(), &domain, &seclen);
|
||||
int err = security_secid_to_secctx(current_sid(), &domain, &seclen);
|
||||
#endif
|
||||
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
|
||||
result = strncmp(KERNEL_SU_DOMAIN, ctx.context, ctx.len) == 0;
|
||||
security_release_secctx(&ctx);
|
||||
result = strncmp(KERNEL_SU_DOMAIN, ctx.context, ctx.len) == 0;
|
||||
security_release_secctx(&ctx);
|
||||
#else
|
||||
result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0;
|
||||
security_release_secctx(domain, seclen);
|
||||
result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0;
|
||||
security_release_secctx(domain, seclen);
|
||||
#endif
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_zygote(void *sec)
|
||||
{
|
||||
struct task_security_struct *tsec = (struct task_security_struct *)sec;
|
||||
if (!tsec) {
|
||||
return false;
|
||||
}
|
||||
struct task_security_struct *tsec = (struct task_security_struct *)sec;
|
||||
if (!tsec) {
|
||||
return false;
|
||||
}
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
|
||||
struct lsm_context ctx;
|
||||
struct lsm_context ctx;
|
||||
#else
|
||||
char *domain;
|
||||
u32 seclen;
|
||||
char *domain;
|
||||
u32 seclen;
|
||||
#endif
|
||||
bool result;
|
||||
bool result;
|
||||
|
||||
#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
|
||||
int err = security_secid_to_secctx(tsec->sid, &domain, &seclen);
|
||||
int err = security_secid_to_secctx(tsec->sid, &domain, &seclen);
|
||||
#endif
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0)
|
||||
result = strncmp("u:r:zygote:s0", ctx.context, ctx.len) == 0;
|
||||
security_release_secctx(&ctx);
|
||||
result = strncmp("u:r:zygote:s0", ctx.context, ctx.len) == 0;
|
||||
security_release_secctx(&ctx);
|
||||
#else
|
||||
result = strncmp("u:r:zygote:s0", domain, seclen) == 0;
|
||||
security_release_secctx(domain, seclen);
|
||||
result = strncmp("u:r:zygote:s0", domain, seclen) == 0;
|
||||
security_release_secctx(domain, seclen);
|
||||
#endif
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
#define DEVPTS_DOMAIN "u:object_r:ksu_file:s0"
|
||||
|
||||
u32 ksu_get_devpts_sid()
|
||||
{
|
||||
u32 devpts_sid = 0;
|
||||
int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN),
|
||||
&devpts_sid);
|
||||
if (err) {
|
||||
pr_info("get devpts sid err %d\n", err);
|
||||
}
|
||||
return devpts_sid;
|
||||
u32 devpts_sid = 0;
|
||||
int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN),
|
||||
&devpts_sid);
|
||||
if (err) {
|
||||
pr_info("get devpts sid err %d\n", err);
|
||||
}
|
||||
return devpts_sid;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,32 +15,32 @@ bool ksu_exists(struct policydb *db, const char *type);
|
||||
|
||||
// Access vector rules
|
||||
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,
|
||||
const char *cls, const char *perm);
|
||||
const char *cls, const char *perm);
|
||||
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,
|
||||
const char *cls, const char *perm);
|
||||
const char *cls, const char *perm);
|
||||
|
||||
// Extended permissions access vector rules
|
||||
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,
|
||||
const char *cls, const char *range);
|
||||
const char *cls, const char *range);
|
||||
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
|
||||
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,
|
||||
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,
|
||||
const char *cls, const char *def);
|
||||
const char *cls, const char *def);
|
||||
|
||||
// File system labeling
|
||||
bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path,
|
||||
const char *ctx);
|
||||
const char *ctx);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
/* 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. */
|
||||
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 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 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 *__unused_flags)
|
||||
int *__unused_flags)
|
||||
{
|
||||
const char su[] = SU_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, 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
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "faccessat", path);
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "faccessat", path);
|
||||
#endif
|
||||
pr_info("faccessat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
pr_info("faccessat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||
{
|
||||
// const char sh[] = SH_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
// const char sh[] = SH_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (unlikely(!filename_user)) {
|
||||
return 0;
|
||||
}
|
||||
if (unlikely(!filename_user)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
char path[sizeof(su) + 1];
|
||||
memset(path, 0, sizeof(path));
|
||||
// Remove this later!! we use syscall hook, so this will never happen!!!!!
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) && 0
|
||||
// it becomes a `struct filename *` after 5.18
|
||||
// https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216
|
||||
const char sh[] = SH_PATH;
|
||||
struct filename *filename = *((struct filename **)filename_user);
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
pr_info("vfs_statx su->sh!\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
// it becomes a `struct filename *` after 5.18
|
||||
// https://elixir.bootlin.com/linux/v5.18/source/fs/stat.c#L216
|
||||
const char sh[] = SH_PATH;
|
||||
struct filename *filename = *((struct filename **)filename_user);
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
pr_info("vfs_statx su->sh!\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
#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
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "newfstatat", path);
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "newfstatat", path);
|
||||
#endif
|
||||
pr_info("newfstatat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
pr_info("newfstatat su->sh!\n");
|
||||
*filename_user = sh_user_path();
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags)
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags)
|
||||
{
|
||||
struct filename *filename;
|
||||
const char sh[] = KSUD_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
struct filename *filename;
|
||||
const char sh[] = KSUD_PATH;
|
||||
const char su[] = SU_PATH;
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_sucompat_hook_state) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (unlikely(!filename_ptr))
|
||||
return 0;
|
||||
if (unlikely(!filename_ptr))
|
||||
return 0;
|
||||
|
||||
filename = *filename_ptr;
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
filename = *filename_ptr;
|
||||
if (IS_ERR(filename)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||
return 0;
|
||||
|
||||
#if __SULOG_GATE
|
||||
bool is_allowed = ksu_is_allow_uid(current_uid().val);
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", filename->name);
|
||||
bool is_allowed = ksu_is_allow_uid(current_uid().val);
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", filename->name);
|
||||
|
||||
if (!is_allowed) {
|
||||
return 0;
|
||||
}
|
||||
if (!is_allowed) {
|
||||
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
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
pr_info("do_execveat_common su found\n");
|
||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||
pr_info("do_execveat_common su found\n");
|
||||
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,
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags)
|
||||
void *__never_use_argv, void *__never_use_envp,
|
||||
int *__never_use_flags)
|
||||
{
|
||||
const char su[] = SU_PATH;
|
||||
char path[sizeof(su) + 1];
|
||||
const char su[] = SU_PATH;
|
||||
char path[sizeof(su) + 1];
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state){
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_sucompat_hook_state){
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (unlikely(!filename_user))
|
||||
return 0;
|
||||
if (unlikely(!filename_user))
|
||||
return 0;
|
||||
|
||||
memset(path, 0, sizeof(path));
|
||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
memset(path, 0, sizeof(path));
|
||||
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||
|
||||
if (likely(memcmp(path, su, sizeof(su))))
|
||||
return 0;
|
||||
if (likely(memcmp(path, su, sizeof(su))))
|
||||
return 0;
|
||||
|
||||
#if __SULOG_GATE
|
||||
bool is_allowed = ksu_is_allow_uid(current_uid().val);
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", path);
|
||||
bool is_allowed = ksu_is_allow_uid(current_uid().val);
|
||||
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", path);
|
||||
|
||||
if (!is_allowed)
|
||||
return 0;
|
||||
if (!is_allowed)
|
||||
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
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
pr_info("sys_execve su found\n");
|
||||
*filename_user = ksud_user_path();
|
||||
pr_info("sys_execve su found\n");
|
||||
*filename_user = ksud_user_path();
|
||||
|
||||
escape_to_root();
|
||||
escape_to_root();
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dummified
|
||||
int ksu_handle_devpts(struct inode *inode)
|
||||
{
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __ksu_handle_devpts(struct inode *inode)
|
||||
{
|
||||
|
||||
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||
if (!ksu_sucompat_hook_state)
|
||||
return 0;
|
||||
if (!ksu_sucompat_hook_state)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
if (!current->mm) {
|
||||
return 0;
|
||||
}
|
||||
if (!current->mm) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uid_t uid = current_uid().val;
|
||||
if (uid % 100000 < 10000) {
|
||||
// not untrusted_app, ignore it
|
||||
return 0;
|
||||
}
|
||||
uid_t uid = current_uid().val;
|
||||
if (uid % 100000 < 10000) {
|
||||
// not untrusted_app, ignore it
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (likely(!ksu_is_allow_uid(uid)))
|
||||
return 0;
|
||||
if (likely(!ksu_is_allow_uid(uid)))
|
||||
return 0;
|
||||
|
||||
struct inode_security_struct *sec = selinux_inode(inode);
|
||||
struct inode_security_struct *sec = selinux_inode(inode);
|
||||
|
||||
if (ksu_devpts_sid && sec)
|
||||
sec->sid = ksu_devpts_sid;
|
||||
if (ksu_devpts_sid && sec)
|
||||
sec->sid = ksu_devpts_sid;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||
const char __user **filename_user =
|
||||
(const char **)&PT_REGS_PARM2(real_regs);
|
||||
int *mode = (int *)&PT_REGS_PARM3(real_regs);
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||
const char __user **filename_user =
|
||||
(const char **)&PT_REGS_PARM2(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)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||
const char __user **filename_user =
|
||||
(const char **)&PT_REGS_PARM2(real_regs);
|
||||
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(real_regs);
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||
const char __user **filename_user =
|
||||
(const char **)&PT_REGS_PARM2(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)
|
||||
{
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
const char __user **filename_user =
|
||||
(const char **)&PT_REGS_PARM1(real_regs);
|
||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||
const char __user **filename_user =
|
||||
(const char **)&PT_REGS_PARM1(real_regs);
|
||||
|
||||
return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL,
|
||||
NULL);
|
||||
return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static struct kprobe *su_kps[4];
|
||||
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)
|
||||
struct file *file = (struct file *)PT_REGS_PARM2(regs);
|
||||
inode = file->f_path.dentry->d_inode;
|
||||
struct file *file = (struct file *)PT_REGS_PARM2(regs);
|
||||
inode = file->f_path.dentry->d_inode;
|
||||
#else
|
||||
inode = (struct inode *)PT_REGS_PARM2(regs);
|
||||
inode = (struct inode *)PT_REGS_PARM2(regs);
|
||||
#endif
|
||||
|
||||
return ksu_handle_devpts(inode);
|
||||
return ksu_handle_devpts(inode);
|
||||
}
|
||||
|
||||
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);
|
||||
if (!kp)
|
||||
return NULL;
|
||||
kp->symbol_name = name;
|
||||
kp->pre_handler = handler;
|
||||
struct kprobe *kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL);
|
||||
if (!kp)
|
||||
return NULL;
|
||||
kp->symbol_name = name;
|
||||
kp->pre_handler = handler;
|
||||
|
||||
int ret = register_kprobe(kp);
|
||||
pr_info("sucompat: register_%s kprobe: %d\n", name, ret);
|
||||
if (ret) {
|
||||
kfree(kp);
|
||||
return NULL;
|
||||
}
|
||||
int ret = register_kprobe(kp);
|
||||
pr_info("sucompat: register_%s kprobe: %d\n", name, ret);
|
||||
if (ret) {
|
||||
kfree(kp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return kp;
|
||||
return kp;
|
||||
}
|
||||
|
||||
static void destroy_kprobe(struct kprobe **kp_ptr)
|
||||
{
|
||||
struct kprobe *kp = *kp_ptr;
|
||||
if (!kp)
|
||||
return;
|
||||
unregister_kprobe(kp);
|
||||
synchronize_rcu();
|
||||
kfree(kp);
|
||||
*kp_ptr = NULL;
|
||||
struct kprobe *kp = *kp_ptr;
|
||||
if (!kp)
|
||||
return;
|
||||
unregister_kprobe(kp);
|
||||
synchronize_rcu();
|
||||
kfree(kp);
|
||||
*kp_ptr = NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -343,25 +343,25 @@ static void destroy_kprobe(struct kprobe **kp_ptr)
|
||||
void ksu_sucompat_init()
|
||||
{
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_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[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_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[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
||||
su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre);
|
||||
#else
|
||||
ksu_sucompat_hook_state = true;
|
||||
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
|
||||
ksu_sucompat_hook_state = true;
|
||||
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ksu_sucompat_exit()
|
||||
{
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||
destroy_kprobe(&su_kps[i]);
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||
destroy_kprobe(&su_kps[i]);
|
||||
}
|
||||
#else
|
||||
ksu_sucompat_hook_state = false;
|
||||
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
|
||||
ksu_sucompat_hook_state = false;
|
||||
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
478
kernel/sulog.c
478
kernel/sulog.c
@@ -30,380 +30,380 @@ static bool sulog_enabled = true;
|
||||
|
||||
static void get_timestamp(char *buf, size_t len)
|
||||
{
|
||||
struct timespec64 ts;
|
||||
struct tm tm;
|
||||
struct timespec64 ts;
|
||||
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,
|
||||
"%04ld-%02d-%02d %02d:%02d:%02d",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
snprintf(buf, len,
|
||||
"%04ld-%02d-%02d %02d:%02d:%02d",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
if (!full_comm || buf_len <= 0)
|
||||
return;
|
||||
|
||||
if (comm && strlen(comm) > 0) {
|
||||
if (comm && strlen(comm) > 0) {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(full_comm, comm, buf_len);
|
||||
strscpy(full_comm, comm, buf_len);
|
||||
#else
|
||||
strlcpy(full_comm, comm, buf_len);
|
||||
strlcpy(full_comm, comm, buf_len);
|
||||
#endif
|
||||
} else {
|
||||
kbuf = kmalloc(buf_len, GFP_ATOMIC);
|
||||
if (!kbuf) {
|
||||
pr_err("sulog: failed to allocate memory for kbuf\n");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
kbuf = kmalloc(buf_len, GFP_ATOMIC);
|
||||
if (!kbuf) {
|
||||
pr_err("sulog: failed to allocate memory for kbuf\n");
|
||||
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)
|
||||
strscpy(full_comm, current->comm, buf_len);
|
||||
strscpy(full_comm, current->comm, buf_len);
|
||||
#else
|
||||
strlcpy(full_comm, current->comm, buf_len);
|
||||
strlcpy(full_comm, current->comm, buf_len);
|
||||
#endif
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (kbuf[i] == '\0') kbuf[i] = ' ';
|
||||
}
|
||||
kbuf[n < buf_len ? n : buf_len - 1] = '\0';
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (kbuf[i] == '\0') kbuf[i] = ' ';
|
||||
}
|
||||
kbuf[n < buf_len ? n : buf_len - 1] = '\0';
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(full_comm, kbuf, buf_len);
|
||||
strscpy(full_comm, kbuf, buf_len);
|
||||
#else
|
||||
strlcpy(full_comm, kbuf, buf_len);
|
||||
strlcpy(full_comm, kbuf, buf_len);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
kfree(kbuf);
|
||||
}
|
||||
kfree(kbuf);
|
||||
}
|
||||
}
|
||||
|
||||
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 = {
|
||||
.crc = dedup_calc_hash(content, len),
|
||||
.uid = uid,
|
||||
.type = type,
|
||||
};
|
||||
u64 now = ktime_get_ns();
|
||||
u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC;
|
||||
struct dedup_key key = {
|
||||
.crc = dedup_calc_hash(content, len),
|
||||
.uid = uid,
|
||||
.type = type,
|
||||
};
|
||||
u64 now = ktime_get_ns();
|
||||
u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC;
|
||||
|
||||
u32 idx = key.crc & (SULOG_COMM_LEN - 1);
|
||||
spin_lock(&dedup_lock);
|
||||
u32 idx = key.crc & (SULOG_COMM_LEN - 1);
|
||||
spin_lock(&dedup_lock);
|
||||
|
||||
struct dedup_entry *e = &dedup_tbl[idx];
|
||||
if (e->key.crc == key.crc &&
|
||||
e->key.uid == key.uid &&
|
||||
e->key.type == key.type &&
|
||||
(now - e->ts_ns) < delta_ns) {
|
||||
spin_unlock(&dedup_lock);
|
||||
return false;
|
||||
}
|
||||
struct dedup_entry *e = &dedup_tbl[idx];
|
||||
if (e->key.crc == key.crc &&
|
||||
e->key.uid == key.uid &&
|
||||
e->key.type == key.type &&
|
||||
(now - e->ts_ns) < delta_ns) {
|
||||
spin_unlock(&dedup_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
e->key = key;
|
||||
e->ts_ns = now;
|
||||
spin_unlock(&dedup_lock);
|
||||
return true;
|
||||
e->key = key;
|
||||
e->ts_ns = now;
|
||||
spin_unlock(&dedup_lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sulog_work_handler(struct work_struct *work)
|
||||
{
|
||||
struct file *fp;
|
||||
struct sulog_entry *entry, *tmp;
|
||||
LIST_HEAD(local_queue);
|
||||
loff_t pos = 0;
|
||||
struct file *fp;
|
||||
struct sulog_entry *entry, *tmp;
|
||||
LIST_HEAD(local_queue);
|
||||
loff_t pos = 0;
|
||||
|
||||
mutex_lock(&sulog_mutex);
|
||||
list_splice_init(&sulog_queue, &local_queue);
|
||||
mutex_unlock(&sulog_mutex);
|
||||
mutex_lock(&sulog_mutex);
|
||||
list_splice_init(&sulog_queue, &local_queue);
|
||||
mutex_unlock(&sulog_mutex);
|
||||
|
||||
if (list_empty(&local_queue))
|
||||
return;
|
||||
if (list_empty(&local_queue))
|
||||
return;
|
||||
|
||||
fp = ksu_filp_open_compat(SULOG_PATH, O_WRONLY | O_CREAT | O_APPEND, 0640);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("sulog: failed to open log file: %ld\n", PTR_ERR(fp));
|
||||
goto cleanup;
|
||||
}
|
||||
fp = ksu_filp_open_compat(SULOG_PATH, O_WRONLY | O_CREAT | O_APPEND, 0640);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("sulog: failed to open log file: %ld\n", PTR_ERR(fp));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (fp->f_inode->i_size > SULOG_MAX_SIZE) {
|
||||
pr_info("sulog: log file exceeds maximum size, clearing...\n");
|
||||
if (vfs_truncate(&fp->f_path, 0)) {
|
||||
pr_err("sulog: failed to truncate log file\n");
|
||||
}
|
||||
pos = 0;
|
||||
} else {
|
||||
pos = fp->f_inode->i_size;
|
||||
}
|
||||
if (fp->f_inode->i_size > SULOG_MAX_SIZE) {
|
||||
pr_info("sulog: log file exceeds maximum size, clearing...\n");
|
||||
if (vfs_truncate(&fp->f_path, 0)) {
|
||||
pr_err("sulog: failed to truncate log file\n");
|
||||
}
|
||||
pos = 0;
|
||||
} else {
|
||||
pos = fp->f_inode->i_size;
|
||||
}
|
||||
|
||||
list_for_each_entry(entry, &local_queue, list) {
|
||||
ksu_kernel_write_compat(fp, entry->content, strlen(entry->content), &pos);
|
||||
}
|
||||
list_for_each_entry(entry, &local_queue, list) {
|
||||
ksu_kernel_write_compat(fp, entry->content, strlen(entry->content), &pos);
|
||||
}
|
||||
|
||||
vfs_fsync(fp, 0);
|
||||
filp_close(fp, 0);
|
||||
vfs_fsync(fp, 0);
|
||||
filp_close(fp, 0);
|
||||
|
||||
cleanup:
|
||||
list_for_each_entry_safe(entry, tmp, &local_queue, list) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
list_for_each_entry_safe(entry, tmp, &local_queue, list) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
}
|
||||
|
||||
static void sulog_add_entry(const char *content)
|
||||
{
|
||||
struct sulog_entry *entry;
|
||||
struct sulog_entry *entry;
|
||||
|
||||
if (!sulog_enabled || !content)
|
||||
return;
|
||||
if (!sulog_enabled || !content)
|
||||
return;
|
||||
|
||||
entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
if (!entry) {
|
||||
pr_err("sulog: failed to allocate memory for log entry\n");
|
||||
return;
|
||||
}
|
||||
entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
|
||||
if (!entry) {
|
||||
pr_err("sulog: failed to allocate memory for log entry\n");
|
||||
return;
|
||||
}
|
||||
|
||||
#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
|
||||
strlcpy(entry->content, content, SULOG_ENTRY_MAX_LEN - 1);
|
||||
strlcpy(entry->content, content, SULOG_ENTRY_MAX_LEN - 1);
|
||||
#endif
|
||||
|
||||
mutex_lock(&sulog_mutex);
|
||||
list_add_tail(&entry->list, &sulog_queue);
|
||||
mutex_unlock(&sulog_mutex);
|
||||
mutex_lock(&sulog_mutex);
|
||||
list_add_tail(&entry->list, &sulog_queue);
|
||||
mutex_unlock(&sulog_mutex);
|
||||
|
||||
if (sulog_workqueue)
|
||||
queue_work(sulog_workqueue, &sulog_work);
|
||||
if (sulog_workqueue)
|
||||
queue_work(sulog_workqueue, &sulog_work);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for su_grant log\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for su_grant log\n");
|
||||
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,
|
||||
"[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
method ? method : "unknown", current->pid);
|
||||
snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
|
||||
"[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
method ? method : "unknown", current->pid);
|
||||
|
||||
if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
|
||||
sulog_add_entry(log_buf);
|
||||
sulog_add_entry(log_buf);
|
||||
|
||||
cleanup:
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for su_attempt log\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for su_attempt log\n");
|
||||
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,
|
||||
"[%s] SU_EXEC: UID=%d COMM=%s TARGET=%s RESULT=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
target_path ? target_path : "unknown",
|
||||
success ? "SUCCESS" : "DENIED", current->pid);
|
||||
snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
|
||||
"[%s] SU_EXEC: UID=%d COMM=%s TARGET=%s RESULT=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
target_path ? target_path : "unknown",
|
||||
success ? "SUCCESS" : "DENIED", current->pid);
|
||||
|
||||
if (!dedup_should_print(uid, DEDUP_SU_ATTEMPT, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
if (!dedup_should_print(uid, DEDUP_SU_ATTEMPT, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
|
||||
sulog_add_entry(log_buf);
|
||||
sulog_add_entry(log_buf);
|
||||
|
||||
cleanup:
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for permission_check log\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for permission_check log\n");
|
||||
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,
|
||||
"[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
allowed ? "ALLOWED" : "DENIED", current->pid);
|
||||
snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
|
||||
"[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
allowed ? "ALLOWED" : "DENIED", current->pid);
|
||||
|
||||
if (!dedup_should_print(uid, DEDUP_PERM_CHECK, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
if (!dedup_should_print(uid, DEDUP_PERM_CHECK, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
|
||||
sulog_add_entry(log_buf);
|
||||
sulog_add_entry(log_buf);
|
||||
|
||||
cleanup:
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for manager_operation log\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for manager_operation log\n");
|
||||
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,
|
||||
"[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d COMM=%s PID=%d\n",
|
||||
timestamp, operation ? operation : "unknown",
|
||||
manager_uid, target_uid, full_comm, current->pid);
|
||||
snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
|
||||
"[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d COMM=%s PID=%d\n",
|
||||
timestamp, operation ? operation : "unknown",
|
||||
manager_uid, target_uid, full_comm, current->pid);
|
||||
|
||||
if (!dedup_should_print(manager_uid, DEDUP_MANAGER_OP, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
if (!dedup_should_print(manager_uid, DEDUP_MANAGER_OP, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
|
||||
sulog_add_entry(log_buf);
|
||||
sulog_add_entry(log_buf);
|
||||
|
||||
cleanup:
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
if (!sulog_enabled)
|
||||
return;
|
||||
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
timestamp = kmalloc(32, GFP_ATOMIC);
|
||||
full_comm = kmalloc(SULOG_COMM_LEN, GFP_ATOMIC);
|
||||
log_buf = kmalloc(SULOG_ENTRY_MAX_LEN, GFP_ATOMIC);
|
||||
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for syscall log\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!timestamp || !full_comm || !log_buf) {
|
||||
pr_err("sulog: failed to allocate memory for syscall log\n");
|
||||
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,
|
||||
"[%s] SYSCALL: UID=%d COMM=%s SYSCALL=%s ARGS=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
syscall ? syscall : "unknown",
|
||||
args ? args : "none",
|
||||
current->pid);
|
||||
snprintf(log_buf, SULOG_ENTRY_MAX_LEN,
|
||||
"[%s] SYSCALL: UID=%d COMM=%s SYSCALL=%s ARGS=%s PID=%d\n",
|
||||
timestamp, uid, full_comm,
|
||||
syscall ? syscall : "unknown",
|
||||
args ? args : "none",
|
||||
current->pid);
|
||||
|
||||
if (!dedup_should_print(uid, DEDUP_SYSCALL, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
if (!dedup_should_print(uid, DEDUP_SYSCALL, log_buf, strlen(log_buf)))
|
||||
goto cleanup;
|
||||
|
||||
sulog_add_entry(log_buf);
|
||||
sulog_add_entry(log_buf);
|
||||
|
||||
cleanup:
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
if (timestamp) kfree(timestamp);
|
||||
if (full_comm) kfree(full_comm);
|
||||
if (log_buf) kfree(log_buf);
|
||||
}
|
||||
|
||||
int ksu_sulog_init(void)
|
||||
{
|
||||
sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1);
|
||||
if (!sulog_workqueue) {
|
||||
pr_err("sulog: failed to create workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1);
|
||||
if (!sulog_workqueue) {
|
||||
pr_err("sulog: failed to create workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
INIT_WORK(&sulog_work, sulog_work_handler);
|
||||
INIT_WORK(&sulog_work, sulog_work_handler);
|
||||
|
||||
pr_info("sulog: initialized successfully\n");
|
||||
return 0;
|
||||
pr_info("sulog: initialized successfully\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_sulog_exit(void)
|
||||
{
|
||||
struct sulog_entry *entry, *tmp;
|
||||
struct sulog_entry *entry, *tmp;
|
||||
|
||||
sulog_enabled = false;
|
||||
sulog_enabled = false;
|
||||
|
||||
if (sulog_workqueue) {
|
||||
flush_workqueue(sulog_workqueue);
|
||||
destroy_workqueue(sulog_workqueue);
|
||||
sulog_workqueue = NULL;
|
||||
}
|
||||
if (sulog_workqueue) {
|
||||
flush_workqueue(sulog_workqueue);
|
||||
destroy_workqueue(sulog_workqueue);
|
||||
sulog_workqueue = NULL;
|
||||
}
|
||||
|
||||
mutex_lock(&sulog_mutex);
|
||||
list_for_each_entry_safe(entry, tmp, &sulog_queue, list) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
mutex_unlock(&sulog_mutex);
|
||||
mutex_lock(&sulog_mutex);
|
||||
list_for_each_entry_safe(entry, tmp, &sulog_queue, list) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
mutex_unlock(&sulog_mutex);
|
||||
|
||||
pr_info("sulog: cleaned up successfully\n");
|
||||
pr_info("sulog: cleaned up successfully\n");
|
||||
}
|
||||
#endif // __SULOG_GATE
|
||||
@@ -15,6 +15,23 @@ extern struct timezone sys_tz;
|
||||
#define SULOG_COMM_LEN 256
|
||||
#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 {
|
||||
u32 crc;
|
||||
uid_t uid;
|
||||
@@ -41,8 +58,8 @@ static inline u32 dedup_calc_hash(const char *content, size_t len)
|
||||
}
|
||||
|
||||
struct sulog_entry {
|
||||
struct list_head list;
|
||||
char content[SULOG_ENTRY_MAX_LEN];
|
||||
struct list_head list;
|
||||
char content[SULOG_ENTRY_MAX_LEN];
|
||||
};
|
||||
|
||||
void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method);
|
||||
|
||||
609
kernel/supercalls.c
Normal file
609
kernel/supercalls.c
Normal 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
142
kernel/supercalls.h
Normal 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
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "klog.h"
|
||||
#include "throne_comm.h"
|
||||
#include "kernel_compat.h"
|
||||
#include "ksu.h"
|
||||
|
||||
#define PROC_UID_SCANNER "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_load_work;
|
||||
|
||||
extern bool ksu_uid_scanner_enabled;
|
||||
|
||||
// Signal userspace to rescan
|
||||
static bool need_rescan = false;
|
||||
|
||||
static void rescan_work_fn(struct work_struct *work)
|
||||
{
|
||||
// Signal userspace through proc interface
|
||||
need_rescan = true;
|
||||
pr_info("requested userspace uid rescan\n");
|
||||
// Signal userspace through proc interface
|
||||
need_rescan = true;
|
||||
pr_info("requested userspace uid rescan\n");
|
||||
}
|
||||
|
||||
void ksu_request_userspace_scan(void)
|
||||
{
|
||||
if (scanner_wq) {
|
||||
queue_work(scanner_wq, &scan_work);
|
||||
}
|
||||
if (scanner_wq) {
|
||||
queue_work(scanner_wq, &scan_work);
|
||||
}
|
||||
}
|
||||
|
||||
void ksu_handle_userspace_update(void)
|
||||
{
|
||||
// Called when userspace notifies update complete
|
||||
need_rescan = false;
|
||||
pr_info("userspace uid list updated\n");
|
||||
// Called when userspace notifies update complete
|
||||
need_rescan = false;
|
||||
pr_info("userspace uid list updated\n");
|
||||
}
|
||||
|
||||
static void do_save_throne_state(struct work_struct *work)
|
||||
{
|
||||
struct file *fp;
|
||||
char state_char = ksu_uid_scanner_enabled ? '1' : '0';
|
||||
loff_t off = 0;
|
||||
struct file *fp;
|
||||
char state_char = ksu_uid_scanner_enabled ? '1' : '0';
|
||||
loff_t off = 0;
|
||||
|
||||
fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_throne_state create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_err("save_throne_state create file failed: %ld\n", PTR_ERR(fp));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ksu_kernel_write_compat(fp, &state_char, sizeof(state_char), &off) != sizeof(state_char)) {
|
||||
pr_err("save_throne_state write failed\n");
|
||||
goto exit;
|
||||
}
|
||||
if (ksu_kernel_write_compat(fp, &state_char, sizeof(state_char), &off) != sizeof(state_char)) {
|
||||
pr_err("save_throne_state write failed\n");
|
||||
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:
|
||||
filp_close(fp, 0);
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
void do_load_throne_state(struct work_struct *work)
|
||||
{
|
||||
struct file *fp;
|
||||
char state_char;
|
||||
loff_t off = 0;
|
||||
ssize_t ret;
|
||||
struct file *fp;
|
||||
char state_char;
|
||||
loff_t off = 0;
|
||||
ssize_t ret;
|
||||
|
||||
fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_info("throne state file not found, using default: disabled\n");
|
||||
ksu_uid_scanner_enabled = false;
|
||||
return;
|
||||
}
|
||||
fp = ksu_filp_open_compat(UID_SCANNER_STATE_FILE, O_RDONLY, 0);
|
||||
if (IS_ERR(fp)) {
|
||||
pr_info("throne state file not found, using default: disabled\n");
|
||||
ksu_uid_scanner_enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ret = ksu_kernel_read_compat(fp, &state_char, sizeof(state_char), &off);
|
||||
if (ret != sizeof(state_char)) {
|
||||
pr_err("load_throne_state read err: %zd\n", ret);
|
||||
ksu_uid_scanner_enabled = false;
|
||||
goto exit;
|
||||
}
|
||||
ret = ksu_kernel_read_compat(fp, &state_char, sizeof(state_char), &off);
|
||||
if (ret != sizeof(state_char)) {
|
||||
pr_err("load_throne_state read err: %zd\n", ret);
|
||||
ksu_uid_scanner_enabled = false;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ksu_uid_scanner_enabled = (state_char == '1');
|
||||
pr_info("throne state loaded: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
|
||||
ksu_uid_scanner_enabled = (state_char == '1');
|
||||
pr_info("throne state loaded: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
|
||||
|
||||
exit:
|
||||
filp_close(fp, 0);
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (need_rescan) {
|
||||
seq_puts(m, "RESCAN\n");
|
||||
} else {
|
||||
seq_puts(m, "OK\n");
|
||||
}
|
||||
return 0;
|
||||
if (need_rescan) {
|
||||
seq_puts(m, "RESCAN\n");
|
||||
} else {
|
||||
seq_puts(m, "OK\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
char cmd[16];
|
||||
char cmd[16];
|
||||
|
||||
if (count >= sizeof(cmd))
|
||||
return -EINVAL;
|
||||
if (count >= sizeof(cmd))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(cmd, buffer, count))
|
||||
return -EFAULT;
|
||||
if (copy_from_user(cmd, buffer, count))
|
||||
return -EFAULT;
|
||||
|
||||
cmd[count] = '\0';
|
||||
cmd[count] = '\0';
|
||||
|
||||
// Remove newline if present
|
||||
if (count > 0 && cmd[count-1] == '\n')
|
||||
cmd[count-1] = '\0';
|
||||
// Remove newline if present
|
||||
if (count > 0 && cmd[count-1] == '\n')
|
||||
cmd[count-1] = '\0';
|
||||
|
||||
if (strcmp(cmd, "UPDATED") == 0) {
|
||||
ksu_handle_userspace_update();
|
||||
pr_info("received userspace update notification\n");
|
||||
}
|
||||
if (strcmp(cmd, "UPDATED") == 0) {
|
||||
ksu_handle_userspace_update();
|
||||
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 = {
|
||||
.proc_open = uid_scanner_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_write = uid_scanner_write,
|
||||
.proc_write = uid_scanner_write,
|
||||
.proc_lseek = seq_lseek,
|
||||
.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)
|
||||
{
|
||||
// Create workqueue
|
||||
scanner_wq = alloc_workqueue("ksu_scanner", WQ_UNBOUND, 1);
|
||||
if (!scanner_wq) {
|
||||
pr_err("failed to create scanner workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
// Create workqueue
|
||||
scanner_wq = alloc_workqueue("ksu_scanner", WQ_UNBOUND, 1);
|
||||
if (!scanner_wq) {
|
||||
pr_err("failed to create scanner workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
INIT_WORK(&scan_work, rescan_work_fn);
|
||||
INIT_WORK(&scan_work, rescan_work_fn);
|
||||
|
||||
// Create proc entry
|
||||
proc_entry = proc_create(PROC_UID_SCANNER, 0600, NULL, &uid_scanner_proc_ops);
|
||||
if (!proc_entry) {
|
||||
pr_err("failed to create proc entry\n");
|
||||
destroy_workqueue(scanner_wq);
|
||||
return -ENOMEM;
|
||||
}
|
||||
// Create proc entry
|
||||
proc_entry = proc_create(PROC_UID_SCANNER, 0600, NULL, &uid_scanner_proc_ops);
|
||||
if (!proc_entry) {
|
||||
pr_err("failed to create proc entry\n");
|
||||
destroy_workqueue(scanner_wq);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pr_info("throne communication initialized\n");
|
||||
return 0;
|
||||
pr_info("throne communication initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ksu_throne_comm_exit(void)
|
||||
{
|
||||
if (proc_entry) {
|
||||
proc_remove(proc_entry);
|
||||
proc_entry = NULL;
|
||||
}
|
||||
if (proc_entry) {
|
||||
proc_remove(proc_entry);
|
||||
proc_entry = NULL;
|
||||
}
|
||||
|
||||
if (scanner_wq) {
|
||||
destroy_workqueue(scanner_wq);
|
||||
scanner_wq = NULL;
|
||||
}
|
||||
if (scanner_wq) {
|
||||
destroy_workqueue(scanner_wq);
|
||||
scanner_wq = NULL;
|
||||
}
|
||||
|
||||
pr_info("throne communication cleaned up\n");
|
||||
pr_info("throne communication cleaned up\n");
|
||||
}
|
||||
|
||||
int ksu_uid_init(void)
|
||||
{
|
||||
INIT_WORK(&ksu_state_save_work, do_save_throne_state);
|
||||
INIT_WORK(&ksu_state_load_work, do_load_throne_state);
|
||||
return 0;
|
||||
INIT_WORK(&ksu_state_save_work, do_save_throne_state);
|
||||
INIT_WORK(&ksu_state_load_work, do_load_throne_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
@@ -6,410 +6,412 @@
|
||||
#include <android/log.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
NativeBridge(becomeManager, jboolean, jstring pkg) {
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, JNI_FALSE);
|
||||
bool result = become_manager(cpkg);
|
||||
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
|
||||
return result;
|
||||
}
|
||||
|
||||
NativeBridgeNP(getVersion, jint) {
|
||||
return get_version();
|
||||
uint32_t version = get_version();
|
||||
if (version > INT32_MAX) {
|
||||
LogDebug("Version overflow: %u", version);
|
||||
}
|
||||
return (jint)version;
|
||||
}
|
||||
|
||||
// get VERSION FULL
|
||||
NativeBridgeNP(getFullVersion, jstring) {
|
||||
char buff[255] = { 0 };
|
||||
get_full_version((char *) &buff);
|
||||
return GetEnvironment()->NewStringUTF(env, buff);
|
||||
char buff[255] = { 0 };
|
||||
get_full_version((char *) &buff);
|
||||
return GetEnvironment()->NewStringUTF(env, buff);
|
||||
}
|
||||
|
||||
NativeBridgeNP(getAllowList, jintArray) {
|
||||
int uids[1024];
|
||||
int size = 0;
|
||||
bool result = get_allow_list(uids, &size);
|
||||
struct ksu_get_allow_list_cmd cmd = {};
|
||||
bool result = get_allow_list(&cmd);
|
||||
|
||||
LogDebug("getAllowList: %d, size: %d", result, size);
|
||||
if (result) {
|
||||
jsize array_size = (jsize)cmd.count;
|
||||
if (array_size < 0 || (unsigned int)array_size != cmd.count) {
|
||||
LogDebug("Invalid array size: %u", cmd.count);
|
||||
return GetEnvironment()->NewIntArray(env, 0);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
jintArray array = GetEnvironment()->NewIntArray(env, size);
|
||||
GetEnvironment()->SetIntArrayRegion(env, array, 0, size, uids);
|
||||
jintArray array = GetEnvironment()->NewIntArray(env, array_size);
|
||||
GetEnvironment()->SetIntArrayRegion(env, array, 0, array_size, (const jint *)(cmd.uids));
|
||||
|
||||
return array;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
return GetEnvironment()->NewIntArray(env, 0);
|
||||
return GetEnvironment()->NewIntArray(env, 0);
|
||||
}
|
||||
|
||||
NativeBridgeNP(isSafeMode, jboolean) {
|
||||
return is_safe_mode();
|
||||
return is_safe_mode();
|
||||
}
|
||||
|
||||
NativeBridgeNP(isLkmMode, jboolean) {
|
||||
return is_lkm_mode();
|
||||
return is_lkm_mode();
|
||||
}
|
||||
|
||||
NativeBridgeNP(isManager, jboolean) {
|
||||
return is_manager();
|
||||
}
|
||||
|
||||
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, data[i]);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
}
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, data[i]);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
}
|
||||
}
|
||||
|
||||
static void addIntToList(JNIEnv *env, jobject list, int ele) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, ele);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, integerCls, "<init>", "(I)V");
|
||||
jobject integer = GetEnvironment()->NewObject(env, integerCls, constructor, ele);
|
||||
GetEnvironment()->CallBooleanMethod(env, list, add, integer);
|
||||
}
|
||||
|
||||
static uint64_t capListToBits(JNIEnv *env, jobject list) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
jint listSize = GetEnvironment()->CallIntMethod(env, list, size);
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < listSize; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
int data = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
jint listSize = GetEnvironment()->CallIntMethod(env, list, size);
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < listSize; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
int data = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
|
||||
if (cap_valid(data)) {
|
||||
result |= (1ULL << data);
|
||||
}
|
||||
}
|
||||
if (cap_valid(data)) {
|
||||
result |= (1ULL << data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getListSize(JNIEnv *env, jobject list) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
return GetEnvironment()->CallIntMethod(env, list, size);
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID size = GetEnvironment()->GetMethodID(env, cls, "size", "()I");
|
||||
return GetEnvironment()->CallIntMethod(env, list, size);
|
||||
}
|
||||
|
||||
static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
data[i] = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
}
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||
jmethodID get = GetEnvironment()->GetMethodID(env, cls, "get", "(I)Ljava/lang/Object;");
|
||||
jclass integerCls = GetEnvironment()->FindClass(env, "java/lang/Integer");
|
||||
jmethodID intValue = GetEnvironment()->GetMethodID(env, integerCls, "intValue", "()I");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
jobject integer = GetEnvironment()->CallObjectMethod(env, list, get, i);
|
||||
data[i] = GetEnvironment()->CallIntMethod(env, integer, intValue);
|
||||
}
|
||||
}
|
||||
|
||||
NativeBridge(getAppProfile, jobject, jstring pkg, jint uid) {
|
||||
if (GetEnvironment()->GetStringLength(env, pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||
return NULL;
|
||||
}
|
||||
if (GetEnvironment()->GetStringLength(env, pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, nullptr);
|
||||
strcpy(key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
|
||||
char key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, nullptr);
|
||||
strcpy(key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
|
||||
|
||||
struct app_profile profile = { 0 };
|
||||
profile.version = KSU_APP_PROFILE_VER;
|
||||
struct app_profile profile = { 0 };
|
||||
profile.version = KSU_APP_PROFILE_VER;
|
||||
|
||||
strcpy(profile.key, key);
|
||||
profile.current_uid = uid;
|
||||
strcpy(profile.key, key);
|
||||
profile.current_uid = uid;
|
||||
|
||||
bool useDefaultProfile = !get_app_profile(key, &profile);
|
||||
bool useDefaultProfile = get_app_profile(&profile) != 0;
|
||||
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
||||
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
||||
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
|
||||
GetEnvironment()->SetObjectField(env, obj, keyField, GetEnvironment()->NewStringUTF(env, profile.key));
|
||||
GetEnvironment()->SetIntField(env, obj, currentUidField, profile.current_uid);
|
||||
GetEnvironment()->SetObjectField(env, obj, keyField, GetEnvironment()->NewStringUTF(env, profile.key));
|
||||
GetEnvironment()->SetIntField(env, obj, currentUidField, profile.current_uid);
|
||||
|
||||
if (useDefaultProfile) {
|
||||
// no profile found, so just use default profile:
|
||||
// don't allow root and use default profile!
|
||||
LogDebug("use default profile for: %s, %d", key, uid);
|
||||
if (useDefaultProfile) {
|
||||
// no profile found, so just use default profile:
|
||||
// don't allow root and use default profile!
|
||||
LogDebug("use default profile for: %s, %d", key, uid);
|
||||
|
||||
// allow_su = false
|
||||
// non root use default = true
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, false);
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, true);
|
||||
// allow_su = false
|
||||
// non root use default = true
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, false);
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, true);
|
||||
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool allowSu = profile.allow_su;
|
||||
bool allowSu = profile.allow_su;
|
||||
|
||||
if (allowSu) {
|
||||
GetEnvironment()->SetBooleanField(env, obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
|
||||
if (strlen(profile.rp_config.template_name) > 0) {
|
||||
GetEnvironment()->SetObjectField(env, obj, rootTemplateField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.template_name));
|
||||
}
|
||||
if (allowSu) {
|
||||
GetEnvironment()->SetBooleanField(env, obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
|
||||
if (strlen(profile.rp_config.template_name) > 0) {
|
||||
GetEnvironment()->SetObjectField(env, obj, rootTemplateField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.template_name));
|
||||
}
|
||||
|
||||
GetEnvironment()->SetIntField(env, obj, uidField, profile.rp_config.profile.uid);
|
||||
GetEnvironment()->SetIntField(env, obj, gidField, profile.rp_config.profile.gid);
|
||||
GetEnvironment()->SetIntField(env, obj, uidField, profile.rp_config.profile.uid);
|
||||
GetEnvironment()->SetIntField(env, obj, gidField, profile.rp_config.profile.gid);
|
||||
|
||||
jobject groupList = GetEnvironment()->GetObjectField(env, obj, groupsField);
|
||||
int groupCount = profile.rp_config.profile.groups_count;
|
||||
if (groupCount > KSU_MAX_GROUPS) {
|
||||
LogDebug("kernel group count too large: %d???", groupCount);
|
||||
groupCount = KSU_MAX_GROUPS;
|
||||
}
|
||||
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
|
||||
jobject groupList = GetEnvironment()->GetObjectField(env, obj, groupsField);
|
||||
int groupCount = profile.rp_config.profile.groups_count;
|
||||
if (groupCount > KSU_MAX_GROUPS) {
|
||||
LogDebug("kernel group count too large: %d???", groupCount);
|
||||
groupCount = KSU_MAX_GROUPS;
|
||||
}
|
||||
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
|
||||
|
||||
jobject capList = GetEnvironment()->GetObjectField(env, obj, capabilitiesField);
|
||||
for (int i = 0; i <= CAP_LAST_CAP; i++) {
|
||||
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
|
||||
addIntToList(env, capList, i);
|
||||
}
|
||||
}
|
||||
jobject capList = GetEnvironment()->GetObjectField(env, obj, capabilitiesField);
|
||||
for (int i = 0; i <= CAP_LAST_CAP; i++) {
|
||||
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
|
||||
addIntToList(env, capList, i);
|
||||
}
|
||||
}
|
||||
|
||||
GetEnvironment()->SetObjectField(env, obj, domainField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.profile.selinux_domain));
|
||||
GetEnvironment()->SetIntField(env, obj, namespacesField, profile.rp_config.profile.namespaces);
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, profile.allow_su);
|
||||
} else {
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, profile.nrp_config.use_default);
|
||||
GetEnvironment()->SetBooleanField(env, obj, umountModulesField, profile.nrp_config.profile.umount_modules);
|
||||
}
|
||||
GetEnvironment()->SetObjectField(env, obj, domainField,
|
||||
GetEnvironment()->NewStringUTF(env, profile.rp_config.profile.selinux_domain));
|
||||
GetEnvironment()->SetIntField(env, obj, namespacesField, profile.rp_config.profile.namespaces);
|
||||
GetEnvironment()->SetBooleanField(env, obj, allowSuField, profile.allow_su);
|
||||
} else {
|
||||
GetEnvironment()->SetBooleanField(env, obj, nonRootUseDefaultField, profile.nrp_config.use_default);
|
||||
GetEnvironment()->SetBooleanField(env, obj, umountModulesField, profile.nrp_config.profile.umount_modules);
|
||||
}
|
||||
|
||||
return obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
NativeBridge(setAppProfile, jboolean, jobject profile) {
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
jfieldID keyField = GetEnvironment()->GetFieldID(env, cls, "name", "Ljava/lang/String;");
|
||||
jfieldID currentUidField = GetEnvironment()->GetFieldID(env, cls, "currentUid", "I");
|
||||
jfieldID allowSuField = GetEnvironment()->GetFieldID(env, cls, "allowSu", "Z");
|
||||
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
jfieldID rootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "rootUseDefault", "Z");
|
||||
jfieldID rootTemplateField = GetEnvironment()->GetFieldID(env, cls, "rootTemplate", "Ljava/lang/String;");
|
||||
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
jfieldID uidField = GetEnvironment()->GetFieldID(env, cls, "uid", "I");
|
||||
jfieldID gidField = GetEnvironment()->GetFieldID(env, cls, "gid", "I");
|
||||
jfieldID groupsField = GetEnvironment()->GetFieldID(env, cls, "groups", "Ljava/util/List;");
|
||||
jfieldID capabilitiesField = GetEnvironment()->GetFieldID(env, cls, "capabilities", "Ljava/util/List;");
|
||||
jfieldID domainField = GetEnvironment()->GetFieldID(env, cls, "context", "Ljava/lang/String;");
|
||||
jfieldID namespacesField = GetEnvironment()->GetFieldID(env, cls, "namespace", "I");
|
||||
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
jfieldID nonRootUseDefaultField = GetEnvironment()->GetFieldID(env, cls, "nonRootUseDefault", "Z");
|
||||
jfieldID umountModulesField = GetEnvironment()->GetFieldID(env, cls, "umountModules", "Z");
|
||||
|
||||
jobject key = GetEnvironment()->GetObjectField(env, profile, keyField);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
if (GetEnvironment()->GetStringLength(env, (jstring) key) > KSU_MAX_PACKAGE_NAME) {
|
||||
return false;
|
||||
}
|
||||
jobject key = GetEnvironment()->GetObjectField(env, profile, keyField);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
if (GetEnvironment()->GetStringLength(env, (jstring) key) > KSU_MAX_PACKAGE_NAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, (jstring) key, nullptr);
|
||||
char p_key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
strcpy(p_key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) key, cpkg);
|
||||
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, (jstring) key, nullptr);
|
||||
char p_key[KSU_MAX_PACKAGE_NAME] = { 0 };
|
||||
strcpy(p_key, cpkg);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) key, cpkg);
|
||||
|
||||
jint currentUid = GetEnvironment()->GetIntField(env, profile, currentUidField);
|
||||
jint currentUid = GetEnvironment()->GetIntField(env, profile, currentUidField);
|
||||
|
||||
jint uid = GetEnvironment()->GetIntField(env, profile, uidField);
|
||||
jint gid = GetEnvironment()->GetIntField(env, profile, gidField);
|
||||
jobject groups = GetEnvironment()->GetObjectField(env, profile, groupsField);
|
||||
jobject capabilities = GetEnvironment()->GetObjectField(env, profile, capabilitiesField);
|
||||
jobject domain = GetEnvironment()->GetObjectField(env, profile, domainField);
|
||||
jboolean allowSu = GetEnvironment()->GetBooleanField(env, profile, allowSuField);
|
||||
jboolean umountModules = GetEnvironment()->GetBooleanField(env, profile, umountModulesField);
|
||||
jint uid = GetEnvironment()->GetIntField(env, profile, uidField);
|
||||
jint gid = GetEnvironment()->GetIntField(env, profile, gidField);
|
||||
jobject groups = GetEnvironment()->GetObjectField(env, profile, groupsField);
|
||||
jobject capabilities = GetEnvironment()->GetObjectField(env, profile, capabilitiesField);
|
||||
jobject domain = GetEnvironment()->GetObjectField(env, profile, domainField);
|
||||
jboolean allowSu = GetEnvironment()->GetBooleanField(env, profile, allowSuField);
|
||||
jboolean umountModules = GetEnvironment()->GetBooleanField(env, profile, umountModulesField);
|
||||
|
||||
struct app_profile p = { 0 };
|
||||
p.version = KSU_APP_PROFILE_VER;
|
||||
struct app_profile p = { 0 };
|
||||
p.version = KSU_APP_PROFILE_VER;
|
||||
|
||||
strcpy(p.key, p_key);
|
||||
p.allow_su = allowSu;
|
||||
p.current_uid = currentUid;
|
||||
strcpy(p.key, p_key);
|
||||
p.allow_su = allowSu;
|
||||
p.current_uid = currentUid;
|
||||
|
||||
if (allowSu) {
|
||||
p.rp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, rootUseDefaultField);
|
||||
jobject templateName = GetEnvironment()->GetObjectField(env, profile, rootTemplateField);
|
||||
if (templateName) {
|
||||
const char* ctemplateName = GetEnvironment()->GetStringUTFChars(env, (jstring) templateName, nullptr);
|
||||
strcpy(p.rp_config.template_name, ctemplateName);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) templateName, ctemplateName);
|
||||
}
|
||||
if (allowSu) {
|
||||
p.rp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, rootUseDefaultField);
|
||||
jobject templateName = GetEnvironment()->GetObjectField(env, profile, rootTemplateField);
|
||||
if (templateName) {
|
||||
const char* ctemplateName = GetEnvironment()->GetStringUTFChars(env, (jstring) templateName, nullptr);
|
||||
strcpy(p.rp_config.template_name, ctemplateName);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) templateName, ctemplateName);
|
||||
}
|
||||
|
||||
p.rp_config.profile.uid = uid;
|
||||
p.rp_config.profile.gid = gid;
|
||||
p.rp_config.profile.uid = uid;
|
||||
p.rp_config.profile.gid = gid;
|
||||
|
||||
int groups_count = getListSize(env, groups);
|
||||
if (groups_count > KSU_MAX_GROUPS) {
|
||||
LogDebug("groups count too large: %d", groups_count);
|
||||
return false;
|
||||
}
|
||||
p.rp_config.profile.groups_count = groups_count;
|
||||
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
|
||||
int groups_count = getListSize(env, groups);
|
||||
if (groups_count > KSU_MAX_GROUPS) {
|
||||
LogDebug("groups count too large: %d", groups_count);
|
||||
return false;
|
||||
}
|
||||
p.rp_config.profile.groups_count = groups_count;
|
||||
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
|
||||
|
||||
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
|
||||
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
|
||||
|
||||
const char* cdomain = GetEnvironment()->GetStringUTFChars(env, (jstring) domain, nullptr);
|
||||
strcpy(p.rp_config.profile.selinux_domain, cdomain);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) domain, cdomain);
|
||||
const char* cdomain = GetEnvironment()->GetStringUTFChars(env, (jstring) domain, nullptr);
|
||||
strcpy(p.rp_config.profile.selinux_domain, cdomain);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, (jstring) domain, cdomain);
|
||||
|
||||
p.rp_config.profile.namespaces = GetEnvironment()->GetIntField(env, profile, namespacesField);
|
||||
} else {
|
||||
p.nrp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, nonRootUseDefaultField);
|
||||
p.nrp_config.profile.umount_modules = umountModules;
|
||||
}
|
||||
p.rp_config.profile.namespaces = GetEnvironment()->GetIntField(env, profile, namespacesField);
|
||||
} else {
|
||||
p.nrp_config.use_default = GetEnvironment()->GetBooleanField(env, profile, nonRootUseDefaultField);
|
||||
p.nrp_config.profile.umount_modules = umountModules;
|
||||
}
|
||||
|
||||
return set_app_profile(&p);
|
||||
return set_app_profile(&p);
|
||||
}
|
||||
|
||||
NativeBridge(uidShouldUmount, jboolean, jint uid) {
|
||||
return uid_should_umount(uid);
|
||||
return uid_should_umount(uid);
|
||||
}
|
||||
|
||||
NativeBridgeNP(isSuEnabled, jboolean) {
|
||||
return is_su_enabled();
|
||||
return is_su_enabled();
|
||||
}
|
||||
|
||||
NativeBridge(setSuEnabled, jboolean, jboolean enabled) {
|
||||
return set_su_enabled(enabled);
|
||||
return set_su_enabled(enabled);
|
||||
}
|
||||
|
||||
// Check if KPM is enabled
|
||||
NativeBridgeNP(isKPMEnabled, jboolean) {
|
||||
return is_KPM_enable();
|
||||
return is_KPM_enable();
|
||||
}
|
||||
|
||||
// Get HOOK type
|
||||
NativeBridgeNP(getHookType, jstring) {
|
||||
char hook_type[16];
|
||||
get_hook_type(hook_type, sizeof(hook_type));
|
||||
return GetEnvironment()->NewStringUTF(env, hook_type);
|
||||
char hook_type[32] = { 0 };
|
||||
get_hook_type((char *) &hook_type);
|
||||
return GetEnvironment()->NewStringUTF(env, hook_type);
|
||||
}
|
||||
|
||||
// dynamic manager
|
||||
NativeBridge(setDynamicManager, jboolean, jint size, jstring hash) {
|
||||
if (!hash) {
|
||||
LogDebug("setDynamicManager: hash is null");
|
||||
return false;
|
||||
}
|
||||
if (!hash) {
|
||||
LogDebug("setDynamicManager: hash is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
|
||||
bool result = set_dynamic_manager((unsigned int)size, chash);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
|
||||
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
|
||||
bool result = set_dynamic_manager((unsigned int)size, chash);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
|
||||
|
||||
LogDebug("setDynamicManager: size=0x%x, result=%d", size, result);
|
||||
return result;
|
||||
LogDebug("setDynamicManager: size=0x%x, result=%d", size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
NativeBridgeNP(getDynamicManager, jobject) {
|
||||
struct dynamic_manager_user_config config;
|
||||
bool result = get_dynamic_manager(&config);
|
||||
struct dynamic_manager_user_config config;
|
||||
bool result = get_dynamic_manager(&config);
|
||||
|
||||
if (!result) {
|
||||
LogDebug("getDynamicManager: failed to get dynamic manager config");
|
||||
return NULL;
|
||||
}
|
||||
if (!result) {
|
||||
LogDebug("getDynamicManager: failed to get dynamic manager config");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicManagerConfig");
|
||||
|
||||
SET_INT_FIELD(obj, cls, size, (jint)config.size);
|
||||
SET_STRING_FIELD(obj, cls, hash, config.hash);
|
||||
SET_INT_FIELD(obj, cls, size, (jint)config.size);
|
||||
SET_STRING_FIELD(obj, cls, hash, config.hash);
|
||||
|
||||
LogDebug("getDynamicManager: size=0x%x, hash=%.16s...", config.size, config.hash);
|
||||
return obj;
|
||||
LogDebug("getDynamicManager: size=0x%x, hash=%.16s...", config.size, config.hash);
|
||||
return obj;
|
||||
}
|
||||
|
||||
NativeBridgeNP(clearDynamicManager, jboolean) {
|
||||
bool result = clear_dynamic_manager();
|
||||
LogDebug("clearDynamicManager: result=%d", result);
|
||||
return result;
|
||||
bool result = clear_dynamic_manager();
|
||||
LogDebug("clearDynamicManager: result=%d", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get a list of active managers
|
||||
NativeBridgeNP(getManagersList, jobject) {
|
||||
struct manager_list_info managerListInfo;
|
||||
bool result = get_managers_list(&managerListInfo);
|
||||
struct manager_list_info managerListInfo;
|
||||
bool result = get_managers_list(&managerListInfo);
|
||||
|
||||
if (!result) {
|
||||
LogDebug("getManagersList: failed to get active managers list");
|
||||
return NULL;
|
||||
}
|
||||
if (!result) {
|
||||
LogDebug("getManagersList: failed to get active managers list");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$ManagersList");
|
||||
jclass managerListCls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$ManagersList");
|
||||
jobject obj = CREATE_JAVA_OBJECT("com/sukisu/ultra/Natives$ManagersList");
|
||||
jclass managerListCls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$ManagersList");
|
||||
|
||||
SET_INT_FIELD(obj, managerListCls, count, (jint)managerListInfo.count);
|
||||
SET_INT_FIELD(obj, managerListCls, count, (jint)managerListInfo.count);
|
||||
|
||||
jobject managersList = CREATE_ARRAYLIST();
|
||||
jobject managersList = CREATE_ARRAYLIST();
|
||||
|
||||
for (int i = 0; i < managerListInfo.count; i++) {
|
||||
jobject managerInfo = CREATE_JAVA_OBJECT_WITH_PARAMS(
|
||||
"com/sukisu/ultra/Natives$ManagerInfo",
|
||||
"(II)V",
|
||||
(jint)managerListInfo.managers[i].uid,
|
||||
(jint)managerListInfo.managers[i].signature_index
|
||||
);
|
||||
ADD_TO_LIST(managersList, managerInfo);
|
||||
}
|
||||
for (int i = 0; i < managerListInfo.count; i++) {
|
||||
jobject managerInfo = CREATE_JAVA_OBJECT_WITH_PARAMS(
|
||||
"com/sukisu/ultra/Natives$ManagerInfo",
|
||||
"(II)V",
|
||||
(jint)managerListInfo.managers[i].uid,
|
||||
(jint)managerListInfo.managers[i].signature_index
|
||||
);
|
||||
ADD_TO_LIST(managersList, managerInfo);
|
||||
}
|
||||
|
||||
SET_OBJECT_FIELD(obj, managerListCls, managers, managersList);
|
||||
SET_OBJECT_FIELD(obj, managerListCls, managers, managersList);
|
||||
|
||||
LogDebug("getManagersList: count=%d", managerListInfo.count);
|
||||
return obj;
|
||||
LogDebug("getManagersList: count=%d", managerListInfo.count);
|
||||
return obj;
|
||||
}
|
||||
|
||||
NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||
if (!modulePath) {
|
||||
LogDebug("verifyModuleSignature: modulePath is null");
|
||||
return false;
|
||||
}
|
||||
if (!modulePath) {
|
||||
LogDebug("verifyModuleSignature: modulePath is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* cModulePath = GetEnvironment()->GetStringUTFChars(env, modulePath, nullptr);
|
||||
bool result = verify_module_signature(cModulePath);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, modulePath, cModulePath);
|
||||
const char* cModulePath = GetEnvironment()->GetStringUTFChars(env, modulePath, nullptr);
|
||||
bool result = verify_module_signature(cModulePath);
|
||||
GetEnvironment()->ReleaseStringUTFChars(env, modulePath, cModulePath);
|
||||
|
||||
LogDebug("verifyModuleSignature: path=%s, result=%d", cModulePath, result);
|
||||
return result;
|
||||
LogDebug("verifyModuleSignature: path=%s, result=%d", cModulePath, result);
|
||||
return result;
|
||||
#else
|
||||
LogDebug("verifyModuleSignature: not supported on non-ARM architecture");
|
||||
return false;
|
||||
LogDebug("verifyModuleSignature: not supported on non-ARM architecture");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
NativeBridgeNP(isUidScannerEnabled, jboolean) {
|
||||
return is_uid_scanner_enabled();
|
||||
return is_uid_scanner_enabled();
|
||||
}
|
||||
|
||||
NativeBridge(setUidScannerEnabled, jboolean, jboolean enabled) {
|
||||
return set_uid_scanner_enabled(enabled);
|
||||
return set_uid_scanner_enabled(enabled);
|
||||
}
|
||||
|
||||
NativeBridgeNP(clearUidScannerEnvironment, jboolean) {
|
||||
return clear_uid_scanner_environment();
|
||||
return clear_uid_scanner_environment();
|
||||
}
|
||||
@@ -7,6 +7,11 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <android/log.h>
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "prelude.h"
|
||||
#include "ksu.h"
|
||||
@@ -21,238 +26,276 @@ extern const char* zako_file_verrcidx2str(uint8_t index);
|
||||
|
||||
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
|
||||
|
||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||
static int fd = -1;
|
||||
|
||||
#define CMD_GRANT_ROOT 0
|
||||
static inline int scan_driver_fd() {
|
||||
const char *kName = "[ksu_driver]";
|
||||
DIR *fd_dir = opendir("/proc/self/fd");
|
||||
if (!fd_dir) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define CMD_BECOME_MANAGER 1
|
||||
#define CMD_GET_VERSION 2
|
||||
#define CMD_ALLOW_SU 3
|
||||
#define CMD_DENY_SU 4
|
||||
#define CMD_GET_SU_LIST 5
|
||||
#define CMD_GET_DENY_LIST 6
|
||||
#define CMD_CHECK_SAFEMODE 9
|
||||
int found = -1;
|
||||
struct dirent *de;
|
||||
char path[64];
|
||||
char target[PATH_MAX];
|
||||
|
||||
#define CMD_GET_APP_PROFILE 10
|
||||
#define CMD_SET_APP_PROFILE 11
|
||||
while ((de = readdir(fd_dir)) != NULL) {
|
||||
if (de->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
char *endptr = nullptr;
|
||||
long fd_long = strtol(de->d_name, &endptr, 10);
|
||||
if (!de->d_name[0] || *endptr != '\0' || fd_long < 0 || fd_long > INT_MAX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#define CMD_GET_VERSION_FULL 0xC0FFEE1A
|
||||
snprintf(path, sizeof(path), "/proc/self/fd/%s", de->d_name);
|
||||
ssize_t n = readlink(path, target, sizeof(target) - 1);
|
||||
if (n < 0) {
|
||||
continue;
|
||||
}
|
||||
target[n] = '\0';
|
||||
|
||||
#define CMD_ENABLE_KPM 100
|
||||
#define CMD_HOOK_TYPE 101
|
||||
#define CMD_DYNAMIC_MANAGER 103
|
||||
#define CMD_GET_MANAGERS 104
|
||||
#define CMD_ENABLE_UID_SCANNER 105
|
||||
const char *base = strrchr(target, '/');
|
||||
base = base ? base + 1 : target;
|
||||
|
||||
#define DYNAMIC_MANAGER_OP_SET 0
|
||||
#define DYNAMIC_MANAGER_OP_GET 1
|
||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||
if (strstr(base, kName)) {
|
||||
found = (int)fd_long;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||
int32_t result = 0;
|
||||
int32_t rtn = prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
|
||||
|
||||
return result == KERNEL_SU_OPTION && rtn == -1;
|
||||
closedir(fd_dir);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool become_manager(const char* pkg) {
|
||||
char param[128];
|
||||
uid_t uid = getuid();
|
||||
uint32_t userId = uid / 100000;
|
||||
if (userId == 0) {
|
||||
sprintf(param, "/data/data/%s", pkg);
|
||||
} else {
|
||||
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
|
||||
}
|
||||
|
||||
return ksuctl(CMD_BECOME_MANAGER, param, NULL);
|
||||
static int ksuctl(unsigned long op, void* arg) {
|
||||
if (fd < 0) {
|
||||
fd = scan_driver_fd();
|
||||
}
|
||||
return ioctl(fd, op, arg);
|
||||
}
|
||||
|
||||
// cache the result to avoid unnecessary syscall
|
||||
static bool is_lkm;
|
||||
int get_version() {
|
||||
int32_t version = -1;
|
||||
int32_t flags = 0;
|
||||
ksuctl(CMD_GET_VERSION, &version, &flags);
|
||||
if (!is_lkm && (flags & 0x1)) {
|
||||
is_lkm = true;
|
||||
}
|
||||
return version;
|
||||
static struct ksu_get_info_cmd g_version = {0};
|
||||
|
||||
struct ksu_get_info_cmd get_info() {
|
||||
if (!g_version.version) {
|
||||
ksuctl(KSU_IOCTL_GET_INFO, &g_version);
|
||||
}
|
||||
return g_version;
|
||||
}
|
||||
|
||||
void get_full_version(char* buff) {
|
||||
ksuctl(CMD_GET_VERSION_FULL, buff, NULL);
|
||||
uint32_t get_version() {
|
||||
auto info = get_info();
|
||||
return info.version;
|
||||
}
|
||||
|
||||
bool get_allow_list(int *uids, int *size) {
|
||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||
bool get_allow_list(struct ksu_get_allow_list_cmd *cmd) {
|
||||
return ksuctl(KSU_IOCTL_GET_ALLOW_LIST, cmd) == 0;
|
||||
}
|
||||
|
||||
bool is_safe_mode() {
|
||||
return ksuctl(CMD_CHECK_SAFEMODE, NULL, NULL);
|
||||
struct ksu_check_safemode_cmd cmd = {};
|
||||
ksuctl(KSU_IOCTL_CHECK_SAFEMODE, &cmd);
|
||||
return cmd.in_safe_mode;
|
||||
}
|
||||
|
||||
bool is_lkm_mode() {
|
||||
// you should call get_version first!
|
||||
return is_lkm;
|
||||
auto info = get_info();
|
||||
return (info.flags & 0x1) != 0;
|
||||
}
|
||||
|
||||
bool is_manager() {
|
||||
auto info = get_info();
|
||||
return (info.flags & 0x2) != 0;
|
||||
}
|
||||
|
||||
bool uid_should_umount(int uid) {
|
||||
int should;
|
||||
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should;
|
||||
struct ksu_uid_should_umount_cmd cmd = {};
|
||||
cmd.uid = uid;
|
||||
ksuctl(KSU_IOCTL_UID_SHOULD_UMOUNT, &cmd);
|
||||
return cmd.should_umount;
|
||||
}
|
||||
|
||||
bool set_app_profile(const struct app_profile* profile) {
|
||||
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL);
|
||||
bool set_app_profile(const struct app_profile *profile) {
|
||||
struct ksu_set_app_profile_cmd cmd = {};
|
||||
cmd.profile = *profile;
|
||||
return ksuctl(KSU_IOCTL_SET_APP_PROFILE, &cmd) == 0;
|
||||
}
|
||||
|
||||
bool get_app_profile(char* key, struct app_profile* profile) {
|
||||
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL);
|
||||
int get_app_profile(struct app_profile *profile) {
|
||||
struct ksu_get_app_profile_cmd cmd = {.profile = *profile};
|
||||
int ret = ksuctl(KSU_IOCTL_GET_APP_PROFILE, &cmd);
|
||||
*profile = cmd.profile;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool set_su_enabled(bool enabled) {
|
||||
return ksuctl(CMD_ENABLE_SU, (void*) enabled, NULL);
|
||||
struct ksu_enable_su_cmd cmd = {.enable = enabled};
|
||||
return ksuctl(KSU_IOCTL_ENABLE_SU, &cmd) == 0;
|
||||
}
|
||||
|
||||
bool is_su_enabled() {
|
||||
int enabled = true;
|
||||
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
|
||||
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL);
|
||||
return enabled;
|
||||
struct ksu_is_su_enabled_cmd cmd = {};
|
||||
return ksuctl(KSU_IOCTL_IS_SU_ENABLED, &cmd) == 0 && cmd.enabled;
|
||||
}
|
||||
|
||||
bool is_KPM_enable() {
|
||||
int enabled = false;
|
||||
ksuctl(CMD_ENABLE_KPM, &enabled, NULL);
|
||||
return enabled;
|
||||
void get_full_version(char* buff) {
|
||||
struct ksu_get_full_version_cmd cmd = {0};
|
||||
if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) {
|
||||
strncpy(buff, cmd.version_full, KSU_FULL_VERSION_STRING - 1);
|
||||
buff[KSU_FULL_VERSION_STRING - 1] = '\0';
|
||||
} else {
|
||||
buff[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
bool get_hook_type(char* hook_type, size_t size) {
|
||||
if (hook_type == NULL || size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static char cached_hook_type[16] = {0};
|
||||
if (cached_hook_type[0] == '\0') {
|
||||
if (!ksuctl(CMD_HOOK_TYPE, cached_hook_type, NULL)) {
|
||||
strcpy(cached_hook_type, "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(hook_type, cached_hook_type, size);
|
||||
hook_type[size - 1] = '\0';
|
||||
return true;
|
||||
bool is_KPM_enable(void)
|
||||
{
|
||||
struct ksu_enable_kpm_cmd cmd = {};
|
||||
return ksuctl(KSU_IOCTL_ENABLE_KPM, &cmd) == 0 && cmd.enabled;
|
||||
}
|
||||
|
||||
bool set_dynamic_manager(unsigned int size, const char* hash) {
|
||||
if (hash == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dynamic_manager_user_config config;
|
||||
config.operation = DYNAMIC_MANAGER_OP_SET;
|
||||
config.size = size;
|
||||
strncpy(config.hash, hash, sizeof(config.hash) - 1);
|
||||
config.hash[sizeof(config.hash) - 1] = '\0';
|
||||
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||
void get_hook_type(char *buff)
|
||||
{
|
||||
struct ksu_hook_type_cmd cmd = {0};
|
||||
if (ksuctl(KSU_IOCTL_HOOK_TYPE, &cmd) == 0) {
|
||||
strncpy(buff, cmd.hook_type, 32 - 1);
|
||||
buff[32 - 1] = '\0';
|
||||
} else {
|
||||
strcpy(buff, "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
bool get_dynamic_manager(struct dynamic_manager_user_config* config) {
|
||||
if (config == NULL) {
|
||||
return false;
|
||||
}
|
||||
bool set_dynamic_manager(unsigned int size, const char *hash)
|
||||
{
|
||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
||||
cmd.config.operation = DYNAMIC_MANAGER_OP_SET;
|
||||
cmd.config.size = size;
|
||||
strlcpy(cmd.config.hash, hash, sizeof(cmd.config.hash));
|
||||
|
||||
config->operation = DYNAMIC_MANAGER_OP_GET;
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
|
||||
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
|
||||
}
|
||||
|
||||
bool clear_dynamic_manager() {
|
||||
struct dynamic_manager_user_config config;
|
||||
config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||
bool get_dynamic_manager(struct dynamic_manager_user_config *cfg)
|
||||
{
|
||||
if (!cfg)
|
||||
return false;
|
||||
|
||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
||||
cmd.config.operation = DYNAMIC_MANAGER_OP_GET;
|
||||
|
||||
if (ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) != 0)
|
||||
return false;
|
||||
|
||||
*cfg = cmd.config;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_managers_list(struct manager_list_info* info) {
|
||||
if (info == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ksuctl(CMD_GET_MANAGERS, info, NULL);
|
||||
bool clear_dynamic_manager(void)
|
||||
{
|
||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
||||
cmd.config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
||||
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
|
||||
}
|
||||
|
||||
bool get_managers_list(struct manager_list_info *info)
|
||||
{
|
||||
if (!info)
|
||||
return false;
|
||||
struct ksu_get_managers_cmd cmd = {0};
|
||||
if (ksuctl(KSU_IOCTL_GET_MANAGERS, &cmd) != 0)
|
||||
return false;
|
||||
|
||||
*info = cmd.manager_info;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool verify_module_signature(const char* input) {
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||
if (input == NULL) {
|
||||
LogDebug("verify_module_signature: input path is null");
|
||||
return false;
|
||||
}
|
||||
if (input == NULL) {
|
||||
LogDebug("verify_module_signature: input path is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd = zako_sys_file_open(input);
|
||||
if (fd < 0) {
|
||||
LogDebug("verify_module_signature: failed to open file: %s", input);
|
||||
return false;
|
||||
}
|
||||
int file_fd = zako_sys_file_open(input);
|
||||
if (file_fd < 0) {
|
||||
LogDebug("verify_module_signature: failed to open file: %s", input);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t results = zako_file_verify_esig(fd, 0);
|
||||
uint32_t results = zako_file_verify_esig(file_fd, 0);
|
||||
|
||||
if (results != 0) {
|
||||
/* If important error occured, verification process should
|
||||
be considered as failed due to unexpected modification
|
||||
potentially happened. */
|
||||
if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) {
|
||||
LogDebug("verify_module_signature: Verification failed! (important error)");
|
||||
} else {
|
||||
/* This is for manager that doesn't want to do certificate checks */
|
||||
LogDebug("verify_module_signature: Verification partially passed");
|
||||
}
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Verification passed!");
|
||||
goto exit;
|
||||
}
|
||||
if (results != 0) {
|
||||
/* If important error occured, verification process should
|
||||
be considered as failed due to unexpected modification
|
||||
potentially happened. */
|
||||
if ((results & ZAKO_ESV_IMPORTANT_ERROR) != 0) {
|
||||
LogDebug("verify_module_signature: Verification failed! (important error)");
|
||||
} else {
|
||||
/* This is for manager that doesn't want to do certificate checks */
|
||||
LogDebug("verify_module_signature: Verification partially passed");
|
||||
}
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Verification passed!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Go through all bit fields */
|
||||
for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) {
|
||||
if ((results & (1 << i)) == 0) {
|
||||
continue;
|
||||
}
|
||||
/* Go through all bit fields */
|
||||
for (size_t i = 0; i < sizeof(uint32_t) * 8; i++) {
|
||||
if ((results & (1 << i)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Convert error bit field index into human readable string */
|
||||
const char* message = zako_file_verrcidx2str((uint8_t)i);
|
||||
// Error message: message
|
||||
if (message != NULL) {
|
||||
LogDebug("verify_module_signature: Error bit %zu: %s", i, message);
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Error bit %zu: Unknown error", i);
|
||||
}
|
||||
}
|
||||
/* Convert error bit field index into human readable string */
|
||||
const char* message = zako_file_verrcidx2str((uint8_t)i);
|
||||
// Error message: message
|
||||
if (message != NULL) {
|
||||
LogDebug("verify_module_signature: Error bit %zu: %s", i, message);
|
||||
} else {
|
||||
LogDebug("verify_module_signature: Error bit %zu: Unknown error", i);
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
close(fd);
|
||||
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
|
||||
input, results, (results == 0) ? "true" : "false");
|
||||
return results == 0;
|
||||
exit:
|
||||
close(file_fd);
|
||||
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
|
||||
input, results, (results == 0) ? "true" : "false");
|
||||
return results == 0;
|
||||
#else
|
||||
LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null");
|
||||
return false;
|
||||
LogDebug("verify_module_signature: not supported on non-ARM architecture, path=%s", input ? input : "null");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_uid_scanner_enabled() {
|
||||
bool status = false;
|
||||
ksuctl(CMD_ENABLE_UID_SCANNER, (void*)0, &status);
|
||||
return status;
|
||||
bool is_uid_scanner_enabled(void)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
||||
.operation = UID_SCANNER_OP_GET_STATUS,
|
||||
.status_ptr = (__u64)(uintptr_t)&status
|
||||
};
|
||||
|
||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd) == 0 != 0 && status;
|
||||
}
|
||||
|
||||
bool set_uid_scanner_enabled(bool enabled) {
|
||||
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)1, (void*)enabled);
|
||||
bool set_uid_scanner_enabled(bool enabled)
|
||||
{
|
||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
||||
.operation = UID_SCANNER_OP_TOGGLE,
|
||||
.enabled = enabled
|
||||
};
|
||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
||||
}
|
||||
|
||||
bool clear_uid_scanner_environment() {
|
||||
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)2, NULL);
|
||||
bool clear_uid_scanner_environment(void)
|
||||
{
|
||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
||||
.operation = UID_SCANNER_OP_CLEAR_ENV
|
||||
};
|
||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
||||
}
|
||||
@@ -8,14 +8,11 @@
|
||||
#include "prelude.h"
|
||||
#include <linux/capability.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
bool become_manager(const char *);
|
||||
#define KSU_FULL_VERSION_STRING 255
|
||||
|
||||
void get_full_version(char* buff);
|
||||
|
||||
int get_version();
|
||||
|
||||
bool get_allow_list(int *uids, int *size);
|
||||
uint32_t get_version();
|
||||
|
||||
bool uid_should_umount(int uid);
|
||||
|
||||
@@ -23,6 +20,10 @@ bool is_safe_mode();
|
||||
|
||||
bool is_lkm_mode();
|
||||
|
||||
bool is_manager();
|
||||
|
||||
void get_full_version(char* buff);
|
||||
|
||||
#define KSU_APP_PROFILE_VER 2
|
||||
#define KSU_MAX_PACKAGE_NAME 256
|
||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
||||
@@ -33,72 +34,76 @@ bool is_lkm_mode();
|
||||
#define DYNAMIC_MANAGER_OP_GET 1
|
||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||
|
||||
#define UID_SCANNER_OP_GET_STATUS 0
|
||||
#define UID_SCANNER_OP_TOGGLE 1
|
||||
#define UID_SCANNER_OP_CLEAR_ENV 2
|
||||
|
||||
struct dynamic_manager_user_config {
|
||||
unsigned int operation;
|
||||
unsigned int size;
|
||||
char hash[65];
|
||||
unsigned int operation;
|
||||
unsigned int size;
|
||||
char hash[65];
|
||||
};
|
||||
|
||||
|
||||
struct root_profile {
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
int32_t groups_count;
|
||||
int32_t groups[KSU_MAX_GROUPS];
|
||||
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
uint64_t effective;
|
||||
uint64_t permitted;
|
||||
uint64_t inheritable;
|
||||
} capabilities;
|
||||
// kernel_cap_t is u32[2] for capabilities v3
|
||||
struct {
|
||||
uint64_t effective;
|
||||
uint64_t permitted;
|
||||
uint64_t inheritable;
|
||||
} capabilities;
|
||||
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||
|
||||
int32_t namespaces;
|
||||
int32_t namespaces;
|
||||
};
|
||||
|
||||
struct non_root_profile {
|
||||
bool umount_modules;
|
||||
bool umount_modules;
|
||||
};
|
||||
|
||||
struct app_profile {
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
uint32_t version;
|
||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||
uint32_t version;
|
||||
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
// this is usually the package of the app, but can be other value for special apps
|
||||
char key[KSU_MAX_PACKAGE_NAME];
|
||||
int32_t current_uid;
|
||||
bool allow_su;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
union {
|
||||
struct {
|
||||
bool use_default;
|
||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
struct root_profile profile;
|
||||
} rp_config;
|
||||
|
||||
struct {
|
||||
bool use_default;
|
||||
struct {
|
||||
bool use_default;
|
||||
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
struct non_root_profile profile;
|
||||
} nrp_config;
|
||||
};
|
||||
};
|
||||
|
||||
struct manager_list_info {
|
||||
int count;
|
||||
struct {
|
||||
uid_t uid;
|
||||
int signature_index;
|
||||
} managers[2];
|
||||
int count;
|
||||
struct {
|
||||
uid_t uid;
|
||||
int signature_index;
|
||||
} managers[2];
|
||||
};
|
||||
|
||||
bool set_app_profile(const struct app_profile* profile);
|
||||
|
||||
bool get_app_profile(char* key, struct app_profile* profile);
|
||||
int get_app_profile(struct app_profile* profile);
|
||||
|
||||
bool set_su_enabled(bool enabled);
|
||||
|
||||
@@ -106,8 +111,7 @@ bool is_su_enabled();
|
||||
|
||||
bool is_KPM_enable();
|
||||
|
||||
bool get_hook_type(char* hook_type, size_t size);
|
||||
|
||||
void get_hook_type(char* hook_type);
|
||||
|
||||
bool set_dynamic_manager(unsigned int size, const char* hash);
|
||||
|
||||
@@ -125,4 +129,119 @@ bool set_uid_scanner_enabled(bool enabled);
|
||||
|
||||
bool clear_uid_scanner_environment();
|
||||
|
||||
struct ksu_become_daemon_cmd {
|
||||
__u8 token[65]; // Input: daemon token (null-terminated)
|
||||
};
|
||||
|
||||
struct ksu_get_info_cmd {
|
||||
__u32 version; // Output: KERNEL_SU_VERSION
|
||||
__u32 flags; // Output: flags (bit 0: MODULE mode)
|
||||
};
|
||||
|
||||
struct ksu_report_event_cmd {
|
||||
__u32 event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc.
|
||||
};
|
||||
|
||||
struct ksu_set_sepolicy_cmd {
|
||||
__u64 cmd; // Input: sepolicy command
|
||||
__aligned_u64 arg; // Input: sepolicy argument pointer
|
||||
};
|
||||
|
||||
struct ksu_check_safemode_cmd {
|
||||
__u8 in_safe_mode; // Output: true if in safe mode, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_get_allow_list_cmd {
|
||||
__u32 uids[128]; // Output: array of allowed/denied UIDs
|
||||
__u32 count; // Output: number of UIDs in array
|
||||
__u8 allow; // Input: true for allow list, false for deny list
|
||||
};
|
||||
|
||||
struct ksu_uid_granted_root_cmd {
|
||||
__u32 uid; // Input: target UID to check
|
||||
__u8 granted; // Output: true if granted, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_uid_should_umount_cmd {
|
||||
__u32 uid; // Input: target UID to check
|
||||
__u8 should_umount; // Output: true if should umount, false otherwise
|
||||
};
|
||||
|
||||
struct ksu_get_manager_uid_cmd {
|
||||
__u32 uid; // Output: manager UID
|
||||
};
|
||||
|
||||
struct ksu_set_manager_uid_cmd {
|
||||
__u32 uid; // Input: new manager UID
|
||||
};
|
||||
|
||||
struct ksu_get_app_profile_cmd {
|
||||
struct app_profile profile; // Input/Output: app profile structure
|
||||
};
|
||||
|
||||
struct ksu_set_app_profile_cmd {
|
||||
struct app_profile profile; // Input: app profile structure
|
||||
};
|
||||
|
||||
struct ksu_is_su_enabled_cmd {
|
||||
__u8 enabled; // Output: true if su compat enabled
|
||||
};
|
||||
|
||||
struct ksu_enable_su_cmd {
|
||||
__u8 enable; // Input: true to enable, false to disable
|
||||
};
|
||||
|
||||
// Other command structures
|
||||
struct ksu_get_full_version_cmd {
|
||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
||||
};
|
||||
|
||||
struct ksu_hook_type_cmd {
|
||||
char hook_type[32]; // Output: hook type string
|
||||
};
|
||||
|
||||
struct ksu_enable_kpm_cmd {
|
||||
__u8 enabled; // Output: true if KPM is enabled
|
||||
};
|
||||
|
||||
struct ksu_dynamic_manager_cmd {
|
||||
struct dynamic_manager_user_config config; // Input/Output: dynamic manager config
|
||||
};
|
||||
|
||||
struct ksu_get_managers_cmd {
|
||||
struct manager_list_info manager_info; // Output: manager list information
|
||||
};
|
||||
|
||||
struct ksu_enable_uid_scanner_cmd {
|
||||
__u32 operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV)
|
||||
__u32 enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE)
|
||||
__u64 status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS)
|
||||
};
|
||||
|
||||
// IOCTL command definitions
|
||||
#define KSU_IOCTL_GRANT_ROOT _IO('K', 1)
|
||||
#define KSU_IOCTL_GET_INFO _IOR('K', 2, struct ksu_get_info_cmd)
|
||||
#define KSU_IOCTL_REPORT_EVENT _IOW('K', 3, struct ksu_report_event_cmd)
|
||||
#define KSU_IOCTL_SET_SEPOLICY _IOWR('K', 4, struct ksu_set_sepolicy_cmd)
|
||||
#define KSU_IOCTL_CHECK_SAFEMODE _IOR('K', 5, struct ksu_check_safemode_cmd)
|
||||
#define KSU_IOCTL_GET_ALLOW_LIST _IOWR('K', 6, struct ksu_get_allow_list_cmd)
|
||||
#define KSU_IOCTL_GET_DENY_LIST _IOWR('K', 7, struct ksu_get_allow_list_cmd)
|
||||
#define KSU_IOCTL_UID_GRANTED_ROOT _IOWR('K', 8, struct ksu_uid_granted_root_cmd)
|
||||
#define KSU_IOCTL_UID_SHOULD_UMOUNT _IOWR('K', 9, struct ksu_uid_should_umount_cmd)
|
||||
#define KSU_IOCTL_GET_MANAGER_UID _IOR('K', 10, struct ksu_get_manager_uid_cmd)
|
||||
#define KSU_IOCTL_GET_APP_PROFILE _IOWR('K', 11, struct ksu_get_app_profile_cmd)
|
||||
#define KSU_IOCTL_SET_APP_PROFILE _IOW('K', 12, struct ksu_set_app_profile_cmd)
|
||||
#define KSU_IOCTL_IS_SU_ENABLED _IOR('K', 13, struct ksu_is_su_enabled_cmd)
|
||||
#define KSU_IOCTL_ENABLE_SU _IOW('K', 14, struct ksu_enable_su_cmd)
|
||||
|
||||
// Other IOCTL command definitions
|
||||
#define KSU_IOCTL_GET_FULL_VERSION _IOR('K', 100, struct ksu_get_full_version_cmd)
|
||||
#define KSU_IOCTL_HOOK_TYPE _IOR('K', 101, struct ksu_hook_type_cmd)
|
||||
#define KSU_IOCTL_ENABLE_KPM _IOR('K', 102, struct ksu_enable_kpm_cmd)
|
||||
#define KSU_IOCTL_DYNAMIC_MANAGER _IOWR('K', 103, struct ksu_dynamic_manager_cmd)
|
||||
#define KSU_IOCTL_GET_MANAGERS _IOWR('K', 104, struct ksu_get_managers_cmd)
|
||||
#define KSU_IOCTL_ENABLE_UID_SCANNER _IOWR('K', 105, struct ksu_enable_uid_scanner_cmd)
|
||||
|
||||
bool get_allow_list(struct ksu_get_allow_list_cmd*);
|
||||
|
||||
#endif //KERNELSU_KSU_H
|
||||
@@ -14,51 +14,51 @@
|
||||
|
||||
// Macros to simplify field setup
|
||||
#define SET_BOOLEAN_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Z"); \
|
||||
GetEnvironment()->SetBooleanField(env, obj, field, value); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Z"); \
|
||||
GetEnvironment()->SetBooleanField(env, obj, field, value); \
|
||||
} while(0)
|
||||
|
||||
#define SET_INT_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "I"); \
|
||||
GetEnvironment()->SetIntField(env, obj, field, value); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "I"); \
|
||||
GetEnvironment()->SetIntField(env, obj, field, value); \
|
||||
} while(0)
|
||||
|
||||
#define SET_STRING_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/lang/String;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, GetEnvironment()->NewStringUTF(env, value)); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/lang/String;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, GetEnvironment()->NewStringUTF(env, value)); \
|
||||
} while(0)
|
||||
|
||||
#define SET_OBJECT_FIELD(obj, cls, fieldName, value) do { \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/util/List;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, value); \
|
||||
jfieldID field = GetEnvironment()->GetFieldID(env, cls, #fieldName, "Ljava/util/List;"); \
|
||||
GetEnvironment()->SetObjectField(env, obj, field, value); \
|
||||
} while(0)
|
||||
|
||||
// Macros for creating Java objects
|
||||
#define CREATE_JAVA_OBJECT(className) ({ \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor); \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor); \
|
||||
})
|
||||
|
||||
// Macros for creating ArrayList
|
||||
#define CREATE_ARRAYLIST() ({ \
|
||||
jclass arrayListCls = GetEnvironment()->FindClass(env, "java/util/ArrayList"); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, arrayListCls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, arrayListCls, constructor); \
|
||||
jclass arrayListCls = GetEnvironment()->FindClass(env, "java/util/ArrayList"); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, arrayListCls, "<init>", "()V"); \
|
||||
GetEnvironment()->NewObject(env, arrayListCls, constructor); \
|
||||
})
|
||||
|
||||
// Macros for adding elements to an ArrayList
|
||||
#define ADD_TO_LIST(list, item) do { \
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list); \
|
||||
jmethodID addMethod = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z"); \
|
||||
GetEnvironment()->CallBooleanMethod(env, list, addMethod, item); \
|
||||
jclass cls = GetEnvironment()->GetObjectClass(env, list); \
|
||||
jmethodID addMethod = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z"); \
|
||||
GetEnvironment()->CallBooleanMethod(env, list, addMethod, item); \
|
||||
} while(0)
|
||||
|
||||
// Macros for creating Java objects with parameter constructors
|
||||
#define CREATE_JAVA_OBJECT_WITH_PARAMS(className, signature, ...) ({ \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", signature); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor, __VA_ARGS__); \
|
||||
jclass cls = GetEnvironment()->FindClass(env, className); \
|
||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", signature); \
|
||||
GetEnvironment()->NewObject(env, cls, constructor, __VA_ARGS__); \
|
||||
})
|
||||
|
||||
#ifdef NDEBUG
|
||||
|
||||
@@ -16,17 +16,15 @@ object Natives {
|
||||
// 10946: add capabilities
|
||||
// 10977: change groups_count and groups to avoid overflow write
|
||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.8"
|
||||
|
||||
// 11640: Support query working mode, LKM or GKI
|
||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||
// 12143: breaking: new supercall impl
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 12143
|
||||
|
||||
// 12040: Support disable sucompat mode
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
|
||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||
|
||||
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.8"
|
||||
|
||||
const val MINIMAL_SUPPORTED_KPM = 12800
|
||||
|
||||
const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215
|
||||
@@ -66,8 +64,6 @@ object Natives {
|
||||
System.loadLibrary("kernelsu")
|
||||
}
|
||||
|
||||
// become root manager, return true if success.
|
||||
external fun becomeManager(pkg: String?): Boolean
|
||||
val version: Int
|
||||
external get
|
||||
|
||||
@@ -81,6 +77,9 @@ object Natives {
|
||||
val isLkmMode: Boolean
|
||||
external get
|
||||
|
||||
val isManager: Boolean
|
||||
external get
|
||||
|
||||
external fun uidShouldUmount(uid: Int): Boolean
|
||||
|
||||
/**
|
||||
@@ -99,6 +98,7 @@ object Natives {
|
||||
*/
|
||||
external fun isSuEnabled(): Boolean
|
||||
external fun setSuEnabled(enabled: Boolean): Boolean
|
||||
external fun grantRoot(): Boolean
|
||||
external fun isKPMEnabled(): Boolean
|
||||
external fun getHookType(): String
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val isManager = Natives.becomeManager(packageName)
|
||||
val isManager = Natives.isManager
|
||||
if (isManager) {
|
||||
install()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.ramcosta.composedestinations.spec.RouteOrDirection
|
||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.MainActivity
|
||||
import com.sukisu.ultra.ui.activity.util.*
|
||||
import com.sukisu.ultra.ui.activity.util.AppData.getKpmVersionUse
|
||||
@@ -29,7 +28,7 @@ import com.sukisu.ultra.ui.util.*
|
||||
@Composable
|
||||
fun BottomBar(navController: NavHostController) {
|
||||
val navigator = navController.rememberDestinationsNavigator()
|
||||
val isFullFeatured = AppData.isFullFeatured(ksuApp.packageName)
|
||||
val isFullFeatured = AppData.isFullFeatured()
|
||||
val kpmVersion = getKpmVersionUse()
|
||||
val cardColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
val activity = LocalContext.current as MainActivity
|
||||
|
||||
@@ -175,8 +175,8 @@ object AppData {
|
||||
/**
|
||||
* 检查是否是完整功能模式
|
||||
*/
|
||||
fun isFullFeatured(packageName: String): Boolean {
|
||||
val isManager = Natives.becomeManager(packageName)
|
||||
fun isFullFeatured(): Boolean {
|
||||
val isManager = Natives.isManager
|
||||
return isManager && !Natives.requireNewKernel() && rootAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.sukisu.ultra.ksuApp
|
||||
fun KsuIsValid(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val isManager = Natives.isManager
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
if (ksuVersion != null) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
@@ -48,8 +49,9 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.LocalDateTime
|
||||
@@ -179,127 +181,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
// UID 扫描开关
|
||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_UID_SCANNER) {
|
||||
var uidAutoScanEnabled by rememberSaveable {
|
||||
mutableStateOf(prefs.getBoolean("uid_auto_scan", false))
|
||||
}
|
||||
|
||||
var uidMultiUserScanEnabled by rememberSaveable {
|
||||
mutableStateOf(prefs.getBoolean("uid_multi_user_scan", false))
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
uidAutoScanEnabled = Natives.isUidScannerEnabled()
|
||||
uidMultiUserScanEnabled = getUidMultiUserScan()
|
||||
|
||||
prefs.edit {
|
||||
putBoolean("uid_auto_scan", uidAutoScanEnabled)
|
||||
putBoolean("uid_multi_user_scan", uidMultiUserScanEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
// 用户态扫描应用列表开关
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Scanner,
|
||||
title = stringResource(R.string.uid_auto_scan_title),
|
||||
summary = stringResource(R.string.uid_auto_scan_summary),
|
||||
checked = uidAutoScanEnabled,
|
||||
onCheckedChange = { enabled ->
|
||||
scope.launch {
|
||||
try {
|
||||
if (setUidAutoScan(enabled)) {
|
||||
uidAutoScanEnabled = enabled
|
||||
prefs.edit { putBoolean("uid_auto_scan", enabled) }
|
||||
|
||||
if (!enabled) {
|
||||
uidMultiUserScanEnabled = false
|
||||
prefs.edit { putBoolean("uid_multi_user_scan", false) }
|
||||
}
|
||||
} else {
|
||||
snackBarHost.showSnackbar(context.getString(R.string.uid_scanner_setting_failed))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
snackBarHost.showSnackbar(
|
||||
context.getString(
|
||||
R.string.uid_scanner_setting_error,
|
||||
e.message ?: ""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 多用户应用扫描开关 - 仅在启用用户态扫描时显示
|
||||
AnimatedVisibility(
|
||||
visible = uidAutoScanEnabled,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Groups,
|
||||
title = stringResource(R.string.uid_multi_user_scan_title),
|
||||
summary = stringResource(R.string.uid_multi_user_scan_summary),
|
||||
checked = uidMultiUserScanEnabled,
|
||||
onCheckedChange = { enabled ->
|
||||
scope.launch {
|
||||
try {
|
||||
if (setUidMultiUserScan(enabled)) {
|
||||
uidMultiUserScanEnabled = enabled
|
||||
prefs.edit { putBoolean("uid_multi_user_scan", enabled) }
|
||||
} else {
|
||||
snackBarHost.showSnackbar(context.getString(R.string.uid_scanner_setting_failed))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
snackBarHost.showSnackbar(
|
||||
context.getString(
|
||||
R.string.uid_scanner_setting_error,
|
||||
e.message ?: ""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
// 清理运行环境
|
||||
AnimatedVisibility(
|
||||
visible = uidAutoScanEnabled,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
SettingItem(
|
||||
icon = Icons.Filled.CleaningServices,
|
||||
title = stringResource(R.string.clean_runtime_environment),
|
||||
summary = stringResource(R.string.clean_runtime_environment_summary),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
val result = confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.clean_runtime_environment),
|
||||
content = context.getString(R.string.clean_runtime_environment_confirm)
|
||||
)
|
||||
if (result == ConfirmResult.Confirmed) {
|
||||
val cleanResult = cleanRuntimeEnvironment()
|
||||
if (cleanResult) {
|
||||
uidAutoScanEnabled = false
|
||||
prefs.edit { putBoolean("uid_auto_scan", false) }
|
||||
|
||||
uidMultiUserScanEnabled = false
|
||||
prefs.edit { putBoolean("uid_multi_user_scan", false) }
|
||||
|
||||
Natives.setUidScannerEnabled(false)
|
||||
|
||||
snackBarHost.showSnackbar(context.getString(R.string.clean_runtime_environment_success))
|
||||
} else {
|
||||
snackBarHost.showSnackbar(context.getString(R.string.clean_runtime_environment_failed))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
UidScannerSection(prefs, snackBarHost, scope, context)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -453,7 +335,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
|
||||
val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
|
||||
val lkmMode = Natives.isLkmMode
|
||||
if (lkmMode) {
|
||||
UninstallItem(navigator) {
|
||||
loadingDialog.withLoading(it)
|
||||
@@ -481,24 +363,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
fun cleanRuntimeEnvironment(): Boolean {
|
||||
val shell = getRootShell()
|
||||
return try {
|
||||
try {
|
||||
ShellUtils.fastCmd(shell, "/data/adb/uid_scanner stop")
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/misc/user_uid")
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/uid_scanner")
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/ksu/bin/user_uid")
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/service.d/uid_scanner.sh")
|
||||
Natives.clearUidScannerEnvironment()
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsGroupCard(
|
||||
title: String,
|
||||
@@ -961,3 +825,124 @@ private fun TopBar(
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
|
||||
/**
|
||||
@@ -34,7 +35,7 @@ private fun getKsuDaemonPath(): String {
|
||||
}
|
||||
|
||||
object KsuCli {
|
||||
val SHELL: Shell = createRootShell()
|
||||
var SHELL: Shell = createRootShell()
|
||||
val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
|
||||
}
|
||||
|
||||
@@ -578,11 +579,10 @@ fun getUidScannerDaemonPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libuid_scanner.so"
|
||||
}
|
||||
|
||||
private const val targetPath = "/data/adb/uid_scanner"
|
||||
fun ensureUidScannerExecutable(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val uidScannerPath = getUidScannerDaemonPath()
|
||||
val targetPath = "/data/adb/uid_scanner"
|
||||
|
||||
if (!ShellUtils.fastCmdResult(shell, "test -f $targetPath")) {
|
||||
val copyResult = ShellUtils.fastCmdResult(shell, "cp $uidScannerPath $targetPath")
|
||||
if (!copyResult) {
|
||||
@@ -593,7 +593,6 @@ fun ensureUidScannerExecutable(): Boolean {
|
||||
val result = ShellUtils.fastCmdResult(shell, "chmod 755 $targetPath")
|
||||
return result
|
||||
}
|
||||
private const val targetPath = "/data/adb/uid_scanner"
|
||||
|
||||
fun setUidAutoScan(enabled: Boolean): Boolean {
|
||||
val shell = getRootShell()
|
||||
@@ -634,3 +633,30 @@ fun getUidMultiUserScan(): Boolean {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun cleanRuntimeEnvironment(): Boolean {
|
||||
val shell = getRootShell()
|
||||
return try {
|
||||
try {
|
||||
ShellUtils.fastCmd(shell, "/data/adb/uid_scanner stop")
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/misc/user_uid")
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/uid_scanner")
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/ksu/bin/user_uid")
|
||||
ShellUtils.fastCmdResult(shell, "rm -rf /data/adb/service.d/uid_scanner.sh")
|
||||
Natives.clearUidScannerEnvironment()
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun readUidScannerFile(): Boolean {
|
||||
val shell = getRootShell()
|
||||
return try {
|
||||
ShellUtils.fastCmd(shell, "cat /data/adb/ksu/.uid_scanner").trim() == "1"
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,18 +121,12 @@ class HomeViewModel : ViewModel() {
|
||||
try {
|
||||
val kernelVersion = getKernelVersion()
|
||||
val isManager = try {
|
||||
Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra")
|
||||
Natives.isManager
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
val ksuVersion = if (isManager) {
|
||||
try {
|
||||
Natives.version
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
} else null
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
val fullVersion = try {
|
||||
Natives.getFullVersion()
|
||||
@@ -163,13 +157,7 @@ class HomeViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
val lkmMode = ksuVersion?.let {
|
||||
try {
|
||||
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) {
|
||||
Natives.isLkmMode
|
||||
} else null
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
if (kernelVersion.isGKI()) Natives.isLkmMode else null
|
||||
}
|
||||
|
||||
val isRootAvailable = try {
|
||||
@@ -346,7 +334,7 @@ class HomeViewModel : ViewModel() {
|
||||
try {
|
||||
// 检查KSU状态是否发生变化
|
||||
val currentKsuVersion = try {
|
||||
if (Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra")) {
|
||||
if (Natives.isManager) {
|
||||
Natives.version
|
||||
} else null
|
||||
} catch (_: Exception) {
|
||||
|
||||
@@ -17,7 +17,7 @@ class KsuLibSuProvider : IProvider {
|
||||
|
||||
override fun isAvailable() = true
|
||||
|
||||
override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName)
|
||||
override suspend fun isAuthorized() = Natives.isManager
|
||||
|
||||
private val serviceIntent
|
||||
get() = PlatformIntent(
|
||||
|
||||
@@ -1,10 +1,133 @@
|
||||
const EVENT_POST_FS_DATA: u64 = 1;
|
||||
const EVENT_BOOT_COMPLETED: u64 = 2;
|
||||
const EVENT_MODULE_MOUNTED: u64 = 3;
|
||||
use std::fs;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
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"))]
|
||||
pub fn get_version() -> i32 {
|
||||
rustix::process::ksu_get_version()
|
||||
get_info().version as i32
|
||||
}
|
||||
|
||||
#[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"))]
|
||||
fn report_event(event: u64) {
|
||||
rustix::process::ksu_report_event(event)
|
||||
pub fn grant_root() -> std::io::Result<()> {
|
||||
ksuctl(KSU_IOCTL_GRANT_ROOT, std::ptr::null_mut::<u8>())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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"))]
|
||||
pub fn check_kernel_safemode() -> bool {
|
||||
rustix::process::ksu_check_kernel_safemode()
|
||||
fn report_event(event: u32) {
|
||||
let mut cmd = ReportEventCmd { event };
|
||||
let _ = ksuctl(KSU_IOCTL_REPORT_EVENT, &mut cmd as *mut _);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub fn check_kernel_safemode() -> bool {
|
||||
false
|
||||
}
|
||||
fn report_event(_event: u32) {}
|
||||
|
||||
pub fn report_post_fs_data() {
|
||||
report_event(EVENT_POST_FS_DATA);
|
||||
@@ -41,3 +166,21 @@ pub fn report_boot_complete() {
|
||||
pub fn report_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(())
|
||||
}
|
||||
@@ -20,7 +20,7 @@ fn parse_single_word(input: &str) -> IResult<&str, &str> {
|
||||
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, _)) = (
|
||||
tag("{"),
|
||||
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()))
|
||||
}
|
||||
|
||||
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)?;
|
||||
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)?;
|
||||
Ok((input, vec!["*"]))
|
||||
}
|
||||
@@ -43,12 +43,12 @@ fn parse_star<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
|
||||
// 1. a single sepolicy word
|
||||
// 2. { obj1 obj2 obj3 ...}
|
||||
// 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)?;
|
||||
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)?;
|
||||
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()?;
|
||||
|
||||
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.");
|
||||
if strict {
|
||||
return Err(anyhow::anyhow!("apply rule {:?} failed.", statement));
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn grant_root(global_mnt: bool) -> Result<()> {
|
||||
rustix::process::ksu_grant_root()?;
|
||||
crate::ksucalls::grant_root()?;
|
||||
|
||||
let mut command = Command::new("sh");
|
||||
let command = unsafe {
|
||||
|
||||
Reference in New Issue
Block a user