This commit is contained in:
tiann
2022-12-09 22:03:03 +08:00
parent fa71c6cfe1
commit 51c84400cf
76 changed files with 2579 additions and 0 deletions

5
kernel/Kconfig Normal file
View File

@@ -0,0 +1,5 @@
config KSU
tristate "KernelSU module"
default y
help
This is the KSU privilege driver for android system.

7
kernel/Makefile Normal file
View File

@@ -0,0 +1,7 @@
obj-y += ksu.o
obj-y += allowlist.o
obj-y += apk_sign.o
obj-y += module_api.o
obj-y += selinux/
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat

211
kernel/allowlist.c Normal file
View File

@@ -0,0 +1,211 @@
#include "linux/uidgid.h"
#include <linux/cpu.h>
#include <linux/memory.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <asm-generic/errno-base.h>
#include <linux/rcupdate.h>
#include <linux/fdtable.h>
#include <linux/fs.h>
#include <linux/fs_struct.h>
#include <linux/namei.h>
#include <linux/delay.h> // msleep
#include "klog.h"
struct perm_data {
struct list_head list;
uid_t uid;
bool allow;
};
static struct list_head allow_list;
#define KERNEL_SU_DIR "/data/adb/kernelsu"
static struct workqueue_struct *ksu_workqueue;
static struct work_struct ksu_save_work;
static struct work_struct ksu_load_work;
bool persistent_allow_list();
bool ksu_allow_uid(uid_t uid, bool allow) {
// find the node first!
struct perm_data* p;
struct list_head* pos;
bool result;
list_for_each(pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("ksu_allow_uid :%d, allow: %d\n", p->uid, p->allow);
if (uid == p->uid) {
p->allow = allow;
result = true;
goto exit;
}
}
// not found, alloc a new node!
p = (struct perm_data*) kmalloc(sizeof(struct perm_data), GFP_KERNEL);
if (!p) {
pr_err("alloc allow node failed.\n");
return false;
}
p->uid = uid;
p->allow = allow;
list_add_tail(&p->list, &allow_list);
result = true;
exit:
persistent_allow_list();
return result;
}
bool ksu_is_allow_uid(uid_t uid) {
struct perm_data* p;
struct list_head* pos;
list_for_each(pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("uid :%d, allow: %d\n", p->uid, p->allow);
if (uid == p->uid) {
return p->allow;
}
}
return false;
}
bool ksu_get_allow_list(int* array, int* length, bool allow) {
struct perm_data* p;
struct list_head* pos;
int i = 0;
list_for_each(pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("uid: %d allow: %d\n", p->uid, p->allow);
if (p->allow == allow) {
array[i++] = p->uid;
}
}
*length = i;
return true;
}
void do_persistent_allow_list(struct work_struct *work)
{
struct perm_data* p;
struct list_head* pos;
loff_t off;
struct file* fp = filp_open("/data/adb/ksu_list", O_WRONLY|O_CREAT, 0644);
if (IS_ERR(fp)) {
pr_err("work creat file failed: %d\n", PTR_ERR(fp));
return;
}
pr_info("work create file success!\n");
list_for_each(pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
pr_info("uid :%d, allow: %d\n", p->uid, p->allow);
kernel_write(fp, &p->uid, sizeof(p->uid), &off);
kernel_write(fp, &p->allow, sizeof(p->allow), &off);
}
filp_close(fp, 0);
}
void do_load_allow_list(struct work_struct *work) {
loff_t off;
ssize_t ret;
struct file* fp;
fp = filp_open("/data/adb/", O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("work open '/data/adb' failed: %d\n", PTR_ERR(fp));
mdelay(2000);
queue_work(ksu_workqueue, &ksu_load_work);
return;
}
filp_close(fp, 0);
// load allowlist now!
fp = filp_open("/data/adb/ksu_list", O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("work open file failed: %d\n", PTR_ERR(fp));
return;
}
pr_info("work open file success!\n");
while (true) {
u32 uid;
bool allow;
ret = kernel_read(fp, &uid, sizeof(uid), &off);
pr_info("kernel read ret: %d, off: %ld\n", ret, off);
if (ret <= 0) {
pr_info("read err: %d\n", ret);
break;
}
ret = kernel_read(fp, &allow, sizeof(allow), &off);
pr_info("load_allow_uid: %d, allow: %d\n", uid, allow);
ksu_allow_uid(uid, allow);
}
filp_close(fp, 0);
}
static int init_work(void) {
ksu_workqueue = alloc_workqueue("kernelsu_work", 0, 0);
INIT_WORK(&ksu_save_work, do_persistent_allow_list);
INIT_WORK(&ksu_load_work, do_load_allow_list);
return 0;
}
// make sure allow list works cross boot
bool persistent_allow_list() {
queue_work(ksu_workqueue, &ksu_save_work);
return true;
}
bool load_allow_list() {
queue_work(ksu_workqueue, &ksu_load_work);
return true;
}
bool ksu_allowlist_init() {
INIT_LIST_HEAD(&allow_list);
init_work();
// load_allow_list();
return true;
}
bool ksu_allowlist_exit() {
destroy_workqueue(ksu_workqueue);
return true;
}

14
kernel/allowlist.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef __KSU_H_ALLOWLIST
#define __KSU_H_ALLOWLIST
bool ksu_allowlist_init();
bool ksu_allowlist_exit();
bool ksu_is_allow_uid(uid_t uid);
bool ksu_allow_uid(uid_t uid, bool allow);
bool ksu_get_allow_list(int* array, int* length, bool allow);
#endif

120
kernel/apk_sign.c Normal file
View File

@@ -0,0 +1,120 @@
#include <linux/fs.h>
#include "apk_sign.h"
#include "klog.h"
static int check_v2_signature(char* path, unsigned expected_size, unsigned expected_hash) {
unsigned char buffer[0x11] = {0};
u32 size4;
u64 size8, size_of_block;
loff_t pos;
int sign = -1;
struct file* fp = filp_open(path, O_RDONLY, 0);
if (IS_ERR(fp)) {
pr_err("open %s error.", path);
return PTR_ERR(fp);
}
sign = 1;
// https://en.wikipedia.org/wiki/Zip_(file_format)#End_of_central_directory_record_(EOCD)
for (int i = 0;; ++i) {
unsigned short n;
pos = generic_file_llseek(fp, -i - 2, SEEK_END);
kernel_read(fp, &n, 2, &pos);
if (n == i) {
pos -= 22;
kernel_read(fp, &size4, 4, &pos);
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
break;
}
}
if (i == 0xffff) {
pr_info("error: cannot find eocd\n");
goto clean;
}
}
pos += 12;
// offset
kernel_read(fp, &size4, 0x4, &pos);
pos = size4 - 0x18;
kernel_read(fp, &size8, 0x8, &pos);
kernel_read(fp, buffer, 0x10, &pos);
if (strcmp((char *) buffer, "APK Sig Block 42")) {
goto clean;
}
pos = size4 - (size8 + 0x8);
kernel_read(fp, &size_of_block, 0x8, &pos);
if (size_of_block != size8) {
goto clean;
}
for (;;) {
uint32_t id;
uint32_t offset;
kernel_read(fp, &size8, 0x8, &pos); // sequence length
if (size8 == size_of_block) {
break;
}
kernel_read(fp, &id, 0x4, &pos); // id
offset = 4;
pr_info("id: 0x%08x\n", id);
if ((id ^ 0xdeadbeefu) == 0xafa439f5u || (id ^ 0xdeadbeefu) == 0x2efed62f) {
kernel_read(fp, &size4, 0x4, &pos); // signer-sequence length
kernel_read(fp, &size4, 0x4, &pos); // signer length
kernel_read(fp, &size4, 0x4, &pos); // signed data length
offset += 0x4 * 3;
kernel_read(fp, &size4, 0x4, &pos); // digests-sequence length
pos += size4;
offset += 0x4 + size4;
kernel_read(fp, &size4, 0x4, &pos); // certificates length
kernel_read(fp, &size4, 0x4, &pos); // certificate length
offset += 0x4 * 2;
#if 0
int hash = 1;
signed char c;
for (unsigned i = 0; i < size4; ++i) {
kernel_read(fp, &c, 0x1, &pos);
hash = 31 * hash + c;
}
offset += size4;
pr_info(" size: 0x%04x, hash: 0x%08x\n", size4, ((unsigned) hash) ^ 0x14131211u);
#else
if (size4 == expected_size) {
int hash = 1;
signed char c;
for (unsigned i = 0; i < size4; ++i) {
kernel_read(fp, &c, 0x1, &pos);
hash = 31 * hash + c;
}
offset += size4;
if ((((unsigned) hash) ^ 0x14131211u) == expected_hash) {
sign = 0;
break;
}
}
// don't try again.
break;
#endif
}
pos += (size8 - offset);
}
clean:
filp_close(fp, 0);
return sign;
}
#define EXPECTED_SIZE 0x023f
#define EXPECTED_HASH 0x9eb9c1ea
int is_manager_apk(char* path) {
return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH);
}

7
kernel/apk_sign.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef __KSU_H_APK_V2_SIGN
#define __KSU_H_APK_V2_SIGN
// return 0 if signature match
int is_manager_apk(char* path);
#endif

9
kernel/klog.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef __KSU_H_KLOG
#define __KSU_H_KLOG
#ifdef pr_fmt
#undef pr_fmt
#define pr_fmt(fmt) "KernelSU: " fmt
#endif
#endif

250
kernel/ksu.c Normal file
View File

@@ -0,0 +1,250 @@
#include "linux/uidgid.h"
#include <linux/cpu.h>
#include <linux/memory.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <asm-generic/errno-base.h>
#include <linux/rcupdate.h>
#include <linux/fdtable.h>
#include <linux/fs.h>
#include <linux/fs_struct.h>
#include <linux/namei.h>
#include <linux/delay.h> // mslepp
#include "selinux/selinux.h"
#include "klog.h"
#include "apk_sign.h"
#include "allowlist.h"
#define KERNEL_SU_VERSION 3
#define KERNEL_SU_OPTION 0xDEADBEEF
#define CMD_GRANT_ROOT 0
#define CMD_BECOME_MANAGER 1
#define CMD_GET_VERSION 2
#define CMD_ALLOW_SU 3
#define CMD_DENY_SU 4
#define CMD_GET_ALLOW_LIST 5
#define CMD_GET_DENY_LIST 6
static void escape_to_root(void) {
struct cred* cred;
cred = (struct cred *)__task_cred(current);
memset(&cred->uid, 0, sizeof(cred->uid));
memset(&cred->gid, 0, sizeof(cred->gid));
memset(&cred->suid, 0, sizeof(cred->suid));
memset(&cred->euid, 0, sizeof(cred->euid));
memset(&cred->egid, 0, sizeof(cred->egid));
memset(&cred->fsuid, 0, sizeof(cred->fsuid));
memset(&cred->fsgid, 0, sizeof(cred->fsgid));
memset(&cred->cap_inheritable, 0xff, sizeof(cred->cap_inheritable));
memset(&cred->cap_permitted, 0xff, sizeof(cred->cap_permitted));
memset(&cred->cap_effective, 0xff, sizeof(cred->cap_effective));
memset(&cred->cap_bset, 0xff, sizeof(cred->cap_bset));
memset(&cred->cap_ambient, 0xff, sizeof(cred->cap_ambient));
// DISABLE SECCOMP
current_thread_info()->flags = 0;
current->seccomp.mode = 0;
current->seccomp.filter = NULL;
setup_selinux();
}
int startswith(char* s, char* prefix) {
return strncmp(s, prefix, strlen(prefix));
}
int endswith(const char *s, const char *t)
{
size_t slen = strlen(s);
size_t tlen = strlen(t);
if (tlen > slen) return 1;
return strcmp(s + slen - tlen, t);
}
static uid_t __manager_uid;
static bool is_manager() {
return __manager_uid == current_uid().val;
}
static bool become_manager() {
if (__manager_uid != 0) {
pr_info("manager already exist: %d\n", __manager_uid);
return true;
}
// list current process's files
struct files_struct *current_files;
struct fdtable *files_table;
int i = 0;
struct path files_path;
char *cwd;
char *buf = (char *)kmalloc(GFP_KERNEL, PATH_MAX);
bool result = false;
current_files = current->files;
files_table = files_fdtable(current_files);
// todo: use iterate_fd
while(files_table->fd[i] != NULL) {
files_path = files_table->fd[i]->f_path;
if (!d_is_reg(files_path.dentry)) {
i++;
continue;
}
cwd = d_path(&files_path, buf, PATH_MAX);
if (startswith(cwd, "/data/app/") == 0 && endswith(cwd, "/base.apk") == 0) {
// we have found the apk!
pr_info("found apk: %s", cwd);
if (is_manager_apk(cwd) == 0) {
// check passed
uid_t uid = current_uid().val;
pr_info("manager uid: %d\n", uid);
__manager_uid = uid;
result = true;
goto clean;
} else {
pr_info("manager signature invalid!");
}
break;
}
i++;
}
clean:
kfree(buf);
return result;
}
static bool is_allow_su() {
uid_t uid = current_uid().val;
if (uid == __manager_uid) {
// we are manager, allow!
return true;
}
if (uid == 0) {
// we are already root, allow!
return true;
}
return ksu_is_allow_uid(uid);
}
static int handler_pre(struct kprobe *p, struct pt_regs *regs) {
struct pt_regs* real_regs = (struct pt_regs*) regs->regs[0];
int option = (int) real_regs->regs[0];
unsigned long arg2 = (unsigned long) real_regs->regs[1];
unsigned long arg3 = (unsigned long) real_regs->regs[2];
unsigned long arg4 = (unsigned long) real_regs->regs[3];
unsigned long arg5 = (unsigned long) real_regs->regs[4];
// if success, we modify the arg5 as result!
u32* result = (u32*) arg5;
u32 reply_ok = KERNEL_SU_OPTION;
if (KERNEL_SU_OPTION != option) {
return 0;
}
pr_info("option: 0x%x, cmd: %ld\n", option, arg2);
if (arg2 == CMD_BECOME_MANAGER) {
// someone wants to be root manager, just check it!
bool success = become_manager();
if (success) {
copy_to_user(result, &reply_ok, sizeof(reply_ok));
}
return 0;
}
if (arg2 == CMD_GRANT_ROOT) {
if (is_allow_su()) {
pr_info("allow root for: %d\n", current_uid());
escape_to_root();
} else {
pr_info("deny root for: %d\n", current_uid());
// add it to deny list!
ksu_allow_uid(current_uid().val, false);
}
return 0;
}
// all other cmds are for 'root manager'
if (!is_manager()) {
pr_info("Only manager can do cmd: %d\n", arg2);
return 0;
}
// we are already manager
if (arg2 == CMD_ALLOW_SU || arg2 == CMD_DENY_SU) {
bool allow = arg2 == CMD_ALLOW_SU;
bool success = false;
uid_t uid = (uid_t) arg3;
success = ksu_allow_uid(uid, allow);
if (success) {
copy_to_user(result, &reply_ok, sizeof(reply_ok));
}
} else if (arg2 == CMD_GET_ALLOW_LIST || arg2 == CMD_GET_DENY_LIST) {
u32 array[128];
u32 array_length;
bool success = ksu_get_allow_list(array, &array_length, arg2 == CMD_GET_ALLOW_LIST);
if (success) {
copy_to_user(arg4, &array_length, sizeof(array_length));
copy_to_user(arg3, array, sizeof(u32) * array_length);
copy_to_user(result, &reply_ok, sizeof(reply_ok));
}
} else if (arg2 == CMD_GET_VERSION) {
u32 version = KERNEL_SU_VERSION;
copy_to_user(arg3, &version, sizeof(version));
}
return 0;
}
static struct kprobe kp = {
.symbol_name = "__arm64_sys_prctl",
.pre_handler = handler_pre,
};
int kernelsu_init(void){
int rc = 0;
ksu_allowlist_init();
rc = register_kprobe(&kp);
return rc;
}
void kernelsu_exit(void){
// should never happen...
unregister_kprobe(&kp);
ksu_allowlist_exit();
}
module_init(kernelsu_init);
module_exit(kernelsu_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("weishu");
MODULE_DESCRIPTION("Android GKI KernelSU");

33
kernel/module_api.c Normal file
View File

@@ -0,0 +1,33 @@
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#define RE_EXPORT_SYMBOL1(ret, func, t1, v1) \
ret ksu_##func(t1 v1) { \
return func(v1); \
} \
EXPORT_SYMBOL(ksu_##func); \
#define RE_EXPORT_SYMBOL2(ret, func, t1, v1, t2, v2) \
ret ksu_##func(t1 v1, t2 v2) { \
return func(v1, v2); \
} \
EXPORT_SYMBOL(ksu_##func); \
RE_EXPORT_SYMBOL1(unsigned long, kallsyms_lookup_name, const char*, name)
// RE_EXPORT_SYMBOL2(int, register_kprobe, struct kprobe *, p)
// RE_EXPORT_SYMBOL2(void, unregister_kprobe, struct kprobe *, p)
// RE_EXPORT_SYMBOL2(int, register_kprobe, struct kprobe *, p)
// RE_EXPORT_SYMBOL2(void, unregister_kprobe, struct kprobe *, p)
// int ksu_register_kprobe(struct kprobe *p);
// void ksu_unregister_kprobe(struct kprobe *p);
// int ksu_register_kprobes(struct kprobe **kps, int num);
// void ksu_unregister_kprobes(struct kprobe **kps, int num);
// int ksu_register_kretprobe(struct kretprobe *rp);
// void unregister_kretprobe(struct kretprobe *rp);
// int register_kretprobes(struct kretprobe **rps, int num);
// void unregister_kretprobes(struct kretprobe **rps, int num);

3
kernel/selinux/Makefile Normal file
View File

@@ -0,0 +1,3 @@
obj-y += selinux.o
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion

View File

@@ -0,0 +1 @@
#include "../../../security/selinux/av_permissions.h"

1
kernel/selinux/flask.h Normal file
View File

@@ -0,0 +1 @@
#include "../../../security/selinux/flask.h"

View File

@@ -0,0 +1 @@
#include "../../../security/selinux/include/security.h"

86
kernel/selinux/selinux.c Normal file
View File

@@ -0,0 +1,86 @@
#include <linux/cpu.h>
#include <linux/memory.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include "../../../security/selinux/ss/sidtab.h"
#include "../../../security/selinux/ss/services.h"
#include "../../../security/selinux/include/objsec.h"
#include "selinux.h"
#include "../klog.h"
#define KERNEL_SU_DOMAIN "u:r:su:s0"
static int transive_to_domain(const char* domain) {
struct cred* cred;
struct task_security_struct* tsec;
u32 sid;
int error;
cred = (struct cred *)__task_cred(current);
tsec = cred->security;
if (!tsec) {
pr_err("tsec == NULL!\n");
return -1;
}
error = security_secctx_to_secid(domain, strlen(domain), &sid);
pr_info("error: %d, sid: %d\n", error, sid);
if (!error) {
tsec->sid = sid;
tsec->create_sid = 0;
tsec->keycreate_sid = 0;
tsec->sockcreate_sid = 0;
}
return error;
}
static int set_domain_permissive() {
u32 sid;
struct selinux_policy *policy;
struct sidtab_entry *entry;
struct ebitmap *permissive;
sid = current_sid();
pr_info("set sid (%d) to permissive", sid);
rcu_read_lock();
policy = rcu_dereference(selinux_state.policy);
entry = sidtab_search_entry(policy->sidtab, sid);
if (entry == NULL){
pr_info("entry == NULL");
rcu_read_unlock();
return -EFAULT;
}
// FIXME: keep mls
permissive = &(policy->policydb.permissive_map);
ebitmap_set_bit(permissive, entry->context.type, 1);
rcu_read_unlock();
return 0;
}
static bool is_domain_permissive;
void setup_selinux() {
if (transive_to_domain(KERNEL_SU_DOMAIN)) {
pr_err("transive domain failed.");
return;
}
if (!is_domain_permissive) {
if (set_domain_permissive() == 0) {
is_domain_permissive = true;
}
}
}

8
kernel/selinux/selinux.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __KSU_H_SELINUX
#define __KSU_H_SELINUX
void setup_selinux();
#endif