diff --git a/.github/workflows/build-lkm.yml b/.github/workflows/build-lkm.yml index 2da532cc0659..e5ed7eac475e 100644 --- a/.github/workflows/build-lkm.yml +++ b/.github/workflows/build-lkm.yml @@ -5,6 +5,7 @@ on: jobs: build-lkm: strategy: + fail-fast: false matrix: kmi: - android12-5.10 diff --git a/.github/workflows/build-manager.yml b/.github/workflows/build-manager.yml index 9031b92da45d..e3cd051d5e77 100644 --- a/.github/workflows/build-manager.yml +++ b/.github/workflows/build-manager.yml @@ -7,19 +7,24 @@ on: - '.github/workflows/build-manager.yml' - '.github/workflows/build-lkm.yml' - '.github/workflows/ksud.yml' + - '.github/workflows/ddl-lkm.yml' - 'manager/**' - 'kernel/**' - 'userspace/**' + - 'scripts/ksubot.py' pull_request: branches: [ "main", "dev" ] paths: - '.github/workflows/build-manager.yml' - '.github/workflows/build-lkm.yml' - '.github/workflows/ksud.yml' + - '.github/workflows/ddl-lkm.yml' - 'manager/**' - 'kernel/**' - 'userspace/**' + - 'scripts/ksubot.py' workflow_call: + workflow_dispatch: jobs: build-lkm: @@ -79,14 +84,14 @@ jobs: - name: Setup need_upload id: need_upload run: | - if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then + if [ ! -z "${{ secrets.BOT_TOKEN }}" ] && [ "${{ github.event_name }}" != "workflow_dispatch" ]; then echo "UPLOAD=true" >> $GITHUB_OUTPUT else echo "UPLOAD=false" >> $GITHUB_OUTPUT fi - name: Write key - if: ${{ ( github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' )) || github.ref_type == 'tag' }} + if: ${{ github.event_name != 'pull_request' || github.ref_type == 'tag' }} run: | if [ ! -z "${{ secrets.KEYSTORE }}" ]; then { @@ -95,7 +100,7 @@ jobs: echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}' echo KEYSTORE_FILE='key.jks' } >> gradle.properties - echo ${{ secrets.KEYSTORE }} | base64 -d > key.jks + echo "${{ secrets.KEYSTORE }}" | base64 -d > key.jks fi - name: Setup Java @@ -157,11 +162,10 @@ jobs: - name: Upload to telegram if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' env: - CHAT_ID: ${{ secrets.CHAT_ID }} + CHAT_ID: ${{ vars.CHAT_ID }} BOT_TOKEN: ${{ secrets.BOT_TOKEN }} - MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }} - COMMIT_MESSAGE: ${{ github.event.head_commit.message }} - COMMIT_URL: ${{ github.event.head_commit.url }} + MESSAGE_THREAD_ID: ${{ vars.MESSAGE_THREAD_ID }} + GITHUB_EVENT: ${{ toJson(github.event) }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} TITLE: Manager BRANCH: ${{ github.ref_name }} diff --git a/.github/workflows/ddk-lkm.yml b/.github/workflows/ddk-lkm.yml index 4e8e041b39ff..b51722720d91 100644 --- a/.github/workflows/ddk-lkm.yml +++ b/.github/workflows/ddk-lkm.yml @@ -31,6 +31,21 @@ jobs: cd kernel + if [[ "${{ inputs.kmi }}" == *"5.1"* ]]; then + echo "=== fix modpost ===" + sed -i '/s->module = exp->module;/s/^/\/\//' /opt/ddk/src/*/scripts/mod/modpost.c + pushd /opt/ddk/kdir/* + CMD_O=$(grep 'cmd_scripts/mod/modpost.o := ' scripts/mod/.modpost.o.cmd | sed 's/cmd_scripts\/mod\/modpost.o := //g') + CMD_2=$(grep 'cmd_scripts/mod/file2alias.o := ' scripts/mod/.file2alias.o.cmd | sed 's/cmd_scripts\/mod\/file2alias.o := //g') + CMD_3=$(grep 'cmd_scripts/mod/sumversion.o := ' scripts/mod/.sumversion.o.cmd | sed 's/cmd_scripts\/mod\/sumversion.o := //g') + CMD_L=$(grep 'cmd_scripts/mod/modpost := ' scripts/mod/.modpost.cmd | sed 's/cmd_scripts\/mod\/modpost := //g') + $CMD_O + $CMD_2 + $CMD_3 + $CMD_L + popd + fi + echo "=== Building kernelsu.ko for KMI: ${{ inputs.kmi }} ===" CONFIG_KSU=m CC=clang make diff --git a/.gitignore b/.gitignore index 9e7e95876351..79d923791aec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .idea -.vscode +/.vscode CLAUDE.md AGENTS.md \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 03a9346ea904..7a9148fa8a54 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,10 @@ **English** | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [한국어](README_KR.md) | [Polski](README_PL.md) | [Português (Brasil)](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md) | [Tiếng Việt](README_VI.md) | [Indonesia](README_ID.md) | [עברית](README_IW.md) | [हिंदी](README_IN.md) | [Italiano](README_IT.md) -# KernelSU +# My KernelSU logo -A kernel-based root solution for Android devices. +A [KernelSU](https://github.com/tiann/KernelSU/commit/423eefe38598ba8090bdc43144e7d5d25a412031)-based root solution for Android devices. [![Latest release](https://img.shields.io/github/v/release/tiann/KernelSU?label=Release&logo=github)](https://github.com/tiann/KernelSU/releases/latest) [![Weblate](https://img.shields.io/badge/Localization-Weblate-teal?logo=weblate)](https://hosted.weblate.org/engage/kernelsu) diff --git a/kernel/.gitignore b/kernel/.gitignore index 15547df1c7de..85fecbf8cdb4 100644 --- a/kernel/.gitignore +++ b/kernel/.gitignore @@ -12,7 +12,7 @@ compile_commands.json *.mod.c *.symvers* *.order -.*.ko.cmd +.*.cmd .tmp_versions/ libs/ obj/ diff --git a/kernel/.vscode/generate_compdb.py b/kernel/.vscode/generate_compdb.py index 8866913868f4..22c5a0007d8c 100755 --- a/kernel/.vscode/generate_compdb.py +++ b/kernel/.vscode/generate_compdb.py @@ -44,6 +44,8 @@ def gen_compile_commands(cmd_file_search_path, out_dir): if not cmd_file_search_path: cmd_file_search_path = [out_dir] + else: + cmd_file_search_path += [out_dir] cmd_files = [] for search_path in cmd_file_search_path: diff --git a/kernel/Kbuild b/kernel/Kbuild index 70511859bd85..5f7157e62ce0 100644 --- a/kernel/Kbuild +++ b/kernel/Kbuild @@ -14,7 +14,10 @@ kernelsu-objs += feature.o kernelsu-objs += ksud.o kernelsu-objs += seccomp_cache.o kernelsu-objs += file_wrapper.o -kernelsu-objs += util.o +kernelsu-objs += pte.o +kernelsu-objs += patch.o +kernelsu-objs += inline_hook.o +kernelsu-objs += trampoline.o kernelsu-objs += selinux/selinux.o kernelsu-objs += selinux/sepolicy.o @@ -60,12 +63,35 @@ $(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git repo ccflags-y += -DKSU_VERSION=16 endif +KSU_NEW_DCACHE_FLUSH := $(shell grep -q __flush_dcache_area $(srctree)/arch/arm64/include/asm/cacheflush.h ; echo $$?) +$(info -- KSU_NEW_DCACHE_FLUSH: $(KSU_NEW_DCACHE_FLUSH)) + +KSU_MTE_SYNC_TAGS_DEF := $(shell grep 'void mte_sync_tags' $(srctree)/arch/arm64/include/asm/mte.h | grep -v 'static inline') +# 6.6 +KSU_MTE_SYNC_TAGS_NR_PAGES := $(shell echo '$(KSU_MTE_SYNC_TAGS_DEF)' | grep -q 'nr_pages' && echo 1 || echo 0) +# 6.1 +KSU_MTE_SYNC_TAGS_NORMAL := $(shell echo '$(KSU_MTE_SYNC_TAGS_DEF)' | grep -q '(pte_t pte)' && echo 1 || echo 0) +# 13-5.10 +KSU_MTE_SYNC_TAGS_OLD_PTE := $(shell echo '$(KSU_MTE_SYNC_TAGS_DEF)' | grep -q 'old_pte' && echo 1 || echo 0) +# 12-5.10 +KSU_MTE_SYNC_TAGS_PTEP := $(shell echo '$(KSU_MTE_SYNC_TAGS_DEF)' | grep -q '*ptep' && echo 1 || echo 0) +KSU_DEF_MTE_SYNC_TAGS := 'extern $(KSU_MTE_SYNC_TAGS_DEF)' + +$(info -- KSU_DEF_MTE_SYNC_TAGS: $(KSU_DEF_MTE_SYNC_TAGS)) +$(info -- KSU_MTE_SYNC_TAGS_NR_PAGES: $(KSU_MTE_SYNC_TAGS_NR_PAGES)) +$(info -- KSU_MTE_SYNC_TAGS_PTEP: $(KSU_MTE_SYNC_TAGS_PTEP)) +$(info -- KSU_MTE_SYNC_TAGS_OLD_PTE: $(KSU_MTE_SYNC_TAGS_OLD_PTE)) +$(info -- KSU_MTE_SYNC_TAGS_NORMAL: $(KSU_MTE_SYNC_TAGS_NORMAL)) + +OFFICIAL_EXPECTED_SIZE := 0x033b +OFFICIAL_EXPECTED_HASH := c371061b19d8c7d7d6133c6a9bafe198fa944e50c1b31c9d8daa8d7f1fc2d2d6 + ifndef KSU_EXPECTED_SIZE -KSU_EXPECTED_SIZE := 0x033b +KSU_EXPECTED_SIZE := 384 endif ifndef KSU_EXPECTED_HASH -KSU_EXPECTED_HASH := c371061b19d8c7d7d6133c6a9bafe198fa944e50c1b31c9d8daa8d7f1fc2d2d6 +KSU_EXPECTED_HASH := 7e0c6d7278a3bb8e364e0fcba95afaf3666cf5ff3c245a3b63c8833bd0445cc4 endif ifdef KSU_MANAGER_PACKAGE @@ -78,6 +104,16 @@ $(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH)) ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE) ccflags-y += -DEXPECTED_HASH=\"$(KSU_EXPECTED_HASH)\" +ccflags-y += -DOFFICIAL_EXPECTED_SIZE=$(OFFICIAL_EXPECTED_SIZE) +ccflags-y += -DOFFICIAL_EXPECTED_HASH=\"$(OFFICIAL_EXPECTED_HASH)\" + +ccflags-y += -DKSU_NEW_DCACHE_FLUSH=$(KSU_NEW_DCACHE_FLUSH) + +ccflags-y += -DKSU_DEF_MTE_SYNC_TAGS=$(KSU_DEF_MTE_SYNC_TAGS) +ccflags-y += -DKSU_MTE_SYNC_TAGS_NR_PAGES=$(KSU_MTE_SYNC_TAGS_NR_PAGES) +ccflags-y += -DKSU_MTE_SYNC_TAGS_PTEP=$(KSU_MTE_SYNC_TAGS_PTEP) +ccflags-y += -DKSU_MTE_SYNC_TAGS_OLD_PTE=$(KSU_MTE_SYNC_TAGS_OLD_PTE) +ccflags-y += -DKSU_MTE_SYNC_TAGS_NORMAL=$(KSU_MTE_SYNC_TAGS_NORMAL) ccflags-y += -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat -Wno-missing-prototypes ccflags-y += -Wno-declaration-after-statement -Wno-unused-function diff --git a/kernel/Makefile b/kernel/Makefile index 28e5a3a802d6..4cda37033424 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -7,7 +7,7 @@ $(info -- MDIR: $(MDIR)) .PHONY: all compdb clean format check-format all: check_symbol - make -C $(KDIR) M=$(MDIR) modules + CONFIG_KSU=m make -C $(KDIR) M=$(MDIR) modules ./check_symbol kernelsu.ko $(KDIR)/vmlinux compdb: python3 $(MDIR)/.vscode/generate_compdb.py -O $(KDIR) $(MDIR) diff --git a/kernel/allowlist.c b/kernel/allowlist.c index 25de2a7ffd9a..e45c5a8d6d72 100644 --- a/kernel/allowlist.c +++ b/kernel/allowlist.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "klog.h" // IWYU pragma: keep #include "ksu.h" @@ -35,26 +37,9 @@ 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 i; - for (i = 0; i < allow_list_pointer; i++) { - if (allow_list_arr[i] == uid) { - int remaining = allow_list_pointer - 1 - i; - if (remaining > 0) { - memmove(&allow_list_arr[i], &allow_list_arr[i + 1], - remaining * sizeof(allow_list_arr[0])); - } - allow_list_pointer--; - allow_list_arr[allow_list_pointer] = -1; - return; - } - } -} +// protected by rcu +static struct root_profile *current_default_root_profile; +static struct non_root_profile *current_default_non_root_profile; static void init_default_profiles() { @@ -68,21 +53,24 @@ static void init_default_profiles() sizeof(default_root_profile.capabilities.effective)); default_root_profile.namespaces = KSU_NS_INHERITED; strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN); + current_default_root_profile = &default_root_profile; // This means that we will umount modules by default! default_non_root_profile.umount_modules = true; + current_default_non_root_profile = &default_non_root_profile; } struct perm_data { - struct list_head list; + struct hlist_node list; struct rcu_head rcu; + struct kref ref; struct app_profile profile; }; -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) +// protected by rcu +#define ALLOW_LIST_BITS 8 +static DEFINE_HASHTABLE(allow_list, ALLOW_LIST_BITS); +static u16 allow_list_count = 0; #define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist" @@ -90,11 +78,12 @@ void ksu_persistent_allow_list(void); void ksu_show_allow_list(void) { + int i; struct perm_data *p = NULL; pr_info("ksu_show_allow_list\n"); rcu_read_lock(); - list_for_each_entry_rcu (p, &allow_list, list) { - pr_info("uid :%d, allow: %d\n", p->profile.current_uid, + hash_for_each_rcu (allow_list, i, p, list) { + pr_info("uid :%d, allow: %d\n", p->profile.curr_uid, p->profile.allow_su); } rcu_read_unlock(); @@ -106,7 +95,7 @@ static void ksu_grant_root_to_shell() struct app_profile profile = { .version = KSU_APP_PROFILE_VER, .allow_su = true, - .current_uid = 2000, + .curr_uid = 2000, }; strcpy(profile.key, "com.android.shell"); strcpy(profile.rp_config.profile.selinux_domain, @@ -115,25 +104,25 @@ static void ksu_grant_root_to_shell() } #endif -bool ksu_get_app_profile(struct app_profile *profile) +struct app_profile *ksu_get_app_profile(uid_t uid) { struct perm_data *p = NULL; - bool found = false; - rcu_read_lock(); - list_for_each_entry_rcu (p, &allow_list, list) { - bool uid_match = profile->current_uid == p->profile.current_uid; - if (uid_match) { + hash_for_each_possible_rcu (allow_list, p, list, uid) { + if (uid == p->profile.curr_uid) { // found it, override it with ours - memcpy(profile, &p->profile, sizeof(*profile)); - found = true; - goto exit; + break; } } -exit: - rcu_read_unlock(); - return found; + if (!p) + return NULL; + + if (!kref_get_unless_zero(&p->ref)) { + return NULL; + } + + return &p->profile; } static inline bool forbid_system_uid(uid_t uid) @@ -167,11 +156,34 @@ static bool profile_valid(struct app_profile *profile) return true; } +static void release_perm_data(struct kref *ref) +{ + struct perm_data *p = container_of(ref, struct perm_data, ref); + kfree(p); +} + +static void put_perm_data(struct perm_data *data) +{ + kref_put(&data->ref, release_perm_data); +} + +static void release_perm_data_rcu(struct kref *ref) +{ + struct perm_data *p = container_of(ref, struct perm_data, ref); + kfree_rcu(p, rcu); +} + +static void put_perm_data_rcu(struct perm_data *data) +{ + kref_put(&data->ref, release_perm_data_rcu); +} + int ksu_set_app_profile(struct app_profile *profile) { - struct perm_data *p = NULL, *np; + struct perm_data *p, *np; + struct root_profile *old_current_default_root_profile; + struct non_root_profile *old_current_default_non_root_profile; int result = 0; - u16 count = 0; if (!profile_valid(profile)) { pr_err("Failed to set app profile: invalid profile!\n"); @@ -180,11 +192,13 @@ int ksu_set_app_profile(struct app_profile *profile) mutex_lock(&allowlist_mutex); - list_for_each_entry (p, &allow_list, list) { - ++count; - // both uid and package must match, otherwise it will break multiple package with different user id - if (profile->current_uid == p->profile.current_uid && - !strcmp(profile->key, p->profile.key)) { + hash_for_each_possible (allow_list, p, list, profile->curr_uid) { + if (profile->curr_uid == p->profile.curr_uid) { + if (strcmp(profile->key, p->profile.key) != 0) { + pr_warn( + "ksu_set_app_profile: key changed: uid=%d orig=%s new=%s\n", + profile->curr_uid, p->profile.key, profile->key); + } // found it, just override it all! np = (struct perm_data *)kzalloc(sizeof(struct perm_data), GFP_KERNEL); @@ -192,75 +206,77 @@ int ksu_set_app_profile(struct app_profile *profile) result = -ENOMEM; goto out_unlock; } + kref_init(&np->ref); memcpy(&np->profile, profile, sizeof(*profile)); - list_replace_rcu(&p->list, &np->list); - kfree_rcu(p, rcu); + hlist_replace_rcu(&p->list, &np->list); + put_perm_data_rcu(p); goto out; } } - if (unlikely(count == U16_MAX)) { + if (unlikely(allow_list_count == U16_MAX)) { pr_err("too many app profile\n"); result = -E2BIG; goto out_unlock; } // not found, alloc a new node! - p = (struct perm_data *)kzalloc(sizeof(struct perm_data), GFP_KERNEL); - if (!p) { + np = (struct perm_data *)kzalloc(sizeof(struct perm_data), GFP_KERNEL); + if (!np) { pr_err("ksu_set_app_profile alloc failed\n"); result = -ENOMEM; goto out_unlock; } - memcpy(&p->profile, profile, sizeof(*profile)); + kref_init(&np->ref); + memcpy(&np->profile, profile, sizeof(*profile)); if (profile->allow_su) { pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n", - profile->key, profile->current_uid, - profile->rp_config.profile.gid, + profile->key, profile->curr_uid, profile->rp_config.profile.gid, profile->rp_config.profile.selinux_domain); } else { pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n", - profile->key, profile->current_uid, + profile->key, profile->curr_uid, profile->nrp_config.profile.umount_modules); } - list_add_tail_rcu(&p->list, &allow_list); + hash_add_rcu(allow_list, &np->list, np->profile.curr_uid); + ++allow_list_count; out: result = 0; // 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 - memcpy(&default_non_root_profile, &profile->nrp_config.profile, - sizeof(default_non_root_profile)); - } else if (unlikely(!strcmp(profile->key, "#"))) { - // set default root profile - // TODO: Do we really need this? - memcpy(&default_root_profile, &profile->rp_config.profile, - sizeof(default_root_profile)); - } else 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); - } else { - allow_list_arr[allow_list_pointer++] = profile->current_uid; + if (unlikely(profile->curr_uid == KSU_APP_PROFILE_PRESERVE_UID)) { + if (unlikely(!strcmp(profile->key, "$"))) { + // set default non root profile + kref_get(&np->ref); + old_current_default_non_root_profile = + rcu_dereference_protected(current_default_non_root_profile, + lockdep_is_held(&allowlist_mutex)); + rcu_assign_pointer(current_default_non_root_profile, + &np->profile.nrp_config.profile); + if (unlikely(old_current_default_non_root_profile != + &default_non_root_profile)) { + p = container_of(old_current_default_non_root_profile, + struct perm_data, profile.nrp_config.profile); + put_perm_data_rcu(p); + } + } else if (unlikely(!strcmp(profile->key, "#"))) { + // set default root profile + // TODO: Do we really need this? + kref_get(&np->ref); + old_current_default_root_profile = + rcu_dereference_protected(current_default_root_profile, + lockdep_is_held(&allowlist_mutex)); + rcu_assign_pointer(current_default_root_profile, + &np->profile.rp_config.profile); + if (unlikely(old_current_default_root_profile != + &default_root_profile)) { + p = container_of(old_current_default_root_profile, + struct perm_data, profile.rp_config.profile); + put_perm_data_rcu(p); } - } else { - remove_uid_from_arr(profile->current_uid); } } @@ -271,7 +287,7 @@ int ksu_set_app_profile(struct app_profile *profile) bool __ksu_is_allow_uid(uid_t uid) { - int i; + struct perm_data *p; if (forbid_system_uid(uid)) { // do not bother going through the list if it's system @@ -284,15 +300,18 @@ bool __ksu_is_allow_uid(uid_t uid) return true; } - 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; + if (unlikely(allow_shell) && uid == 2000) { + return true; + } + + rcu_read_lock(); + hash_for_each_possible_rcu (allow_list, p, list, uid) { + if (uid == p->profile.curr_uid && p->profile.allow_su) { + rcu_read_unlock(); + return true; } } + rcu_read_unlock(); return false; } @@ -308,54 +327,87 @@ bool __ksu_is_allow_uid_for_current(uid_t uid) bool ksu_uid_should_umount(uid_t uid) { - struct app_profile profile = { .current_uid = uid }; + struct app_profile *profile; + bool res; if (likely(ksu_is_manager_appid_valid()) && unlikely(ksu_get_manager_appid() == uid % PER_USER_RANGE)) { // we should not umount on manager! return false; } - bool found = ksu_get_app_profile(&profile); - if (!found) { + rcu_read_lock(); + profile = ksu_get_app_profile(uid); + if (!profile) { // no app profile found, it must be non root app - return default_non_root_profile.umount_modules; - } - if (profile.allow_su) { + res = current_default_non_root_profile->umount_modules; + } else if (profile->allow_su) { // if found and it is granted to su, we shouldn't umount for it - return false; + res = false; } else { // found an app profile - if (profile.nrp_config.use_default) { - return default_non_root_profile.umount_modules; + if (profile->nrp_config.use_default) { + res = current_default_non_root_profile->umount_modules; } else { - return profile.nrp_config.profile.umount_modules; + res = profile->nrp_config.profile.umount_modules; } } + rcu_read_unlock(); + + if (profile) + ksu_put_app_profile(profile); + return res; } -void ksu_get_root_profile(uid_t uid, struct root_profile *profile) +void ksu_put_app_profile(struct app_profile *profile) +{ + struct perm_data *p = container_of(profile, struct perm_data, profile); + put_perm_data(p); +} + +struct root_profile *ksu_get_root_profile(uid_t uid) { struct perm_data *p = NULL; + struct root_profile *res = NULL; + rcu_read_lock(); if (is_uid_manager(uid)) { goto use_default; } - rcu_read_lock(); - list_for_each_entry_rcu (p, &allow_list, list) { - if (uid == p->profile.current_uid && p->profile.allow_su) { + if (unlikely(allow_shell && uid == SHELL_UID)) { + goto use_default; + } + + hash_for_each_possible_rcu (allow_list, p, list, uid) { + if (uid == p->profile.curr_uid && p->profile.allow_su) { if (!p->profile.rp_config.use_default) { - memcpy(profile, &p->profile.rp_config.profile, - sizeof(*profile)); - rcu_read_unlock(); - return; + if (kref_get_unless_zero(&p->ref)) + res = &p->profile.rp_config.profile; } + break; } } + + if (unlikely(!res)) { + use_default: + res = current_default_root_profile; + if (unlikely(res != &default_root_profile)) { + if (kref_get_unless_zero(&p->ref)) + p = container_of(res, struct perm_data, + profile.rp_config.profile); + } + } + rcu_read_unlock(); + return res; +} -use_default: - // use default profile - memcpy(profile, &default_root_profile, sizeof(*profile)); +void ksu_put_root_profile(struct root_profile *profile) +{ + if (likely(profile == &default_root_profile)) + return; + struct perm_data *p = + container_of(profile, struct perm_data, profile.rp_config.profile); + put_perm_data(p); } bool ksu_get_allow_list(int *array, u16 length, u16 *out_length, u16 *out_total, @@ -363,13 +415,14 @@ bool ksu_get_allow_list(int *array, u16 length, u16 *out_length, u16 *out_total, { struct perm_data *p = NULL; u16 i = 0, j = 0; + int iter; rcu_read_lock(); - list_for_each_entry_rcu (p, &allow_list, list) { + hash_for_each_rcu (allow_list, iter, p, list) { // pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow); if (p->profile.allow_su == allow && - !is_uid_manager(p->profile.current_uid)) { + !is_uid_manager(p->profile.curr_uid)) { if (j < length) { - array[j++] = p->profile.current_uid; + array[j++] = p->profile.curr_uid; } ++i; } @@ -392,6 +445,7 @@ static void do_persistent_allow_list(struct callback_head *_cb) u32 version = FILE_FORMAT_VERSION; struct perm_data *p = NULL; loff_t off = 0; + int i; const struct cred *saved = override_creds(ksu_cred); struct file *fp = @@ -413,9 +467,9 @@ static void do_persistent_allow_list(struct callback_head *_cb) } mutex_lock(&allowlist_mutex); - list_for_each_entry (p, &allow_list, list) { + hash_for_each (allow_list, i, p, list) { pr_info("save allow list, name: %s uid :%d, allow: %d\n", - p->profile.key, p->profile.current_uid, p->profile.allow_su); + p->profile.key, p->profile.curr_uid, p->profile.allow_su); kernel_write(fp, &p->profile, sizeof(p->profile), &off); } @@ -432,11 +486,14 @@ void ksu_persistent_allow_list() { struct task_struct *tsk; + rcu_read_lock(); tsk = get_pid_task(find_vpid(1), PIDTYPE_PID); if (!tsk) { + rcu_read_unlock(); pr_err("save_allow_list find init task err\n"); return; } + rcu_read_unlock(); struct callback_head *cb = kzalloc(sizeof(struct callback_head), GFP_KERNEL); @@ -499,7 +556,7 @@ void ksu_load_allow_list() } pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", profile.key, - profile.current_uid, profile.allow_su); + profile.curr_uid, profile.allow_su); ksu_set_app_profile(&profile); } @@ -512,7 +569,8 @@ void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data) { struct perm_data *np = NULL; - struct perm_data *n = NULL; + struct hlist_node *tmp; + int i; if (!ksu_boot_completed) { pr_info("boot not completed, skip prune\n"); @@ -521,21 +579,17 @@ void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), bool modified = false; mutex_lock(&allowlist_mutex); - list_for_each_entry_safe (np, n, &allow_list, list) { - uid_t uid = np->profile.current_uid; + hash_for_each_safe (allow_list, i, tmp, np, list) { + uid_t uid = np->profile.curr_uid; char *package = np->profile.key; // we use this uid for special cases, don't prune it! bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID; if (!is_preserved_uid && !is_uid_valid(uid, package, data)) { modified = true; pr_info("prune uid: %d, package: %s\n", uid, package); - list_del_rcu(&np->list); - kfree_rcu(np, rcu); - if (likely(uid <= BITMAP_UID_MAX)) { - allow_list_bitmap[uid / BITS_PER_BYTE] &= - ~(1 << (uid % BITS_PER_BYTE)); - } - remove_uid_from_arr(uid); + hlist_del_rcu(&np->list); + put_perm_data_rcu(np); + --allow_list_count; } } mutex_unlock(&allowlist_mutex); @@ -548,29 +602,31 @@ void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, 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_default_profiles(); } void ksu_allowlist_exit(void) { struct perm_data *np = NULL; - struct perm_data *n = NULL; + struct hlist_node *tmp; + int i; // free allowlist mutex_lock(&allowlist_mutex); - list_for_each_entry_safe (np, n, &allow_list, list) { - list_del(&np->list); - kfree(np); + if (unlikely(current_default_non_root_profile != + &default_non_root_profile)) { + np = container_of(current_default_non_root_profile, struct perm_data, + profile.nrp_config.profile); + put_perm_data_rcu(np); + } + if (unlikely(current_default_root_profile != &default_root_profile)) { + np = container_of(current_default_root_profile, struct perm_data, + profile.rp_config.profile); + put_perm_data_rcu(np); + } + hash_for_each_safe (allow_list, i, tmp, np, list) { + hlist_del(&np->list); + put_perm_data_rcu(np); } mutex_unlock(&allowlist_mutex); } diff --git a/kernel/allowlist.h b/kernel/allowlist.h index 2afc303836c2..418a3a9d4197 100644 --- a/kernel/allowlist.h +++ b/kernel/allowlist.h @@ -35,11 +35,16 @@ void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, char *, void *), void *data); void ksu_persistent_allow_list(); -bool ksu_get_app_profile(struct app_profile *); +// should be called with rcu read lock +struct app_profile *ksu_get_app_profile(uid_t uid); +// only used to put the app_profile returned by ksu_get_app_profile +void ksu_put_app_profile(struct app_profile *); int ksu_set_app_profile(struct app_profile *); bool ksu_uid_should_umount(uid_t uid); -void ksu_get_root_profile(uid_t uid, struct root_profile *); +struct root_profile *ksu_get_root_profile(uid_t uid); +// only used to put the root_profile returned by ksu_get_root_profile +void ksu_put_root_profile(struct root_profile *); static inline bool is_appuid(uid_t uid) { @@ -53,3 +58,5 @@ static inline bool is_isolated_process(uid_t uid) return appid >= FIRST_ISOLATED_UID && appid <= LAST_ISOLATED_UID; } #endif + +extern bool allow_shell; diff --git a/kernel/apk_sign.c b/kernel/apk_sign.c index 1c84127bd367..65a2f3fe26db 100644 --- a/kernel/apk_sign.c +++ b/kernel/apk_sign.c @@ -360,5 +360,7 @@ bool is_manager_apk(char *path) return false; } #endif - return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH); + return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH) || + check_v2_signature(path, OFFICIAL_EXPECTED_SIZE, + OFFICIAL_EXPECTED_HASH); } \ No newline at end of file diff --git a/kernel/app_profile.c b/kernel/app_profile.c index f544dd8de4a3..453aee65dd0d 100644 --- a/kernel/app_profile.c +++ b/kernel/app_profile.c @@ -66,7 +66,7 @@ static void disable_seccomp(void) { struct task_struct *fake; - fake = kmalloc(sizeof(*fake), GFP_ATOMIC); + fake = kmalloc(sizeof(*fake), GFP_KERNEL); if (!fake) { pr_warn("failed to alloc fake task_struct\n"); return; @@ -107,7 +107,7 @@ void escape_with_root_profile(void) struct cred *cred; struct task_struct *p = current; struct task_struct *t; - struct root_profile profile; + struct root_profile *profile; cred = prepare_creds(); if (!cred) { @@ -121,34 +121,34 @@ void escape_with_root_profile(void) return; } - ksu_get_root_profile(cred->uid.val, &profile); + profile = ksu_get_root_profile(cred->uid.val); - cred->uid.val = profile.uid; - cred->suid.val = profile.uid; - cred->euid.val = profile.uid; - cred->fsuid.val = profile.uid; + cred->uid.val = profile->uid; + cred->suid.val = profile->uid; + cred->euid.val = profile->uid; + cred->fsuid.val = profile->uid; - cred->gid.val = profile.gid; - cred->fsgid.val = profile.gid; - cred->sgid.val = profile.gid; - cred->egid.val = profile.gid; + cred->gid.val = profile->gid; + cred->fsgid.val = profile->gid; + cred->sgid.val = profile->gid; + cred->egid.val = profile->gid; cred->securebits = 0; - BUILD_BUG_ON(sizeof(profile.capabilities.effective) != + BUILD_BUG_ON(sizeof(profile->capabilities.effective) != sizeof(kernel_cap_t)); // setup capabilities // we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process // we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec! - u64 cap_for_ksud = profile.capabilities.effective | CAP_DAC_READ_SEARCH; + u64 cap_for_ksud = profile->capabilities.effective | CAP_DAC_READ_SEARCH; memcpy(&cred->cap_effective, &cap_for_ksud, sizeof(cred->cap_effective)); - memcpy(&cred->cap_permitted, &profile.capabilities.effective, + memcpy(&cred->cap_permitted, &profile->capabilities.effective, sizeof(cred->cap_permitted)); - memcpy(&cred->cap_bset, &profile.capabilities.effective, + memcpy(&cred->cap_bset, &profile->capabilities.effective, sizeof(cred->cap_bset)); - setup_groups(&profile, cred); - setup_selinux(profile.selinux_domain, cred); + setup_groups(profile, cred); + setup_selinux(profile->selinux_domain, cred); commit_creds(cred); @@ -158,7 +158,8 @@ void escape_with_root_profile(void) ksu_set_task_tracepoint_flag(t); } - setup_mount_ns(profile.namespaces); + setup_mount_ns(profile->namespaces); + ksu_put_root_profile(profile); } void escape_to_root_for_init(void) diff --git a/kernel/app_profile.h b/kernel/app_profile.h index 357d0a1db947..2b54ff55333d 100644 --- a/kernel/app_profile.h +++ b/kernel/app_profile.h @@ -41,7 +41,7 @@ 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; + int32_t curr_uid; bool allow_su; union { diff --git a/kernel/hook.h b/kernel/hook.h new file mode 100644 index 000000000000..fab52d5a77ea --- /dev/null +++ b/kernel/hook.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023 bmax121. All Rights Reserved. + */ + +#ifndef __KSU_H_HOOK_ +#define __KSU_H_HOOK_ +#include "linux/types.h" // IWYU pragma: keep +#include "linux/version.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0) +#include "asm/patching.h" // IWYU pragma: keep +#else +#include "asm/insn.h" // IWYU pragma: keep +#endif + +// https://github.com/bmax121/KernelPatch/blob/94e5be9cc3f8a6fbd9574155e3e9753200ab9bfb/kernel/include/hook.h#L54 + +#define HOOK_INTO_BRANCH_FUNC + +typedef enum { + HOOK_NO_ERR = 0, + HOOK_BAD_ADDRESS = 4095, + HOOK_DUPLICATED = 4094, + HOOK_NO_MEM = 4093, + HOOK_BAD_RELO = 4092, + HOOK_TRANSIT_NO_MEM = 4091, + HOOK_CHAIN_FULL = 4090, +} hook_err_t; + +#define HOOK_MEM_REGION_NUM 4 +#define TRAMPOLINE_MAX_NUM 6 +#define RELOCATE_INST_NUM (4 * 8 + 8 - 4) + +#define HOOK_CHAIN_NUM 0x10 + +#define ARM64_NOP 0xd503201f +#define ARM64_BTI_C 0xd503245f +#define ARM64_BTI_J 0xd503249f +#define ARM64_BTI_JC 0xd50324df +#define ARM64_PACIASP 0xd503233f +#define ARM64_PACIBSP 0xd503237f + +typedef struct { + // in + uint64_t func_addr; + uint64_t origin_addr; + uint64_t replace_addr; + uint64_t relo_addr; + // out + int32_t tramp_insts_num; + int32_t relo_insts_num; + uint32_t origin_insts[TRAMPOLINE_MAX_NUM] __attribute__((aligned(8))); + uint32_t tramp_insts[TRAMPOLINE_MAX_NUM] __attribute__((aligned(8))); + uint32_t relo_insts[RELOCATE_INST_NUM] __attribute__((aligned(8))); +} hook_t __attribute__((aligned(8))); + +static inline int is_bad_address(void *addr) +{ + return ((uint64_t)addr & 0x8000000000000000) != 0x8000000000000000; +} + +int32_t branch_from_to(uint32_t *tramp_buf, uint64_t src_addr, + uint64_t dst_addr); +int32_t branch_relative(uint32_t *buf, uint64_t src_addr, uint64_t dst_addr); +int32_t branch_absolute(uint32_t *buf, uint64_t addr); +int32_t ret_absolute(uint32_t *buf, uint64_t addr); + +hook_err_t hook_prepare(hook_t *hook); +int hook_install(hook_t *hook); + +/** + * @brief Inline-hook function which address is @param func with function @param replace, + * after hook, original @param func is backuped in @param backup. + * + * @note If multiple modules hook this function simultaneously, + * it will cause abnormality when unload the modules. Please use hook_wrap instead + * + * @see hook_wrap + * + * @param func + * @param replace + * @param backup + * @return hook_err_t + */ +hook_err_t hook(void *func, void *replace, void **backup); + +#define KSU_PATCH_TEXT_FLUSH_DCACHE 1 +#define KSU_PATCH_TEXT_FLUSH_ICACHE 2 + +int ksu_patch_text(void *dst, void *src, size_t len, int flags); + +#endif diff --git a/kernel/inline_hook.c b/kernel/inline_hook.c new file mode 100644 index 000000000000..9a991db17799 --- /dev/null +++ b/kernel/inline_hook.c @@ -0,0 +1,607 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023 bmax121. All Rights Reserved. + */ +#include "hook.h" +#include "klog.h" // IWYU pragma: keep +#include "linux/compiler.h" +#include "linux/cpumask.h" +#include "linux/gfp.h" // IWYU pragma: keep +#include "linux/uaccess.h" +#include "linux/vmalloc.h" +#include "linux/stop_machine.h" +#include "asm/cacheflush.h" +#include "asm-generic/fixmap.h" +#include "asm/pgtable.h" + +#include "pte.h" + +// https://github.com/bmax121/KernelPatch/blob/94e5be9cc3f8a6fbd9574155e3e9753200ab9bfb/kernel/base/hook.c#L562-L603 + +#define bits32(n, high, low) \ + ((uint32_t)((n) << (31u - (high))) >> (31u - (high) + (low))) +#define bit(n, st) (((n) >> (st)) & 1) +#define sign64_extend(n, len) \ + (((uint64_t)((n) << (63u - (len - 1))) >> 63u) ? \ + ((n) | (0xFFFFFFFFFFFFFFFF << (len))) : \ + n) +#define align_ceil(x, align) \ + (((u64)(x) + (u64)(align) - 1) & ~((u64)(align) - 1)) + +typedef uint32_t inst_type_t; +typedef uint32_t inst_mask_t; + +#define INST_B 0x14000000 +#define INST_BC 0x54000000 +#define INST_BL 0x94000000 +#define INST_ADR 0x10000000 +#define INST_ADRP 0x90000000 +#define INST_LDR_32 0x18000000 +#define INST_LDR_64 0x58000000 +#define INST_LDRSW_LIT 0x98000000 +#define INST_PRFM_LIT 0xD8000000 +#define INST_LDR_SIMD_32 0x1C000000 +#define INST_LDR_SIMD_64 0x5C000000 +#define INST_LDR_SIMD_128 0x9C000000 +#define INST_CBZ 0x34000000 +#define INST_CBNZ 0x35000000 +#define INST_TBZ 0x36000000 +#define INST_TBNZ 0x37000000 +#define INST_HINT 0xD503201F +#define INST_IGNORE 0x0 + +#define MASK_B 0xFC000000 +#define MASK_BC 0xFF000010 +#define MASK_BL 0xFC000000 +#define MASK_ADR 0x9F000000 +#define MASK_ADRP 0x9F000000 +#define MASK_LDR_32 0xFF000000 +#define MASK_LDR_64 0xFF000000 +#define MASK_LDRSW_LIT 0xFF000000 +#define MASK_PRFM_LIT 0xFF000000 +#define MASK_LDR_SIMD_32 0xFF000000 +#define MASK_LDR_SIMD_64 0xFF000000 +#define MASK_LDR_SIMD_128 0xFF000000 +#define MASK_CBZ 0x7F000000u +#define MASK_CBNZ 0x7F000000u +#define MASK_TBZ 0x7F000000u +#define MASK_TBNZ 0x7F000000u +#define MASK_HINT 0xFFFFF01F +#define MASK_IGNORE 0x0 + +static inst_mask_t masks[] = { + MASK_B, MASK_BC, MASK_BL, MASK_ADR, + MASK_ADRP, MASK_LDR_32, MASK_LDR_64, MASK_LDRSW_LIT, + MASK_PRFM_LIT, MASK_LDR_SIMD_32, MASK_LDR_SIMD_64, MASK_LDR_SIMD_128, + MASK_CBZ, MASK_CBNZ, MASK_TBZ, MASK_TBNZ, + MASK_IGNORE, +}; +static inst_type_t types[] = { + INST_B, INST_BC, INST_BL, INST_ADR, + INST_ADRP, INST_LDR_32, INST_LDR_64, INST_LDRSW_LIT, + INST_PRFM_LIT, INST_LDR_SIMD_32, INST_LDR_SIMD_64, INST_LDR_SIMD_128, + INST_CBZ, INST_CBNZ, INST_TBZ, INST_TBNZ, + INST_IGNORE, +}; + +static int32_t relo_len[] = { + 6, 8, 8, 4, 4, 6, 6, 6, 8, 8, 8, 8, 6, 6, 6, 6, 2 +}; + +// static uint64_t sign_extend(uint64_t x, uint32_t len) +// { +// char sign_bit = bit(x, len - 1); +// unsigned long sign_mask = 0 - sign_bit; +// x |= ((sign_mask >> len) << len); +// return x; +// } + +static int is_in_tramp(hook_t *hook, uint64_t addr) +{ + uint64_t tramp_start = hook->origin_addr; + uint64_t tramp_end = tramp_start + hook->tramp_insts_num * 4; + if (addr >= tramp_start && addr < tramp_end) { + return 1; + } + return 0; +} + +static uint64_t relo_in_tramp(hook_t *hook, uint64_t addr) +{ + uint64_t tramp_start = hook->origin_addr; + uint64_t tramp_end = tramp_start + hook->tramp_insts_num * 4; + if (!(addr >= tramp_start && addr < tramp_end)) + return addr; + uint32_t addr_inst_index = (addr - tramp_start) / 4; + uint64_t fix_addr = hook->relo_addr; + for (int i = 0; i < addr_inst_index; i++) { + inst_type_t inst = hook->origin_insts[i]; + for (int j = 0; j < sizeof(relo_len) / sizeof(relo_len[0]); j++) { + if ((inst & masks[j]) == types[j]) { + fix_addr += relo_len[j] * 4; + break; + } + } + } + return fix_addr; +} + +#ifdef HOOK_INTO_BRANCH_FUNC + +#endif + +static hook_err_t relo_b(hook_t *hook, uint64_t inst_addr, uint32_t inst, + inst_type_t type) +{ + uint32_t *buf = hook->relo_insts + hook->relo_insts_num; + uint64_t imm64; + if (type == INST_BC) { + uint64_t imm19 = bits32(inst, 23, 5); + imm64 = sign64_extend(imm19 << 2u, 21u); + } else { + uint64_t imm26 = bits32(inst, 25, 0); + imm64 = sign64_extend(imm26 << 2u, 28u); + } + uint64_t addr = inst_addr + imm64; + addr = relo_in_tramp(hook, addr); + + uint32_t idx = 0; + if (type == INST_BC) { + buf[idx++] = (inst & 0xFF00001F) | 0x40u; // B. #8 + buf[idx++] = 0x14000006; // B #24 + } + buf[idx++] = 0x58000051; // LDR X17, #8 + buf[idx++] = 0x14000003; // B #12 + buf[idx++] = addr & 0xFFFFFFFF; + buf[idx++] = addr >> 32u; + if (type == INST_BL) { + buf[idx++] = 0x1000001E; // ADR X30, . + buf[idx++] = 0x910033DE; // ADD X30, X30, #12 + buf[idx++] = 0xD65F0220; // RET X17 + } else { + buf[idx++] = 0xD65F0220; // RET X17 + } + buf[idx++] = ARM64_NOP; + return HOOK_NO_ERR; +} + +static hook_err_t relo_adr(hook_t *hook, uint64_t inst_addr, uint32_t inst, + inst_type_t type) +{ + uint32_t *buf = hook->relo_insts + hook->relo_insts_num; + + uint32_t xd = bits32(inst, 4, 0); + uint64_t immlo = bits32(inst, 30, 29); + uint64_t immhi = bits32(inst, 23, 5); + uint64_t addr; + + if (type == INST_ADR) { + addr = inst_addr + sign64_extend((immhi << 2u) | immlo, 21u); + } else { + addr = + (inst_addr + sign64_extend((immhi << 14u) | (immlo << 12u), 33u)) & + 0xFFFFFFFFFFFFF000; + if (is_in_tramp(hook, addr)) + return -HOOK_BAD_RELO; + } + buf[0] = 0x58000040u | xd; // LDR Xd, #8 + buf[1] = 0x14000003; // B #12 + buf[2] = addr & 0xFFFFFFFF; + buf[3] = addr >> 32u; + return HOOK_NO_ERR; +} + +static hook_err_t relo_ldr(hook_t *hook, uint64_t inst_addr, uint32_t inst, + inst_type_t type) +{ + uint32_t *buf = hook->relo_insts + hook->relo_insts_num; + + uint32_t rt = bits32(inst, 4, 0); + uint64_t imm19 = bits32(inst, 23, 5); + uint64_t offset = sign64_extend((imm19 << 2u), 21u); + uint64_t addr = inst_addr + offset; + + if (is_in_tramp(hook, addr) && type != INST_PRFM_LIT) + return -HOOK_BAD_RELO; + + addr = relo_in_tramp(hook, addr); + + if (type == INST_LDR_32 || type == INST_LDR_64 || type == INST_LDRSW_LIT) { + buf[0] = 0x58000060u | rt; // LDR Xt, #12 + if (type == INST_LDR_32) { + buf[1] = 0xB9400000 | rt | (rt << 5u); // LDR Wt, [Xt] + } else if (type == INST_LDR_64) { + buf[1] = 0xF9400000 | rt | (rt << 5u); // LDR Xt, [Xt] + } else { + // LDRSW_LIT + buf[1] = 0xB9800000 | rt | (rt << 5u); // LDRSW Xt, [Xt] + } + buf[2] = 0x14000004; // B #16 + buf[3] = ARM64_NOP; + buf[4] = addr & 0xFFFFFFFF; + buf[5] = addr >> 32u; + } else { + buf[0] = 0xA93F47F0; // STP X16, X17, [SP, -0x10] + buf[1] = 0x58000091; // LDR X17, #16 + if (type == INST_PRFM_LIT) { + buf[2] = 0xF9800220 | rt; // PRFM Rt, [X17] + } else if (type == INST_LDR_SIMD_32) { + buf[2] = 0xBD400220 | rt; // LDR St, [X17] + } else if (type == INST_LDR_SIMD_64) { + buf[2] = 0xFD400220 | rt; // LDR Dt, [X17] + } else { + // LDR_SIMD_128 + buf[2] = 0x3DC00220u | rt; // LDR Qt, [X17] + } + buf[3] = 0xF85F83F1; // LDR X17, [SP, -0x8] + buf[4] = 0x14000004; // B #16 + buf[5] = ARM64_NOP; + buf[6] = addr & 0xFFFFFFFF; + buf[7] = addr >> 32u; + } + return HOOK_NO_ERR; +} + +static hook_err_t relo_cb(hook_t *hook, uint64_t inst_addr, uint32_t inst, + inst_type_t type) +{ + uint32_t *buf = hook->relo_insts + hook->relo_insts_num; + + uint64_t imm19 = bits32(inst, 23, 5); + uint64_t offset = sign64_extend((imm19 << 2u), 21u); + uint64_t addr = inst_addr + offset; + addr = relo_in_tramp(hook, addr); + + buf[0] = (inst & 0xFF00001F) | 0x40u; // CB(N)Z Rt, #8 + buf[1] = 0x14000005; // B #20 + buf[2] = 0x58000051; // LDR X17, #8 + buf[3] = 0xD65F0220; // RET X17 + buf[4] = addr & 0xFFFFFFFF; + buf[5] = addr >> 32u; + return HOOK_NO_ERR; +} + +static hook_err_t relo_tb(hook_t *hook, uint64_t inst_addr, uint32_t inst, + inst_type_t type) +{ + uint32_t *buf = hook->relo_insts + hook->relo_insts_num; + + uint64_t imm14 = bits32(inst, 18, 5); + uint64_t offset = sign64_extend((imm14 << 2u), 16u); + uint64_t addr = inst_addr + offset; + addr = relo_in_tramp(hook, addr); + + buf[0] = (inst & 0xFFF8001F) | 0x40u; // TB(N)Z Rt, #, #8 + buf[1] = 0x14000005; // B #20 + buf[2] = 0x58000051; // LDR X17, #8 + buf[3] = 0xd65f0220; // RET X17 + buf[4] = addr & 0xFFFFFFFF; + buf[5] = addr >> 32u; + return HOOK_NO_ERR; +} + +static hook_err_t relo_ignore(hook_t *hook, uint64_t inst_addr, uint32_t inst, + inst_type_t type) +{ + uint32_t *buf = hook->relo_insts + hook->relo_insts_num; + buf[0] = inst; + buf[1] = ARM64_NOP; + return HOOK_NO_ERR; +} + +static uint32_t can_b_rel(uint64_t src_addr, uint64_t dst_addr) +{ +#define B_REL_RANGE ((1 << 25) << 2) + return ((dst_addr >= src_addr) && (dst_addr - src_addr <= B_REL_RANGE)) || + ((src_addr >= dst_addr) && (src_addr - dst_addr <= B_REL_RANGE)); +} + +int32_t branch_relative(uint32_t *buf, uint64_t src_addr, uint64_t dst_addr) +{ + if (can_b_rel(src_addr, dst_addr)) { + buf[0] = 0x14000000u | + (((dst_addr - src_addr) & 0x0FFFFFFFu) >> 2u); // B