diff --git a/kernel/allowlist.c b/kernel/allowlist.c index 2a76a1b0..1e37f3fe 100644 --- a/kernel/allowlist.c +++ b/kernel/allowlist.c @@ -1,5 +1,7 @@ +#include "ksu.h" #include "linux/delay.h" #include "linux/fs.h" +#include "linux/gfp.h" #include "linux/kernel.h" #include "linux/list.h" #include "linux/printk.h" @@ -8,16 +10,32 @@ #include "klog.h" // IWYU pragma: keep #include "selinux/selinux.h" #include "kernel_compat.h" +#include "allowlist.h" #define FILE_MAGIC 0x7f4b5355 // ' KSU', u32 -#define FILE_FORMAT_VERSION 1 // u32 +#define FILE_FORMAT_VERSION 2 // u32 static DEFINE_MUTEX(allowlist_mutex); +// default root identify +static struct root_identity default_root_identity; +static bool default_umount_modules = true; + +static void init_root_identity() +{ + default_root_identity.uid = 0; + default_root_identity.gid = 0; + default_root_identity.groups_count = 1; + default_root_identity.groups[0] = 0; + memset(&default_root_identity.capabilities, 0xff, + sizeof(default_root_identity.capabilities)); + default_root_identity.namespaces = 0; + strcpy(default_root_identity.selinux_domain, "su"); +} + struct perm_data { struct list_head list; - uid_t uid; - bool allow; + struct app_profile profile; }; static struct list_head allow_list; @@ -36,20 +54,65 @@ void ksu_show_allow_list(void) pr_info("ksu_show_allow_list"); 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); + pr_info("uid :%d, allow: %d\n", p->profile.current_uid, + p->profile.allow_su); } } -bool ksu_allow_uid(uid_t uid, bool allow, bool persist) +static void ksu_grant_root_to_shell() +{ + struct app_profile profile = { + .allow_su = true, + .current_uid = 2000, + }; + strcpy(profile.key, "com.android.shell"); + ksu_set_app_profile(&profile, false); +} + +bool ksu_get_app_profile(struct app_profile *profile) +{ + struct perm_data *p = NULL; + struct list_head *pos = NULL; + bool found = false; + + list_for_each (pos, &allow_list) { + p = list_entry(pos, struct perm_data, list); + if (!strcmp(profile->key, p->profile.key)) { + // found it, override it with ours + memcpy(profile, &p->profile, sizeof(*profile)); + found = true; + goto exit; + } + } + + if (!found) { + // don't found, fill it with default profile + if (profile->allow_su) { + profile->root_profile.use_default = true; + memcpy(&profile->root_profile.identity, + &default_root_identity, + sizeof(default_root_identity)); + } else { + profile->non_root_profile.use_default = true; + profile->non_root_profile.umount_modules = + default_umount_modules; + } + } +exit: + return found; +} + +bool ksu_set_app_profile(struct app_profile *profile, bool persist) { - // find the node first! struct perm_data *p = NULL; struct list_head *pos = NULL; bool result = false; + list_for_each (pos, &allow_list) { p = list_entry(pos, struct perm_data, list); - if (uid == p->uid) { - p->allow = allow; + if (!strcmp(profile->key, p->profile.key)) { + // found it, just override it all! + memcpy(&p->profile, profile, sizeof(*profile)); result = true; goto exit; } @@ -58,14 +121,13 @@ bool ksu_allow_uid(uid_t uid, bool allow, bool persist) // 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"); + pr_err("ksu_set_app_profile alloc failed\n"); return false; } - p->uid = uid; - p->allow = allow; - - pr_info("allow_uid: %d, allow: %d", uid, allow); + memcpy(&p->profile, profile, sizeof(*profile)); + pr_info("set app profile, key: %s, uid: %d\n", profile->key, + profile->current_uid); list_add_tail(&p->list, &allow_list); result = true; @@ -89,8 +151,8 @@ bool ksu_is_allow_uid(uid_t uid) list_for_each (pos, &allow_list) { p = list_entry(pos, struct perm_data, list); // pr_info("is_allow_uid uid :%d, allow: %d\n", p->uid, p->allow); - if (uid == p->uid) { - return p->allow; + if (uid == p->profile.current_uid) { + return p->profile.allow_su; } } @@ -105,8 +167,8 @@ bool ksu_get_allow_list(int *array, int *length, bool allow) list_for_each (pos, &allow_list) { p = list_entry(pos, struct perm_data, list); // pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow); - if (p->allow == allow) { - array[i++] = p->uid; + if (p->profile.allow_su == allow) { + array[i++] = p->profile.allow_su; } } *length = i; @@ -114,7 +176,7 @@ bool ksu_get_allow_list(int *array, int *length, bool allow) return true; } -void do_persistent_allow_list(struct work_struct *work) +void do_save_allow_list(struct work_struct *work) { u32 magic = FILE_MAGIC; u32 version = FILE_FORMAT_VERSION; @@ -131,7 +193,8 @@ void do_persistent_allow_list(struct work_struct *work) } // store magic and version - if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) { + if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != + sizeof(magic)) { pr_err("save_allow_list write magic failed.\n"); goto exit; } @@ -144,10 +207,12 @@ void do_persistent_allow_list(struct work_struct *work) list_for_each (pos, &allow_list) { p = list_entry(pos, struct perm_data, list); - pr_info("save allow list uid :%d, allow: %d\n", p->uid, - p->allow); - ksu_kernel_write_compat(fp, &p->uid, sizeof(p->uid), &off); - ksu_kernel_write_compat(fp, &p->allow, sizeof(p->allow), &off); + pr_info("save allow list, name: %s uid :%d, allow: %d\n", + p->profile.key, p->profile.current_uid, + p->profile.allow_su); + + ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile), + &off); } exit: @@ -163,27 +228,21 @@ void do_load_allow_list(struct work_struct *work) u32 version; KWORKER_INSTALL_KEYRING(); +#ifdef CONFIG_KSU_DEBUG + // always allow adb shell by default + ksu_grant_root_to_shell(); +#endif // load allowlist now! fp = filp_open(KERNEL_SU_ALLOWLIST, O_RDONLY, 0); if (IS_ERR(fp)) { -#ifdef CONFIG_KSU_DEBUG - int errno = PTR_ERR(fp); - if (errno == -ENOENT) { - ksu_allow_uid(2000, true, - true); // allow adb shell by default - } else { - pr_err("load_allow_list open file failed: %ld\n", - PTR_ERR(fp)); - } -#else pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp)); -#endif return; } // verify magic - if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) || + if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != + sizeof(magic) || magic != FILE_MAGIC) { pr_err("allowlist file invalid: %d!\n", magic); goto exit; @@ -198,18 +257,19 @@ void do_load_allow_list(struct work_struct *work) pr_info("allowlist version: %d\n", version); while (true) { - u32 uid; - bool allow = false; - ret = ksu_kernel_read_compat(fp, &uid, sizeof(uid), &off); + struct app_profile profile; + + ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile), + &off); + if (ret <= 0) { pr_info("load_allow_list read err: %zd\n", ret); break; } - ret = ksu_kernel_read_compat(fp, &allow, sizeof(allow), &off); - pr_info("load_allow_uid: %d, allow: %d\n", uid, allow); - - ksu_allow_uid(uid, allow, false); + pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", + profile.key, profile.current_uid, profile.allow_su); + ksu_set_app_profile(&profile, false); } exit: @@ -226,7 +286,7 @@ void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data) // TODO: use RCU! mutex_lock(&allowlist_mutex); list_for_each_entry_safe (np, n, &allow_list, list) { - uid_t uid = np->uid; + uid_t uid = np->profile.current_uid; if (!is_uid_exist(uid, data)) { modified = true; pr_info("prune uid: %d\n", uid); @@ -256,8 +316,11 @@ void ksu_allowlist_init(void) { INIT_LIST_HEAD(&allow_list); - INIT_WORK(&ksu_save_work, do_persistent_allow_list); + INIT_WORK(&ksu_save_work, do_save_allow_list); INIT_WORK(&ksu_load_work, do_load_allow_list); + + // init default_root_identity, which is used for root identity when root profile is not set. + init_root_identity(); } void ksu_allowlist_exit(void) @@ -265,7 +328,7 @@ void ksu_allowlist_exit(void) struct perm_data *np = NULL; struct perm_data *n = NULL; - do_persistent_allow_list(NULL); + do_save_allow_list(NULL); // free allowlist mutex_lock(&allowlist_mutex); diff --git a/kernel/allowlist.h b/kernel/allowlist.h index 8783f2d9..beb9c995 100644 --- a/kernel/allowlist.h +++ b/kernel/allowlist.h @@ -2,6 +2,7 @@ #define __KSU_H_ALLOWLIST #include "linux/types.h" +#include "ksu.h" void ksu_allowlist_init(void); @@ -13,10 +14,10 @@ void ksu_show_allow_list(void); bool ksu_is_allow_uid(uid_t uid); -bool ksu_allow_uid(uid_t uid, bool allow, bool persist); - bool ksu_get_allow_list(int *array, int *length, bool allow); void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data); +bool ksu_get_app_profile(struct app_profile *); +bool ksu_set_app_profile(struct app_profile *, bool persist); #endif \ No newline at end of file diff --git a/kernel/core_hook.c b/kernel/core_hook.c index 1fbacfb7..6e11e434 100644 --- a/kernel/core_hook.c +++ b/kernel/core_hook.c @@ -186,7 +186,8 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, if (userId == 0) { prefix = "/data/data"; } else { - snprintf(prefixTmp, sizeof(prefixTmp), "/data/user/%d", userId); + snprintf(prefixTmp, sizeof(prefixTmp), "/data/user/%d", + userId); prefix = prefixTmp; } @@ -227,10 +228,6 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { pr_err("grant_root: prctl reply error\n"); } - } else { - pr_info("deny root for: %d\n", current_uid()); - // add it to deny list! - ksu_allow_uid(current_uid().val, false, true); } return 0; } @@ -332,17 +329,37 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3, } // 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, true); - if (success) { + if (arg2 == CMD_GET_APP_PROFILE) { + struct app_profile profile; + if (copy_from_user(&profile, arg3, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + + ksu_get_app_profile(&profile); + if (copy_to_user(arg3, &profile, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { + pr_err("prctl reply error, cmd: %d\n", arg2); + } + return 0; + } + + if (arg2 == CMD_SET_APP_PROFILE) { + struct app_profile profile; + if (copy_from_user(&profile, arg3, sizeof(profile))) { + pr_err("copy profile failed\n"); + return 0; + } + + // todo: validate the params + if (ksu_set_app_profile(&profile, true)) { if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { pr_err("prctl reply error, cmd: %d\n", arg2); } } - ksu_show_allow_list(); return 0; } @@ -366,7 +383,8 @@ static bool should_umount(struct path *path) } if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) { - pr_info("ignore global mnt namespace process: %d\n", current_uid().val); + pr_info("ignore global mnt namespace process: %d\n", + current_uid().val); return false; } diff --git a/kernel/ksu.h b/kernel/ksu.h index 3ace692d..aefff8ff 100644 --- a/kernel/ksu.h +++ b/kernel/ksu.h @@ -1,13 +1,17 @@ #ifndef __KSU_H_KSU #define __KSU_H_KSU +#include "linux/capability.h" #include "linux/workqueue.h" #ifndef KSU_GIT_VERSION -#warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!" +#warning \ + "KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!" #define KERNEL_SU_VERSION (16) #else -#define KERNEL_SU_VERSION (10000 + KSU_GIT_VERSION + 200) // major * 10000 + git version + 200 for historical reasons +#define KERNEL_SU_VERSION \ + (10000 + KSU_GIT_VERSION + \ + 200) // major * 10000 + git version + 200 for historical reasons #endif #define KERNEL_SU_OPTION 0xDEADBEEF @@ -22,10 +26,51 @@ #define CMD_REPORT_EVENT 7 #define CMD_SET_SEPOLICY 8 #define CMD_CHECK_SAFEMODE 9 +#define CMD_GET_APP_PROFILE 10 +#define CMD_SET_APP_PROFILE 11 #define EVENT_POST_FS_DATA 1 #define EVENT_BOOT_COMPLETED 2 +#define KSU_MAX_PACKAGE_NAME 256 +// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. +#define KSU_MAX_GROUPS 32 +#define KSU_SELINUX_DOMAIN 64 + +struct root_identity { + int32_t uid; + int32_t gid; + + int32_t groups[KSU_MAX_GROUPS]; + int32_t groups_count; + + kernel_cap_t capabilities; + char selinux_domain[KSU_SELINUX_DOMAIN]; + + int32_t namespaces; +}; + +struct app_profile { + // this is usually the package of the app, but can be other value for special apps + char key[KSU_MAX_PACKAGE_NAME]; + int32_t current_uid; + bool allow_su; + + union { + struct { + bool use_default; + char template_name[KSU_MAX_PACKAGE_NAME]; + + struct root_identity identity; + } root_profile; + + struct { + bool use_default; + bool umount_modules; + } non_root_profile; + }; +}; + bool ksu_queue_work(struct work_struct *work); static inline int startswith(char *s, char *prefix)