22 Commits

Author SHA1 Message Date
ShirkNeko
7b6074cfc3 kernel: Fix when calling iterate_dir() under an encrypted directory (F2FS + file-based encryption), the kernel encountered a NEON/FPSIMD register state error while decrypting filenames.
Error Log :
[ T4681] Call trace:
[ T4681]  fpsimd_save_state+0x4/0x58
[ T4681]  cts_cbc_decrypt+0x268/0x384
[ T4681]  fscrypt_fname_disk_to_usr+0x1dc/0x338
[ T4681]  f2fs_fill_dentries+0x1cc/0x330
[ T4681]  f2fs_readdir+0x1a0/0x3ec
[ T4681]  iterate_dir+0x80/0x170
[ T4681]  scan_user_data_for_uids+0x170/0x560
[ T4681]  throne_tracker_thread+0x68/0x290
2025-09-16 22:36:26 +08:00
ShirkNeko
4e8d699654 sporadic deadlock fix
move to always kthreaded and mitigate sporadic deadlocks on

Co-authored-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
2025-09-16 19:36:47 +08:00
ShirkNeko
60d122c01b kernel: Add support for concurrent scanning of user data apps 2025-09-16 18:16:19 +08:00
ShirkNeko
335ddc4432 kernel: Enhanced user data scanning
Added filesystem type checks to prevent dangerous paths
2025-09-16 17:27:00 +08:00
ShirkNeko
765106c56a kernel: Separate and modularize the user data scanner scan function 2025-09-16 17:04:49 +08:00
ShirkNeko
b685f03a6e kernel: Separate kern_path() and iterate_dir() operations to avoid lock contention. 2025-09-16 15:45:29 +08:00
ShirkNeko
fae301c161 kernel: Remove duplicate #include <linux/list.h> 2025-09-16 15:09:13 +08:00
ShirkNeko
73cd1f2cf3 kernel: Optimizing thread scheduling during user scans
`[   23.379244][ T5074] ufshcd-qcom 1d84000.ufshc: ............. ufs dump complete ..........
[   23.379263][ T5074] dump-reason-buffer-size: 256
[   23.379267][ T5074] dump-reason-pidbuffer:PID: 5074, Process Name: throne_tracker
[   23.379295][ T5074] qcom_q6v5_pas a3380000.remoteproc-soccp: waking SOCCP from panic path
[   23.379455][ T5074] CPU0 next event is 23368000000
[   23.379456][ T5074] CP.rkp_only' to 'true' in property file '/odm/build.prop': SELinux permission check failed
[    1.248057][    T1] init: Do not have permissions to set 'ro.oplus.nfc.support.tee' to 'true' in pro   23.379459][ T5074] CPU5 next event is 23368000000
[   23.3794   1.248059][    T1] init: Do not have permissions to set 'ro.oplus.eid.enable.state' to '1' in property file '/odm/build.prop':l-3d0: snapshot: device is powered off
[   23.589323][ T5074] Kernel Offset: 0x1738a00000 from 0xffffffc080000000
[   23.589325][ T5074] PHYS_OFFSET: 0x80000000
[   23.589326][ T5074] CPU features: 0x000000,00000000,70024f43,95fffea7
[   23.589328][ T5074] Memory Limit: none
[   23.589490][ T5074] mhi mhi_110e_00.01.00: [E][mhi_debug_reg_dump] host pm_state:M2 dev_state:M2 ee:MISSION MODE
[   23.589505][ T5074] mhi mhi_110e_00.01.00: [E][mhi_debug_reg_dump] device ee: MISSION MODE dev_state: M2`

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-09-16 15:00:05 +08:00
ShirkNeko
eb5d8fa770 kernel: Use a cached buffer as an array-based stack to avoid panics caused by overly deep traversal.
INTCAM: no information
       TPU: no information
       TNR: no information
       MFC: no information
        BO: no information
[   4.715484] [I] [DSS] Last AVB: avb_ret=ERROR_VERIFICATION
[   4.715890] [I] [DSS] Last AVB: avb_veritymode=enforcing
[   4.716289] [I] [DSS] Last AVB: avb_error_parts=boot
[   4.717085] [I] [LNXDBG] build info set by kernel
RAMDUMP_MSG.txt:
  reset message: KP: kernel stack overflow
  UUID: e2faff80-83ea-c240-ac75-d7b8a528c892
  last kernel version: 6.1.134-android14-11-g23e556daebf3-ab13800907
  aosp kernel version: 6.1.145-android14-11-g8d713f9e8e7b-ab13202960
  build: google/shiba/shiba:16/BP3A.250905.014/13873947:user/release-keys
  RST_STAT: 0x1 - CLUSTER0_NONCPU_WDTRESET
  GSA_RESET_STATUS: 0x0 -
  Reboot reason: 0xbaba - Kernel PANIC
  Reboot mode: 0x0 - Normal Boot
[   4.719030] [I] [DSS] -------------------- DSS LOGS END --------------------

Reboot Info:
  RST_STAT: 0x180000 - PIN_RESET | PO_RESET
  GSA_RESET_STATUS: 0x0 -
  Reboot reason: 0xbaba - Kernel PANIC
  Reboot mode: 0x0 - Normal Boot

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-09-16 00:53:48 +08:00
ShirkNeko
a197600cb5 kernel: Add optional full-user scanning capability using prctl 2025-09-15 19:14:55 +08:00
ShirkNeko
39c1b45257 Sync with latest official KernelSU commit 4d3560b12bec5f238fe11f908a246f0ac97e9c27
Co-authored-by: simonpunk <simonpunk2016@gmail.com>
2025-09-15 15:54:46 +08:00
Wang Han
4be4758334 Unmount isolated process which forks from zygote unconditionally (#2747)
Rethink about this patch, isolated process which directly forks from
zygote is just like normal app, so there is no reason apps won't crash
but Isolated process will. Also zygote reopens fd before actually fork,
so it should be fine.

This reverts commit 2a1741de96a789957555053cf5a397cbef1eb3e4.
2025-09-15 15:19:00 +08:00
ShirkNeko
6892a23c6a kernel: Fixed an issue where scanning could cause the application to freeze. 2025-09-14 21:49:26 +08:00
ShirkNeko
f8abf097d7 kernel: Improve dynamic manager functions and logging 2025-09-14 19:31:21 +08:00
ShirkNeko
fb2ad3ec7b kernel: keep legacy throne tracker as an option
kernel: keep legacy throne tracker as an option

This change restores older throne tracker that uses packages.list scanning
to track app UIDs. It's intended for ultra-legacy Linux 3.X kernels that
experience deadlocks or crashes with the newer implementation due to issues
in user_data_actor().

We have to remember that the whole iterate_dir, and filldir subsystem is way
different on 3.X.

Changes:
- CONFIG_KSU_THRONE_TRACKER_LEGACY in Kconfig
- conditional compilation in Makefile
- throne_tracker_legacy.c which keeps the old implementation

Enable this option if newer throne tracker (tiann #2757crashes on you.)

Co-authored-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
2025-09-14 17:38:25 +08:00
ShirkNeko
debd7d5a01 kernel: throne_tracker: offload to kthread (tiann[#2632](https://github.com/SukiSU-Ultra/SukiSU-Ultra/issues/2632))
Run throne_tracker() in kthread instead of blocking the caller.
Prevents full lockup during installation and removing the manager.

First run remains synchronous for compatibility purposes (FDE, FBEv1, FBEv2)

Features:
- run track_throne() in a kthread after the first synchronous run
- prevent duplicate thread creation with a single-instance check
- spinlock-on-d_lock based polling adressing possible race conditions.

Race conditions adressed
- single instance kthread lock, smp_mb()
- is_manager_apk, apk, spinlock-on-d_lock based polling

This is a squash of:
https://github.com/tiann/KernelSU/pull/2632

Rebased on top of
https://github.com/tiann/KernelSU/pull/2757

Original skeleton based on:
`kernelsu: move throne_tracker() to kthread`
`kernelsu: check locking before accessing files and dirs during searching manager`
`kernelsu: look for manager UID in /data/system/packages.list, not /data/system/packages.list.tmp`
0b05e927...8783badd

Co-Authored-By: backslashxx <118538522+backslashxx@users.noreply.github.com>
Co-Authored-By: Yaroslav Zviezda <10716792+acroreiser@users.noreply.github.com>
Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
2025-09-14 11:53:33 +08:00
ShirkNeko
4c3bdcd016 kernel: Switch to using pr_info for printing 2025-09-14 11:25:59 +08:00
ShirkNeko
c5a2e06b94 kernel: Simplify and improve readability 2025-09-14 10:22:40 +08:00
ShirkNeko
307bb67856 Add vfs_getattr compatibility for kernels < 4.14
Co-authored-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-09-14 10:00:14 +08:00
ShirkNeko
63d9bdd9d6 kernel: Use iterate_dir for multi-user traversal instead of a fixed user ID range. 2025-09-13 21:24:40 +08:00
ShirkNeko
eb87c1355b Fixed some minor issues that may have existed 2025-09-13 20:26:44 +08:00
ShirkNeko
316cb79f32 kernel: Remove fallback scan for packages.list
Enhance scan support for active users in /data/user_de

Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
2025-09-12 16:02:51 +08:00
16 changed files with 1682 additions and 490 deletions

View File

@@ -8,6 +8,15 @@ config KSU
To compile as a module, choose M here: the
module will be called kernelsu.
config KSU_THRONE_TRACKER_LEGACY
bool "Use legacy throne tracker (packages.list scanning)"
depends on KSU
default n
help
Use legacy throne tracker that scans packages.list for app UIDs.
This is kept for Ultra-Legacy Linux 3.X kernels which are prone to deadlocks.
Enable this if default scanning deadlocks/crashes on you.
config KSU_DEBUG
bool "KernelSU debug mode"
depends on KSU

View File

@@ -3,7 +3,6 @@ kernelsu-objs += allowlist.o
kernelsu-objs += dynamic_manager.o
kernelsu-objs += apk_sign.o
kernelsu-objs += sucompat.o
kernelsu-objs += throne_tracker.o
kernelsu-objs += core_hook.o
kernelsu-objs += ksud.o
kernelsu-objs += embed_ksud.o
@@ -17,6 +16,14 @@ kernelsu-objs += selinux/selinux.o
kernelsu-objs += selinux/sepolicy.o
kernelsu-objs += selinux/rules.o
ifeq ($(CONFIG_KSU_THRONE_TRACKER_LEGACY),y)
kernelsu-objs += throne_tracker_legacy.o
else
kernelsu-objs += throne_tracker.o
endif
kernelsu-objs += user_data_scanner.o
ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
@@ -124,7 +131,6 @@ ifeq ($(shell grep "ssize_t kernel_write" $(srctree)/fs/read_write.c | grep -q "
ccflags-y += -DKSU_OPTIONAL_KERNEL_WRITE
endif
ifeq ($(shell grep -q "inode_security_struct\s\+\*selinux_inode" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
$(info -- KernelSU: kernel has selinux_inode.)
ccflags-y += -DKSU_OPTIONAL_SELINUX_INODE
endif
ifeq ($(shell grep -q "int\s\+path_umount" $(srctree)/fs/namespace.c; echo $$?),0)
@@ -144,6 +150,12 @@ ifeq ($(shell grep -q "SEC_SELINUX_PORTING_COMMON" $(srctree)/security/selinux/a
ccflags-y += -DSAMSUNG_SELINUX_PORTING
endif
# Check new vfs_getattr()
ifeq ($(shell grep -A1 "^int vfs_getattr" $(srctree)/fs/stat.c | grep -q "query_flags" ; echo $$?),0)
$(info -- KernelSU/compat: new vfs_getattr() found)
ccflags-y += -DKSU_HAS_NEW_VFS_GETATTR
endif
# Custom Signs
ifdef KSU_EXPECTED_SIZE
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
@@ -171,7 +183,7 @@ $(eval SUSFS_VERSION=$(shell cat $(srctree)/include/linux/susfs.h | grep -E '^#d
$(info )
$(info -- SUSFS_VERSION: $(SUSFS_VERSION))
else
$(info -- You have not integrate susfs in your kernel.)
$(info -- You have not integrated susfs in your kernel yet.)
$(info -- Read: https://gitlab.com/simonpunk/susfs4ksu)
endif
# Keep a new line here!! Because someone may append config

View File

@@ -20,7 +20,7 @@
#include "manager.h"
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
#define FILE_FORMAT_VERSION 3 // u32
#define FILE_FORMAT_VERSION 4 // u32
#define KSU_APP_PROFILE_PRESERVE_UID 9999 // NOBODY_UID
#define KSU_DEFAULT_SELINUX_DOMAIN "u:r:su:s0"
@@ -34,6 +34,8 @@ static struct non_root_profile default_non_root_profile;
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE);
static int allow_list_pointer __read_mostly = 0;
bool scan_all_users __read_mostly = false;
static void remove_uid_from_arr(uid_t uid)
{
int *temp_arr;
@@ -351,10 +353,27 @@ bool ksu_get_allow_list(int *array, int *length, bool allow)
return true;
}
bool ksu_set_scan_all_users(bool enabled)
{
mutex_lock(&allowlist_mutex);
scan_all_users = enabled;
mutex_unlock(&allowlist_mutex);
pr_info("scan_all_users set to: %d\n", enabled);
return persistent_allow_list();
}
bool ksu_get_scan_all_users(void)
{
return scan_all_users;
}
static void do_save_allow_list(struct work_struct *work)
{
u32 magic = FILE_MAGIC;
u32 version = FILE_FORMAT_VERSION;
u32 scan_setting = scan_all_users ? 1 : 0;
struct perm_data *p = NULL;
struct list_head *pos = NULL;
loff_t off = 0;
@@ -379,6 +398,13 @@ static void do_save_allow_list(struct work_struct *work)
goto exit;
}
// Save scan_all_users settings
if (ksu_kernel_write_compat(fp, &scan_setting, sizeof(scan_setting), &off) !=
sizeof(scan_setting)) {
pr_err("save_allow_list write scan_setting 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",
@@ -400,6 +426,7 @@ static void do_load_allow_list(struct work_struct *work)
struct file *fp = NULL;
u32 magic;
u32 version;
u32 scan_setting = 0;
#ifdef CONFIG_KSU_DEBUG
// always allow adb shell by default
@@ -429,6 +456,24 @@ static void do_load_allow_list(struct work_struct *work)
pr_info("allowlist version: %d\n", version);
if (version >= 4) {
if (ksu_kernel_read_compat(fp, &scan_setting, sizeof(scan_setting), &off) !=
sizeof(scan_setting)) {
pr_warn("allowlist read scan_setting failed, using default\n");
scan_setting = 0;
}
mutex_lock(&allowlist_mutex);
scan_all_users = (scan_setting != 0);
mutex_unlock(&allowlist_mutex);
pr_info("loaded scan_all_users: %d\n", scan_all_users);
} else {
mutex_lock(&allowlist_mutex);
scan_all_users = false;
mutex_unlock(&allowlist_mutex);
}
while (true) {
struct app_profile profile;

View File

@@ -24,4 +24,9 @@ bool ksu_set_app_profile(struct app_profile *, bool persist);
bool ksu_uid_should_umount(uid_t uid);
struct root_profile *ksu_get_root_profile(uid_t uid);
bool ksu_set_scan_all_users(bool enabled);
bool ksu_get_scan_all_users(void);
extern bool scan_all_users __read_mostly;
#endif

View File

@@ -15,10 +15,10 @@
#endif
#include "apk_sign.h"
#include "dynamic_manager.h"
#include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h"
#include "manager_sign.h"
#include "dynamic_manager.h"
struct sdesc {
struct shash_desc shash;
@@ -88,19 +88,16 @@ static int ksu_sha256(const unsigned char *data, unsigned int datalen,
return ret;
}
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;
unsigned int dynamic_size = 0;
const char *dynamic_hash = NULL;
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(&dynamic_size, &dynamic_hash)) {
return false;
}
if (size4 != current_dynamic_key.size) {
if (size4 != dynamic_size) {
return false;
}
@@ -123,9 +120,9 @@ static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *mat
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(DM_LOG_PREFIX "dynamic sign verified sha256: %s, expected: %s, (index: %d)\n", hash_str, dynamic_hash, DYNAMIC_SIGN_INDEX);
if (strcmp(current_dynamic_key.hash, hash_str) == 0) {
if (strcmp(dynamic_hash, hash_str) == 0) {
if (matched_index) {
*matched_index = DYNAMIC_SIGN_INDEX;
}
@@ -255,6 +252,39 @@ static bool has_v1_signature_file(struct file *fp)
return false;
}
/*
* small helper to check if lock is held
* false - file is stable
* true - file is being deleted/renamed
* possibly optional
*
*/
bool is_lock_held(const char *path)
{
struct path kpath;
// kern_path returns 0 on success
if (kern_path(path, 0, &kpath))
return true;
// just being defensive
if (!kpath.dentry) {
path_put(&kpath);
return true;
}
if (!spin_trylock(&kpath.dentry->d_lock)) {
pr_info("%s: lock held, bail out!\n", __func__);
path_put(&kpath);
return true;
}
// we hold it ourselves here!
spin_unlock(&kpath.dentry->d_lock);
path_put(&kpath);
return false;
}
static __always_inline bool check_v2_signature(char *path, bool check_multi_manager, int *signature_index)
{
unsigned char buffer[0x11] = { 0 };
@@ -269,6 +299,23 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
bool v3_1_signing_exist = false;
int matched_index = -1;
int i;
struct path kpath;
if (kern_path(path, 0, &kpath))
return false;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
if (inode_is_locked(kpath.dentry->d_inode))
#else
if (mutex_is_locked(&kpath.dentry->d_inode->i_mutex))
#endif
{
pr_info("%s: inode is locked for %s\n", __func__, path);
path_put(&kpath);
return false;
}
path_put(&kpath);
struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("open %s error.\n", path);
@@ -384,7 +431,7 @@ clean:
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);
pr_info("[ApkSign] multi-manager APK detected (signature_index=%d)\n", matched_index);
return true;
}
return false;
@@ -422,10 +469,42 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
bool is_manager_apk(char *path)
{
int tries = 0;
while (tries++ < 10) {
if (!is_lock_held(path))
break;
pr_info("%s: waiting for %s\n", __func__, path);
msleep(100);
}
// let it go, if retry fails, check_v2_signature will fail to open it anyway
if (tries == 10) {
pr_info("%s: timeout for %s\n", __func__, path);
return false;
}
return check_v2_signature(path, false, NULL);
}
bool is_dynamic_manager_apk(char *path, int *signature_index)
{
int tries = 0;
while (tries++ < 10) {
if (!is_lock_held(path))
break;
pr_info("%s: waiting for %s\n", __func__, path);
msleep(100);
}
// let it go, if retry fails, check_v2_signature will fail to open it anyway
if (tries == 10) {
pr_info("%s: timeout for %s\n", __func__, path);
return false;
}
return check_v2_signature(path, true, signature_index);
}

View File

@@ -147,7 +147,7 @@ static inline bool is_allow_su(void)
return ksu_is_allow_uid(current_uid().val);
}
static inline bool is_unsupported_uid(uid_t uid)
static inline bool is_unsupported_app_uid(uid_t uid)
{
#define LAST_APPLICATION_UID 19999
uid_t appid = uid % 100000;
@@ -228,11 +228,11 @@ static void disable_seccomp(struct task_struct *tsk)
#endif
}
void escape_to_root(void)
void escape_to_root(bool do_check_first)
{
struct cred *newcreds;
if (current_euid().val == 0) {
if (do_check_first && current_euid().val == 0) {
pr_warn("Already root, don't escape!\n");
return;
}
@@ -392,7 +392,7 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
if (arg2 == CMD_GRANT_ROOT) {
if (is_allow_su()) {
pr_info("allow root for: %d\n", current_uid().val);
escape_to_root();
escape_to_root(true);
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("grant_root: prctl reply error\n");
}
@@ -490,12 +490,12 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
switch (arg3) {
case EVENT_POST_FS_DATA: {
static bool post_fs_data_lock = false;
#ifdef CONFIG_KSU_SUSFS
susfs_on_post_fs_data();
#endif
if (!post_fs_data_lock) {
post_fs_data_lock = true;
pr_info("post-fs-data triggered\n");
#ifdef CONFIG_KSU_SUSFS
susfs_on_post_fs_data();
#endif
on_post_fs_data();
// Initializing Dynamic Signatures
ksu_dynamic_manager_init();
@@ -588,6 +588,34 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0;
}
if (arg2 == CMD_SCAN_ALL_USERS) {
if (!from_root && !from_manager) {
return 0;
}
// Get or Set scan_all_users
if (arg3 == 0) {
bool current_state = ksu_get_scan_all_users();
if (copy_to_user((void __user *)arg4, &current_state, sizeof(current_state))) {
pr_err("scan_all_users: copy current state failed\n");
return 0;
}
} else {
// Set new state (arg3 = 1: Enable, arg3 = 2: Disable)
bool new_state = (arg3 == 1);
if (ksu_set_scan_all_users(new_state)) {
pr_info("scan_all_users set to: %d\n", new_state);
} else {
pr_err("Failed to set scan_all_users to: %d\n", new_state);
return 0;
}
}
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("scan_all_users: prctl reply error\n");
}
return 0;
}
#ifdef CONFIG_KPM
// ADD: 添加KPM模块控制
if(sukisu_is_kpm_control_code(arg2)) {
@@ -1119,14 +1147,18 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0;
}
static bool is_appuid(kuid_t uid)
static bool is_non_appuid(kuid_t uid)
{
#define PER_USER_RANGE 100000
#define FIRST_APPLICATION_UID 10000
#define LAST_APPLICATION_UID 19999
uid_t appid = uid.val % PER_USER_RANGE;
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
return appid < FIRST_APPLICATION_UID;
}
static inline bool is_some_system_uid(uid_t uid)
{
return uid >= 1000 && uid < 10000;
}
static bool should_umount(struct path *path)
@@ -1231,11 +1263,11 @@ static void try_umount(const char *mnt, bool check_mnt, int flags)
void susfs_try_umount_all(uid_t uid) {
susfs_try_umount(uid);
/* For Legacy KSU only */
try_umount("/odm", true, 0, uid);
try_umount("/system", true, 0, uid);
try_umount("/system_ext", true, 0, uid);
try_umount("/vendor", true, 0, uid);
try_umount("/product", true, 0, uid);
try_umount("/odm", true, 0, uid);
// - For '/data/adb/modules' we pass 'false' here because it is a loop device that we can't determine whether
// its dev_name is KSU or not, and it is safe to just umount it if it is really a mountpoint
try_umount("/data/adb/modules", false, MNT_DETACH, uid);
@@ -1273,80 +1305,52 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
}
#ifdef CONFIG_KSU_SUSFS
// check if current process is zygote
bool is_zygote_child = susfs_is_sid_equal(old->security, susfs_zygote_sid);
#endif // #ifdef CONFIG_KSU_SUSFS
if (likely(is_zygote_child)) {
// if spawned process is non user app process
if (unlikely(new_uid.val < 10000 && new_uid.val >= 1000)) {
#ifdef CONFIG_KSU_SUSFS_SUS_SU
// set flag if zygote spawned system process is allowed for root access
if (!ksu_is_allow_uid(new_uid.val)) {
task_lock(current);
susfs_set_current_proc_su_not_allowed();
task_unlock(current);
}
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
// umount for the system process if path DATA_ADB_UMOUNT_FOR_ZYGOTE_SYSTEM_PROCESS exists
if (susfs_is_umount_for_zygote_system_process_enabled) {
goto out_try_umount;
}
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
if (is_some_system_uid(new_uid.val) && is_zygote_child) {
if (ksu_is_allow_uid(new_uid.val)) {
return 0;
}
#ifdef CONFIG_KSU_SUSFS
// - here we check if uid is a isolated service spawned by zygote directly
// - Apps that do not use "useAppZyogte" to start a isolated service will be directly
// spawned by zygote which KSU will ignore it by default, the only fix for now is to
// force a umount for those uid
// - Therefore make sure your root app doesn't use isolated service for root access
// - Kudos to ThePedroo, the author and maintainer of Rezygisk for finding and reporting
// the detection, really big helps here!
else if (new_uid.val >= 90000 && new_uid.val < 1000000) {
task_lock(current);
susfs_set_current_non_root_user_app_proc();
#ifdef CONFIG_KSU_SUSFS_SUS_SU
susfs_set_current_proc_su_not_allowed();
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU
task_unlock(current);
#ifdef CONFIG_KSU_SUSFS_SUS_PATH
susfs_run_sus_path_loop(new_uid.val);
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_PATH
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
if (susfs_is_umount_for_zygote_iso_service_enabled) {
goto out_susfs_try_umount_all;
}
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
}
}
#endif // #ifdef CONFIG_KSU_SUSFS
if (!is_appuid(new_uid) || is_unsupported_uid(new_uid.val)) {
// pr_info("handle setuid ignore non application or isolated uid: %d\n", new_uid.val);
return 0;
}
if (ksu_is_allow_uid(new_uid.val)) {
// pr_info("handle setuid ignore allowed application: %d\n", new_uid.val);
return 0;
}
#ifdef CONFIG_KSU_SUSFS
else {
task_lock(current);
susfs_set_current_non_root_user_app_proc();
#ifdef CONFIG_KSU_SUSFS_SUS_SU
susfs_set_current_proc_su_not_allowed();
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU
task_unlock(current);
#ifdef CONFIG_KSU_SUSFS_SUS_PATH
susfs_run_sus_path_loop(new_uid.val);
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_PATH
if (susfs_is_umount_for_zygote_system_process_enabled) {
goto do_umount;
}
}
#endif // #ifdef CONFIG_KSU_SUSFS
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
out_try_umount:
if (is_non_appuid(new_uid)) {
#ifdef CONFIG_KSU_DEBUG
pr_info("handle setuid ignore non application uid: %d\n", new_uid.val);
#endif
return 0;
}
// isolated process may be directly forked from zygote, always unmount
if (is_unsupported_app_uid(new_uid.val)) {
#ifdef CONFIG_KSU_DEBUG
pr_info("handle umount for unsupported application uid: %d\n", new_uid.val);
#endif
#ifdef CONFIG_KSU_SUSFS
susfs_set_current_non_root_user_app_proc();
#endif // #ifdef CONFIG_KSU_SUSFS
#ifdef CONFIG_KSU_SUSFS_SUS_SU
susfs_set_current_proc_su_not_allowed();
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU
#ifdef CONFIG_KSU_SUSFS_SUS_PATH
susfs_run_sus_path_loop(new_uid.val);
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_PATH
goto do_umount;
}
if (ksu_is_allow_uid(new_uid.val)) {
#ifdef CONFIG_KSU_DEBUG
pr_info("handle setuid ignore allowed application: %d\n", new_uid.val);
#endif
return 0;
}
if (!ksu_uid_should_umount(new_uid.val)) {
return 0;
} else {
@@ -1354,26 +1358,42 @@ out_try_umount:
pr_info("uid: %d should not umount!\n", current_uid().val);
#endif
}
#ifndef CONFIG_KSU_SUSFS
// check old process's selinux context, if it is not zygote, ignore it!
// because some su apps may setuid to untrusted_app but they are in global mount namespace
// when we umount for such process, that is a disaster!
bool is_zygote_child = is_zygote(old->security);
#endif
do_umount:
// check old process's selinux context, if it is not zygote, ignore it!
// because some su apps may setuid to untrusted_app but they are in global mount namespace
// when we umount for such process, that is a disaster!
#ifdef CONFIG_KSU_SUSFS
if (!is_zygote_child) {
#else
if (!is_zygote(old->security)) {
#endif
pr_info("handle umount ignore non zygote child: %d\n",
current->pid);
return 0;
}
#ifdef CONFIG_KSU_DEBUG
// umount the target mnt
pr_info("handle umount for uid: %d, pid: %d\n", new_uid.val,
current->pid);
#endif
#ifdef CONFIG_KSU_SUSFS
susfs_set_current_non_root_user_app_proc();
#endif
#ifdef CONFIG_KSU_SUSFS_SUS_SU
susfs_set_current_proc_su_not_allowed();
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_SU
#ifdef CONFIG_KSU_SUSFS_SUS_PATH
susfs_run_sus_path_loop(new_uid.val);
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_PATH
#ifdef CONFIG_KSU_SUSFS_TRY_UMOUNT
out_susfs_try_umount_all:
// susfs come first, and lastly umount by ksu, make sure umount in reversed order
susfs_try_umount_all(new_uid.val);
#else
// fixme: use `collect_mounts` and `iterate_mount` to iterate all mountpoint and
// filter the mountpoint whose target is `/data/adb`
try_umount("/odm", true, 0);
try_umount("/system", true, 0);
try_umount("/vendor", true, 0);
try_umount("/product", true, 0);

View File

@@ -24,8 +24,8 @@
// Dynamic sign configuration
static struct dynamic_manager_config dynamic_manager = {
.size = 0x300,
.hash = "0000000000000000000000000000000000000000000000000000000000000000",
.size = 0,
.hash = "",
.is_set = 0
};
@@ -45,7 +45,7 @@ bool ksu_is_dynamic_manager_enabled(void)
bool enabled;
spin_lock_irqsave(&dynamic_manager_lock, flags);
enabled = dynamic_manager.is_set;
enabled = dynamic_manager.is_set && dynamic_manager.size > 0 && strlen(dynamic_manager.hash) == 64;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
return enabled;
@@ -57,7 +57,6 @@ void ksu_add_manager(uid_t uid, int signature_index)
int i;
if (!ksu_is_dynamic_manager_enabled()) {
pr_info("Dynamic sign not enabled, skipping multi-manager add\n");
return;
}
@@ -68,7 +67,7 @@ void ksu_add_manager(uid_t uid, int signature_index)
if (active_managers[i].is_active && active_managers[i].uid == uid) {
active_managers[i].signature_index = signature_index;
spin_unlock_irqrestore(&managers_lock, flags);
pr_info("Updated manager uid=%d, signature_index=%d\n", uid, signature_index);
pr_info(DM_LOG_PREFIX "updated manager uid=%d, signature_index=%d\n", uid, signature_index);
return;
}
}
@@ -80,13 +79,13 @@ void ksu_add_manager(uid_t uid, int signature_index)
active_managers[i].signature_index = signature_index;
active_managers[i].is_active = true;
spin_unlock_irqrestore(&managers_lock, flags);
pr_info("Added manager uid=%d, signature_index=%d\n", uid, signature_index);
pr_info(DM_LOG_PREFIX "added manager uid=%d, signature_index=%d\n", uid, signature_index);
return;
}
}
spin_unlock_irqrestore(&managers_lock, flags);
pr_warn("Failed to add manager, no free slots\n");
pr_warn(DM_LOG_PREFIX "failed to add manager, no free slots\n");
}
void ksu_remove_manager(uid_t uid)
@@ -103,7 +102,7 @@ void ksu_remove_manager(uid_t uid)
for (i = 0; i < MAX_MANAGERS; i++) {
if (active_managers[i].is_active && active_managers[i].uid == uid) {
active_managers[i].is_active = false;
pr_info("Removed manager uid=%d\n", uid);
pr_info(DM_LOG_PREFIX "removed manager uid=%d\n", uid);
break;
}
}
@@ -142,7 +141,7 @@ int ksu_get_manager_signature_index(uid_t uid)
// Check traditional manager first
if (ksu_manager_uid != KSU_INVALID_UID && uid == ksu_manager_uid) {
return DYNAMIC_SIGN_INDEX;
return 0;
}
if (!ksu_is_dynamic_manager_enabled()) {
@@ -162,57 +161,32 @@ int ksu_get_manager_signature_index(uid_t uid)
return signature_index;
}
static void clear_dynamic_manager(void)
static void ksu_invalidate_dynamic_managers(void)
{
unsigned long flags;
int i;
bool had_active = false;
spin_lock_irqsave(&managers_lock, flags);
for (i = 0; i < MAX_MANAGERS; i++) {
if (active_managers[i].is_active) {
pr_info("Clearing dynamic manager uid=%d (signature_index=%d) for rescan\n",
active_managers[i].uid, active_managers[i].signature_index);
pr_info(DM_LOG_PREFIX "invalidating dynamic manager uid=%d\n", active_managers[i].uid);
active_managers[i].is_active = false;
had_active = true;
}
}
spin_unlock_irqrestore(&managers_lock, flags);
if (had_active) {
pr_info(DM_LOG_PREFIX "all dynamic managers invalidated\n");
}
}
int ksu_get_active_managers(struct manager_list_info *info)
static void clear_dynamic_manager(void)
{
unsigned long flags;
int i, count = 0;
if (!info) {
return -EINVAL;
}
// Add traditional manager first
if (ksu_manager_uid != KSU_INVALID_UID && count < 2) {
info->managers[count].uid = ksu_manager_uid;
info->managers[count].signature_index = 0;
count++;
}
// Add dynamic managers
if (ksu_is_dynamic_manager_enabled()) {
spin_lock_irqsave(&managers_lock, flags);
for (i = 0; i < MAX_MANAGERS && count < 2; i++) {
if (active_managers[i].is_active) {
info->managers[count].uid = active_managers[i].uid;
info->managers[count].signature_index = active_managers[i].signature_index;
count++;
}
}
spin_unlock_irqrestore(&managers_lock, flags);
}
info->count = count;
return 0;
ksu_invalidate_dynamic_managers();
}
static void do_save_dynamic_manager(struct work_struct *work)
@@ -229,34 +203,23 @@ static void do_save_dynamic_manager(struct work_struct *work)
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
if (!config_to_save.is_set) {
pr_info("Dynamic sign config not set, skipping save\n");
return;
}
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("save_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
pr_err(DM_LOG_PREFIX "save config failed: %ld\n", PTR_ERR(fp));
return;
}
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
pr_err("save_dynamic_manager write magic failed.\n");
goto exit;
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version) ||
ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
pr_err(DM_LOG_PREFIX "save config write failed\n");
} else {
pr_info(DM_LOG_PREFIX "config saved successfully\n");
}
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("save_dynamic_manager write version failed.\n");
goto exit;
}
if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
pr_err("save_dynamic_manager write config failed.\n");
goto exit;
}
pr_info("Dynamic sign config saved successfully\n");
exit:
filp_close(fp, 0);
}
@@ -273,45 +236,36 @@ static void do_load_dynamic_manager(struct work_struct *work)
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
if (IS_ERR(fp)) {
if (PTR_ERR(fp) == -ENOENT) {
pr_info("No saved dynamic manager config found\n");
} else {
pr_err("load_dynamic_manager open file failed: %ld\n", PTR_ERR(fp));
if (PTR_ERR(fp) != -ENOENT) {
pr_err(DM_LOG_PREFIX "load config failed: %ld\n", PTR_ERR(fp));
}
return;
}
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
magic != DYNAMIC_MANAGER_FILE_MAGIC) {
pr_err("dynamic manager file invalid magic: %x!\n", magic);
pr_err(DM_LOG_PREFIX "invalid magic: %x\n", magic);
goto exit;
}
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("dynamic manager read version failed\n");
pr_err(DM_LOG_PREFIX "read version failed\n");
goto exit;
}
pr_info("dynamic manager file version: %d\n", version);
ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off);
if (ret <= 0) {
pr_info("load_dynamic_manager read err: %zd\n", ret);
goto exit;
}
if (ret != sizeof(loaded_config)) {
pr_err("load_dynamic_manager read incomplete config: %zd/%zu\n", ret, sizeof(loaded_config));
pr_err(DM_LOG_PREFIX "read config failed: %zd\n", ret);
goto exit;
}
if (loaded_config.size < 0x100 || loaded_config.size > 0x1000) {
pr_err("Invalid saved config size: 0x%x\n", loaded_config.size);
pr_err(DM_LOG_PREFIX "invalid size: 0x%x\n", loaded_config.size);
goto exit;
}
if (strlen(loaded_config.hash) != 64) {
pr_err("Invalid saved config hash length: %zu\n", strlen(loaded_config.hash));
pr_err(DM_LOG_PREFIX "invalid hash length: %zu\n", strlen(loaded_config.hash));
goto exit;
}
@@ -319,7 +273,7 @@ static void do_load_dynamic_manager(struct work_struct *work)
for (i = 0; i < 64; i++) {
char c = loaded_config.hash[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
pr_err("Invalid saved config hash character at position %d: %c\n", i, c);
pr_err(DM_LOG_PREFIX "invalid hash character at position %d: %c\n", i, c);
goto exit;
}
}
@@ -328,7 +282,7 @@ static void do_load_dynamic_manager(struct work_struct *work)
dynamic_manager = loaded_config;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n",
pr_info(DM_LOG_PREFIX "config loaded: size=0x%x, hash=%.16s...\n",
loaded_config.size, loaded_config.hash);
exit:
@@ -350,15 +304,13 @@ static void do_clear_dynamic_manager(struct work_struct *work)
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("clear_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
pr_err(DM_LOG_PREFIX "clear config file failed: %ld\n", PTR_ERR(fp));
return;
}
// Write null bytes to overwrite the file content
if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
pr_err("clear_dynamic_manager write null bytes failed.\n");
} else {
pr_info("Dynamic sign config file cleared successfully\n");
pr_err(DM_LOG_PREFIX "clear config write failed\n");
}
filp_close(fp, 0);
@@ -382,12 +334,12 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
switch (config->operation) {
case DYNAMIC_MANAGER_OP_SET:
if (config->size < 0x100 || config->size > 0x1000) {
pr_err("invalid size: 0x%x\n", config->size);
pr_err(DM_LOG_PREFIX "invalid size: 0x%x\n", config->size);
return -EINVAL;
}
if (strlen(config->hash) != 64) {
pr_err("invalid hash length: %zu\n", strlen(config->hash));
pr_err(DM_LOG_PREFIX "invalid hash length: %zu\n", strlen(config->hash));
return -EINVAL;
}
@@ -395,11 +347,13 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
for (i = 0; i < 64; i++) {
char c = config->hash[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
pr_err("invalid hash character at position %d: %c\n", i, c);
pr_err(DM_LOG_PREFIX "invalid hash character at position %d: %c\n", i, c);
return -EINVAL;
}
}
clear_dynamic_manager();
spin_lock_irqsave(&dynamic_manager_lock, flags);
dynamic_manager.size = config->size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
@@ -411,13 +365,13 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
persistent_dynamic_manager();
pr_info("dynamic manager updated: size=0x%x, hash=%.16s... (multi-manager enabled)\n",
pr_info(DM_LOG_PREFIX "config updated and activated: size=0x%x, hash=%.16s...\n",
config->size, config->hash);
break;
case DYNAMIC_MANAGER_OP_GET:
spin_lock_irqsave(&dynamic_manager_lock, flags);
if (dynamic_manager.is_set) {
if (dynamic_manager.is_set && dynamic_manager.size > 0 && strlen(dynamic_manager.hash) == 64) {
config->size = dynamic_manager.size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(config->hash, dynamic_manager.hash, sizeof(config->hash));
@@ -432,23 +386,24 @@ int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
break;
case DYNAMIC_MANAGER_OP_CLEAR:
// 改进:先无效化所有动态管理器
pr_info(DM_LOG_PREFIX "clearing dynamic manager config\n");
clear_dynamic_manager();
spin_lock_irqsave(&dynamic_manager_lock, flags);
dynamic_manager.size = 0x300;
strcpy(dynamic_manager.hash, "0000000000000000000000000000000000000000000000000000000000000000");
dynamic_manager.size = 0;
memset(dynamic_manager.hash, 0, sizeof(dynamic_manager.hash));
dynamic_manager.is_set = 0;
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
// Clear only dynamic managers, preserve default manager
clear_dynamic_manager();
// Clear file using the same method as save
// Clear file
clear_dynamic_manager_file();
pr_info("Dynamic sign config cleared (multi-manager disabled)\n");
pr_info(DM_LOG_PREFIX "config cleared\n");
break;
default:
pr_err("Invalid dynamic manager operation: %d\n", config->operation);
pr_err(DM_LOG_PREFIX "invalid operation: %d\n", config->operation);
return -EINVAL;
}
@@ -475,7 +430,7 @@ void ksu_dynamic_manager_init(void)
ksu_load_dynamic_manager();
pr_info("Dynamic sign initialized with conditional multi-manager support\n");
pr_info(DM_LOG_PREFIX "init\n");
}
void ksu_dynamic_manager_exit(void)
@@ -484,7 +439,7 @@ void ksu_dynamic_manager_exit(void)
// Save current config before exit
do_save_dynamic_manager(NULL);
pr_info("Dynamic sign exited with persistent storage\n");
pr_info(DM_LOG_PREFIX "exited\n");
}
// Get dynamic manager configuration for signature verification
@@ -494,7 +449,7 @@ bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash)
bool valid = false;
spin_lock_irqsave(&dynamic_manager_lock, flags);
if (dynamic_manager.is_set) {
if (dynamic_manager.is_set && dynamic_manager.size > 0 && strlen(dynamic_manager.hash) == 64) {
if (size) *size = dynamic_manager.size;
if (hash) *hash = dynamic_manager.hash;
valid = true;
@@ -503,3 +458,67 @@ bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash)
return valid;
}
int ksu_get_active_managers(struct manager_list_info *info)
{
unsigned long flags;
int i, count = 0;
if (!info) {
return -EINVAL;
}
memset(info, 0, sizeof(*info));
if (ksu_manager_uid != KSU_INVALID_UID && count < ARRAY_SIZE(info->managers)) {
info->managers[count].uid = ksu_manager_uid;
info->managers[count].signature_index = 0;
count++;
pr_info(DM_LOG_PREFIX "added traditional manager: uid=%d\n", ksu_manager_uid);
}
if (ksu_is_dynamic_manager_enabled() && count < ARRAY_SIZE(info->managers)) {
spin_lock_irqsave(&managers_lock, flags);
for (i = 0; i < MAX_MANAGERS && count < ARRAY_SIZE(info->managers); i++) {
if (active_managers[i].is_active) {
info->managers[count].uid = active_managers[i].uid;
info->managers[count].signature_index = active_managers[i].signature_index;
count++;
pr_info(DM_LOG_PREFIX "added dynamic manager: uid=%d, signature_index=%d\n",
active_managers[i].uid, active_managers[i].signature_index);
}
}
spin_unlock_irqrestore(&managers_lock, flags);
}
info->count = count;
pr_info(DM_LOG_PREFIX "total active managers: %d\n", count);
return 0;
}
bool ksu_has_dynamic_managers(void)
{
unsigned long flags;
bool has_managers = false;
int i;
if (!ksu_is_dynamic_manager_enabled()) {
return false;
}
spin_lock_irqsave(&managers_lock, flags);
for (i = 0; i < MAX_MANAGERS; i++) {
if (active_managers[i].is_active) {
has_managers = true;
break;
}
}
spin_unlock_irqrestore(&managers_lock, flags);
return has_managers;
}

View File

@@ -9,14 +9,16 @@
#define KERNEL_SU_DYNAMIC_MANAGER "/data/adb/ksu/.dynamic_manager"
#define DYNAMIC_SIGN_INDEX 100
#define DM_LOG_PREFIX "[Dynamic Manager] "
struct dynamic_sign_key {
unsigned int size;
const char *hash;
};
#define DYNAMIC_SIGN_DEFAULT_CONFIG { \
.size = 0x300, \
.hash = "0000000000000000000000000000000000000000000000000000000000000000" \
.size = 0, \
.hash = "" \
}
struct dynamic_manager_config {
@@ -31,21 +33,17 @@ struct manager_info {
bool is_active;
};
// Dynamic sign operations
void ksu_dynamic_manager_init(void);
void ksu_dynamic_manager_exit(void);
int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config);
bool ksu_load_dynamic_manager(void);
bool ksu_is_dynamic_manager_enabled(void);
// Multi-manager operations
void ksu_add_manager(uid_t uid, int signature_index);
void ksu_remove_manager(uid_t uid);
bool ksu_is_any_manager(uid_t uid);
int ksu_get_manager_signature_index(uid_t uid);
int ksu_get_active_managers(struct manager_list_info *info);
// Configuration access for signature verification
int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config);
bool ksu_load_dynamic_manager(void);
void ksu_dynamic_manager_init(void);
void ksu_dynamic_manager_exit(void);
bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash);
int ksu_get_active_managers(struct manager_list_info *info);
bool ksu_has_dynamic_managers(void);
#endif

View File

@@ -6,7 +6,6 @@
#include <linux/cred.h>
#include "ss/policydb.h"
#include "linux/key.h"
#include <linux/list.h>
/**
* list_count_nodes - count the number of nodes in a list

View File

@@ -23,6 +23,7 @@
#define CMD_UID_SHOULD_UMOUNT 13
#define CMD_IS_SU_ENABLED 14
#define CMD_ENABLE_SU 15
#define CMD_SCAN_ALL_USERS 17
#define CMD_GET_FULL_VERSION 0xC0FFEE1A

View File

@@ -11,4 +11,6 @@ bool ksu_is_safe_mode(void);
extern u32 ksu_devpts_sid;
extern void escape_to_root(bool do_check_first);
#endif

View File

@@ -28,8 +28,6 @@
#define SU_PATH "/system/bin/su"
#define SH_PATH "/system/bin/sh"
extern void escape_to_root(void);
static const char sh_path[] = "/system/bin/sh";
static const char ksud_path[] = KSUD_PATH;
static const char su[] = SU_PATH;
@@ -188,7 +186,7 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
pr_info("do_execveat_common su found\n");
memcpy((void *)filename->name, ksud_path, sizeof(ksud_path));
escape_to_root();
escape_to_root(true);
return 0;
}
@@ -237,7 +235,7 @@ int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
pr_info("sys_execve su found\n");
*filename_user = ksud_user_path();
escape_to_root();
escape_to_root(true);
return 0;
}

View File

@@ -5,28 +5,23 @@
#include <linux/string.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/stat.h>
#include <linux/namei.h>
#include "allowlist.h"
#include "klog.h" // IWYU pragma: keep
#include "ksu.h"
#include "ksud.h"
#include "manager.h"
#include "throne_tracker.h"
#include "kernel_compat.h"
#include "dynamic_manager.h"
#include "user_data_scanner.h"
#include <linux/kthread.h>
#include <linux/sched.h>
uid_t ksu_manager_uid = KSU_INVALID_UID;
#define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list.tmp"
#define USER_DATA_PATH "/data/user_de/0"
#define USER_DATA_PATH_LEN 256
struct uid_data {
struct list_head list;
u32 uid;
char package[KSU_MAX_PACKAGE_NAME];
};
static struct task_struct *throne_thread;
static int get_pkg_from_apk_path(char *pkg, const char *path)
{
@@ -67,35 +62,33 @@ static int get_pkg_from_apk_path(char *pkg, const char *path)
return 0;
}
static void crown_manager(const char *apk, struct list_head *uid_data, int signature_index)
static void crown_manager(const char *apk, struct list_head *uid_data,
int signature_index, struct work_buffers *work_buf)
{
char pkg[KSU_MAX_PACKAGE_NAME];
if (get_pkg_from_apk_path(pkg, apk) < 0) {
if (get_pkg_from_apk_path(work_buf->package_buffer, apk) < 0) {
pr_err("Failed to get package name from apk path: %s\n", apk);
return;
}
pr_info("manager pkg: %s, signature_index: %d\n", pkg, signature_index);
pr_info("manager pkg: %s, signature_index: %d\n", work_buf->package_buffer, signature_index);
#ifdef KSU_MANAGER_PACKAGE
// pkg is `/<real package>`
if (strncmp(pkg, KSU_MANAGER_PACKAGE, sizeof(KSU_MANAGER_PACKAGE))) {
if (strncmp(work_buf->package_buffer, KSU_MANAGER_PACKAGE, sizeof(KSU_MANAGER_PACKAGE))) {
pr_info("manager package is inconsistent with kernel build: %s\n",
KSU_MANAGER_PACKAGE);
return;
}
#endif
struct list_head *list = (struct list_head *)uid_data;
struct uid_data *np;
list_for_each_entry(np, uid_data, list) {
if (strncmp(np->package, work_buf->package_buffer, KSU_MAX_PACKAGE_NAME) == 0) {
pr_info("Crowning manager: %s(uid=%d, signature_index=%d, user=%u)\n",
work_buf->package_buffer, np->uid, signature_index, np->user_id);
list_for_each_entry (np, list, list) {
if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) {
pr_info("Crowning manager: %s(uid=%d, signature_index=%d)\n", pkg, np->uid, signature_index);
// Dynamic Sign index (1) or multi-manager signatures (2+)
if (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2) {
ksu_add_manager(np->uid, signature_index);
if (!ksu_is_manager_uid_valid()) {
ksu_set_manager_uid(np->uid);
}
@@ -107,8 +100,6 @@ static void crown_manager(const char *apk, struct list_head *uid_data, int signa
}
}
#define DATA_PATH_LEN 384 // 384 is enough for /data/app/<package>/base.apk
struct data_path {
char dirpath[DATA_PATH_LEN];
int depth;
@@ -121,7 +112,7 @@ struct apk_path_hash {
struct list_head list;
};
static struct list_head apk_path_hash_list = LIST_HEAD_INIT(apk_path_hash_list);
static struct list_head apk_path_hash_list;
struct my_dir_context {
struct dir_context ctx;
@@ -130,149 +121,10 @@ struct my_dir_context {
void *private_data;
int depth;
int *stop;
bool found_dynamic_manager;
struct work_buffers *work_buf; // Passing the work buffer
size_t processed_count;
};
// https://docs.kernel.org/filesystems/porting.html
// filldir_t (readdir callbacks) calling conventions have changed. Instead of returning 0 or -E... it returns bool now. false means "no more" (as -E... used to) and true - "keep going" (as 0 in old calling conventions). Rationale: callers never looked at specific -E... values anyway. -> iterate_shared() instances require no changes at all, all filldir_t ones in the tree converted.
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
#define FILLDIR_RETURN_TYPE bool
#define FILLDIR_ACTOR_CONTINUE true
#define FILLDIR_ACTOR_STOP false
#else
#define FILLDIR_RETURN_TYPE int
#define FILLDIR_ACTOR_CONTINUE 0
#define FILLDIR_ACTOR_STOP -EINVAL
#endif
struct uid_scan_stats {
size_t total_found;
size_t errors_encountered;
};
struct user_data_context {
struct dir_context ctx;
struct list_head *uid_list;
struct uid_scan_stats *stats;
};
FILLDIR_RETURN_TYPE user_data_actor(struct dir_context *ctx, const char *name,
int namelen, loff_t off, u64 ino,
unsigned int d_type)
{
struct user_data_context *my_ctx =
container_of(ctx, struct user_data_context, ctx);
if (!my_ctx || !my_ctx->uid_list) {
return FILLDIR_ACTOR_STOP;
}
if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen))
return FILLDIR_ACTOR_CONTINUE;
if (d_type != DT_DIR)
return FILLDIR_ACTOR_CONTINUE;
if (namelen >= KSU_MAX_PACKAGE_NAME) {
pr_warn("Package name too long: %.*s\n", namelen, name);
if (my_ctx->stats)
my_ctx->stats->errors_encountered++;
return FILLDIR_ACTOR_CONTINUE;
}
char package_path[USER_DATA_PATH_LEN];
if (snprintf(package_path, sizeof(package_path), "%s/%.*s",
USER_DATA_PATH, namelen, name) >= sizeof(package_path)) {
pr_err("Path too long for package: %.*s\n", namelen, name);
if (my_ctx->stats)
my_ctx->stats->errors_encountered++;
return FILLDIR_ACTOR_CONTINUE;
}
struct path path;
int err = kern_path(package_path, LOOKUP_FOLLOW, &path);
if (err) {
pr_debug("Package path lookup failed: %s (err: %d)\n", package_path, err);
if (my_ctx->stats)
my_ctx->stats->errors_encountered++;
return FILLDIR_ACTOR_CONTINUE;
}
struct kstat stat;
err = vfs_getattr(&path, &stat, STATX_UID, AT_STATX_SYNC_AS_STAT);
path_put(&path);
if (err) {
pr_debug("Failed to get attributes for: %s (err: %d)\n", package_path, err);
if (my_ctx->stats)
my_ctx->stats->errors_encountered++;
return FILLDIR_ACTOR_CONTINUE;
}
uid_t uid = from_kuid(&init_user_ns, stat.uid);
if (uid == (uid_t)-1) {
pr_warn("Invalid UID for package: %.*s\n", namelen, name);
if (my_ctx->stats)
my_ctx->stats->errors_encountered++;
return FILLDIR_ACTOR_CONTINUE;
}
struct uid_data *data = kzalloc(sizeof(struct uid_data), GFP_ATOMIC);
if (!data) {
pr_err("Failed to allocate memory for package: %.*s\n", namelen, name);
if (my_ctx->stats)
my_ctx->stats->errors_encountered++;
return FILLDIR_ACTOR_CONTINUE;
}
data->uid = uid;
size_t copy_len = min(namelen, KSU_MAX_PACKAGE_NAME - 1);
strncpy(data->package, name, copy_len);
data->package[copy_len] = '\0';
list_add_tail(&data->list, my_ctx->uid_list);
if (my_ctx->stats)
my_ctx->stats->total_found++;
pr_info("UserDE UID: Found package: %s, uid: %u\n", data->package, data->uid);
return FILLDIR_ACTOR_CONTINUE;
}
int scan_user_data_for_uids(struct list_head *uid_list)
{
struct file *dir_file;
struct uid_scan_stats stats = {0};
int ret = 0;
if (!uid_list) {
return -EINVAL;
}
dir_file = ksu_filp_open_compat(USER_DATA_PATH, O_RDONLY, 0);
if (IS_ERR(dir_file)) {
pr_err("UserDE UID: Failed to open %s: %ld\n", USER_DATA_PATH, PTR_ERR(dir_file));
return PTR_ERR(dir_file);
}
struct user_data_context ctx = {
.ctx.actor = user_data_actor,
.uid_list = uid_list,
.stats = &stats
};
ret = iterate_dir(dir_file, &ctx.ctx);
filp_close(dir_file, NULL);
if (stats.errors_encountered > 0) {
pr_warn("Encountered %zu errors while scanning user data directory\n",
stats.errors_encountered);
}
pr_info("UserDE UID: Scanned user data directory, found %zu packages with %zu errors\n",
stats.total_found, stats.errors_encountered);
return ret;
}
FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
int namelen, loff_t off, u64 ino,
@@ -280,12 +132,18 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
{
struct my_dir_context *my_ctx =
container_of(ctx, struct my_dir_context, ctx);
char dirpath[DATA_PATH_LEN];
struct work_buffers *work_buf = my_ctx->work_buf;
if (!my_ctx) {
pr_err("Invalid context\n");
return FILLDIR_ACTOR_STOP;
}
my_ctx->processed_count++;
if (my_ctx->processed_count % SCHEDULE_INTERVAL == 0) {
cond_resched();
}
if (my_ctx->stop && *my_ctx->stop) {
pr_info("Stop searching\n");
return FILLDIR_ACTOR_STOP;
@@ -295,12 +153,12 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".."
if (d_type == DT_DIR && namelen >= 8 && !strncmp(name, "vmdl", 4) &&
!strncmp(name + namelen - 4, ".tmp", 4)) {
pr_info("Skipping directory: %.*s\n", namelen, name);
return FILLDIR_ACTOR_CONTINUE; // Skip staging package
}
!strncmp(name + namelen - 4, ".tmp", 4)) {
pr_info("Skipping directory: %.*s\n", namelen, name);
return FILLDIR_ACTOR_CONTINUE; // Skip staging package
}
if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
if (snprintf(work_buf->path_buffer, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
namelen, name) >= DATA_PATH_LEN) {
pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen,
name);
@@ -312,20 +170,20 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
struct data_path *data = kmalloc(sizeof(struct data_path), GFP_ATOMIC);
if (!data) {
pr_err("Failed to allocate memory for %s\n", dirpath);
pr_err("Failed to allocate memory for %s\n", work_buf->path_buffer);
return FILLDIR_ACTOR_CONTINUE;
}
strscpy(data->dirpath, dirpath, DATA_PATH_LEN);
strscpy(data->dirpath, work_buf->path_buffer, DATA_PATH_LEN);
data->depth = my_ctx->depth - 1;
list_add_tail(&data->list, my_ctx->data_path_list);
} else {
if ((namelen == 8) && (strncmp(name, "base.apk", namelen) == 0)) {
struct apk_path_hash *pos, *n;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
unsigned int hash = full_name_hash(dirpath, strlen(dirpath));
unsigned int hash = full_name_hash(work_buf->path_buffer, strlen(work_buf->path_buffer));
#else
unsigned int hash = full_name_hash(NULL, dirpath, strlen(dirpath));
unsigned int hash = full_name_hash(NULL, work_buf->path_buffer, strlen(work_buf->path_buffer));
#endif
list_for_each_entry(pos, &apk_path_hash_list, list) {
if (hash == pos->hash) {
@@ -336,14 +194,16 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
int signature_index = -1;
bool is_multi_manager = is_dynamic_manager_apk(
dirpath, &signature_index);
work_buf->path_buffer, &signature_index);
pr_info("Found new base.apk at path: %s, is_multi_manager: %d, signature_index: %d\n",
dirpath, is_multi_manager, signature_index);
work_buf->path_buffer, is_multi_manager, signature_index);
// Check for dynamic sign or multi-manager signatures
if (is_multi_manager && (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2)) {
crown_manager(dirpath, my_ctx->private_data, signature_index);
my_ctx->found_dynamic_manager = true;
crown_manager(work_buf->path_buffer, my_ctx->private_data,
signature_index, work_buf);
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
if (apk_data) {
@@ -351,21 +211,27 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
apk_data->exists = true;
list_add_tail(&apk_data->list, &apk_path_hash_list);
}
} else if (is_manager_apk(dirpath)) {
crown_manager(dirpath, my_ctx->private_data, 0);
} else if (is_manager_apk(work_buf->path_buffer)) {
crown_manager(work_buf->path_buffer,
my_ctx->private_data, 0, work_buf);
if (!my_ctx->found_dynamic_manager && !ksu_is_dynamic_manager_enabled()) {
*my_ctx->stop = 1;
}
// Manager found, clear APK cache list
if (!ksu_is_dynamic_manager_enabled()) {
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) {
list_del(&pos->list);
kfree(pos);
}
}
} else {
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
if (apk_data) {
apk_data->hash = hash;
apk_data->exists = true;
list_add_tail(&apk_data->list, &apk_path_hash_list);
apk_data->hash = hash;
apk_data->exists = true;
list_add_tail(&apk_data->list, &apk_path_hash_list);
}
}
}
@@ -378,8 +244,17 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
{
int i, stop = 0;
struct list_head data_path_list;
struct work_buffers *work_buf = get_work_buffer();
if (!work_buf) {
pr_err("Failed to get work buffer for search_manager\n");
return;
}
INIT_LIST_HEAD(&data_path_list);
INIT_LIST_HEAD(&apk_path_hash_list);
unsigned long data_app_magic = 0;
bool found_dynamic_manager = false;
// Initialize APK cache list
struct apk_path_hash *pos, *n;
@@ -398,15 +273,18 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
list_for_each_entry_safe(pos, n, &data_path_list, list) {
struct my_dir_context ctx = { .ctx.actor = my_actor,
.data_path_list = &data_path_list,
.parent_dir = pos->dirpath,
.private_data = uid_data,
.depth = pos->depth,
.stop = &stop };
.data_path_list = &data_path_list,
.parent_dir = pos->dirpath,
.private_data = uid_data,
.depth = pos->depth,
.stop = &stop,
.found_dynamic_manager = false,
.work_buf = work_buf,
.processed_count = 0 };
struct file *file;
if (!stop) {
file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0);
file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW | O_DIRECTORY, 0);
if (IS_ERR(file)) {
pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file));
goto skip_iterate;
@@ -432,12 +310,20 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
iterate_dir(file, &ctx.ctx);
filp_close(file, NULL);
if (ctx.found_dynamic_manager) {
found_dynamic_manager = true;
}
cond_resched();
}
skip_iterate:
list_del(&pos->list);
if (pos != &data)
kfree(pos);
}
cond_resched();
}
// Remove stale cached APK entries
@@ -455,7 +341,7 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
struct uid_data *np;
bool exist = false;
list_for_each_entry (np, list, list) {
list_for_each_entry(np, list, list) {
if (np->uid == uid % 100000 &&
strncmp(np->package, package, KSU_MAX_PACKAGE_NAME) == 0) {
exist = true;
@@ -465,78 +351,18 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
return exist;
}
void track_throne(void)
static void track_throne_function(void)
{
struct list_head uid_list;
INIT_LIST_HEAD(&uid_list);
pr_info("Starting UID scan from user data directory\n");
int ret = scan_user_data_for_uids(&uid_list);
// scan user data for uids
int ret = scan_user_data_for_uids(&uid_list, scan_all_users);
if (ret < 0) {
pr_warn("Failed to scan user data directory (%d), falling back to packages.list\n", ret);
// fallback to packages.list method
struct file *fp = ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("Both user data scan and packages.list failed: %ld\n", PTR_ERR(fp));
pr_err("Improved UserDE UID scan failed: %d. scan_all_users=%d\n", ret, scan_all_users);
goto out;
}
char chr = 0;
loff_t pos = 0;
loff_t line_start = 0;
char buf[KSU_MAX_PACKAGE_NAME];
size_t fallback_count = 0;
for (;;) {
ssize_t count =
ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos);
if (count != sizeof(chr))
break;
if (chr != '\n')
continue;
count = ksu_kernel_read_compat(fp, buf, sizeof(buf),
&line_start);
struct uid_data *data =
kzalloc(sizeof(struct uid_data), GFP_ATOMIC);
if (!data) {
filp_close(fp, 0);
goto out;
}
char *tmp = buf;
const char *delim = " ";
char *package = strsep(&tmp, delim);
char *uid = strsep(&tmp, delim);
if (!uid || !package) {
pr_err("update_uid: package or uid is NULL!\n");
kfree(data);
break;
}
u32 res;
if (kstrtou32(uid, 10, &res)) {
pr_err("update_uid: uid parse err\n");
kfree(data);
break;
}
data->uid = res;
strncpy(data->package, package, KSU_MAX_PACKAGE_NAME);
list_add_tail(&data->list, &uid_list);
fallback_count++;
// reset line start
line_start = pos;
}
filp_close(fp, 0);
pr_info("Loaded %zu packages from packages.list fallback\n", fallback_count);
} else {
pr_info("UserDE UID: Successfully loaded %zu packages from user data directory\n", list_count_nodes(&uid_list));
}
// now update uid list
struct uid_data *np;
struct uid_data *n;
@@ -545,7 +371,7 @@ void track_throne(void)
bool manager_exist = false;
bool dynamic_manager_exist = false;
list_for_each_entry (np, &uid_list, list) {
list_for_each_entry(np, &uid_list, list) {
// if manager is installed in work profile, the uid in packages.list is still equals main profile
// don't delete it in this case!
int manager_uid = ksu_get_manager_uid() % 100000;
@@ -556,12 +382,16 @@ void track_throne(void)
}
// Check for dynamic managers
if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) {
list_for_each_entry (np, &uid_list, list) {
if (ksu_is_dynamic_manager_enabled()) {
dynamic_manager_exist = ksu_has_dynamic_managers();
if (!dynamic_manager_exist) {
list_for_each_entry(np, &uid_list, list) {
// Check if this uid is a dynamic manager (not the traditional manager)
if (ksu_is_any_manager(np->uid) && np->uid != ksu_get_manager_uid()) {
dynamic_manager_exist = true;
break;
if (ksu_is_any_manager(np->uid) && np->uid != ksu_get_manager_uid()) {
dynamic_manager_exist = true;
break;
}
}
}
}
@@ -575,8 +405,8 @@ void track_throne(void)
pr_info("Searching manager...\n");
search_manager("/data/app", 2, &uid_list);
pr_info("Search manager finished\n");
} else if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) {
// Always perform search when called from dynamic manager rescan
} else if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) {
pr_info("Dynamic sign enabled, Searching manager...\n");
search_manager("/data/app", 2, &uid_list);
pr_info("Search Dynamic sign manager finished\n");
@@ -587,12 +417,39 @@ prune:
ksu_prune_allowlist(is_uid_exist, &uid_list);
out:
// free uid_list
list_for_each_entry_safe (np, n, &uid_list, list) {
list_for_each_entry_safe(np, n, &uid_list, list) {
list_del(&np->list);
kfree(np);
}
}
static int throne_tracker_thread(void *data)
{
pr_info("%s: pid: %d started\n", __func__, current->pid);
// for the kthread, we need to escape to root
// since it does not inherit the caller's context.
// this runs as root but without the capabilities, so call it with false
escape_to_root(false);
track_throne_function();
throne_thread = NULL;
smp_mb();
pr_info("%s: pid: %d exit!\n", __func__, current->pid);
return 0;
}
void track_throne(void)
{
smp_mb();
if (throne_thread != NULL) // single instance lock
return;
throne_thread = kthread_run(throne_tracker_thread, NULL, "throne_tracker");
if (IS_ERR(throne_thread)) {
throne_thread = NULL;
return;
}
}
void ksu_throne_tracker_init(void)
{
// nothing to do

View File

@@ -0,0 +1,412 @@
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/stat.h>
#include <linux/namei.h>
#include "allowlist.h"
#include "klog.h" // IWYU pragma: keep
#include "ksu.h"
#include "manager.h"
#include "throne_tracker.h"
#include "kernel_compat.h"
// legacy throne tracker
// this is kept for UL purposes
// reason: can't solve deadlock on user_data_actor()
// xx - 20200914
uid_t ksu_manager_uid = KSU_INVALID_UID;
#define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list.tmp"
struct uid_data {
struct list_head list;
u32 uid;
char package[KSU_MAX_PACKAGE_NAME];
};
static int get_pkg_from_apk_path(char *pkg, const char *path)
{
int len = strlen(path);
if (len >= KSU_MAX_PACKAGE_NAME || len < 1)
return -1;
const char *last_slash = NULL;
const char *second_last_slash = NULL;
int i;
for (i = len - 1; i >= 0; i--) {
if (path[i] == '/') {
if (!last_slash) {
last_slash = &path[i];
} else {
second_last_slash = &path[i];
break;
}
}
}
if (!last_slash || !second_last_slash)
return -1;
const char *last_hyphen = strchr(second_last_slash, '-');
if (!last_hyphen || last_hyphen > last_slash)
return -1;
int pkg_len = last_hyphen - second_last_slash - 1;
if (pkg_len >= KSU_MAX_PACKAGE_NAME || pkg_len <= 0)
return -1;
// Copying the package name
strncpy(pkg, second_last_slash + 1, pkg_len);
pkg[pkg_len] = '\0';
return 0;
}
static void crown_manager(const char *apk, struct list_head *uid_data)
{
char pkg[KSU_MAX_PACKAGE_NAME];
if (get_pkg_from_apk_path(pkg, apk) < 0) {
pr_err("Failed to get package name from apk path: %s\n", apk);
return;
}
pr_info("manager pkg: %s\n", pkg);
#ifdef KSU_MANAGER_PACKAGE
// pkg is `/<real package>`
if (strncmp(pkg, KSU_MANAGER_PACKAGE, sizeof(KSU_MANAGER_PACKAGE))) {
pr_info("manager package is inconsistent with kernel build: %s\n",
KSU_MANAGER_PACKAGE);
return;
}
#endif
struct list_head *list = (struct list_head *)uid_data;
struct uid_data *np;
list_for_each_entry (np, list, list) {
if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) {
pr_info("Crowning manager: %s(uid=%d)\n", pkg, np->uid);
ksu_set_manager_uid(np->uid);
break;
}
}
}
#define DATA_PATH_LEN 384 // 384 is enough for /data/app/<package>/base.apk
struct data_path {
char dirpath[DATA_PATH_LEN];
int depth;
struct list_head list;
};
struct apk_path_hash {
unsigned int hash;
bool exists;
struct list_head list;
};
static struct list_head apk_path_hash_list = LIST_HEAD_INIT(apk_path_hash_list);
struct my_dir_context {
struct dir_context ctx;
struct list_head *data_path_list;
char *parent_dir;
void *private_data;
int depth;
int *stop;
};
// https://docs.kernel.org/filesystems/porting.html
// filldir_t (readdir callbacks) calling conventions have changed. Instead of returning 0 or -E... it returns bool now. false means "no more" (as -E... used to) and true - "keep going" (as 0 in old calling conventions). Rationale: callers never looked at specific -E... values anyway. -> iterate_shared() instances require no changes at all, all filldir_t ones in the tree converted.
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
#define FILLDIR_RETURN_TYPE bool
#define FILLDIR_ACTOR_CONTINUE true
#define FILLDIR_ACTOR_STOP false
#else
#define FILLDIR_RETURN_TYPE int
#define FILLDIR_ACTOR_CONTINUE 0
#define FILLDIR_ACTOR_STOP -EINVAL
#endif
FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
int namelen, loff_t off, u64 ino,
unsigned int d_type)
{
struct my_dir_context *my_ctx =
container_of(ctx, struct my_dir_context, ctx);
char dirpath[DATA_PATH_LEN];
if (!my_ctx) {
pr_err("Invalid context\n");
return FILLDIR_ACTOR_STOP;
}
if (my_ctx->stop && *my_ctx->stop) {
pr_info("Stop searching\n");
return FILLDIR_ACTOR_STOP;
}
if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen))
return FILLDIR_ACTOR_CONTINUE; // Skip "." and ".."
if (d_type == DT_DIR && namelen >= 8 && !strncmp(name, "vmdl", 4) &&
!strncmp(name + namelen - 4, ".tmp", 4)) {
pr_info("Skipping directory: %.*s\n", namelen, name);
return FILLDIR_ACTOR_CONTINUE; // Skip staging package
}
if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
namelen, name) >= DATA_PATH_LEN) {
pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen,
name);
return FILLDIR_ACTOR_CONTINUE;
}
if (d_type == DT_DIR && my_ctx->depth > 0 &&
(my_ctx->stop && !*my_ctx->stop)) {
struct data_path *data = kmalloc(sizeof(struct data_path), GFP_ATOMIC);
if (!data) {
pr_err("Failed to allocate memory for %s\n", dirpath);
return FILLDIR_ACTOR_CONTINUE;
}
strscpy(data->dirpath, dirpath, DATA_PATH_LEN);
data->depth = my_ctx->depth - 1;
list_add_tail(&data->list, my_ctx->data_path_list);
} else {
if ((namelen == 8) && (strncmp(name, "base.apk", namelen) == 0)) {
struct apk_path_hash *pos, *n;
unsigned int hash = full_name_hash(NULL, dirpath, strlen(dirpath));
list_for_each_entry(pos, &apk_path_hash_list, list) {
if (hash == pos->hash) {
pos->exists = true;
return FILLDIR_ACTOR_CONTINUE;
}
}
bool is_manager = is_manager_apk(dirpath);
pr_info("Found new base.apk at path: %s, is_manager: %d\n",
dirpath, is_manager);
if (is_manager) {
crown_manager(dirpath, my_ctx->private_data);
*my_ctx->stop = 1;
// Manager found, clear APK cache list
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) {
list_del(&pos->list);
kfree(pos);
}
} else {
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
apk_data->hash = hash;
apk_data->exists = true;
list_add_tail(&apk_data->list, &apk_path_hash_list);
}
}
}
return FILLDIR_ACTOR_CONTINUE;
}
void search_manager(const char *path, int depth, struct list_head *uid_data)
{
int i, stop = 0;
struct list_head data_path_list;
INIT_LIST_HEAD(&data_path_list);
unsigned long data_app_magic = 0;
// Initialize APK cache list
struct apk_path_hash *pos, *n;
list_for_each_entry(pos, &apk_path_hash_list, list) {
pos->exists = false;
}
// First depth
struct data_path data;
strscpy(data.dirpath, path, DATA_PATH_LEN);
data.depth = depth;
list_add_tail(&data.list, &data_path_list);
for (i = depth; i >= 0; i--) {
struct data_path *pos, *n;
list_for_each_entry_safe(pos, n, &data_path_list, list) {
struct my_dir_context ctx = { .ctx.actor = my_actor,
.data_path_list = &data_path_list,
.parent_dir = pos->dirpath,
.private_data = uid_data,
.depth = pos->depth,
.stop = &stop };
struct file *file;
if (!stop) {
file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0);
if (IS_ERR(file)) {
pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file));
goto skip_iterate;
}
// grab magic on first folder, which is /data/app
if (!data_app_magic) {
if (file->f_inode->i_sb->s_magic) {
data_app_magic = file->f_inode->i_sb->s_magic;
pr_info("%s: dir: %s got magic! 0x%lx\n", __func__, pos->dirpath, data_app_magic);
} else {
filp_close(file, NULL);
goto skip_iterate;
}
}
if (file->f_inode->i_sb->s_magic != data_app_magic) {
pr_info("%s: skip: %s magic: 0x%lx expected: 0x%lx\n", __func__, pos->dirpath,
file->f_inode->i_sb->s_magic, data_app_magic);
filp_close(file, NULL);
goto skip_iterate;
}
iterate_dir(file, &ctx.ctx);
filp_close(file, NULL);
}
skip_iterate:
list_del(&pos->list);
if (pos != &data)
kfree(pos);
}
}
// Remove stale cached APK entries
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) {
if (!pos->exists) {
list_del(&pos->list);
kfree(pos);
}
}
}
static bool is_uid_exist(uid_t uid, char *package, void *data)
{
struct list_head *list = (struct list_head *)data;
struct uid_data *np;
bool exist = false;
list_for_each_entry (np, list, list) {
if (np->uid == uid % 100000 &&
strncmp(np->package, package, KSU_MAX_PACKAGE_NAME) == 0) {
exist = true;
break;
}
}
return exist;
}
void track_throne()
{
struct file *fp =
ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n",
__func__, PTR_ERR(fp));
return;
}
struct list_head uid_list;
INIT_LIST_HEAD(&uid_list);
char chr = 0;
loff_t pos = 0;
loff_t line_start = 0;
char buf[KSU_MAX_PACKAGE_NAME];
for (;;) {
ssize_t count =
ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos);
if (count != sizeof(chr))
break;
if (chr != '\n')
continue;
count = ksu_kernel_read_compat(fp, buf, sizeof(buf),
&line_start);
struct uid_data *data =
kzalloc(sizeof(struct uid_data), GFP_ATOMIC);
if (!data) {
filp_close(fp, 0);
goto out;
}
char *tmp = buf;
const char *delim = " ";
char *package = strsep(&tmp, delim);
char *uid = strsep(&tmp, delim);
if (!uid || !package) {
pr_err("update_uid: package or uid is NULL!\n");
break;
}
u32 res;
if (kstrtou32(uid, 10, &res)) {
pr_err("update_uid: uid parse err\n");
break;
}
data->uid = res;
strncpy(data->package, package, KSU_MAX_PACKAGE_NAME);
list_add_tail(&data->list, &uid_list);
// reset line start
line_start = pos;
}
filp_close(fp, 0);
// now update uid list
struct uid_data *np;
struct uid_data *n;
// first, check if manager_uid exist!
bool manager_exist = false;
list_for_each_entry (np, &uid_list, list) {
// if manager is installed in work profile, the uid in packages.list is still equals main profile
// don't delete it in this case!
int manager_uid = ksu_get_manager_uid() % 100000;
if (np->uid == manager_uid) {
manager_exist = true;
break;
}
}
if (!manager_exist) {
if (ksu_is_manager_uid_valid()) {
pr_info("manager is uninstalled, invalidate it!\n");
ksu_invalidate_manager_uid();
goto prune;
}
pr_info("Searching manager...\n");
search_manager("/data/app", 2, &uid_list);
pr_info("Search manager finished\n");
}
prune:
// then prune the allowlist
ksu_prune_allowlist(is_uid_exist, &uid_list);
out:
// free uid_list
list_for_each_entry_safe (np, n, &uid_list, list) {
list_del(&np->list);
kfree(np);
}
}
void ksu_throne_tracker_init()
{
// nothing to do
}
void ksu_throne_tracker_exit()
{
// nothing to do
}

640
kernel/user_data_scanner.c Normal file
View File

@@ -0,0 +1,640 @@
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/stat.h>
#include <linux/namei.h>
#include <linux/sched.h>
#include <linux/mount.h>
#include <linux/magic.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/atomic.h>
#include <linux/mutex.h>
#include <linux/preempt.h>
#include <linux/hardirq.h>
#include "klog.h"
#include "ksu.h"
#include "kernel_compat.h"
#include "user_data_scanner.h"
#define KERN_PATH_TIMEOUT_MS 100
#define MAX_FUSE_CHECK_RETRIES 3
// Magic Number: File System Superblock Identifier
#define FUSE_SUPER_MAGIC 0x65735546 // FUSE (Userspace filesystem)
#define OVERLAYFS_SUPER_MAGIC 0x794c7630 // OverlayFS
#define TMPFS_MAGIC 0x01021994 // tmpfs
#define F2FS_SUPER_MAGIC 0xF2F52010 // F2FS (Flash-Friendly File System)
#define EXT4_SUPER_MAGIC 0xEF53 // ext4
extern bool is_lock_held(const char *path);
static struct workqueue_struct *scan_workqueue;
struct work_buffers *get_work_buffer(void)
{
static struct work_buffers global_buffer;
return &global_buffer;
}
// Check the file system type
static bool is_dangerous_fs_magic(unsigned long magic)
{
switch (magic) {
case FUSE_SUPER_MAGIC:
case OVERLAYFS_SUPER_MAGIC:
case TMPFS_MAGIC:
case F2FS_SUPER_MAGIC:
case EXT4_SUPER_MAGIC:
return true;
default:
return false;
}
}
// Check whether the file system is an encrypted user data file system
static bool is_encrypted_userdata_fs(struct super_block *sb, const char *path)
{
if (!sb || !path)
return true;
if (strstr(path, "/data/user_de") || strstr(path, "/data/user")) {
return true;
}
if (is_dangerous_fs_magic(sb->s_magic)) {
return true;
}
return false;
}
static bool is_path_for_kern_path(const char *path, struct super_block *expected_sb)
{
if (fatal_signal_pending(current)) {
pr_warn("Fatal signal pending, skip path: %s\n", path);
return false;
}
if (need_resched()) {
cond_resched();
if (fatal_signal_pending(current))
return false;
}
if (in_interrupt() || in_atomic()) {
pr_warn("Cannot scan path in atomic context: %s\n", path);
return false;
}
if (!path || strlen(path) == 0 || strlen(path) >= PATH_MAX) {
return false;
}
if (strstr(path, ".tmp") || strstr(path, ".removing") ||
strstr(path, ".unmounting") || strstr(path, ".pending")) {
pr_debug("Path appears to be in transition state: %s\n", path);
return false;
}
if (expected_sb) {
if (is_dangerous_fs_magic(expected_sb->s_magic)) {
pr_info("Skipping dangerous filesystem (magic=0x%lx): %s\n",
expected_sb->s_magic, path);
return false;
}
if (is_encrypted_userdata_fs(expected_sb, path)) {
pr_warn("Skipping potentially encrypted userdata filesystem: %s\n", path);
return false;
}
}
return true;
}
static int kern_path_with_timeout(const char *path, unsigned int flags,
struct path *result)
{
unsigned long start_time = jiffies;
unsigned long timeout = start_time + msecs_to_jiffies(KERN_PATH_TIMEOUT_MS);
int retries = 0;
int err;
if (!is_path_for_kern_path(path, NULL)) {
return -EPERM;
}
do {
if (time_after(jiffies, timeout)) {
pr_warn("kern_path timeout for: %s\n", path);
return -ETIMEDOUT;
}
if (fatal_signal_pending(current)) {
pr_warn("Fatal signal during kern_path: %s\n", path);
return -EINTR;
}
if (in_atomic() || irqs_disabled()) {
pr_warn("Cannot call kern_path in atomic context: %s\n", path);
return -EINVAL;
}
err = kern_path(path, flags, result);
if (err == 0) {
if (!is_path_for_kern_path(path, result->mnt->mnt_sb)) {
path_put(result);
return -EPERM;
}
return 0;
}
if (err == -ENOENT || err == -ENOTDIR || err == -EACCES || err == -EPERM) {
return err;
}
if (err == -EBUSY || err == -EAGAIN) {
retries++;
if (retries >= MAX_FUSE_CHECK_RETRIES) {
pr_warn("Max retries reached for: %s (err=%d)\n", path, err);
return err;
}
usleep_range(1000, 2000);
continue;
}
return err;
} while (retries < MAX_FUSE_CHECK_RETRIES);
return err;
}
FILLDIR_RETURN_TYPE scan_user_packages(struct dir_context *ctx, const char *name,
int namelen, loff_t off, u64 ino, unsigned int d_type)
{
struct user_dir_ctx *uctx = container_of(ctx, struct user_dir_ctx, ctx);
struct user_scan_ctx *scan_ctx = uctx->scan_ctx;
if (!scan_ctx || !scan_ctx->deferred_paths)
return FILLDIR_ACTOR_STOP;
scan_ctx->processed_count++;
if (scan_ctx->processed_count % SCHEDULE_INTERVAL == 0) {
cond_resched();
if (fatal_signal_pending(current)) {
pr_info("Fatal signal received, stopping scan\n");
return FILLDIR_ACTOR_STOP;
}
}
if (d_type != DT_DIR || namelen <= 0)
return FILLDIR_ACTOR_CONTINUE;
if (name[0] == '.' && (namelen == 1 || (namelen == 2 && name[1] == '.')))
return FILLDIR_ACTOR_CONTINUE;
if (namelen >= KSU_MAX_PACKAGE_NAME) {
pr_warn("Package name too long: %.*s (user %u)\n", namelen, name, scan_ctx->user_id);
scan_ctx->error_count++;
return FILLDIR_ACTOR_CONTINUE;
}
struct deferred_path_info *path_info = kzalloc(sizeof(struct deferred_path_info), GFP_KERNEL);
if (!path_info) {
pr_err("Memory allocation failed for path info: %.*s\n", namelen, name);
scan_ctx->error_count++;
return FILLDIR_ACTOR_CONTINUE;
}
int path_len = snprintf(path_info->path, sizeof(path_info->path),
"%s/%u/%.*s", USER_DATA_BASE_PATH, scan_ctx->user_id, namelen, name);
if (path_len >= sizeof(path_info->path)) {
pr_err("Path too long for: %.*s (user %u)\n", namelen, name, scan_ctx->user_id);
kfree(path_info);
scan_ctx->error_count++;
return FILLDIR_ACTOR_CONTINUE;
}
path_info->user_id = scan_ctx->user_id;
size_t copy_len = min_t(size_t, namelen, KSU_MAX_PACKAGE_NAME - 1);
strncpy(path_info->package_name, name, copy_len);
path_info->package_name[copy_len] = '\0';
list_add_tail(&path_info->list, scan_ctx->deferred_paths);
scan_ctx->pkg_count++;
return FILLDIR_ACTOR_CONTINUE;
}
static int process_deferred_paths(struct list_head *deferred_paths, struct list_head *uid_list)
{
struct deferred_path_info *path_info, *n;
int success_count = 0;
int skip_count = 0;
list_for_each_entry_safe(path_info, n, deferred_paths, list) {
if (!is_path_for_kern_path(path_info->path, NULL)) {
pr_debug("Skipping unsafe path: %s\n", path_info->path);
skip_count++;
list_del(&path_info->list);
kfree(path_info);
continue;
}
// Retrieve path information
struct path path;
int err = kern_path_with_timeout(path_info->path, LOOKUP_FOLLOW, &path);
if (err) {
if (err != -ENOENT && err != -EPERM) {
pr_debug("Path lookup failed: %s (%d)\n", path_info->path, err);
}
list_del(&path_info->list);
kfree(path_info);
continue;
}
// Check lock status
int tries = 0;
do {
if (!is_lock_held(path_info->path))
break;
tries++;
pr_info("%s: waiting for lock on %s (try %d)\n", __func__, path_info->path, tries);
msleep(100);
} while (tries < 10);
struct kstat stat;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0) || defined(KSU_HAS_NEW_VFS_GETATTR)
err = vfs_getattr(&path, &stat, STATX_UID, AT_STATX_SYNC_AS_STAT);
#else
err = vfs_getattr(&path, &stat);
#endif
path_put(&path);
if (err) {
pr_debug("Failed to get attributes: %s (%d)\n", path_info->path, err);
list_del(&path_info->list);
kfree(path_info);
continue;
}
uid_t uid = from_kuid(&init_user_ns, stat.uid);
if (uid == (uid_t)-1) {
pr_warn("Invalid UID for: %s\n", path_info->path);
list_del(&path_info->list);
kfree(path_info);
continue;
}
struct uid_data *uid_entry = kzalloc(sizeof(struct uid_data), GFP_KERNEL);
if (!uid_entry) {
pr_err("Memory allocation failed for UID entry: %s\n", path_info->path);
list_del(&path_info->list);
kfree(path_info);
continue;
}
uid_entry->uid = uid;
uid_entry->user_id = path_info->user_id;
strncpy(uid_entry->package, path_info->package_name, KSU_MAX_PACKAGE_NAME - 1);
uid_entry->package[KSU_MAX_PACKAGE_NAME - 1] = '\0';
list_add_tail(&uid_entry->list, uid_list);
success_count++;
pr_info("Package: %s, UID: %u, User: %u\n", uid_entry->package, uid, path_info->user_id);
list_del(&path_info->list);
kfree(path_info);
if (success_count % 10 == 0) {
cond_resched();
if (fatal_signal_pending(current)) {
pr_info("Fatal signal received, stopping path processing\n");
break;
}
}
}
if (skip_count > 0) {
pr_info("Skipped %d potentially dangerous paths for safety\n", skip_count);
}
return success_count;
}
static int scan_primary_user_apps(struct list_head *uid_list,
size_t *pkg_count, size_t *error_count,
struct work_buffers *work_buf)
{
struct file *dir_file;
struct list_head deferred_paths;
int ret;
*pkg_count = *error_count = 0;
INIT_LIST_HEAD(&deferred_paths);
pr_info("Scanning primary user (0) applications in %s\n", PRIMARY_USER_PATH);
dir_file = ksu_filp_open_compat(PRIMARY_USER_PATH, O_RDONLY, 0);
if (IS_ERR(dir_file)) {
pr_err("Cannot open primary user path: %s (%ld)\n", PRIMARY_USER_PATH, PTR_ERR(dir_file));
return PTR_ERR(dir_file);
}
// Check file system security
if (!is_path_for_kern_path(PRIMARY_USER_PATH, dir_file->f_inode->i_sb)) {
pr_err("Primary user path is not safe for scanning, aborting\n");
filp_close(dir_file, NULL);
return -EOPNOTSUPP;
}
struct user_scan_ctx scan_ctx = {
.deferred_paths = &deferred_paths,
.user_id = 0,
.pkg_count = 0,
.error_count = 0,
.work_buf = work_buf,
.processed_count = 0
};
struct user_dir_ctx uctx = {
.ctx.actor = scan_user_packages,
.scan_ctx = &scan_ctx
};
ret = iterate_dir(dir_file, &uctx.ctx);
filp_close(dir_file, NULL);
int processed = process_deferred_paths(&deferred_paths, uid_list);
*pkg_count = processed;
*error_count = scan_ctx.error_count;
pr_info("Primary user scan completed: %zu packages found, %zu errors\n",
*pkg_count, *error_count);
return ret;
}
FILLDIR_RETURN_TYPE collect_user_ids(struct dir_context *ctx, const char *name,
int namelen, loff_t off, u64 ino, unsigned int d_type)
{
struct user_id_ctx *uctx = container_of(ctx, struct user_id_ctx, ctx);
uctx->processed_count++;
if (uctx->processed_count % SCHEDULE_INTERVAL == 0) {
cond_resched();
if (fatal_signal_pending(current))
return FILLDIR_ACTOR_STOP;
}
if (d_type != DT_DIR || namelen <= 0)
return FILLDIR_ACTOR_CONTINUE;
if (name[0] == '.' && (namelen == 1 || (namelen == 2 && name[1] == '.')))
return FILLDIR_ACTOR_CONTINUE;
uid_t uid = 0;
for (int i = 0; i < namelen; i++) {
if (name[i] < '0' || name[i] > '9')
return FILLDIR_ACTOR_CONTINUE;
uid = uid * 10 + (name[i] - '0');
}
if (uctx->count >= uctx->max_count)
return FILLDIR_ACTOR_STOP;
uctx->user_ids[uctx->count++] = uid;
return FILLDIR_ACTOR_CONTINUE;
}
static int get_all_active_users(struct work_buffers *work_buf, size_t *found_count)
{
struct file *dir_file;
int ret;
*found_count = 0;
dir_file = ksu_filp_open_compat(USER_DATA_BASE_PATH, O_RDONLY, 0);
if (IS_ERR(dir_file)) {
pr_err("Cannot open user data base path: %s (%ld)\n", USER_DATA_BASE_PATH, PTR_ERR(dir_file));
return PTR_ERR(dir_file);
}
// Check the file system type of the base path
if (!is_path_for_kern_path(USER_DATA_BASE_PATH, dir_file->f_inode->i_sb)) {
pr_warn("User data base path is not safe for scanning, using primary user only\n");
filp_close(dir_file, NULL);
work_buf->user_ids_buffer[0] = 0;
*found_count = 1;
return 0;
}
struct user_id_ctx uctx = {
.ctx.actor = collect_user_ids,
.user_ids = work_buf->user_ids_buffer,
.count = 0,
.max_count = MAX_SUPPORTED_USERS,
.processed_count = 0
};
ret = iterate_dir(dir_file, &uctx.ctx);
filp_close(dir_file, NULL);
*found_count = uctx.count;
if (uctx.count > 0) {
pr_info("Found %zu active users: ", uctx.count);
for (size_t i = 0; i < uctx.count; i++) {
pr_cont("%u ", work_buf->user_ids_buffer[i]);
}
pr_cont("\n");
}
return ret;
}
static void scan_user_worker(struct work_struct *work)
{
struct scan_work_item *item = container_of(work, struct scan_work_item, work);
char path_buffer[DATA_PATH_LEN];
struct file *dir_file;
struct list_head deferred_paths;
int processed = 0;
INIT_LIST_HEAD(&deferred_paths);
snprintf(path_buffer, sizeof(path_buffer), "%s/%u", USER_DATA_BASE_PATH, item->user_id);
dir_file = ksu_filp_open_compat(path_buffer, O_RDONLY, 0);
if (IS_ERR(dir_file)) {
pr_debug("Cannot open user path: %s (%ld)\n", path_buffer, PTR_ERR(dir_file));
atomic_inc(item->total_error_count);
goto done;
}
// Check User Directory Security
if (!is_path_for_kern_path(path_buffer, dir_file->f_inode->i_sb)) {
pr_warn("User path %s is not safe for scanning, skipping\n", path_buffer);
filp_close(dir_file, NULL);
goto done;
}
struct user_scan_ctx scan_ctx = {
.deferred_paths = &deferred_paths,
.user_id = item->user_id,
.pkg_count = 0,
.error_count = 0,
.work_buf = NULL,
.processed_count = 0
};
struct user_dir_ctx uctx = {
.ctx.actor = scan_user_packages,
.scan_ctx = &scan_ctx
};
iterate_dir(dir_file, &uctx.ctx);
filp_close(dir_file, NULL);
mutex_lock(item->uid_list_mutex);
processed = process_deferred_paths(&deferred_paths, item->uid_list);
mutex_unlock(item->uid_list_mutex);
atomic_add(processed, item->total_pkg_count);
atomic_add(scan_ctx.error_count, item->total_error_count);
if (processed > 0 || scan_ctx.error_count > 0) {
pr_info("User %u: %d packages, %zu errors\n", item->user_id, processed, scan_ctx.error_count);
}
done:
if (atomic_dec_and_test(item->remaining_workers)) {
complete(item->work_completion);
}
kfree(item);
}
static int scan_secondary_users_apps(struct list_head *uid_list,
struct work_buffers *work_buf, size_t user_count,
size_t *total_pkg_count, size_t *total_error_count)
{
DECLARE_COMPLETION(work_completion);
DEFINE_MUTEX(uid_list_mutex);
atomic_t atomic_pkg_count = ATOMIC_INIT(0);
atomic_t atomic_error_count = ATOMIC_INIT(0);
atomic_t remaining_workers = ATOMIC_INIT(0);
int submitted_workers = 0;
if (!scan_workqueue) {
scan_workqueue = create_workqueue("ksu_scan");
if (!scan_workqueue) {
pr_err("Failed to create workqueue\n");
return -ENOMEM;
}
}
for (size_t i = 0; i < user_count; i++) {
// Skip the main user since it has already been scanned.
if (work_buf->user_ids_buffer[i] == 0)
continue;
struct scan_work_item *work_item = kzalloc(sizeof(struct scan_work_item), GFP_KERNEL);
if (!work_item) {
pr_err("Failed to allocate work item for user %u\n", work_buf->user_ids_buffer[i]);
continue;
}
INIT_WORK(&work_item->work, scan_user_worker);
work_item->user_id = work_buf->user_ids_buffer[i];
work_item->uid_list = uid_list;
work_item->uid_list_mutex = &uid_list_mutex;
work_item->total_pkg_count = &atomic_pkg_count;
work_item->total_error_count = &atomic_error_count;
work_item->work_completion = &work_completion;
work_item->remaining_workers = &remaining_workers;
atomic_inc(&remaining_workers);
if (queue_work(scan_workqueue, &work_item->work)) {
submitted_workers++;
} else {
atomic_dec(&remaining_workers);
kfree(work_item);
}
}
if (submitted_workers > 0) {
pr_info("Submitted %d concurrent scan workers\n", submitted_workers);
wait_for_completion(&work_completion);
}
*total_pkg_count = atomic_read(&atomic_pkg_count);
*total_error_count = atomic_read(&atomic_error_count);
return 0;
}
int scan_user_data_for_uids(struct list_head *uid_list, bool scan_all_users)
{
if (!uid_list)
return -EINVAL;
if (in_interrupt() || in_atomic()) {
pr_err("Cannot scan user data in atomic context\n");
return -EINVAL;
}
struct work_buffers *work_buf = get_work_buffer();
if (!work_buf) {
pr_err("Failed to get work buffer\n");
return -ENOMEM;
}
// Scan primary user (User 0)
size_t primary_pkg_count, primary_error_count;
int ret = scan_primary_user_apps(uid_list, &primary_pkg_count, &primary_error_count, work_buf);
if (ret < 0 && primary_pkg_count == 0) {
pr_err("Primary user scan failed completely: %d\n", ret);
return ret;
}
// If scanning all users is not required, stop here.
if (!scan_all_users) {
pr_info("Scan completed (primary user only): %zu packages, %zu errors\n",
primary_pkg_count, primary_error_count);
return primary_pkg_count > 0 ? 0 : -ENOENT;
}
// Retrieve all active users
size_t active_users;
ret = get_all_active_users(work_buf, &active_users);
if (ret < 0 || active_users == 0) {
pr_warn("Failed to get active users or no additional users found, using primary user only: %d\n", ret);
return primary_pkg_count > 0 ? 0 : -ENOENT;
}
size_t secondary_pkg_count, secondary_error_count;
ret = scan_secondary_users_apps(uid_list, work_buf, active_users,
&secondary_pkg_count, &secondary_error_count);
size_t total_packages = primary_pkg_count + secondary_pkg_count;
size_t total_errors = primary_error_count + secondary_error_count;
if (total_errors > 0)
pr_warn("Scan completed with %zu errors\n", total_errors);
pr_info("Complete scan finished: %zu users, %zu total packages\n",
active_users, total_packages);
return total_packages > 0 ? 0 : -ENOENT;
}

View File

@@ -0,0 +1,96 @@
#ifndef _KSU_USER_DATA_SCANNER_H_
#define _KSU_USER_DATA_SCANNER_H_
#include <linux/list.h>
#include <linux/types.h>
#include <linux/fs.h>
#define USER_DATA_BASE_PATH "/data/user_de"
#define PRIMARY_USER_PATH "/data/user_de/0"
#define DATA_PATH_LEN 384 // 384 is enough for /data/user_de/{userid}/<package> and /data/app/<package>/base.apk
#define MAX_SUPPORTED_USERS 32 // Supports up to 32 users
#define SMALL_BUFFER_SIZE 64
#define SCHEDULE_INTERVAL 100
#define MAX_CONCURRENT_WORKERS 8
// https://docs.kernel.org/filesystems/porting.html
// filldir_t (readdir callbacks) calling conventions have changed. Instead of returning 0 or -E... it returns bool now. false means "no more" (as -E... used to) and true - "keep going" (as 0 in old calling conventions). Rationale: callers never looked at specific -E... values anyway. -> iterate_shared() instances require no changes at all, all filldir_t ones in the tree converted.
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
#define FILLDIR_RETURN_TYPE bool
#define FILLDIR_ACTOR_CONTINUE true
#define FILLDIR_ACTOR_STOP false
#else
#define FILLDIR_RETURN_TYPE int
#define FILLDIR_ACTOR_CONTINUE 0
#define FILLDIR_ACTOR_STOP -EINVAL
#endif
// Global work buffer to avoid stack allocation
struct work_buffers {
char path_buffer[DATA_PATH_LEN];
char package_buffer[KSU_MAX_PACKAGE_NAME];
char small_buffer[SMALL_BUFFER_SIZE];
uid_t user_ids_buffer[MAX_SUPPORTED_USERS];
};
struct work_buffers *get_work_buffer(void);
struct uid_data {
struct list_head list;
u32 uid;
char package[KSU_MAX_PACKAGE_NAME];
uid_t user_id;
};
struct deferred_path_info {
struct list_head list;
char path[DATA_PATH_LEN];
char package_name[KSU_MAX_PACKAGE_NAME];
uid_t user_id;
};
struct user_scan_ctx {
struct list_head *deferred_paths;
uid_t user_id;
size_t pkg_count;
size_t error_count;
struct work_buffers *work_buf;
size_t processed_count;
};
struct user_dir_ctx {
struct dir_context ctx;
struct user_scan_ctx *scan_ctx;
};
struct user_id_ctx {
struct dir_context ctx;
uid_t *user_ids;
size_t count;
size_t max_count;
size_t processed_count;
};
struct scan_work_item {
struct work_struct work;
uid_t user_id;
struct list_head *uid_list;
struct mutex *uid_list_mutex;
atomic_t *total_pkg_count;
atomic_t *total_error_count;
struct completion *work_completion;
atomic_t *remaining_workers;
};
int scan_user_data_for_uids(struct list_head *uid_list, bool scan_all_users);
FILLDIR_RETURN_TYPE scan_user_packages(struct dir_context *ctx, const char *name,
int namelen, loff_t off, u64 ino, unsigned int d_type);
FILLDIR_RETURN_TYPE collect_user_ids(struct dir_context *ctx, const char *name,
int namelen, loff_t off, u64 ino, unsigned int d_type);
static int process_deferred_paths(struct list_head *deferred_paths, struct list_head *uid_list);
static int scan_primary_user_apps(struct list_head *uid_list, size_t *pkg_count,
size_t *error_count, struct work_buffers *work_buf);
static int get_all_active_users(struct work_buffers *work_buf, size_t *found_count);
static void scan_user_worker(struct work_struct *work);
#endif /* _KSU_USER_DATA_SCANNER_H_ */