refactor: replace throne tracker with ksud token
Co-authored-by: Ylarod <me@ylarod.cn>
This commit is contained in:
@@ -1,14 +1,10 @@
|
||||
kernelsu-objs := ksu.o
|
||||
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
|
||||
kernelsu-objs += kernel_compat.o
|
||||
kernelsu-objs += throne_comm.o
|
||||
kernelsu-objs += sulog.o
|
||||
ifeq ($(CONFIG_KSU_MANUAL_SU), y)
|
||||
kernelsu-objs += manual_su.o
|
||||
@@ -87,24 +83,6 @@ endif
|
||||
ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
|
||||
ccflags-y += -DKSU_VERSION_FULL=\"$(KSU_VERSION_FULL)\"
|
||||
|
||||
# Custom Signs
|
||||
ifdef KSU_EXPECTED_SIZE
|
||||
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
|
||||
$(info -- Custom KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
||||
endif
|
||||
|
||||
ifdef KSU_EXPECTED_HASH
|
||||
ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\"
|
||||
$(info -- Custom KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||
endif
|
||||
|
||||
ifdef KSU_MANAGER_PACKAGE
|
||||
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
||||
|
||||
ifeq ($(CONFIG_KSU_KPROBES_HOOK), y)
|
||||
$(info -- SukiSU: CONFIG_KSU_KPROBES_HOOK)
|
||||
else ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/version.h>
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
#include <linux/moduleparam.h>
|
||||
#endif
|
||||
#include <crypto/hash.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
#include <crypto/sha2.h>
|
||||
#else
|
||||
#include <crypto/sha.h>
|
||||
#endif
|
||||
|
||||
#include "apk_sign.h"
|
||||
#include "dynamic_manager.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "manager_sign.h"
|
||||
|
||||
struct sdesc {
|
||||
struct shash_desc shash;
|
||||
char ctx[];
|
||||
};
|
||||
|
||||
static apk_sign_key_t apk_sign_keys[] = {
|
||||
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
|
||||
#ifdef EXPECTED_SIZE
|
||||
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct sdesc *init_sdesc(struct crypto_shash *alg)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static int calc_hash(struct crypto_shash *alg, const unsigned char *data,
|
||||
unsigned int datalen, unsigned char *digest)
|
||||
{
|
||||
struct sdesc *sdesc;
|
||||
int ret;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int ksu_sha256(const unsigned char *data, unsigned int datalen,
|
||||
unsigned char *digest)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
if (ksu_get_dynamic_manager_config(¤t_dynamic_key.size, ¤t_dynamic_key.hash)) {
|
||||
pr_debug("Using dynamic manager config: size=0x%x, hash=%.16s...\n",
|
||||
current_dynamic_key.size, current_dynamic_key.hash);
|
||||
}
|
||||
|
||||
if (size4 != current_dynamic_key.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#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 (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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length
|
||||
|
||||
*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;
|
||||
|
||||
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];
|
||||
|
||||
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 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);
|
||||
|
||||
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;
|
||||
} __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";
|
||||
|
||||
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';
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// disable inotify for this file
|
||||
fp->f_mode |= FMODE_NONOTIFY;
|
||||
|
||||
// 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 += 12;
|
||||
// offset
|
||||
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
|
||||
pos = size4 - 0x18;
|
||||
|
||||
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);
|
||||
#endif
|
||||
}
|
||||
pos += (size8 - offset);
|
||||
}
|
||||
|
||||
if (v2_signing_blocks != 1) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_err("Unexpected v2 signature count: %d\n",
|
||||
v2_signing_blocks);
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
}
|
||||
clean:
|
||||
filp_close(fp, 0);
|
||||
|
||||
if (v3_signing_exist || v3_1_signing_exist) {
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_err("Unexpected v3 signature scheme found!\n");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
|
||||
int ksu_debug_manager_uid = -1;
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static struct kernel_param_ops expected_size_ops = {
|
||||
.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);
|
||||
|
||||
#endif
|
||||
|
||||
bool is_manager_apk(char *path)
|
||||
{
|
||||
return check_v2_signature(path, false, NULL);
|
||||
}
|
||||
|
||||
bool is_dynamic_manager_apk(char *path, int *signature_index)
|
||||
{
|
||||
return check_v2_signature(path, true, signature_index);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef __KSU_H_APK_V2_SIGN
|
||||
#define __KSU_H_APK_V2_SIGN
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "ksu.h"
|
||||
|
||||
bool is_manager_apk(char *path);
|
||||
|
||||
bool is_dynamic_manager_apk(char *path, int *signature_index);
|
||||
|
||||
#endif
|
||||
@@ -43,10 +43,7 @@
|
||||
#include "ksud.h"
|
||||
#include "manager.h"
|
||||
#include "selinux/selinux.h"
|
||||
#include "throne_tracker.h"
|
||||
#include "throne_comm.h"
|
||||
#include "kernel_compat.h"
|
||||
#include "dynamic_manager.h"
|
||||
#include "sulog.h"
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
@@ -318,48 +315,6 @@ void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid)
|
||||
}
|
||||
#endif
|
||||
|
||||
int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry)
|
||||
{
|
||||
if (!current->mm) {
|
||||
// skip kernel threads
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (current_uid().val != 1000) {
|
||||
// skip non system uid
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!old_dentry || !new_dentry) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// /data/system/packages.list.tmp -> /data/system/packages.list
|
||||
if (strcmp(new_dentry->d_iname, "packages.list")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char path[128];
|
||||
char *buf = dentry_path_raw(new_dentry, path, sizeof(path));
|
||||
if (IS_ERR(buf)) {
|
||||
pr_err("dentry_path_raw failed.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strstr(buf, "/system/packages.list")) {
|
||||
return 0;
|
||||
}
|
||||
pr_info("renameat: %s -> %s, new path: %s\n", old_dentry->d_iname,
|
||||
new_dentry->d_iname, buf);
|
||||
|
||||
if (ksu_uid_scanner_enabled) {
|
||||
ksu_request_userspace_scan();
|
||||
}
|
||||
|
||||
track_throne();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXT4_FS
|
||||
static void nuke_ext4_sysfs() {
|
||||
@@ -459,48 +414,21 @@ static void sulog_prctl_cmd(uid_t uid, unsigned long cmd)
|
||||
int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
|
||||
unsigned long arg4, unsigned long arg5)
|
||||
{
|
||||
// if success, we modify the arg5 as result!
|
||||
bool is_manual_su_cmd = false;
|
||||
u32 *result = (u32 *)arg5;
|
||||
u32 reply_ok = KERNEL_SU_OPTION;
|
||||
uid_t current_uid_val = current_uid().val;
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
is_manual_su_cmd = (arg2 == CMD_SU_ESCALATION_REQUEST ||
|
||||
arg2 == CMD_ADD_PENDING_ROOT);
|
||||
#endif
|
||||
|
||||
// skip this private space support if uid below 100k
|
||||
if (current_uid_val < 100000)
|
||||
goto skip_check;
|
||||
|
||||
uid_t manager_uid = ksu_get_manager_uid();
|
||||
if (current_uid_val != manager_uid &&
|
||||
current_uid_val % 100000 == manager_uid) {
|
||||
ksu_set_manager_uid(current_uid_val);
|
||||
}
|
||||
|
||||
skip_check:
|
||||
// yes this causes delay, but this keeps the delay consistent, which is what we want
|
||||
// with a barrier for safety as the compiler might try to do something smart.
|
||||
DONT_GET_SMART();
|
||||
if (!is_allow_su() && !is_system_uid())
|
||||
return 0;
|
||||
|
||||
// we move it after uid check here so they cannot
|
||||
// compare 0xdeadbeef call to a non-0xdeadbeef call
|
||||
if (KERNEL_SU_OPTION != option)
|
||||
return 0;
|
||||
|
||||
// just continue old logic
|
||||
bool from_root = !current_uid().val;
|
||||
bool from_daemon = is_daemon();
|
||||
bool from_manager = is_manager();
|
||||
bool from_system_uid = is_system_uid();
|
||||
bool from_system_bin_su = is_system_bin_su();
|
||||
|
||||
sulog_prctl_cmd(current_uid().val, arg2);
|
||||
|
||||
if (!from_root && !from_manager
|
||||
&& !(is_manual_su_cmd ? is_system_uid():
|
||||
(is_allow_su() && is_system_bin_su()))) {
|
||||
if (!from_root && !from_daemon && !from_manager && !from_system_uid && !from_system_bin_su) {
|
||||
// only root or manager can access this interface
|
||||
return 0;
|
||||
}
|
||||
@@ -510,12 +438,39 @@ skip_check:
|
||||
#endif
|
||||
|
||||
if (arg2 == CMD_BECOME_MANAGER) {
|
||||
if (from_manager) {
|
||||
// Manager app verifies its identity
|
||||
if (ksu_is_manager_uid_valid() &&
|
||||
ksu_get_manager_uid() == current_uid().val) {
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("become_manager: prctl reply error\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_BECOME_DAEMON) {
|
||||
// Only root can become daemon (ksud daemon runs as root)
|
||||
if (!from_root) {
|
||||
pr_err("become_daemon: not from root\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char token[65];
|
||||
if (copy_from_user(token, (void __user *)arg3, 65)) {
|
||||
pr_err("become_daemon: copy token failed\n");
|
||||
return 0;
|
||||
}
|
||||
token[64] = '\0';
|
||||
|
||||
if (ksu_verify_daemon_token(token)) {
|
||||
ksu_set_daemon_pid(current->pid);
|
||||
pr_info("ksud daemon registered, pid: %d\n", current->pid);
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("become_daemon: prctl reply error\n");
|
||||
}
|
||||
} else {
|
||||
pr_err("become_daemon: invalid token\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -628,10 +583,6 @@ skip_check:
|
||||
pr_info("post-fs-data triggered\n");
|
||||
on_post_fs_data();
|
||||
ksu_sulog_init();
|
||||
// Initialize UID scanner if enabled
|
||||
init_uid_scanner();
|
||||
// Initializing Dynamic Signatures
|
||||
ksu_dynamic_manager_init();
|
||||
pr_info("Dynamic sign config loaded during post-fs-data\n");
|
||||
}
|
||||
break;
|
||||
@@ -745,6 +696,28 @@ skip_check:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg2 == CMD_SET_MANAGER_UID) {
|
||||
// Only daemon can set manager uid
|
||||
if (!from_daemon) {
|
||||
pr_err("set_manager_uid: not daemon\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uid_t uid;
|
||||
if (copy_from_user(&uid, (void __user *)arg3, sizeof(uid))) {
|
||||
pr_err("set_manager_uid: copy uid failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ksu_set_manager_uid(uid);
|
||||
pr_info("manager uid set to %d\n", uid);
|
||||
|
||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||
pr_err("prctl reply error, cmd: %lu\n", arg2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KPM
|
||||
// ADD: 添加KPM模块控制
|
||||
if(sukisu_is_kpm_control_code(arg2)) {
|
||||
@@ -827,9 +800,8 @@ skip_check:
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// all other cmds are for 'root manager'
|
||||
if (!from_manager) {
|
||||
// all other cmds are for 'daemon or manager'
|
||||
if (!from_daemon && !from_manager) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1087,26 +1059,6 @@ static struct kprobe prctl_kp = {
|
||||
.pre_handler = handler_pre,
|
||||
};
|
||||
|
||||
static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
|
||||
// https://elixir.bootlin.com/linux/v5.12-rc1/source/include/linux/fs.h
|
||||
struct renamedata *rd = PT_REGS_PARM1(regs);
|
||||
struct dentry *old_entry = rd->old_dentry;
|
||||
struct dentry *new_entry = rd->new_dentry;
|
||||
#else
|
||||
struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs);
|
||||
struct dentry *new_entry = (struct dentry *)PT_REGS_CCALL_PARM4(regs);
|
||||
#endif
|
||||
|
||||
return ksu_handle_rename(old_entry, new_entry);
|
||||
}
|
||||
|
||||
static struct kprobe renameat_kp = {
|
||||
.symbol_name = "vfs_rename",
|
||||
.pre_handler = renameat_handler_pre,
|
||||
};
|
||||
|
||||
__maybe_unused int ksu_kprobe_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
@@ -1117,16 +1069,12 @@ __maybe_unused int ksu_kprobe_init(void)
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = register_kprobe(&renameat_kp);
|
||||
pr_info("renameat kp: %d\n", rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
__maybe_unused int ksu_kprobe_exit(void)
|
||||
{
|
||||
unregister_kprobe(&prctl_kp);
|
||||
unregister_kprobe(&renameat_kp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1206,12 +1154,6 @@ static int ksu_task_alloc(struct task_struct *task,
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ksu_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
|
||||
struct inode *new_inode, struct dentry *new_dentry)
|
||||
{
|
||||
return ksu_handle_rename(old_dentry, new_dentry);
|
||||
}
|
||||
|
||||
static int ksu_task_fix_setuid(struct cred *new, const struct cred *old,
|
||||
int flags)
|
||||
{
|
||||
@@ -1221,7 +1163,6 @@ static int ksu_task_fix_setuid(struct cred *new, const struct cred *old,
|
||||
#ifndef MODULE
|
||||
static struct security_hook_list ksu_hooks[] = {
|
||||
LSM_HOOK_INIT(task_prctl, ksu_task_prctl),
|
||||
LSM_HOOK_INIT(inode_rename, ksu_inode_rename),
|
||||
LSM_HOOK_INIT(task_fix_setuid, ksu_task_fix_setuid),
|
||||
LSM_HOOK_INIT(inode_permission, ksu_inode_permission),
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
@@ -1386,26 +1327,6 @@ void __init ksu_lsm_hook_init(void)
|
||||
} else {
|
||||
pr_warn("Failed to find task_prctl!\n");
|
||||
}
|
||||
|
||||
int inode_killpriv_index = -1;
|
||||
void *cap_killpriv = GET_SYMBOL_ADDR(cap_inode_killpriv);
|
||||
find_head_addr(cap_killpriv, &inode_killpriv_index);
|
||||
if (inode_killpriv_index < 0) {
|
||||
pr_warn("Failed to find inode_rename, use kprobe instead!\n");
|
||||
register_kprobe(&renameat_kp);
|
||||
} else {
|
||||
int inode_rename_index = inode_killpriv_index +
|
||||
&security_hook_heads.inode_rename -
|
||||
&security_hook_heads.inode_killpriv;
|
||||
struct hlist_head *head_start =
|
||||
(struct hlist_head *)&security_hook_heads;
|
||||
void *inode_rename_head = head_start + inode_rename_index;
|
||||
if (inode_rename_head != &security_hook_heads.inode_rename) {
|
||||
pr_warn("inode_rename's address has shifted!\n");
|
||||
}
|
||||
KSU_LSM_HOOK_HACK_INIT(inode_rename_head, inode_rename,
|
||||
ksu_inode_rename);
|
||||
}
|
||||
void *cap_setuid = GET_SYMBOL_ADDR(cap_task_fix_setuid);
|
||||
void *setuid_head = find_head_addr(cap_setuid, NULL);
|
||||
if (setuid_head) {
|
||||
@@ -1428,8 +1349,6 @@ void __init ksu_core_init(void)
|
||||
|
||||
void ksu_core_exit(void)
|
||||
{
|
||||
ksu_uid_exit();
|
||||
ksu_throne_comm_exit();
|
||||
ksu_sulog_exit();
|
||||
#ifdef CONFIG_KPROBE
|
||||
pr_info("ksu_core_kprobe_exit\n");
|
||||
|
||||
@@ -1,505 +0,0 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/workqueue.h>
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
#include <linux/moduleparam.h>
|
||||
#endif
|
||||
#include <crypto/hash.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
||||
#include <crypto/sha2.h>
|
||||
#else
|
||||
#include <crypto/sha.h>
|
||||
#endif
|
||||
|
||||
#include "dynamic_manager.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "kernel_compat.h"
|
||||
#include "manager.h"
|
||||
|
||||
#define MAX_MANAGERS 2
|
||||
|
||||
// Dynamic sign configuration
|
||||
static struct dynamic_manager_config dynamic_manager = {
|
||||
.size = 0x300,
|
||||
.hash = "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
.is_set = 0
|
||||
};
|
||||
|
||||
// Multi-manager state
|
||||
static struct manager_info active_managers[MAX_MANAGERS];
|
||||
static DEFINE_SPINLOCK(managers_lock);
|
||||
static DEFINE_SPINLOCK(dynamic_manager_lock);
|
||||
|
||||
// Work queues for persistent storage
|
||||
static struct work_struct save_dynamic_manager_work;
|
||||
static struct work_struct load_dynamic_manager_work;
|
||||
static struct work_struct clear_dynamic_manager_work;
|
||||
|
||||
bool ksu_is_dynamic_manager_enabled(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
bool enabled;
|
||||
|
||||
spin_lock_irqsave(&dynamic_manager_lock, flags);
|
||||
enabled = dynamic_manager.is_set;
|
||||
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void ksu_add_manager(uid_t uid, int signature_index)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
if (!ksu_is_dynamic_manager_enabled()) {
|
||||
pr_info("Dynamic sign not enabled, skipping multi-manager add\n");
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&managers_lock, flags);
|
||||
|
||||
// Check if manager already exists and update
|
||||
for (i = 0; i < MAX_MANAGERS; i++) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find free slot for new manager
|
||||
for (i = 0; i < MAX_MANAGERS; i++) {
|
||||
if (!active_managers[i].is_active) {
|
||||
active_managers[i].uid = uid;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&managers_lock, flags);
|
||||
pr_warn("Failed to add manager, no free slots\n");
|
||||
}
|
||||
|
||||
void ksu_remove_manager(uid_t uid)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
if (!ksu_is_dynamic_manager_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&managers_lock, flags);
|
||||
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&managers_lock, flags);
|
||||
}
|
||||
|
||||
bool ksu_is_any_manager(uid_t uid)
|
||||
{
|
||||
unsigned long flags;
|
||||
bool is_manager = 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 && active_managers[i].uid == uid) {
|
||||
is_manager = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&managers_lock, flags);
|
||||
return is_manager;
|
||||
}
|
||||
|
||||
int ksu_get_manager_signature_index(uid_t uid)
|
||||
{
|
||||
unsigned long flags;
|
||||
int signature_index = -1;
|
||||
int i;
|
||||
|
||||
// Check traditional manager first
|
||||
if (ksu_manager_uid != KSU_INVALID_UID && uid == ksu_manager_uid) {
|
||||
return DYNAMIC_SIGN_INDEX;
|
||||
}
|
||||
|
||||
if (!ksu_is_dynamic_manager_enabled()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&managers_lock, flags);
|
||||
|
||||
for (i = 0; i < MAX_MANAGERS; i++) {
|
||||
if (active_managers[i].is_active && active_managers[i].uid == uid) {
|
||||
signature_index = active_managers[i].signature_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&managers_lock, flags);
|
||||
return signature_index;
|
||||
}
|
||||
|
||||
static void clear_dynamic_manager(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
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);
|
||||
active_managers[i].is_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&managers_lock, flags);
|
||||
}
|
||||
|
||||
int ksu_get_active_managers(struct manager_list_info *info)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static void do_save_dynamic_manager(struct work_struct *work)
|
||||
{
|
||||
u32 magic = DYNAMIC_MANAGER_FILE_MAGIC;
|
||||
u32 version = DYNAMIC_MANAGER_FILE_VERSION;
|
||||
struct dynamic_manager_config config_to_save;
|
||||
loff_t off = 0;
|
||||
unsigned long flags;
|
||||
struct file *fp;
|
||||
|
||||
spin_lock_irqsave(&dynamic_manager_lock, flags);
|
||||
config_to_save = dynamic_manager;
|
||||
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));
|
||||
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, &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);
|
||||
}
|
||||
|
||||
static void do_load_dynamic_manager(struct work_struct *work)
|
||||
{
|
||||
loff_t off = 0;
|
||||
ssize_t ret = 0;
|
||||
struct file *fp = NULL;
|
||||
u32 magic;
|
||||
u32 version;
|
||||
struct dynamic_manager_config loaded_config;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
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));
|
||||
}
|
||||
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);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||
pr_err("dynamic manager 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));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (loaded_config.size < 0x100 || loaded_config.size > 0x1000) {
|
||||
pr_err("Invalid saved config 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));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Validate hash format
|
||||
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);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dynamic_manager_lock, flags);
|
||||
dynamic_manager = loaded_config;
|
||||
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
|
||||
|
||||
pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n",
|
||||
loaded_config.size, loaded_config.hash);
|
||||
|
||||
exit:
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
static bool persistent_dynamic_manager(void)
|
||||
{
|
||||
return ksu_queue_work(&save_dynamic_manager_work);
|
||||
}
|
||||
|
||||
static void do_clear_dynamic_manager(struct work_struct *work)
|
||||
{
|
||||
loff_t off = 0;
|
||||
struct file *fp;
|
||||
char zero_buffer[512];
|
||||
|
||||
memset(zero_buffer, 0, sizeof(zero_buffer));
|
||||
|
||||
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));
|
||||
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");
|
||||
}
|
||||
|
||||
filp_close(fp, 0);
|
||||
}
|
||||
|
||||
static bool clear_dynamic_manager_file(void)
|
||||
{
|
||||
return ksu_queue_work(&clear_dynamic_manager_work);
|
||||
}
|
||||
|
||||
int ksu_handle_dynamic_manager(struct dynamic_manager_user_config *config)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
if (!config) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (config->operation) {
|
||||
case DYNAMIC_MANAGER_OP_SET:
|
||||
if (config->size < 0x100 || config->size > 0x1000) {
|
||||
pr_err("invalid size: 0x%x\n", config->size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strlen(config->hash) != 64) {
|
||||
pr_err("invalid hash length: %zu\n", strlen(config->hash));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// Validate hash format
|
||||
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);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dynamic_manager_lock, flags);
|
||||
dynamic_manager.size = config->size;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(dynamic_manager.hash, config->hash, sizeof(dynamic_manager.hash));
|
||||
#else
|
||||
strlcpy(dynamic_manager.hash, config->hash, sizeof(dynamic_manager.hash));
|
||||
#endif
|
||||
dynamic_manager.is_set = 1;
|
||||
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
|
||||
|
||||
persistent_dynamic_manager();
|
||||
pr_info("dynamic manager updated: size=0x%x, hash=%.16s... (multi-manager enabled)\n",
|
||||
config->size, config->hash);
|
||||
break;
|
||||
|
||||
case DYNAMIC_MANAGER_OP_GET:
|
||||
spin_lock_irqsave(&dynamic_manager_lock, flags);
|
||||
if (dynamic_manager.is_set) {
|
||||
config->size = dynamic_manager.size;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(config->hash, dynamic_manager.hash, sizeof(config->hash));
|
||||
#else
|
||||
strlcpy(config->hash, dynamic_manager.hash, sizeof(config->hash));
|
||||
#endif
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = -ENODATA;
|
||||
}
|
||||
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
|
||||
break;
|
||||
|
||||
case DYNAMIC_MANAGER_OP_CLEAR:
|
||||
spin_lock_irqsave(&dynamic_manager_lock, flags);
|
||||
dynamic_manager.size = 0x300;
|
||||
strcpy(dynamic_manager.hash, "0000000000000000000000000000000000000000000000000000000000000000");
|
||||
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_dynamic_manager_file();
|
||||
|
||||
pr_info("Dynamic sign config cleared (multi-manager disabled)\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_err("Invalid dynamic manager operation: %d\n", config->operation);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ksu_load_dynamic_manager(void)
|
||||
{
|
||||
return ksu_queue_work(&load_dynamic_manager_work);
|
||||
}
|
||||
|
||||
void ksu_dynamic_manager_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
INIT_WORK(&save_dynamic_manager_work, do_save_dynamic_manager);
|
||||
INIT_WORK(&load_dynamic_manager_work, do_load_dynamic_manager);
|
||||
INIT_WORK(&clear_dynamic_manager_work, do_clear_dynamic_manager);
|
||||
|
||||
// Initialize manager slots
|
||||
for (i = 0; i < MAX_MANAGERS; i++) {
|
||||
active_managers[i].is_active = false;
|
||||
}
|
||||
|
||||
ksu_load_dynamic_manager();
|
||||
|
||||
pr_info("Dynamic sign initialized with conditional multi-manager support\n");
|
||||
}
|
||||
|
||||
void ksu_dynamic_manager_exit(void)
|
||||
{
|
||||
clear_dynamic_manager();
|
||||
|
||||
// Save current config before exit
|
||||
do_save_dynamic_manager(NULL);
|
||||
pr_info("Dynamic sign exited with persistent storage\n");
|
||||
}
|
||||
|
||||
// Get dynamic manager configuration for signature verification
|
||||
bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash)
|
||||
{
|
||||
unsigned long flags;
|
||||
bool valid = false;
|
||||
|
||||
spin_lock_irqsave(&dynamic_manager_lock, flags);
|
||||
if (dynamic_manager.is_set) {
|
||||
if (size) *size = dynamic_manager.size;
|
||||
if (hash) *hash = dynamic_manager.hash;
|
||||
valid = true;
|
||||
}
|
||||
spin_unlock_irqrestore(&dynamic_manager_lock, flags);
|
||||
|
||||
return valid;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef __KSU_H_DYNAMIC_MANAGER
|
||||
#define __KSU_H_DYNAMIC_MANAGER
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "ksu.h"
|
||||
|
||||
#define DYNAMIC_MANAGER_FILE_MAGIC 0x7f445347 // 'DSG', u32
|
||||
#define DYNAMIC_MANAGER_FILE_VERSION 1 // u32
|
||||
#define KERNEL_SU_DYNAMIC_MANAGER "/data/adb/ksu/.dynamic_manager"
|
||||
#define DYNAMIC_SIGN_INDEX 100
|
||||
|
||||
struct dynamic_sign_key {
|
||||
unsigned int size;
|
||||
const char *hash;
|
||||
};
|
||||
|
||||
#define DYNAMIC_SIGN_DEFAULT_CONFIG { \
|
||||
.size = 0x300, \
|
||||
.hash = "0000000000000000000000000000000000000000000000000000000000000000" \
|
||||
}
|
||||
|
||||
struct dynamic_manager_config {
|
||||
unsigned int size;
|
||||
char hash[65];
|
||||
int is_set;
|
||||
};
|
||||
|
||||
struct manager_info {
|
||||
uid_t uid;
|
||||
int signature_index;
|
||||
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
|
||||
bool ksu_get_dynamic_manager_config(unsigned int *size, const char **hash);
|
||||
|
||||
#endif
|
||||
12
kernel/ksu.c
12
kernel/ksu.c
@@ -10,7 +10,7 @@
|
||||
#include "core_hook.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksu.h"
|
||||
#include "throne_tracker.h"
|
||||
#include "manager.h"
|
||||
|
||||
static struct workqueue_struct *ksu_workqueue;
|
||||
|
||||
@@ -45,11 +45,15 @@ int __init kernelsu_init(void)
|
||||
|
||||
ksu_core_init();
|
||||
|
||||
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
||||
ksu_generate_daemon_token();
|
||||
#ifdef CONFIG_KSU_DEBUG
|
||||
pr_info("Daemon token: %s\n", ksu_get_daemon_token());
|
||||
#endif
|
||||
|
||||
ksu_throne_tracker_init = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
||||
|
||||
ksu_allowlist_init();
|
||||
|
||||
ksu_throne_tracker_init();
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
ksu_sucompat_init();
|
||||
ksu_ksud_init();
|
||||
@@ -73,8 +77,6 @@ void kernelsu_exit(void)
|
||||
{
|
||||
ksu_allowlist_exit();
|
||||
|
||||
ksu_throne_tracker_exit();
|
||||
|
||||
destroy_workqueue(ksu_workqueue);
|
||||
|
||||
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#define CMD_UID_SHOULD_UMOUNT 13
|
||||
#define CMD_IS_SU_ENABLED 14
|
||||
#define CMD_ENABLE_SU 15
|
||||
#define CMD_BECOME_DAEMON 17
|
||||
#define CMD_SET_MANAGER_UID 18
|
||||
|
||||
#ifdef CONFIG_KSU_MANUAL_SU
|
||||
#define CMD_SU_ESCALATION_REQUEST 50
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/workqueue.h>
|
||||
@@ -17,9 +18,39 @@
|
||||
#include "arch.h"
|
||||
#include "klog.h" // IWYU pragma: keep
|
||||
#include "ksud.h"
|
||||
#include "manager.h"
|
||||
#include "kernel_compat.h"
|
||||
#include "selinux/selinux.h"
|
||||
|
||||
// Daemon token implementation
|
||||
pid_t ksu_daemon_pid = KSU_INVALID_PID;
|
||||
char ksu_daemon_token[65] = {0};
|
||||
|
||||
void ksu_generate_daemon_token(void)
|
||||
{
|
||||
unsigned char random_bytes[32];
|
||||
get_random_bytes(random_bytes, 32);
|
||||
bin2hex(ksu_daemon_token, random_bytes, 32);
|
||||
ksu_daemon_token[64] = '\0';
|
||||
pr_info("KernelSU daemon token generated\n");
|
||||
}
|
||||
|
||||
const char* ksu_get_daemon_token(void)
|
||||
{
|
||||
return ksu_daemon_token;
|
||||
}
|
||||
|
||||
bool ksu_verify_daemon_token(const char *token)
|
||||
{
|
||||
if (!token || strlen(token) != 64) {
|
||||
return false;
|
||||
}
|
||||
return strncmp(ksu_daemon_token, token, 64) == 0;
|
||||
}
|
||||
|
||||
// Manager UID implementation
|
||||
uid_t ksu_manager_uid = KSU_INVALID_UID;
|
||||
|
||||
|
||||
static const char KERNEL_SU_RC[] =
|
||||
"\n"
|
||||
@@ -42,6 +73,11 @@ static const char KERNEL_SU_RC[] =
|
||||
" exec u:r:su:s0 root -- " KSUD_PATH " boot-completed\n"
|
||||
"\n"
|
||||
|
||||
"on property:sys.boot_completed=1\n"
|
||||
" setenv KSUD_DAEMON_TOKEN __KSU_DAEMON_TOKEN_PLACEHOLDER__\n"
|
||||
" exec u:r:su:s0 root -- " KSUD_PATH " daemon\n"
|
||||
"\n"
|
||||
|
||||
"\n";
|
||||
|
||||
static void stop_vfs_read_hook();
|
||||
@@ -319,7 +355,23 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
buf = *buf_ptr;
|
||||
count = *count_ptr;
|
||||
|
||||
size_t rc_count = strlen(KERNEL_SU_RC);
|
||||
// Prepare RC content with token replacement
|
||||
char rc_with_token[sizeof(KERNEL_SU_RC) + 64];
|
||||
const char *token = ksu_get_daemon_token();
|
||||
const char *placeholder = "__KSU_DAEMON_TOKEN_PLACEHOLDER__";
|
||||
const char *token_pos = strstr(KERNEL_SU_RC, placeholder);
|
||||
|
||||
if (token_pos) {
|
||||
size_t prefix_len = token_pos - KERNEL_SU_RC;
|
||||
memcpy(rc_with_token, KERNEL_SU_RC, prefix_len);
|
||||
memcpy(rc_with_token + prefix_len, token, 64);
|
||||
strcpy(rc_with_token + prefix_len + 64, token_pos + strlen(placeholder));
|
||||
} else {
|
||||
pr_err("Token placeholder not found in RC!\n");
|
||||
strcpy(rc_with_token, KERNEL_SU_RC);
|
||||
}
|
||||
|
||||
size_t rc_count = strlen(rc_with_token);
|
||||
|
||||
pr_info("vfs_read: %s, comm: %s, count: %zu, rc_count: %zu\n", dpath,
|
||||
current->comm, count, rc_count);
|
||||
@@ -329,7 +381,7 @@ int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t ret = copy_to_user(buf, KERNEL_SU_RC, rc_count);
|
||||
size_t ret = copy_to_user(buf, rc_with_token, rc_count);
|
||||
if (ret) {
|
||||
pr_err("copy ksud.rc failed: %zu\n", ret);
|
||||
return 0;
|
||||
|
||||
@@ -4,27 +4,56 @@
|
||||
#include <linux/cred.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define KSU_INVALID_PID -1
|
||||
#define KSU_INVALID_UID -1
|
||||
|
||||
extern uid_t ksu_manager_uid; // DO NOT DIRECT USE
|
||||
// Daemon (ksud) - identified by PID + token
|
||||
extern pid_t ksu_daemon_pid;
|
||||
extern char ksu_daemon_token[65];
|
||||
|
||||
extern bool ksu_is_any_manager(uid_t uid);
|
||||
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_daemon_pid_valid(void)
|
||||
{
|
||||
return ksu_daemon_pid != KSU_INVALID_PID;
|
||||
}
|
||||
|
||||
static inline bool ksu_is_manager_uid_valid()
|
||||
static inline bool is_daemon(void)
|
||||
{
|
||||
return unlikely(ksu_daemon_pid == current->pid);
|
||||
}
|
||||
|
||||
static inline pid_t ksu_get_daemon_pid(void)
|
||||
{
|
||||
return ksu_daemon_pid;
|
||||
}
|
||||
|
||||
static inline void ksu_set_daemon_pid(pid_t pid)
|
||||
{
|
||||
ksu_daemon_pid = pid;
|
||||
}
|
||||
|
||||
static inline void ksu_invalidate_daemon_pid(void)
|
||||
{
|
||||
ksu_daemon_pid = KSU_INVALID_PID;
|
||||
}
|
||||
|
||||
void ksu_generate_daemon_token(void);
|
||||
const char* ksu_get_daemon_token(void);
|
||||
bool ksu_verify_daemon_token(const char *token);
|
||||
|
||||
// Manager (app) - identified by UID
|
||||
extern uid_t ksu_manager_uid;
|
||||
|
||||
static inline bool ksu_is_manager_uid_valid(void)
|
||||
{
|
||||
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_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;
|
||||
}
|
||||
@@ -34,7 +63,7 @@ static inline void ksu_set_manager_uid(uid_t 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;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef MANAGER_SIGN_H
|
||||
#define MANAGER_SIGN_H
|
||||
|
||||
// ShirkNeko/SukiSU
|
||||
#define EXPECTED_SIZE_SHIRKNEKO 0x35c
|
||||
#define EXPECTED_HASH_SHIRKNEKO "947ae944f3de4ed4c21a7e4f7953ecf351bfa2b36239da37a34111ad29993eef"
|
||||
|
||||
// Dynamic Sign
|
||||
#define EXPECTED_SIZE_OTHER 0x300
|
||||
#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
typedef struct {
|
||||
unsigned size;
|
||||
const char *sha256;
|
||||
} apk_sign_key_t;
|
||||
|
||||
#endif /* MANAGER_SIGN_H */
|
||||
@@ -1,204 +0,0 @@
|
||||
#include <linux/fs.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "klog.h"
|
||||
#include "throne_comm.h"
|
||||
#include "kernel_compat.h"
|
||||
|
||||
#define PROC_UID_SCANNER "ksu_uid_scanner"
|
||||
#define UID_SCANNER_STATE_FILE "/data/adb/ksu/.uid_scanner"
|
||||
|
||||
static struct proc_dir_entry *proc_entry = NULL;
|
||||
static struct workqueue_struct *scanner_wq = NULL;
|
||||
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");
|
||||
}
|
||||
|
||||
void ksu_request_userspace_scan(void)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pr_info("throne state saved: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
|
||||
|
||||
exit:
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool ksu_throne_comm_load_state(void)
|
||||
{
|
||||
return ksu_queue_work(&ksu_state_load_work);
|
||||
}
|
||||
|
||||
void ksu_throne_comm_save_state(void)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static int uid_scanner_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
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];
|
||||
|
||||
if (count >= sizeof(cmd))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(cmd, buffer, count))
|
||||
return -EFAULT;
|
||||
|
||||
cmd[count] = '\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");
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct proc_ops uid_scanner_proc_ops = {
|
||||
.proc_open = uid_scanner_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_write = uid_scanner_write,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = single_release,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (scanner_wq) {
|
||||
destroy_workqueue(scanner_wq);
|
||||
scanner_wq = NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void ksu_uid_exit(void)
|
||||
{
|
||||
do_save_throne_state(NULL);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef __KSU_H_THRONE_COMM
|
||||
#define __KSU_H_THRONE_COMM
|
||||
|
||||
void ksu_request_userspace_scan(void);
|
||||
|
||||
void ksu_handle_userspace_update(void);
|
||||
|
||||
int ksu_throne_comm_init(void);
|
||||
|
||||
void ksu_throne_comm_exit(void);
|
||||
|
||||
int ksu_uid_init(void);
|
||||
|
||||
void ksu_uid_exit(void);
|
||||
|
||||
bool ksu_throne_comm_load_state(void);
|
||||
|
||||
void ksu_throne_comm_save_state(void);
|
||||
|
||||
void do_load_throne_state(struct work_struct *work);
|
||||
|
||||
#endif
|
||||
@@ -1,647 +0,0 @@
|
||||
#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"
|
||||
#include "dynamic_manager.h"
|
||||
#include "throne_comm.h"
|
||||
|
||||
uid_t ksu_manager_uid = KSU_INVALID_UID;
|
||||
static uid_t locked_manager_uid = KSU_INVALID_UID;
|
||||
static uid_t locked_dynamic_manager_uid = KSU_INVALID_UID;
|
||||
|
||||
#define KSU_UID_LIST_PATH "/data/misc/user_uid/uid_list"
|
||||
#define USER_DATA_PATH "/data/user_de/0"
|
||||
#define USER_DATA_PATH_LEN 288
|
||||
|
||||
struct uid_data {
|
||||
struct list_head list;
|
||||
u32 uid;
|
||||
char package[KSU_MAX_PACKAGE_NAME];
|
||||
};
|
||||
|
||||
// Try read /data/misc/user_uid/uid_list
|
||||
static int uid_from_um_list(struct list_head *uid_list)
|
||||
{
|
||||
struct file *fp;
|
||||
char *buf = NULL;
|
||||
loff_t size, pos = 0;
|
||||
ssize_t nr;
|
||||
int cnt = 0;
|
||||
|
||||
fp = ksu_filp_open_compat(KSU_UID_LIST_PATH, O_RDONLY, 0);
|
||||
if (IS_ERR(fp))
|
||||
return -ENOENT;
|
||||
|
||||
size = fp->f_inode->i_size;
|
||||
if (size <= 0) {
|
||||
filp_close(fp, NULL);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
buf = kzalloc(size + 1, GFP_ATOMIC);
|
||||
if (!buf) {
|
||||
pr_err("uid_list: OOM %lld B\n", size);
|
||||
filp_close(fp, NULL);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nr = ksu_kernel_read_compat(fp, buf, size, &pos);
|
||||
filp_close(fp, NULL);
|
||||
if (nr != size) {
|
||||
pr_err("uid_list: short read %zd/%lld\n", nr, size);
|
||||
kfree(buf);
|
||||
return -EIO;
|
||||
}
|
||||
buf[size] = '\0';
|
||||
|
||||
for (char *line = buf, *next; line; line = next) {
|
||||
next = strchr(line, '\n');
|
||||
if (next) *next++ = '\0';
|
||||
|
||||
while (*line == ' ' || *line == '\t' || *line == '\r') ++line;
|
||||
if (!*line) continue;
|
||||
|
||||
char *uid_str = strsep(&line, " \t");
|
||||
char *pkg = line;
|
||||
if (!pkg) continue;
|
||||
while (*pkg == ' ' || *pkg == '\t') ++pkg;
|
||||
if (!*pkg) continue;
|
||||
|
||||
u32 uid;
|
||||
if (kstrtou32(uid_str, 10, &uid)) {
|
||||
pr_warn_once("uid_list: bad uid <%s>\n", uid_str);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct uid_data *d = kzalloc(sizeof(*d), GFP_ATOMIC);
|
||||
if (unlikely(!d)) {
|
||||
pr_err("uid_list: OOM uid=%u\n", uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
d->uid = uid;
|
||||
strscpy(d->package, pkg, KSU_MAX_PACKAGE_NAME);
|
||||
list_add_tail(&d->list, uid_list);
|
||||
++cnt;
|
||||
}
|
||||
|
||||
kfree(buf);
|
||||
pr_info("uid_list: loaded %d entries\n", cnt);
|
||||
return cnt > 0 ? 0 : -ENODATA;
|
||||
}
|
||||
|
||||
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, int signature_index)
|
||||
{
|
||||
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, signature_index: %d\n", pkg, signature_index);
|
||||
|
||||
#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 uid_data *np;
|
||||
|
||||
list_for_each_entry(np, uid_data, list) {
|
||||
if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) {
|
||||
bool is_dynamic = (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2);
|
||||
|
||||
if (is_dynamic) {
|
||||
if (locked_dynamic_manager_uid != KSU_INVALID_UID && locked_dynamic_manager_uid != np->uid) {
|
||||
pr_info("Unlocking previous dynamic manager UID: %d\n", locked_dynamic_manager_uid);
|
||||
ksu_remove_manager(locked_dynamic_manager_uid);
|
||||
locked_dynamic_manager_uid = KSU_INVALID_UID;
|
||||
}
|
||||
} else {
|
||||
if (locked_manager_uid != KSU_INVALID_UID && locked_manager_uid != np->uid) {
|
||||
pr_info("Unlocking previous manager UID: %d\n", locked_manager_uid);
|
||||
ksu_invalidate_manager_uid(); // unlock old one
|
||||
locked_manager_uid = KSU_INVALID_UID;
|
||||
}
|
||||
}
|
||||
|
||||
pr_info("Crowning %s manager: %s (uid=%d, signature_index=%d)\n",
|
||||
is_dynamic ? "dynamic" : "traditional", pkg, np->uid, signature_index);
|
||||
|
||||
if (is_dynamic) {
|
||||
ksu_add_manager(np->uid, signature_index);
|
||||
locked_dynamic_manager_uid = np->uid;
|
||||
|
||||
// If there is no traditional manager, set it to the current UID
|
||||
if (!ksu_is_manager_uid_valid()) {
|
||||
ksu_set_manager_uid(np->uid);
|
||||
locked_manager_uid = np->uid;
|
||||
}
|
||||
} else {
|
||||
ksu_set_manager_uid(np->uid); // throne new UID
|
||||
locked_manager_uid = np->uid; // store locked 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
|
||||
|
||||
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_info("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;
|
||||
}
|
||||
|
||||
static 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, err: (%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 %s directory with %zu errors\n",
|
||||
USER_DATA_PATH, stats.errors_encountered);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int signature_index = -1;
|
||||
bool is_multi_manager = is_dynamic_manager_apk(
|
||||
dirpath, &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);
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
} else if (is_manager_apk(dirpath)) {
|
||||
crown_manager(dirpath, my_ctx->private_data, 0);
|
||||
*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);
|
||||
if (apk_data) {
|
||||
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;
|
||||
}
|
||||
|
||||
extern bool ksu_uid_scanner_enabled;
|
||||
|
||||
void track_throne()
|
||||
{
|
||||
struct list_head uid_list;
|
||||
struct uid_data *np, *n;
|
||||
|
||||
// init uid list head
|
||||
INIT_LIST_HEAD(&uid_list);
|
||||
|
||||
if (ksu_uid_scanner_enabled) {
|
||||
pr_info("Scanning %s directory..\n", KSU_UID_LIST_PATH);
|
||||
if (uid_from_um_list(&uid_list) == 0) {
|
||||
pr_info("Loaded UIDs from %s success\n", KSU_UID_LIST_PATH);
|
||||
} else {
|
||||
pr_warn("%s read failed, falling back to %s\n", KSU_UID_LIST_PATH, USER_DATA_PATH);
|
||||
if (scan_user_data_for_uids(&uid_list) < 0)
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
pr_info("User mode scan disabled, scanning %s\n", USER_DATA_PATH);
|
||||
if (scan_user_data_for_uids(&uid_list) < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
// check if manager UID exists
|
||||
bool manager_exist = false;
|
||||
int current_manager_uid = ksu_get_manager_uid() % 100000;
|
||||
|
||||
list_for_each_entry(np, &uid_list, list) {
|
||||
if (np->uid == current_manager_uid) {
|
||||
manager_exist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!manager_exist && locked_manager_uid != KSU_INVALID_UID) {
|
||||
pr_info("Manager APK removed, unlocking previous UID: %d\n", locked_manager_uid);
|
||||
ksu_invalidate_manager_uid();
|
||||
locked_manager_uid = KSU_INVALID_UID;
|
||||
}
|
||||
|
||||
// Check if the Dynamic Manager exists (only check locked UIDs)
|
||||
bool dynamic_manager_exist = false;
|
||||
if (ksu_is_dynamic_manager_enabled() && locked_dynamic_manager_uid != KSU_INVALID_UID) {
|
||||
list_for_each_entry(np, &uid_list, list) {
|
||||
if (np->uid == locked_dynamic_manager_uid) {
|
||||
dynamic_manager_exist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dynamic_manager_exist) {
|
||||
pr_info("Dynamic manager APK removed, unlocking previous UID: %d\n", locked_dynamic_manager_uid);
|
||||
ksu_remove_manager(locked_dynamic_manager_uid);
|
||||
locked_dynamic_manager_uid = KSU_INVALID_UID;
|
||||
}
|
||||
}
|
||||
|
||||
bool need_search = !manager_exist;
|
||||
if (ksu_is_dynamic_manager_enabled() && !dynamic_manager_exist) {
|
||||
need_search = true;
|
||||
}
|
||||
|
||||
if (need_search) {
|
||||
pr_info("Searching for manager(s)...\n");
|
||||
search_manager("/data/app", 2, &uid_list);
|
||||
pr_info("Manager search finished\n");
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef __KSU_H_UID_OBSERVER
|
||||
#define __KSU_H_UID_OBSERVER
|
||||
|
||||
void ksu_throne_tracker_init();
|
||||
|
||||
void ksu_throne_tracker_exit();
|
||||
|
||||
void track_throne();
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user