Hook improvements (take 2) (#563)

Hi @tiann.

Thanks for the great project, I had great fun playing around with it.

This PR mainly tries to further minimize the possible delays caused by
KernelSU hooking.

There are 3 major changes:
- Processes with 0 < UID < 2000 are blocked straight-up before going
through the allow_list.
I don't see any need for such processes to be interested in root, and
this allows returning early before going through a more expensive
lookup.
If there's an expected breakage due to this change, I'll remove it. Let
me know.
- A page-sized (4K) bitmap is added.
This allows O(1) lookup for UID <= 32767.
This speeds up `ksu_is_allow_uid()` by about 4.8x by sacrificing a 4K
memory. IMHO, a good trade-off.
Most notably, this reduces the 99.999% result previously from worrying
milliseconds scale to microseconds scale.
For UID > 32767, another page-sized (4K) sequential array is used to
cache allow_list.

Compared to the previous PR #557, this new approach gives another nice
25% performance boost in average, 63-96% boost in worst cases.

Benchmark results are available at
https://docs.google.com/spreadsheets/d/1w_tO1zRLPNMFRer49pL1TQfL6ndEhilRrDU1XFIcWXY/edit?usp=sharing

Thanks!

---------

Signed-off-by: Juhyung Park <qkrwngud825@gmail.com>
This commit is contained in:
Juhyung Park
2023-06-16 20:53:15 +09:00
committed by GitHub
parent c697398893
commit bd8434f4f4
21 changed files with 233 additions and 102 deletions

View File

@@ -29,6 +29,38 @@ static DEFINE_MUTEX(allowlist_mutex);
static struct root_profile default_root_profile;
static struct non_root_profile default_non_root_profile;
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE);
static int allow_list_pointer __read_mostly = 0;
static void remove_uid_from_arr(uid_t uid)
{
int *temp_arr;
int i, j;
if (allow_list_pointer == 0)
return;
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
if (temp_arr == NULL) {
pr_err("%s: unable to allocate memory\n", __func__);
return;
}
for (i = j = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
continue;
temp_arr[j++] = allow_list_arr[i];
}
allow_list_pointer = j;
for (; j < ARRAY_SIZE(allow_list_arr); j++)
temp_arr[j] = -1;
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
kfree(temp_arr);
}
static void init_default_profiles()
{
default_root_profile.uid = 0;
@@ -51,6 +83,9 @@ struct perm_data {
static struct list_head allow_list;
static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE);
#define BITMAP_UID_MAX ((sizeof(allow_list_bitmap) * BITS_PER_BYTE) - 1)
#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"
static struct work_struct ksu_save_work;
@@ -110,6 +145,11 @@ static bool profile_valid(struct app_profile *profile)
return false;
}
if (profile->current_uid < 2000) {
pr_err("uid lower than 2000 is unsupported: %d\n", profile->current_uid);
return false;
}
if (profile->version < KSU_APP_PROFILE_VER) {
pr_info("Unsupported profile version: %d\n", profile->version);
return false;
@@ -147,7 +187,7 @@ bool ksu_set_app_profile(struct app_profile *profile, bool persist)
// found it, just override it all!
memcpy(&p->profile, profile, sizeof(*profile));
result = true;
goto exit;
goto out;
}
}
@@ -170,9 +210,31 @@ bool ksu_set_app_profile(struct app_profile *profile, bool persist)
profile->nrp_config.profile.umount_modules);
}
list_add_tail(&p->list, &allow_list);
out:
if (profile->current_uid <= BITMAP_UID_MAX) {
if (profile->allow_su)
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
else
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
} else {
if (profile->allow_su) {
/*
* 1024 apps with uid higher than BITMAP_UID_MAX
* registered to request superuser?
*/
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
pr_err("too many apps registered\n");
WARN_ON(1);
return false;
}
allow_list_arr[allow_list_pointer++] = profile->current_uid;
} else {
remove_uid_from_arr(profile->current_uid);
}
}
result = true;
exit:
// check if the default profiles is changed, cache it to a single struct to accelerate access.
if (unlikely(!strcmp(profile->key, "$"))) {
// set default non root profile
@@ -192,21 +254,26 @@ exit:
return result;
}
bool ksu_is_allow_uid(uid_t uid)
bool __ksu_is_allow_uid(uid_t uid)
{
struct perm_data *p = NULL;
struct list_head *pos = NULL;
int i;
if (uid == 0) {
if (unlikely(uid == 0)) {
// already root, but only allow our domain.
return is_ksu_domain();
}
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->profile.current_uid) {
return p->profile.allow_su;
if (uid < 2000) {
// do not bother going through the list if it's system
return false;
}
if (likely(uid <= BITMAP_UID_MAX)) {
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
} else {
for (i = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
return true;
}
}
@@ -386,6 +453,9 @@ void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data)
modified = true;
pr_info("prune uid: %d\n", uid);
list_del(&np->list);
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
remove_uid_from_arr(uid);
smp_mb();
kfree(np);
}
}
@@ -409,6 +479,14 @@ bool ksu_load_allow_list(void)
void ksu_allowlist_init(void)
{
int i;
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
allow_list_arr[i] = -1;
INIT_LIST_HEAD(&allow_list);
INIT_WORK(&ksu_save_work, do_save_allow_list);