new supercall impl (#511)

* refactor: replace throne tracker with ksud token

* use snprintf

* refactor: new supercall impl

- Import the sukisu command

* disable seccomp for supercall users

* kernel: fmt clear

* kernel: Enable macro protection for sulog

- Only enabled on kernel versions greater than 5.10.245

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

* debug mode

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

* kernel: Simplified manual SU command processing for code

* kernel: replace renameat hook with fsnotify

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

This reverts commit aa2cbbf9cd.

* kernel: fix compile

* kernel: fix compile below 6.0

* Fix compile err; Add become_manager

* kernel: install fd for manager automaticlly

- extend to import the corresponding command

* manager: new supercall impl

* temp changes for ksud

* ksud: fix compile

* fix wrong opcode

* kernel: fix compile

* kernel: Fixed hook type and KPM status retrieval errors

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

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

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

* ksud: fix cargo check

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

- kernel: fix spin lock mutual

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

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

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

* seccomp: allow reboot

* use u32

* update clang-format

* 4 spaces save the world

* ksud: Fix build on macOS

* manager: bump minimal supported kernel.

- When get_hook_type is empty, display “Unknown”.


* Fix ksud build (#2841)

* try fix ksud

* fix for macos

* remove any

* Fix ksud build, take 3

* try fix allowlist

* bring lsm hook back

* fix: a lot again

* Fix ksud build, take 4 (#2846)

Remove init_driver_fd function for non-linux/android targets

* manager: Return to the native method via KSUd installation

* Merge with susfs-mian format

---------

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

1
.gitignore vendored
View File

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

View File

@@ -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
...

View File

@@ -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

View File

@@ -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

View File

@@ -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));

View File

@@ -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(&current_dynamic_key.size, &current_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(&current_dynamic_key.size, &current_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

View File

@@ -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"

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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

View File

@@ -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
}

View File

@@ -48,7 +48,7 @@ static inline __maybe_unused size_t list_count_nodes(const struct list_head *hea
list_for_each(pos, head)
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

View File

@@ -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);

View File

@@ -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

View File

@@ -16,7 +16,7 @@ extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
void ksu_trace_execveat_hook_callback(void *data, int *fd, struct filename **filename_ptr,
void *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

View File

@@ -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 */

File diff suppressed because it is too large Load Diff

View File

@@ -13,30 +13,33 @@ extern void ksu_add_manager(uid_t uid, int signature_index);
extern void ksu_remove_manager(uid_t uid);
extern 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

View File

@@ -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 */

View File

@@ -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");

View File

@@ -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
View File

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

View File

@@ -18,119 +18,119 @@
static struct policydb *get_policydb(void)
{
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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -29,312 +29,312 @@ static bool ksu_sucompat_hook_state __read_mostly = true;
static void __user *userspace_stack_buffer(const void *d, size_t len)
{
/* 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
}

View File

@@ -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

View File

@@ -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
View File

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

142
kernel/supercalls.h Normal file
View File

@@ -0,0 +1,142 @@
#ifndef __KSU_H_SUPERCALLS
#define __KSU_H_SUPERCALLS
#include <linux/types.h>
#include <linux/ioctl.h>
#include "ksu.h"
// Magic numbers for reboot hook to install fd
#define KSU_INSTALL_MAGIC1 0xDEADBEEF
#define KSU_INSTALL_MAGIC2 0xCAFEBABE
// Command structures for ioctl
struct ksu_become_daemon_cmd {
__u8 token[65]; // Input: daemon token (null-terminated)
};
struct ksu_get_info_cmd {
__u32 version; // Output: KERNEL_SU_VERSION
__u32 flags; // Output: flags (bit 0: MODULE mode)
};
struct ksu_report_event_cmd {
__u32 event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc.
};
struct ksu_set_sepolicy_cmd {
__u64 cmd; // Input: sepolicy command
__aligned_u64 arg; // Input: sepolicy argument pointer
};
struct ksu_check_safemode_cmd {
__u8 in_safe_mode; // Output: true if in safe mode, false otherwise
};
struct ksu_get_allow_list_cmd {
__u32 uids[128]; // Output: array of allowed/denied UIDs
__u32 count; // Output: number of UIDs in array
__u8 allow; // Input: true for allow list, false for deny list
};
struct ksu_uid_granted_root_cmd {
__u32 uid; // Input: target UID to check
__u8 granted; // Output: true if granted, false otherwise
};
struct ksu_uid_should_umount_cmd {
__u32 uid; // Input: target UID to check
__u8 should_umount; // Output: true if should umount, false otherwise
};
struct ksu_get_manager_uid_cmd {
__u32 uid; // Output: manager UID
};
struct ksu_get_app_profile_cmd {
struct app_profile profile; // Input/Output: app profile structure
};
struct ksu_set_app_profile_cmd {
struct app_profile profile; // Input: app profile structure
};
struct ksu_is_su_enabled_cmd {
__u8 enabled; // Output: true if su compat enabled
};
struct ksu_enable_su_cmd {
__u8 enable; // Input: true to enable, false to disable
};
// Other command structures
struct ksu_get_full_version_cmd {
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
};
struct ksu_hook_type_cmd {
char hook_type[32]; // Output: hook type string
};
struct ksu_enable_kpm_cmd {
__u8 enabled; // Output: true if KPM is enabled
};
struct ksu_dynamic_manager_cmd {
struct dynamic_manager_user_config config; // Input/Output: dynamic manager config
};
struct ksu_get_managers_cmd {
struct manager_list_info manager_info; // Output: manager list information
};
struct ksu_enable_uid_scanner_cmd {
__u32 operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV)
__u32 enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE)
void __user *status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS)
};
// IOCTL command definitions
#define KSU_IOCTL_GRANT_ROOT _IO('K', 1)
#define KSU_IOCTL_GET_INFO _IOR('K', 2, struct ksu_get_info_cmd)
#define KSU_IOCTL_REPORT_EVENT _IOW('K', 3, struct ksu_report_event_cmd)
#define KSU_IOCTL_SET_SEPOLICY _IOWR('K', 4, struct ksu_set_sepolicy_cmd)
#define KSU_IOCTL_CHECK_SAFEMODE _IOR('K', 5, struct ksu_check_safemode_cmd)
#define KSU_IOCTL_GET_ALLOW_LIST _IOWR('K', 6, struct ksu_get_allow_list_cmd)
#define KSU_IOCTL_GET_DENY_LIST _IOWR('K', 7, struct ksu_get_allow_list_cmd)
#define KSU_IOCTL_UID_GRANTED_ROOT _IOWR('K', 8, struct ksu_uid_granted_root_cmd)
#define KSU_IOCTL_UID_SHOULD_UMOUNT _IOWR('K', 9, struct ksu_uid_should_umount_cmd)
#define KSU_IOCTL_GET_MANAGER_UID _IOR('K', 10, struct ksu_get_manager_uid_cmd)
#define KSU_IOCTL_GET_APP_PROFILE _IOWR('K', 11, struct ksu_get_app_profile_cmd)
#define KSU_IOCTL_SET_APP_PROFILE _IOW('K', 12, struct ksu_set_app_profile_cmd)
#define KSU_IOCTL_IS_SU_ENABLED _IOR('K', 13, struct ksu_is_su_enabled_cmd)
#define KSU_IOCTL_ENABLE_SU _IOW('K', 14, struct ksu_enable_su_cmd)
// Other IOCTL command definitions
#define KSU_IOCTL_GET_FULL_VERSION _IOR('K', 100, struct ksu_get_full_version_cmd)
#define KSU_IOCTL_HOOK_TYPE _IOR('K', 101, struct ksu_hook_type_cmd)
#define KSU_IOCTL_ENABLE_KPM _IOR('K', 102, struct ksu_enable_kpm_cmd)
#define KSU_IOCTL_DYNAMIC_MANAGER _IOWR('K', 103, struct ksu_dynamic_manager_cmd)
#define KSU_IOCTL_GET_MANAGERS _IOWR('K', 104, struct ksu_get_managers_cmd)
#define KSU_IOCTL_ENABLE_UID_SCANNER _IOWR('K', 105, struct ksu_enable_uid_scanner_cmd)
// IOCTL handler types
typedef int (*ksu_ioctl_handler_t)(void __user *arg);
typedef bool (*ksu_perm_check_t)(void);
// Permission check functions
bool perm_check_manager(void);
bool perm_check_root(void);
bool perm_check_basic(void);
bool perm_check_all(void);
// IOCTL command mapping
struct ksu_ioctl_cmd_map {
unsigned int cmd;
ksu_ioctl_handler_t handler;
ksu_perm_check_t perm_check; // Permission check function
};
// Install KSU fd to current process
int ksu_install_fd(void);
#endif // __KSU_H_SUPERCALLS

View File

@@ -8,6 +8,7 @@
#include "klog.h"
#include "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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(())
}

View File

@@ -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));

View File

@@ -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 {