From c9c7e84873a77b670022efb1f1eb3078329bee02 Mon Sep 17 00:00:00 2001 From: renoseven Date: Mon, 4 Aug 2025 17:00:17 +0800 Subject: [PATCH 1/3] upatch-manage: fix cannot find do_munmap compile failure Signed-off-by: renoseven --- upatch-manage/kernel_compat.c | 145 +++++++++++++++++++-------------- upatch-manage/kernel_compat.h | 5 +- upatch-manage/process_entity.c | 3 +- 3 files changed, 87 insertions(+), 66 deletions(-) diff --git a/upatch-manage/kernel_compat.c b/upatch-manage/kernel_compat.c index a4ffc1f5..e5e42285 100644 --- a/upatch-manage/kernel_compat.c +++ b/upatch-manage/kernel_compat.c @@ -37,6 +37,76 @@ #include "util.h" +typedef long (*do_mprotect_pkey_fn)( + unsigned long start, + size_t len, + unsigned long prot, + int pkey +); + +typedef int (*do_munmap_fn)( + struct mm_struct *mm, + unsigned long addr, + size_t size, + struct list_head *uf +); + +static const char *do_mprotect_pkey_names[] = { + "do_mprotect_pkey", + "do_mprotect_pkey.constprop.0", + "do_mprotect_pkey.constprop.1", + "do_mprotect_pkey.constprop.2", + NULL +}; + +static const char *do_munmap_names[] = { + "do_munmap", + "do_munmap.constprop.0", + "do_munmap.constprop.1", + "do_munmap.constprop.2", + NULL +}; + +static do_munmap_fn do_munmap_func = NULL; +static do_mprotect_pkey_fn do_mprotect_pkey_func = NULL; + +static void *lookup_kernel_symbol(const char *name) +{ + struct kprobe kp = { + .symbol_name = name, + }; + + int ret; + + if (unlikely(!name)) { + return NULL; + } + + ret = register_kprobe(&kp); + if (ret < 0) { + return NULL; + } + + unregister_kprobe(&kp); + + return (void *)kp.addr; +} + +static void *resolve_kernel_symbol(const char **names) +{ + void *addr = NULL; + const char **name; + + for (name = names; *name; name++) { + addr = lookup_kernel_symbol(*name); + if (addr) { + break; + } + } + + return addr; +} + __always_inline void upatch_vma_iter_init(struct upatch_vma_iter *vmi, struct mm_struct *mm) { if (unlikely(!vmi)) { @@ -112,79 +182,28 @@ __always_inline struct vm_area_struct *upatch_vma_prev(struct upatch_vma_iter *v #endif } -typedef long (*do_mprotect_pkey_fn)( - unsigned long start, - size_t len, - unsigned long prot, - int pkey -); - -static const char *mprotect_symbol_names[] = { - "do_mprotect_pkey", - "do_mprotect_pkey.constprop.0", - "do_mprotect_pkey.constprop.1", - "do_mprotect_pkey.constprop.2", - NULL -}; -static do_mprotect_pkey_fn do_mprotect_pkey = NULL; - -static void *get_kernel_symbol(const char *symbol_name) +int upatch_mprotect(unsigned long addr, size_t len, unsigned long prot) { - struct kprobe kp = { - .symbol_name = symbol_name, - }; - void *addr; - int ret; - - if (!symbol_name) { - return ERR_PTR(-EINVAL); - } - - ret = register_kprobe(&kp); - if (ret < 0) { - return ERR_PTR(ret); - } - - addr = (void *)kp.addr; - if (!addr) { - log_err("kernel symbol '%s' is NULL\n", symbol_name); - unregister_kprobe(&kp); - return ERR_PTR(-EFAULT); - } - - unregister_kprobe(&kp); - return addr; + return do_mprotect_pkey_func(addr, len, prot, -1); } -int upatch_mprotect(unsigned long addr, size_t len, unsigned long prot) +int upatch_munmap(struct mm_struct *mm, unsigned long addr, size_t size, struct list_head *uf) { - if (!do_mprotect_pkey || IS_ERR(do_mprotect_pkey)) { - return -ENOSYS; - } - return do_mprotect_pkey(addr, len, prot, -1); + return do_munmap_func(mm, addr, size, uf); } int __init kernel_compat_init(void) { - int ret; - const char *symbol_name = NULL; - void *addr = NULL; - - for (const char **name = mprotect_symbol_names; *name; name++) { - addr = get_kernel_symbol(*name); - if (IS_ERR(addr)) { - ret = PTR_ERR(addr); - continue; - } - symbol_name = *name; - do_mprotect_pkey = addr; - break; + do_mprotect_pkey_func = resolve_kernel_symbol(do_mprotect_pkey_names); + if (unlikely(!do_mprotect_pkey_func)) { + log_err("cannot find kernel symbol '%s'\n", do_mprotect_pkey_names[0]); + return -ENOSYS; } - if (!symbol_name) { - ret = PTR_ERR(addr); - log_err("cannot find kernel symbol '%s', ret=%d\n", mprotect_symbol_names[0], ret); - return ret; + do_munmap_func = resolve_kernel_symbol(do_munmap_names); + if (unlikely(!do_munmap_func)) { + log_err("cannot find kernel symbol '%s'\n", do_munmap_names[0]); + return -ENOSYS; } return 0; diff --git a/upatch-manage/kernel_compat.h b/upatch-manage/kernel_compat.h index ea034245..5fdd3364 100644 --- a/upatch-manage/kernel_compat.h +++ b/upatch-manage/kernel_compat.h @@ -22,8 +22,7 @@ #define _UPATCH_MANAGE_KERNEL_COMPAT_H #include -#include -#include +#include #include #include @@ -52,6 +51,8 @@ struct vm_area_struct *upatch_vma_prev(struct upatch_vma_iter *vmi); int upatch_mprotect(unsigned long addr, size_t len, unsigned long prot); +int upatch_munmap(struct mm_struct *mm, unsigned long addr, size_t size, struct list_head *uf); + int __init kernel_compat_init(void); void __exit kernel_compat_exit(void); diff --git a/upatch-manage/process_entity.c b/upatch-manage/process_entity.c index c3a7c1e9..f7cd36ab 100644 --- a/upatch-manage/process_entity.c +++ b/upatch-manage/process_entity.c @@ -23,6 +23,7 @@ #include #include +#include "kernel_compat.h" #include "patch_entity.h" #include "target_entity.h" @@ -52,7 +53,7 @@ static int do_free_patch_memory(struct mm_struct *mm, unsigned long addr, size_t return -EFAULT; } - return do_munmap(mm, addr, len, NULL); + return upatch_munmap(mm, addr, len, NULL); } static void free_patch_memory(struct task_struct *task, struct patch_info *patch) -- Gitee From 33dc11f36bcc25a4d6e9eff93b354b2de0f3c0a9 Mon Sep 17 00:00:00 2001 From: renoseven Date: Wed, 30 Jul 2025 17:31:46 +0800 Subject: [PATCH 2/3] upatch-manage: rewrite patch management Signed-off-by: renoseven --- upatch-manage/Makefile | 4 +- upatch-manage/ioctl_dev.c | 193 ++------ upatch-manage/ioctl_dev.h | 18 +- upatch-manage/main.c | 18 +- upatch-manage/patch_entity.c | 225 +++++---- upatch-manage/patch_entity.h | 110 +++-- upatch-manage/patch_load.c | 24 +- upatch-manage/patch_load.h | 8 +- upatch-manage/patch_manage.c | 762 +++++++++++------------------ upatch-manage/patch_manage.h | 12 +- upatch-manage/process_cache.c | 75 +++ upatch-manage/process_cache.h | 34 ++ upatch-manage/process_entity.c | 263 ++++++---- upatch-manage/process_entity.h | 169 +++++-- upatch-manage/stack_check.c | 20 +- upatch-manage/symbol_resolve.c | 10 +- upatch-manage/target_entity.c | 855 ++++++++++++++++++++++++--------- upatch-manage/target_entity.h | 194 +++++--- upatch-manage/util.h | 32 +- 19 files changed, 1762 insertions(+), 1264 deletions(-) create mode 100644 upatch-manage/process_cache.c create mode 100644 upatch-manage/process_cache.h diff --git a/upatch-manage/Makefile b/upatch-manage/Makefile index de6664b5..1dbb1806 100644 --- a/upatch-manage/Makefile +++ b/upatch-manage/Makefile @@ -19,10 +19,10 @@ ifdef GCOV endif obj-m += $(MODULE_NAME).o -$(MODULE_NAME)-objs := main.o ioctl_dev.o +$(MODULE_NAME)-objs := kernel_compat.o process_cache.o ioctl_dev.o main.o $(MODULE_NAME)-objs += patch_entity.o target_entity.o process_entity.o $(MODULE_NAME)-objs += patch_load.o arch/$(ARCH)/patch_load.o symbol_resolve.o stack_check.o -$(MODULE_NAME)-objs += patch_manage.o kernel_compat.o +$(MODULE_NAME)-objs += patch_manage.o ifeq ($(ARCH), arm64) $(MODULE_NAME)-objs += arch/$(ARCH)/insn.o diff --git a/upatch-manage/ioctl_dev.c b/upatch-manage/ioctl_dev.c index 23881a15..9a877800 100644 --- a/upatch-manage/ioctl_dev.c +++ b/upatch-manage/ioctl_dev.c @@ -35,11 +35,6 @@ #include "patch_manage.h" #include "util.h" -struct patch_load_request { - const char *patch_file; - const char *target_elf; -}; - long handle_ioctl(struct file *file, unsigned int code, unsigned long arg); static const struct file_operations UPATCH_DEV_OPS = { @@ -54,24 +49,26 @@ static struct miscdevice upatch_dev = { .mode = UPATCH_DEV_MODE, }; -static char *vmalloc_string_from_user(void __user *addr) +static char *read_patch_requrest_param(void __user *addr) { size_t len; char *buf; int ret; - if (addr == 0) { + if (unlikely(!addr)) { return ERR_PTR(-EINVAL); } len = strnlen_user(addr, MAX_ARG_STRLEN); - if (len > PATH_MAX) { + if (unlikely(len == 0)) { + return ERR_PTR(-EINVAL); + } + if (unlikely(len > PATH_MAX)) { return ERR_PTR(-EOVERFLOW); } buf = vmalloc(len); if (!buf) { - log_err("failed to vmalloc string, len=0x%lx\n", len); return ERR_PTR(-ENOMEM); } @@ -84,175 +81,83 @@ static char *vmalloc_string_from_user(void __user *addr) return buf; } -static int get_load_para_from_user(void __user *user_addr, struct patch_load_request *res) +static int read_patch_request(struct upatch_request *req, void __user *msg) { - struct patch_load_request req; - int error; int ret; - ret = copy_from_user(&req, user_addr, sizeof(struct patch_load_request)); + ret = copy_from_user(req, msg, sizeof(struct upatch_request)); if (ret) { - log_err("failed to get target elf path, ret=%d\n", ret); return -EINVAL; } - res->target_elf = vmalloc_string_from_user((void __user *)req.target_elf); - if (IS_ERR(res->target_elf)) { - error = PTR_ERR(res->target_elf); - log_err("failed to get target elf path, ret=%d\n", error); - return error; - } - - res->patch_file = vmalloc_string_from_user((void __user *)req.patch_file); - if (IS_ERR(res->patch_file)) { - error = PTR_ERR(res->patch_file); - log_err("failed to get patch file path, ret=%d\n", error); - vfree(res->patch_file); - return error; - } - - return 0; -} - -static int ioctl_get_patch_status(void __user * user_addr) -{ - int ret; - - char *patch = vmalloc_string_from_user(user_addr); - - if (IS_ERR(patch)) { - log_err("failed to get patch file path\n"); - return PTR_ERR(patch); - } - - ret = upatch_status(patch); - log_debug("patch '%s' is %s\n", patch, patch_status_str(ret)); - - vfree(patch); - return ret; -} - -static int ioctl_load_patch(void __user * user_addr) -{ - int ret; - struct patch_load_request req; - - if (!try_module_get(THIS_MODULE)) { - log_err("cannot increase '%s' refcnt!", THIS_MODULE->name); - return -ENODEV; - } - - ret = get_load_para_from_user(user_addr, &req); - if (ret) { - log_err("failed to get patch file path\n"); - module_put(THIS_MODULE); + req->target_elf = read_patch_requrest_param((void __user *)req->target_elf); + if (IS_ERR(req->target_elf)) { + ret = PTR_ERR(req->target_elf); + log_err("failed to read target elf\n"); return ret; } - ret = upatch_load(req.patch_file, req.target_elf); - if (ret) { - log_err("failed to load '%s' for '%s', ret=%d\n", - req.patch_file, req.target_elf, ret); - module_put(THIS_MODULE); - } - - vfree(req.patch_file); - vfree(req.target_elf); - return ret; -} - -static int ioctl_active_patch(void __user * user_addr) -{ - int ret; - char *patch = vmalloc_string_from_user(user_addr); - - if (IS_ERR(patch)) { - log_err("failed to get patch file path\n"); - return PTR_ERR(patch); - } - - ret = upatch_active(patch); - if (ret) { - log_err("failed to active patch '%s', ret=%d\n", patch, ret); - } - - vfree(patch); - return ret; -} - -static int ioctl_deactive_patch(void __user * user_addr) -{ - int ret; - char *patch = vmalloc_string_from_user(user_addr); - - if (IS_ERR(patch)) { - log_err("failed to get patch file path\n"); - return PTR_ERR(patch); - } - - ret = upatch_deactive(patch); - if (ret) { - log_err("failed to deactive patch '%s', ret=%d\n", patch, ret); + req->patch_file = read_patch_requrest_param((void __user *)req->patch_file); + if (IS_ERR(req->patch_file)) { + ret = PTR_ERR(req->patch_file); + log_err("failed to read patch file\n"); + vfree(req->patch_file); + return ret; } - vfree(patch); return ret; } -static int ioctl_remove_patch(void __user * user_addr) +static inline void clear_patch_request(struct upatch_request *request) { - int ret; - char *patch = vmalloc_string_from_user(user_addr); - - if (IS_ERR(patch)) { - log_err("failed to get patch file path\n"); - return PTR_ERR(patch); - } - - ret = upatch_remove(patch); - if (ret) { - log_err("failed to remove patch %s, ret=%d\n", patch, ret); - } else { - module_put(THIS_MODULE); - } - - vfree(patch); - return ret; + vfree(request->target_elf); + vfree(request->patch_file); } long handle_ioctl(struct file *file, unsigned int code, unsigned long arg) { unsigned int type = _IOC_TYPE(code); unsigned int nr = _IOC_NR(code); - void __user *argp = (void __user *)arg; + void __user *msg = (void __user *)arg; + + struct upatch_request req; + int ret = 0; - if (type != UPATCH_MAGIC) { - log_err("invalid ioctl type 0x%x\n", type); + if (unlikely(type != UPATCH_MAGIC || !msg)) { + log_err("invalid ioctl message\n"); return -EINVAL; } - switch (nr) { - case UPATCH_STATUS: - return ioctl_get_patch_status(argp); + ret = read_patch_request(&req, msg); + if (unlikely(ret)) { + log_err("failed to read patch requrest\n"); + return ret; + } + switch (nr) { case UPATCH_LOAD: - return ioctl_load_patch(argp); - + ret = upatch_load(req.target_elf, req.patch_file); + break; + case UPATCH_REMOVE: + ret = upatch_remove(req.target_elf, req.patch_file); + break; case UPATCH_ACTIVE: - return ioctl_active_patch(argp); - + ret = upatch_active(req.target_elf, req.patch_file); + break; case UPATCH_DEACTIVE: - return ioctl_deactive_patch(argp); - - case UPATCH_REMOVE: - return ioctl_remove_patch(argp); - + ret = upatch_deactive(req.target_elf, req.patch_file); + break; + case UPATCH_STATUS: + ret = upatch_status(req.target_elf, req.patch_file); + break; default: log_err("invalid ioctl nr 0x%x\n", nr); - return -EINVAL; + ret = -EINVAL; + break; } - return 0; + clear_patch_request(&req); + return ret; } int __init ioctl_device_init(void) diff --git a/upatch-manage/ioctl_dev.h b/upatch-manage/ioctl_dev.h index ad0b82d1..728c74a5 100644 --- a/upatch-manage/ioctl_dev.h +++ b/upatch-manage/ioctl_dev.h @@ -13,12 +13,11 @@ #include #define UPATCH_DEV_NAME "upatch_manage" -#define UPATCH_DEV_PATH "/dev/upatch_manage" #define UPATCH_DEV_MODE 0600 #define UPATCH_MAGIC 0xE5 -enum { +enum upatch_command { UPATCH_LOAD = 1, UPATCH_ACTIVE, UPATCH_DEACTIVE, @@ -26,13 +25,18 @@ enum { UPATCH_STATUS, }; +struct upatch_request { + const char *target_elf; + const char *patch_file; +}; + #define _UPATCH_IOCTL(cmd, type) _IOW(UPATCH_MAGIC, cmd, type) -#define UPATCH_LOAD_IOCTL _UPATCH_IOCTL(UPATCH_LOAD, const struct load_request *) -#define UPATCH_ACTIVE_IOCTL _UPATCH_IOCTL(UPATCH_ACTIVE, const char *) -#define UPATCH_DEACTIVE_IOCTL _UPATCH_IOCTL(UPATCH_DEACTIVE, const char *) -#define UPATCH_REMOVE_IOCTL _UPATCH_IOCTL(UPATCH_REMOVE, const char *) -#define UPATCH_STATUS_IOCTL _UPATCH_IOCTL(UPATCH_STATUS, const char *) +#define UPATCH_LOAD_IOCTL _UPATCH_IOCTL(UPATCH_LOAD, const struct upatch_request *) +#define UPATCH_ACTIVE_IOCTL _UPATCH_IOCTL(UPATCH_ACTIVE, const struct upatch_request *) +#define UPATCH_DEACTIVE_IOCTL _UPATCH_IOCTL(UPATCH_DEACTIVE, const struct upatch_request *) +#define UPATCH_REMOVE_IOCTL _UPATCH_IOCTL(UPATCH_REMOVE, const struct upatch_request *) +#define UPATCH_STATUS_IOCTL _UPATCH_IOCTL(UPATCH_STATUS, const struct upatch_request *) int __init ioctl_device_init(void); void __exit ioctl_device_exit(void); diff --git a/upatch-manage/main.c b/upatch-manage/main.c index 060582b3..17cfc5f8 100644 --- a/upatch-manage/main.c +++ b/upatch-manage/main.c @@ -23,8 +23,9 @@ #include #include "ioctl_dev.h" -#include "patch_manage.h" #include "kernel_compat.h" +#include "process_cache.h" +#include "patch_manage.h" #include "util.h" #ifndef MODNAME @@ -40,13 +41,19 @@ static int __init upatch_module_init(void) int ret; ret = kernel_compat_init(); - if (ret) { + if (unlikely(ret)) { log_err("failed to initialize kernel compat layer, ret=%d\n", ret); return ret; } + ret = process_cache_init(); + if (unlikely(ret)) { + log_err("failed to initialize process cache, ret=%d\n", ret); + return ret; + } + ret = ioctl_device_init(); - if (ret) { + if (unlikely(ret)) { log_err("failed to initialize ioctl device, ret=%d\n", ret); return ret; } @@ -64,10 +71,11 @@ static int __init upatch_module_init(void) */ static void __exit upatch_module_exit(void) { - report_global_table_populated(); + check_target_table_populated(); - kernel_compat_exit(); ioctl_device_exit(); + process_cache_exit(); + kernel_compat_exit(); log_info("%s %s exited\n", MODNAME, MODVER); } diff --git a/upatch-manage/patch_entity.c b/upatch-manage/patch_entity.c index 891418a5..c24e1658 100644 --- a/upatch-manage/patch_entity.c +++ b/upatch-manage/patch_entity.c @@ -20,8 +20,8 @@ #include "patch_entity.h" +#include #include -#include #include "patch_load.h" #include "util.h" @@ -33,59 +33,60 @@ static const char *UPATCH_FUNCS_NAME = ".upatch.funcs"; static const char *UPATCH_FUNCS_RELA_NAME = ".rela.upatch.funcs"; static const char *UPATCH_STRINGS_NAME = ".upatch.strings"; -static inline void count_und_symbol(struct patch_metadata *meta, Elf_Sym *symtab, size_t count) +/* --- Patch life-cycle management --- */ + +static void destroy_patch_file(struct patch_file *patch_file) +{ + iput(patch_file->inode); + + patch_file->path = NULL; + patch_file->inode = NULL; + patch_file->size = 0; + + VFREE_CLEAR(patch_file->buff); + + patch_file->shstrtab_index = 0; + patch_file->symtab_index = 0; + patch_file->strtab_index = 0; + + patch_file->func_index = 0; + patch_file->rela_index = 0; + patch_file->string_index = 0; + + patch_file->funcs = NULL; + patch_file->strings = NULL; + + patch_file->func_num = 0; + patch_file->string_len = 0; + + patch_file->und_sym_num = 0; + patch_file->got_reloc_num = 0; +} + +static inline void resolve_und_symbol_count(struct patch_file *patch_file, Elf_Sym *symtab, size_t count) { size_t i; for (i = 1; i < count; i++) { if (symtab[i].st_shndx == SHN_UNDEF) { - meta->und_sym_num++; + patch_file->und_sym_num++; } } } -static inline void count_got_reloc(struct patch_metadata *meta, Elf_Rela *relas, size_t count) +static inline void resolve_got_reloc_count(struct patch_file *patch_file, Elf_Rela *relas, size_t count) { size_t i; for (i = 0; i < count; i++) { if (is_got_rela_type(ELF_R_TYPE(relas[i].r_info))) { - meta->got_reloc_num++; + patch_file->got_reloc_num++; } } } -static void destroy_patch_metadata(struct patch_metadata *meta) +static int resolve_patch_file(struct patch_file *patch_file, struct file *file) { - KFREE_CLEAR(meta->path); - iput(meta->inode); - meta->inode = NULL; - - VFREE_CLEAR(meta->buff); - meta->size = 0; - - meta->shstrtab_index = 0; - meta->symtab_index = 0; - meta->strtab_index = 0; - - meta->func_index = 0; - meta->rela_index = 0; - meta->string_index = 0; - - meta->funcs = NULL; - meta->strings = NULL; - - meta->func_num = 0; - meta->string_len = 0; - - meta->und_sym_num = 0; - meta->got_reloc_num = 0; -} - -static int resolve_patch_metadata(struct patch_metadata *meta, const char *file_path) -{ - struct file *file; - Elf_Ehdr *ehdr; Elf_Shdr *shdrs; Elf_Half shdr_num; @@ -103,110 +104,106 @@ static int resolve_patch_metadata(struct patch_metadata *meta, const char *file_ int ret = 0; - file = filp_open(file_path, O_RDONLY, 0); - if (IS_ERR(file)) { - log_err("failed to open '%s'\n", file_path); - ret = PTR_ERR(file); - file = NULL; - goto out; - } + rcu_read_lock(); + patch_file->path = file_path(file, patch_file->path_buff, PATH_MAX); + rcu_read_unlock(); - meta->path = kstrdup(file_path, GFP_KERNEL); - if (!meta->path) { - log_err("faild to alloc file path\n"); - ret = -ENOMEM; + if (unlikely(IS_ERR(patch_file->path))) { + ret = PTR_ERR(patch_file->path); + log_err("faild to get file path\n"); goto out; } - meta->inode = igrab(file_inode(file)); - if (!meta->inode) { - log_err("file '%s' inode is invalid\n", meta->path); + patch_file->inode = igrab(file_inode(file)); + if (unlikely(!patch_file->inode)) { + log_err("failed to get file inode\n"); ret = -ENOENT; goto out; } - meta->size = i_size_read(meta->inode); - meta->buff = vmalloc_read(file, 0, meta->size); - if (IS_ERR(meta->buff)) { - log_err("failed to read file, len=0x%llx\n", meta->size); - ret = PTR_ERR(meta->buff); + patch_file->size = i_size_read(patch_file->inode); + + patch_file->buff = vmalloc_read(file, 0, patch_file->size); + if (unlikely(IS_ERR(patch_file->buff))) { + log_err("failed to read file, len=0x%llx\n", patch_file->size); + ret = PTR_ERR(patch_file->buff); goto out; } - ehdr = meta->buff; - if (!is_valid_patch(ehdr, meta->size)) { + ehdr = patch_file->buff; + if (unlikely(!is_valid_patch(ehdr, patch_file->size))) { log_err("invalid file format\n"); ret = -ENOEXEC; goto out; } - meta->shstrtab_index = ehdr->e_shstrndx; + patch_file->shstrtab_index = ehdr->e_shstrndx; - shdrs = meta->buff + ehdr->e_shoff; + shdrs = patch_file->buff + ehdr->e_shoff; shdr_num = ehdr->e_shnum; - shstrtab = meta->buff + shdrs[meta->shstrtab_index].sh_offset; - shstrtab_size = shdrs[meta->shstrtab_index].sh_size; + shstrtab = patch_file->buff + shdrs[patch_file->shstrtab_index].sh_offset; + shstrtab_size = shdrs[patch_file->shstrtab_index].sh_size; for (i = 1; i < shdr_num; i++) { shdr = &shdrs[i]; sec_name = get_string_at(shstrtab, shstrtab_size, shdr->sh_name); - if (sec_name == NULL) { + if (unlikely(sec_name == NULL)) { log_err("invalid section name, index=%u\n", i); ret = -ENOEXEC; goto out; } sec_name = shstrtab + shdr->sh_name; // no need check - if (shdr->sh_type != SHT_NOBITS && shdr->sh_offset + shdr->sh_size > meta->size) { + if (unlikely(shdr->sh_type != SHT_NOBITS && shdr->sh_offset + shdr->sh_size > patch_file->size)) { log_err("section '%s' offset overflow, index=%u\n", sec_name, i); ret = -ENOEXEC; goto out; } - sec_data = meta->buff + shdr->sh_offset; + sec_data = patch_file->buff + shdr->sh_offset; switch (shdr->sh_type) { case SHT_PROGBITS: if (strcmp(sec_name, UPATCH_FUNCS_NAME) == 0) { - meta->func_index = i; - meta->funcs = sec_data; - meta->func_num = shdr->sh_size / sizeof(struct upatch_function); + patch_file->func_index = i; + patch_file->funcs = sec_data; + patch_file->func_num = shdr->sh_size / sizeof(struct upatch_function); } else if (strcmp(sec_name, UPATCH_STRINGS_NAME) == 0) { // .upatch.strings is not SHT_STRTAB - meta->string_index = i; - meta->strings = sec_data; - meta->string_len = shdr->sh_size; + patch_file->string_index = i; + patch_file->strings = sec_data; + patch_file->string_len = shdr->sh_size; } break; case SHT_SYMTAB: - if (shdr->sh_entsize != sizeof(Elf_Sym)) { + if (unlikely(shdr->sh_entsize != sizeof(Elf_Sym))) { log_err("invalid section '%s' entity size\n", sec_name); ret = -ENOEXEC; goto out; } - if (shdr->sh_link > shdr_num) { + if (unlikely(shdr->sh_link > shdr_num)) { log_err("invalid section '%s' string table index\n", sec_name); ret = -ENOEXEC; goto out; } if (strcmp(sec_name, SYMTAB_NAME) == 0) { - meta->symtab_index = i; - meta->strtab_index = shdr->sh_link; - count_und_symbol(meta, sec_data, shdr->sh_size / shdr->sh_entsize); + patch_file->symtab_index = i; + patch_file->strtab_index = shdr->sh_link; + resolve_und_symbol_count(patch_file, sec_data, shdr->sh_size / shdr->sh_entsize); } break; case SHT_RELA: - if (shdr->sh_entsize != sizeof(Elf_Rela)) { + if (unlikely(shdr->sh_entsize != sizeof(Elf_Rela))) { log_err("invalid section '%s' entity size\n", sec_name); ret = -ENOEXEC; goto out; } if (strcmp(sec_name, UPATCH_FUNCS_RELA_NAME) == 0) { - meta->rela_index = i; + patch_file->rela_index = i; relas = sec_data; rela_num = shdr->sh_size / sizeof(struct upatch_relocation); } else if (strncmp(sec_name, TEXT_RELA_NAME, strlen(TEXT_RELA_NAME)) == 0) { - count_got_reloc(meta, sec_data, shdr->sh_size / shdr->sh_entsize); + resolve_got_reloc_count(patch_file, sec_data, shdr->sh_size / shdr->sh_entsize); } break; @@ -215,107 +212,103 @@ static int resolve_patch_metadata(struct patch_metadata *meta, const char *file_ } } - if (!meta->symtab_index || !meta->strtab_index) { + if (unlikely(!patch_file->symtab_index || !patch_file->strtab_index)) { log_err("patch contains no symbol\n"); ret = -ENOEXEC; goto out; } - if (!meta->func_index || !meta->funcs || !meta->func_num) { + if (unlikely(!patch_file->func_index || !patch_file->funcs || !patch_file->func_num)) { log_err("patch contains no function\n"); ret = -ENOEXEC; goto out; } - if (!meta->rela_index || !meta->string_index || - !relas || !meta->strings || - !rela_num || !meta->string_len || - meta->func_num != rela_num) { + if (unlikely(!patch_file->rela_index || !patch_file->string_index || + !relas || !patch_file->strings || + !rela_num || !patch_file->string_len || + patch_file->func_num != rela_num)) { log_err("invalid patch format\n"); ret = -ENOEXEC; goto out; } for (i = 0; i < rela_num; i++) { - meta->funcs[i].name_off = relas[i].name.r_addend; + patch_file->funcs[i].name_off = relas[i].name.r_addend; } out: - if (file) { - filp_close(file, NULL); - } - if (ret) { - destroy_patch_metadata(meta); + if (unlikely(ret)) { + destroy_patch_file(patch_file); } + return ret; } -static int resolve_patch_entity(struct patch_entity *patch, const char *file_path) +static int resolve_patch(struct patch_entity *patch, struct file *file) { int ret; - ret = resolve_patch_metadata(&patch->meta, file_path); + ret = resolve_patch_file(&patch->file, file); if (ret) { return ret; } - - INIT_HLIST_NODE(&patch->table_node); - patch->target = NULL; patch->status = UPATCH_STATUS_NOT_APPLIED; - init_rwsem(&patch->action_rwsem); - INIT_LIST_HEAD(&patch->loaded_node); + INIT_HLIST_NODE(&patch->node); INIT_LIST_HEAD(&patch->actived_node); + kref_init(&patch->kref); return 0; } -static void destroy_patch_entity(struct patch_entity *patch) +static void destroy_patch(struct patch_entity *patch) { - destroy_patch_metadata(&patch->meta); - - WARN_ON(!hlist_unhashed(&patch->table_node)); + WARN_ON(!hlist_unhashed(&patch->node)); + WARN_ON(!list_empty(&patch->actived_node)); - patch->target = NULL; + destroy_patch_file(&patch->file); patch->status = UPATCH_STATUS_NOT_APPLIED; - WARN_ON(!list_empty(&patch->loaded_node)); - WARN_ON(!list_empty(&patch->actived_node)); - INIT_HLIST_NODE(&patch->table_node); - INIT_LIST_HEAD(&patch->loaded_node); + INIT_HLIST_NODE(&patch->node); INIT_LIST_HEAD(&patch->actived_node); } -/* public interface */ -struct patch_entity *new_patch_entity(const char *file_path) +/* --- Public interface --- */ + +struct patch_entity *load_patch(struct file *file) { struct patch_entity *patch = NULL; int ret; - if (unlikely(!file_path)) { + if (unlikely(!file)) { return ERR_PTR(-EINVAL); } patch = kzalloc(sizeof(struct patch_entity), GFP_KERNEL); - if (!patch) { - log_err("failed to alloc patch entity\n"); + if (unlikely(!patch)) { return ERR_PTR(-ENOMEM); } - ret = resolve_patch_entity(patch, file_path); - if (ret) { + ret = resolve_patch(patch, file); + if (unlikely(ret)) { kfree(patch); return ERR_PTR(ret); } + log_debug("new patch %s\n", patch->file.path); return patch; } -void free_patch_entity(struct patch_entity *patch) +void release_patch(struct kref *kref) { - if (unlikely(!patch)) { + struct patch_entity *patch; + + if (unlikely(!kref)) { return; } - log_debug("free patch '%s'\n", patch->meta.path); - destroy_patch_entity(patch); + patch = container_of(kref, struct patch_entity, kref); + log_debug("free patch %s\n", patch->file.path); + + destroy_patch(patch); kfree(patch); } diff --git a/upatch-manage/patch_entity.h b/upatch-manage/patch_entity.h index c7b7e07d..6035aacf 100644 --- a/upatch-manage/patch_entity.h +++ b/upatch-manage/patch_entity.h @@ -24,7 +24,9 @@ #include #include #include +#include +struct file; struct inode; struct target_entity; @@ -76,57 +78,95 @@ struct upatch_function { }; #endif -/* Patch metadata */ -struct patch_metadata { - const char *path; // patch file path - struct inode *inode; // patch file inode +/* Patch file */ +struct patch_file { + char path_buff[PATH_MAX]; - void *buff; // patch file buff - loff_t size; // patch file size + const char *path; // patch file path + struct inode *inode; // patch file path + loff_t size; // patch file size - Elf_Half shstrtab_index; // section '.shstrtab' index - Elf_Half symtab_index; // section '.symtab' index - Elf_Half strtab_index; // section '.strtab' index + void *buff; // patch file buff - Elf_Half func_index; // section '.upatch.funcs' index - Elf_Half rela_index; // section '.rela.upatch.funcs' index - Elf_Half string_index; // section '.upatch.strings' index + Elf_Half shstrtab_index; // section '.shstrtab' index + Elf_Half symtab_index; // section '.symtab' index + Elf_Half strtab_index; // section '.strtab' index - struct upatch_function *funcs; // patch function table - const char *strings; // patch string table + Elf_Half func_index; // section '.upatch.funcs' index + Elf_Half rela_index; // section '.rela.upatch.funcs' index + Elf_Half string_index; // section '.upatch.strings' index - size_t func_num; // patch function count - size_t string_len; // patch string table length + struct upatch_function *funcs; // patch function table + const char *strings; // patch string table - size_t und_sym_num; // undefined symbol count (SHN_UNDEF) - size_t got_reloc_num; // got relocation count + size_t func_num; // patch function count + size_t string_len; // patch string table length + + size_t und_sym_num; // undefined symbol count (SHN_UNDEF) + size_t got_reloc_num; // got relocation count }; /* Patch entity */ struct patch_entity { - struct patch_metadata meta; // patch file metadata - struct hlist_node table_node; // global patch hash table node + struct patch_file file; // patch file + enum upatch_status status; // patch status - struct rw_semaphore action_rwsem; // patch action rw semaphore - struct target_entity *target; // patch target - enum upatch_status status; // patch status + struct hlist_node node; // hash table node + struct list_head actived_node; // target actived patch list node - struct list_head loaded_node; // target loaded patch node - struct list_head actived_node; // target actived patch list node + struct kref kref; }; -/* - * Load a patch file - * @param file_path: patch file path - * @return patch entity +/** + * @brief Load a new patch file + * @param file: Patch file struct pointer + * @return Newly allocated patch entity with refcount=1, or NULL on failure + * + * Allocates and initializes a new patch entity structure with reference count 1. + * The caller is responsible for calling put_patch() when done. */ -struct patch_entity *new_patch_entity(const char *file_path); +struct patch_entity *load_patch(struct file *file); -/* - * Free a patch entity - * @param patch: patch entity - * @return void +/** + * @brief Release patch resources when refcount reaches zero + * @param kref: Reference counter + * + * Called automatically by kref_put(). + * Frees all patch resources and disassociates from target. */ -void free_patch_entity(struct patch_entity *patch); +void release_patch(struct kref *kref); + +/** + * @brief Acquire a reference to a patch entity + * @param patch: Patch entity pointer + * @return Patch with incremented refcount, or NULL if input is NULL + * + * Caller must balance with put_patch(). + */ +static inline struct patch_entity *get_patch(struct patch_entity *patch) +{ + if (unlikely(!patch)) { + return NULL; + } + + kref_get(&patch->kref); + return patch; +} + +/** + * @brief Release a patch entity reference + * @param patch: Patch entity pointer + * + * Decrements refcount and triggers release_patch_entity() when reaching zero. + * Safe to call with NULL. + */ +static inline void put_patch(struct patch_entity *patch) +{ + if (unlikely(!patch)) { + return; + } + + kref_put(&patch->kref, release_patch); +} #endif // _UPATCH_MANAGE_PATCH_ENTITY_H diff --git a/upatch-manage/patch_load.c b/upatch-manage/patch_load.c index 44a608fa..88e7cb5d 100644 --- a/upatch-manage/patch_load.c +++ b/upatch-manage/patch_load.c @@ -121,12 +121,12 @@ static void layout_sections(struct patch_context *ctx) static int init_load_info(struct patch_context *ctx, const struct patch_entity *patch, const struct target_entity *target, unsigned long vma_start) { - void *file_buff = patch->meta.buff; - loff_t file_size = patch->meta.size; + void *file_buff = patch->file.buff; + loff_t file_size = patch->file.size; - ctx->target = &target->meta; - ctx->patch = &patch->meta; - ctx->load_bias = vma_start - target->meta.vma_offset; + ctx->target = &target->file; + ctx->patch = &patch->file; + ctx->load_bias = vma_start - target->file.vma_offset; log_debug("process %d: vma_start=0x%lx, load_bias=0x%lx\n", task_tgid_nr(current), vma_start, ctx->load_bias); // alloc & copy whole patch into kernel temporarily @@ -140,13 +140,13 @@ static int init_load_info(struct patch_context *ctx, ctx->ehdr = ctx->buff; ctx->shdrs = ctx->buff + ctx->ehdr->e_shoff; - ctx->shstrtab_shdr = &ctx->shdrs[patch->meta.shstrtab_index]; - ctx->symtab_shdr = &ctx->shdrs[patch->meta.symtab_index]; - ctx->strtab_shdr = &ctx->shdrs[patch->meta.strtab_index]; + ctx->shstrtab_shdr = &ctx->shdrs[patch->file.shstrtab_index]; + ctx->symtab_shdr = &ctx->shdrs[patch->file.symtab_index]; + ctx->strtab_shdr = &ctx->shdrs[patch->file.strtab_index]; - ctx->func_shdr = &ctx->shdrs[patch->meta.func_index]; - ctx->rela_shdr = &ctx->shdrs[patch->meta.rela_index]; - ctx->string_shdr = &ctx->shdrs[patch->meta.string_index]; + ctx->func_shdr = &ctx->shdrs[patch->file.func_index]; + ctx->rela_shdr = &ctx->shdrs[patch->file.rela_index]; + ctx->string_shdr = &ctx->shdrs[patch->file.string_index]; // alloc patch sections layout_sections(ctx); @@ -518,7 +518,7 @@ int upatch_resolve(struct target_entity *target, struct patch_entity *patch, str goto fail; } - ret = process_write_patch_info(process, patch, &context); + ret = process_load_patch(process, patch, &context); if (ret) { goto fail; } diff --git a/upatch-manage/patch_load.h b/upatch-manage/patch_load.h index bbe00caa..fcc20e0f 100644 --- a/upatch-manage/patch_load.h +++ b/upatch-manage/patch_load.h @@ -25,10 +25,10 @@ #include struct target_entity; -struct target_metadata; +struct target_file; struct patch_entity; -struct patch_metadata; +struct patch_file; struct process_entity; @@ -51,8 +51,8 @@ struct patch_layout { // when load patch, patch need resolve in different process struct patch_context { - const struct target_metadata *target; - const struct patch_metadata *patch; + const struct target_file *target; + const struct patch_file *patch; unsigned long load_bias; void *buff; // patch image in kernelspace diff --git a/upatch-manage/patch_manage.c b/upatch-manage/patch_manage.c index 23bd4d6a..c0e238aa 100644 --- a/upatch-manage/patch_manage.c +++ b/upatch-manage/patch_manage.c @@ -22,9 +22,9 @@ #include #include - +#include #include -#include +#include #include "target_entity.h" #include "process_entity.h" @@ -32,78 +32,35 @@ #include "patch_load.h" #include "util.h" -/* - * ===================================================================== - * LOCKING HIERARCHY - * ===================================================================== - * To prevent deadlocks, the following lock acquisition order MUST be - * strictly followed throughout the entire module: - * - * 1. g_global_table_rwsem - * 2. target_entity->action_rwsem - * 3. patch_entity->action_rwsem - * - * Any deviation from this order will lead to deadlocks. - * ===================================================================== - */ - #define UPROBE_RUN_OLD_FUNC 0 #ifndef UPROBE_ALTER_PC #define UPROBE_ALTER_PC 2 #endif -#define PATCH_TABLE_HASH_BITS 4 -#define TARGET_TABLE_HASH_BITS 4 +#define TARGET_FILE_HASH_BITS 5 // would have less than 2^5 = 32 targets -static DEFINE_HASHTABLE(g_patch_table, PATCH_TABLE_HASH_BITS); -static DEFINE_HASHTABLE(g_target_table, TARGET_TABLE_HASH_BITS); - -static DECLARE_RWSEM(g_global_table_rwsem); +/* --- Forward declarations --- */ static int upatch_uprobe_handler(struct uprobe_consumer *self, struct pt_regs *regs); -static struct uprobe_consumer g_upatch_consumer = { - .handler = upatch_uprobe_handler, -}; +/* --- Global variables --- */ -/* GLOBAL ENTITY HASH TABLE */ -static struct patch_entity *find_patch_by_inode(struct inode *inode) -{ - struct patch_entity *patch; +static DEFINE_HASHTABLE(g_target_table, TARGET_FILE_HASH_BITS); // global target hash table +static DEFINE_SPINLOCK(g_target_table_lock); // lock for global target hash table, SHOULD NOT hold other lock inside it - hash_for_each_possible(g_patch_table, patch, table_node, inode->i_ino) { - if (patch->meta.inode == inode) { - return patch; - } - } - - return NULL; -} - -struct patch_entity *find_patch(const char *path) -{ - struct patch_entity *patch; - struct inode *inode; - - inode = get_path_inode(path); - if (!inode) { - log_err("failed to get '%s' inode\n", path); - return NULL; - } - - patch = find_patch_by_inode(inode); +static struct uprobe_consumer g_uprobe_consumer = { + .handler = upatch_uprobe_handler, +}; - iput(inode); - return patch; -} +/* --- Target table management --- */ -static struct target_entity *find_target_by_inode(struct inode *inode) +static inline struct target_entity *find_target_unlocked(struct inode *inode) { struct target_entity *target; - hash_for_each_possible(g_target_table, target, table_node, inode->i_ino) { - if (target->meta.inode == inode) { + hash_for_each_possible(g_target_table, target, node, hash_inode(inode, TARGET_FILE_HASH_BITS)) { + if (inode_equal(target->file.inode, inode)) { return target; } } @@ -111,25 +68,21 @@ static struct target_entity *find_target_by_inode(struct inode *inode) return NULL; } -static struct target_entity *find_target(const char *path) +static inline struct target_entity *get_target_by_inode(struct inode *inode) { struct target_entity *target; - struct inode *inode; - - inode = get_path_inode(path); - if (!inode) { - log_err("failed to get '%s' inode\n", path); - return NULL; - } - target = find_target_by_inode(inode); + spin_lock(&g_target_table_lock); + target = find_target_unlocked(inode); + get_target(target); + spin_unlock(&g_target_table_lock); - iput(inode); return target; } -/* UPROBE IMPLEMENTATION */ -static struct inode *find_vma_file_inode(unsigned long pc, unsigned long *vma_start) +/* --- Uprobe management --- */ + +static struct inode *get_vma_file_inode(unsigned long pc, unsigned long *vma_start) { struct mm_struct *mm = current->mm; struct vm_area_struct *vma; @@ -166,555 +119,378 @@ out: return inode; } -static int jump_to_new_pc(struct pt_regs *regs, const struct patch_info *patch, unsigned long pc) -{ - struct pc_pair *pair; - struct pc_pair *found_pair = NULL; - - hash_for_each_possible(patch->pc_maps, pair, node, pc) { - if (pair->old_pc == pc) { - found_pair = pair; - break; - } - } - - if (unlikely(!found_pair)) { - log_err("cannot find new pc for 0x%lx\n", pc); - return UPROBE_RUN_OLD_FUNC; - } - - log_debug("jump from 0x%lx -> 0x%lx\n", pc, found_pair->new_pc); - instruction_pointer_set(regs, found_pair->new_pc); - - return UPROBE_ALTER_PC; -} - -static void free_exited_process(struct list_head *process_list) -{ - struct process_entity *process; - struct process_entity *tmp; - - if (unlikely(!process_list)) { - return; - } - - list_for_each_entry_safe(process, tmp, process_list, process_node) { - list_del_init(&process->process_node); - free_process(process); - } -} - static int upatch_uprobe_handler(struct uprobe_consumer *self, struct pt_regs *regs) { - const char *name = current->comm; - pid_t pid = task_pid_nr(current); + const char *proc_name = current->comm; pid_t tgid = task_tgid_nr(current); - unsigned long pc = instruction_pointer(regs); + pid_t pid = task_pid_nr(current); - struct inode *inode; - unsigned long vma_start; + unsigned long pc = instruction_pointer(regs); + unsigned long vma_start = 0; - struct target_entity *target; - struct patch_entity *latest_patch; + struct inode *inode = NULL; + struct target_entity *target = NULL; + struct process_entity *process = NULL; + struct patch_entity *actived_patch = NULL; - struct list_head exited_proc_list = LIST_HEAD_INIT(exited_proc_list); - struct process_entity *process; - struct patch_info *loaded_patch; + struct patch_info *patch_info; + unsigned long jump_addr; int ret = UPROBE_RUN_OLD_FUNC; - log_debug("upatch handler triggered on process '%s' (pid=%d, tgid=%d, pc=0x%lx)\n", name, pid, tgid, pc); + log_debug("uprobe handler triggered on process '%s' (tgid=%d, pid=%d, pc=0x%lx)\n", proc_name, tgid, pid, pc); - /* find vma file and inode out of the lock */ - inode = find_vma_file_inode(pc, &vma_start); + /* Step 1: Get vma corresponding file inode */ + inode = get_vma_file_inode(pc, &vma_start); if (unlikely(!inode)) { - log_err("cannot find vma file inode in '%s'\n", name); - return UPROBE_RUN_OLD_FUNC; + log_err("cannot get vma file inode of '%s'\n", proc_name); + goto release_out; } - down_read(&g_global_table_rwsem); - - /* step 1. find target entity by vma file inode */ - target = find_target_by_inode(inode); - iput(inode); - inode = NULL; - + /* Step 2: Get target entity by vma file inode */ + target = get_target_by_inode(inode); if (unlikely(!target)) { - log_err("cannot find target entity of '%s'\n", name); - ret = UPROBE_RUN_OLD_FUNC; - goto unlock_global_table; - } - - /* step 2. find target latest patch */ - latest_patch = list_first_entry_or_null(&target->actived_list, struct patch_entity, actived_node); - if (unlikely(!latest_patch)) { - ret = UPROBE_RUN_OLD_FUNC; - goto unlock_global_table; + log_err("cannot find target entity of '%s'\n", proc_name); + goto release_out; } - mutex_lock(&target->process_mutex); - - /* step 3. collect all exited process */ - target_gather_exited_processes(target, &exited_proc_list); + /* Step 3: Clean up target exited proesses */ + target_cleanup_process(target); - /* step 4. find or create process entity for current process */ - process = target_get_or_create_process(target); - if (unlikely(!process)) { - log_err("failed to get process of '%s'\n", target->meta.path); - ret = UPROBE_RUN_OLD_FUNC; - mutex_unlock(&target->process_mutex); - goto unlock_global_table; + /* Step 4: Get process entity for current process */ + process = target_get_process(target, current); + if (unlikely(IS_ERR(process))) { + log_err("failed to get '%s' process, ret=%d\n", target->file.path, (int)PTR_ERR(process)); + goto release_out; } - mutex_unlock(&target->process_mutex); + spin_lock(&process->thread_lock); // ensure only one thread could check & resolve patch - /* step 5. check if we need resolve patch on the process */ - mutex_lock(&process->lock); // we want to ensure only one thread can resolve patch + /* Step 5: Get actived patch entity of the target */ + actived_patch = target_get_actived_patch(target); + if (unlikely(!actived_patch)) { + // target does not have any actived patch yet + goto unlock_out; + } - if (!process->latest_patch || process->latest_patch->patch != latest_patch) { - loaded_patch = process_find_loaded_patch(process, latest_patch); - if (loaded_patch) { - log_debug("switch patch to '%s'\n", latest_patch->meta.path); - process->latest_patch = loaded_patch; - } else { - log_debug("applying patch '%s' to process '%s' (pid=%d)\n", latest_patch->meta.path, name, pid); - ret = upatch_resolve(target, latest_patch, process, vma_start); - if (ret) { - log_err("failed to apply patch '%s' to process '%s', ret=%d\n", latest_patch->meta.path, name, ret); - ret = UPROBE_RUN_OLD_FUNC; - goto unlock_process; - } + /* Step 6. Check or resolve patch of the process */ + patch_info = process_switch_and_get_patch(process, actived_patch); + if (unlikely(!patch_info)) { + ret = upatch_resolve(target, actived_patch, process, vma_start); + if (unlikely(ret)) { + log_err("process %d: failed to resolve patch %s, ret=%d\n", tgid, actived_patch->file.path, ret); + goto unlock_out; } } - /* search and set pc register to new address */ - ret = jump_to_new_pc(regs, process->latest_patch, pc); - -unlock_process: - mutex_unlock(&process->lock); + /* Step 7: Find patch function jump addr */ + jump_addr = process_get_jump_addr(process, pc); + if (unlikely(!jump_addr)) { + log_err("process %d: cannot find jump address, pc=0x%lx\n", tgid, pc); + goto unlock_out; + } -unlock_global_table: - up_read(&g_global_table_rwsem); + /* Step 8: Set patch function jump addr to pc register */ + instruction_pointer_set(regs, jump_addr); + log_debug("process %d: jump 0x%lx -> 0x%lx\n", tgid, pc, jump_addr); - free_exited_process(&exited_proc_list); - return ret; -} +unlock_out: + spin_unlock(&process->thread_lock); -/* PATCH MANAGEMENT */ -static void unregister_single_patch_function(struct target_entity *target, struct upatch_function *func) -{ - bool need_unregister = false; +release_out: + put_patch(actived_patch); + put_process(process); + put_target(target); + iput(inode); - target_remove_function(target, func, &need_unregister); - if (need_unregister) { - uprobe_unregister(target->meta.inode, func->old_addr, &g_upatch_consumer); - } + return unlikely(ret) ? UPROBE_RUN_OLD_FUNC : UPROBE_ALTER_PC; } -static void unregister_patch_functions(struct target_entity *target, struct patch_entity *patch, size_t count) +/* --- Public interface --- */ + +int upatch_load(const char *target_file, const char *patch_file) { - struct upatch_function *funcs = patch->meta.funcs; - const char *strings = patch->meta.strings; + struct target_entity *target; + struct file *file; - struct upatch_function *func; - const char *name; - size_t i; + struct target_entity *new_target = NULL; + struct target_entity *found_target = NULL; + int ret = 0; - if (count > patch->meta.func_num) { - log_err("function count %zu exceeds %zu\n", count, patch->meta.func_num); - return; + if (unlikely(!target_file || !patch_file)) { + return -EINVAL; } - log_debug("unregister patch '%s' functions:\n", target->meta.path); - for (i = 0; i < count; i++) { - func = &funcs[i]; - name = strings + func->name_off; + log_debug("%s: loading patch %s...\n", target_file, patch_file); - log_debug("- function: offset=0x%08llx, size=0x%04llx, name='%s'\n", func->old_addr, func->old_size, name); - unregister_single_patch_function(target, func); + /* Step 1: Open target file */ + file = filp_open(target_file, O_RDONLY, 0); + if (unlikely(IS_ERR(file))) { + return PTR_ERR(file); } -} -static int register_single_patch_function(struct target_entity *target, struct upatch_function *func) -{ - bool need_register = false; - int ret; + /* Step 2: Check and get target entity */ + spin_lock(&g_target_table_lock); + target = get_target(find_target_unlocked(file_inode(file))); + spin_unlock(&g_target_table_lock); - ret = target_add_function(target, func, &need_register); - if (ret) { - log_err("failed to add patch function to target\n"); - return ret; - } - - if (need_register) { - ret = uprobe_register(target->meta.inode, func->old_addr, &g_upatch_consumer); - if (ret) { - target_remove_function(target, func, &need_register); // rollback, remove function from target - log_err("failed to register uprobe on '%s' (inode: %lu) at 0x%llx, ret=%d\n", - target->meta.path, target->meta.inode->i_ino, func->old_addr, ret); - return ret; + if (!target) { + /* Step 3: Load target from file */ + new_target = load_target(file); + if (unlikely(IS_ERR(new_target))) { + ret = PTR_ERR(new_target); + log_err("failed to load target %s, ret=%d\n", target_file, ret); + goto release_out; } - } - return 0; -} + /* Step 4: Re-check if the target exists (to handle race) */ + spin_lock(&g_target_table_lock); -static int register_patch_functions(struct target_entity *target, struct patch_entity *patch, size_t count) -{ - struct upatch_function *funcs = patch->meta.funcs; - const char *strings = patch->meta.strings; - - struct upatch_function *func; - const char *name; - size_t i; + found_target = find_target_unlocked(file_inode(file)); + if (likely(!found_target)) { + // nobody inserted the target during load process, insert new target into hash table + target = new_target; + new_target = NULL; + hash_add(g_target_table, &target->node, hash_inode(file_inode(file), TARGET_FILE_HASH_BITS)); + } else { + // someone already inserted the target, use founded one and free the target we load + target = found_target; + } - int ret; + /* Step 5: Get target reference */ + get_target(target); - if (count > patch->meta.func_num) { - log_err("function count %zu exceeds %zu\n", count, patch->meta.func_num); - return -EINVAL; + spin_unlock(&g_target_table_lock); } - log_debug("register target '%s' functions:\n", target->meta.path); - for (i = 0; i < count; i++) { - func = &funcs[i]; - name = strings + func->name_off; - - log_debug("+ function: offset=0x%08llx, size=0x%04llx, name='%s'\n", func->old_addr, func->old_size, name); - ret = register_single_patch_function(target, func); - if (ret) { - log_err("failed to register function '%s'\n", name); - unregister_patch_functions(target, patch, i); - return ret; - } + /* Step 6: Load patch to the target */ + ret = target_load_patch(target, patch_file); + if (unlikely(ret)) { + log_err("failed to load patch %s, ret=%d\n", patch_file, ret); + goto release_out; } - return 0; -} + /* Step 7: Increase current module reference to eunsure it won't be removed */ + try_module_get(THIS_MODULE); -/* public interface */ -enum upatch_status upatch_status(const char *patch_file) -{ - enum upatch_status status = UPATCH_STATUS_NOT_APPLIED; - struct patch_entity *patch = NULL; + log_debug("%s: patch %s is loaded\n", target_file, patch_file); - down_read(&g_global_table_rwsem); - patch = find_patch(patch_file); - if (patch) { - status = patch->status; - } - up_read(&g_global_table_rwsem); +release_out: + /* Step 8: Close opened file */ + filp_close(file, NULL); - return status; + /* Step 9: Release all references we hold */ + put_target(target); // reference of target hash table + put_target(new_target); // reference of we load + + return ret; } -int upatch_load(const char *patch_file, const char *target_file) +int upatch_remove(const char *target_file, const char *patch_file) { - struct patch_entity *patch = NULL; - struct patch_entity *preload_patch = NULL; - struct patch_entity *patch_to_free = NULL; + struct inode *target_inode; + struct inode *patch_inode; + struct target_entity *target = NULL; - struct target_entity *preload_target = NULL; struct target_entity *target_to_free = NULL; int ret = 0; - if (!patch_file || !target_file) { + if (unlikely(!target_file || !patch_file)) { return -EINVAL; } - log_debug("loading patch '%s' -> '%s'...\n", patch_file, target_file); + log_debug("%s: removing patch %s...\n", target_file, patch_file); - /* fast path, return if patch already exists */ - down_read(&g_global_table_rwsem); - if (find_patch(patch_file)) { - log_err("patch '%s' is already exist\n", patch_file); - ret = -EEXIST; - up_read(&g_global_table_rwsem); - goto out; - } - up_read(&g_global_table_rwsem); - - /* preload patch & target file out of the lock */ - preload_patch = new_patch_entity(patch_file); - if (IS_ERR(preload_patch)) { - log_err("failed to load patch '%s'\n", patch_file); - ret = PTR_ERR(preload_patch); - goto out_free; - } - - preload_target = new_target_entity(target_file); - if (IS_ERR(preload_target)) { - log_err("failed to load target '%s'\n", target_file); - patch_to_free = preload_patch; - ret = PTR_ERR(preload_target); - goto out_free; - } - - /* slow path, load patch & target from file */ - down_write(&g_global_table_rwsem); - - /* step 1. recheck patch and target reference */ - patch = find_patch(patch_file); - if (!patch) { - patch = preload_patch; // patch does not exist, use preloaded patch - } else { - log_err("patch '%s' is already exist\n", patch_file); - ret = -EEXIST; - patch_to_free = preload_patch; - target_to_free = preload_target; - goto unlock_global_table; + /* Step 1: Get target & patch file inode */ + target_inode = get_path_inode(target_file); + patch_inode = get_path_inode(patch_file); + if (unlikely(!target_inode || !patch_inode)) { + ret = -ENOENT; + goto release_out; } - target = find_target(target_file); - if (!target) { - target = preload_target; // target does not exist, use preloaded target - } else { - target_to_free = preload_target; // target exists, need free preload one + /* Step 2: Get target entity */ + spin_lock(&g_target_table_lock); + target = get_target(find_target_unlocked(target_inode)); + spin_unlock(&g_target_table_lock); + if (unlikely(!target)) { + log_err("cannot find target entity\n"); + ret = -ENOENT; + goto release_out; } - if (target != preload_target) { - down_write(&target->action_rwsem); // lock global target, patch is always local + /* Step 3: Remove patch from the target */ + ret = target_remove_patch(target, patch_inode); + if (unlikely(ret)) { + log_err("%s: failed to remove patch %s, ret=%d\n", target_file, patch_file, ret); + goto release_out; } - /* step 2. add patch to global patch table */ - hash_add(g_patch_table, &patch->table_node, patch->meta.inode->i_ino); - - /* step 3. add patch to target all patches list */ - list_add(&patch->loaded_node, &target->loaded_list); - - /* step 4. update patch status */ - patch->target = target; - patch->status = UPATCH_STATUS_DEACTIVED; + log_debug("%s: patch %s is removed\n", target_file, patch_file); - if (target != preload_target) { - up_write(&target->action_rwsem); // unlock global target, patch is always local - } else { - /* step 5. add new target to global target table */ - hash_add(g_target_table, &target->table_node, target->meta.inode->i_ino); + /* Step 4: Remove target when last patch was removed */ + mutex_lock(&target->patch_lock); + spin_lock(&g_target_table_lock); + if (hash_empty(target->patches) && !target->is_deleting) { + hash_del(&target->node); + target->is_deleting = true; + target_to_free = target; } + spin_unlock(&g_target_table_lock); + mutex_unlock(&target->patch_lock); -unlock_global_table: - up_write(&g_global_table_rwsem); + /* Step 5: Decrease current module reference */ + module_put(THIS_MODULE); -out_free: - if (patch_to_free) { - free_patch_entity(patch_to_free); - } - if (target_to_free) { - free_target_entity(target_to_free); - } +release_out: + /* Step 6: Release all references we hold */ + put_target(target_to_free); // reference of target hash table + put_target(target); // reference of function context + iput(patch_inode); + iput(target_inode); -out: - if (!ret) { - log_debug("patch '%s' is loaded\n", patch_file); - } return ret; } -int upatch_remove(const char *patch_file) +int upatch_active(const char *target_file, const char *patch_file) { - struct patch_entity *patch = NULL; - struct target_entity *target = NULL; - struct patch_entity *patch_to_free = NULL; - struct target_entity *target_to_free = NULL; + struct inode *target_inode; + struct inode *patch_inode; + struct target_entity *target = NULL; int ret = 0; - log_debug("removing patch '%s'...\n", patch_file); + if (unlikely(!target_file || !patch_file)) { + return -EINVAL; + } - down_write(&g_global_table_rwsem); + log_debug("%s: activating patch %s...\n", target_file, patch_file); - patch = find_patch(patch_file); - if (!patch) { - log_err("cannot find patch entity\n"); + /* Step 1: Get target & patch file inode */ + target_inode = get_path_inode(target_file); + patch_inode = get_path_inode(patch_file); + if (unlikely(!target_inode || !patch_inode)) { ret = -ENOENT; - goto unlock_global_table; - } - - if (patch->status != UPATCH_STATUS_DEACTIVED) { - log_err("invalid patch status\n"); - ret = -EPERM; - goto unlock_global_table; + goto release_out; } - target = patch->target; - if (!target) { + /* Step 2: Get target entity */ + spin_lock(&g_target_table_lock); + target = get_target(find_target_unlocked(target_inode)); + spin_unlock(&g_target_table_lock); + if (unlikely(!target)) { log_err("cannot find target entity\n"); - ret = -EFAULT; - goto unlock_global_table; - } - - down_write(&target->action_rwsem); - down_write(&patch->action_rwsem); - - /* step 1. check if the patch removable */ - ret = target_check_patch_removable(target, patch); - if (ret) { - log_err("patch %s is not removable\n", patch_file); - goto unlock_target; + ret = -ENOENT; + goto release_out; } - /* step 2. remove patch from from global table */ - hash_del(&patch->table_node); - - /* step 3. remove patch from target patch list */ - list_del_init(&patch->loaded_node); - - /* step 4. check & remove target form global table */ - if (list_empty(&target->loaded_list)) { - hash_del(&target->table_node); - target_to_free = target; + /* Step 3: Active patch on the target */ + ret = target_active_patch(target, patch_inode, &g_uprobe_consumer); + if (unlikely(ret)) { + log_err("%s: failed to active patch %s, ret=%d\n", target_file, patch_file, ret); + goto release_out; } - /* step 5. update patch status */ - patch->target = NULL; - patch->status = UPATCH_STATUS_NOT_APPLIED; - patch_to_free = patch; - -unlock_target: - up_write(&target->action_rwsem); + log_debug("%s: patch %s is actived\n", target_file, patch_file); -unlock_global_table: - up_write(&g_global_table_rwsem); +release_out: + /* Step 4: Release all references we hold */ + put_target(target); + iput(patch_inode); + iput(target_inode); - if (patch_to_free) { - free_patch_entity(patch_to_free); - } - if (target_to_free) { - free_target_entity(target_to_free); - } - if (!ret) { - log_debug("patch '%s' is removed\n", patch_file); - } return ret; } -int upatch_active(const char *patch_file) +int upatch_deactive(const char *target_file, const char *patch_file) { - struct patch_entity *patch = NULL; + struct inode *target_inode; + struct inode *patch_inode; + struct target_entity *target = NULL; int ret = 0; - log_debug("activating patch '%s'...\n", patch_file); + if (unlikely(!target_file || !patch_file)) { + return -EINVAL; + } - down_read(&g_global_table_rwsem); + log_debug("%s: deactivating patch %s...\n", target_file, patch_file); - patch = find_patch(patch_file); - if (!patch) { - log_err("cannot find patch entity\n"); + /* Step 1: Get target & patch file inode */ + target_inode = get_path_inode(target_file); + patch_inode = get_path_inode(patch_file); + if (unlikely(!target_inode || !patch_inode)) { ret = -ENOENT; - goto unlock_global_table; - } - - if (patch->status != UPATCH_STATUS_DEACTIVED) { - log_err("invalid patch status\n"); - ret = -EPERM; - goto unlock_global_table; + goto release_out; } - target = patch->target; - if (!target) { + /* Step 2: Get target entity */ + spin_lock(&g_target_table_lock); + target = get_target(find_target_unlocked(target_inode)); + spin_unlock(&g_target_table_lock); + if (unlikely(!target)) { log_err("cannot find target entity\n"); - ret = -EFAULT; - goto unlock_global_table; + ret = -ENOENT; + goto release_out; } - down_write(&target->action_rwsem); - down_write(&patch->action_rwsem); - - /* step 1. register patch functions to target */ - ret = register_patch_functions(target, patch, patch->meta.func_num); - if (ret) { - log_err("failed to register patch functions\n"); - goto unlock_entity; + /* Step 3: Deactive patch on the target */ + ret = target_deactive_patch(target, patch_inode, &g_uprobe_consumer); + if (unlikely(ret)) { + log_err("%s: failed to deactive patch %s, ret=%d\n", target_file, patch_file, ret); + goto release_out; } - /* step 2. add patch to target actived patch list */ - list_add(&patch->actived_node, &target->actived_list); - - /* step 3. update patch status */ - patch->status = UPATCH_STATUS_ACTIVED; + log_debug("%s: patch %s is deactived\n", target_file, patch_file); -unlock_entity: - up_write(&patch->action_rwsem); - up_write(&target->action_rwsem); +release_out: + /* Step 4: Release all references we hold */ + put_target(target); + iput(patch_inode); + iput(target_inode); -unlock_global_table: - up_read(&g_global_table_rwsem); - - if (!ret) { - log_debug("patch '%s' is actived\n", patch_file); - } return ret; } -int upatch_deactive(const char *patch_file) +enum upatch_status upatch_status(const char *target_file, const char *patch_file) { - struct patch_entity *patch = NULL; - struct target_entity *target = NULL; - int ret = 0; - - log_debug("deactivating patch '%s'...\n", patch_file); + struct inode *target_inode; + struct inode *patch_inode; - down_read(&g_global_table_rwsem); - - patch = find_patch(patch_file); - if (!patch) { - log_err("cannot find patch entity\n"); - ret = -ENOENT; - goto unlock_global_table; - } + struct target_entity *target = NULL; + enum upatch_status status = UPATCH_STATUS_NOT_APPLIED; - if (patch->status != UPATCH_STATUS_ACTIVED) { - log_err("invalid patch status\n"); - ret = -EPERM; - goto unlock_global_table; + if (unlikely(!target_file || !patch_file)) { + return status; } - target = patch->target; - if (!target) { - log_err("cannot find target entity\n"); - ret = -EFAULT; - goto unlock_global_table; + /* Step 1: Get target & patch file inode */ + target_inode = get_path_inode(target_file); + patch_inode = get_path_inode(patch_file); + if (unlikely(!target_inode || !patch_inode)) { + goto release_out; } - down_write(&target->action_rwsem); - down_write(&patch->action_rwsem); + /* Step 2: Get target entity */ + spin_lock(&g_target_table_lock); + target = get_target(find_target_unlocked(target_inode)); + spin_unlock(&g_target_table_lock); - /* step 1. remove patch functions from target */ - unregister_patch_functions(target, patch, patch->meta.func_num); + /* Step 3: Get patch status */ + status = target_patch_status(target, patch_inode); - /* step 2. remove patch from target actived patch list */ - list_del_init(&patch->actived_node); +release_out: + /* Step 4: Release all references we hold */ + put_target(target); + iput(patch_inode); + iput(target_inode); - /* step 3. update patch status */ - patch->status = UPATCH_STATUS_DEACTIVED; - - up_write(&patch->action_rwsem); - up_write(&target->action_rwsem); - -unlock_global_table: - up_read(&g_global_table_rwsem); - - if (!ret) { - log_debug("patch '%s' is deactived\n", patch_file); - } - return ret; + return status; } -void __exit report_global_table_populated(void) +void __exit check_target_table_populated(void) { - struct patch_entity *patch; - struct target_entity *target; - int bkt; - - down_read(&g_global_table_rwsem); - hash_for_each(g_patch_table, bkt, patch, table_node) { - log_warn("found patch '%s' on exit, status=%s", - patch->meta.path ? patch->meta.path : "(null)", patch_status_str(patch->status)); - } - hash_for_each(g_target_table, bkt, target, table_node) { - log_err("found target '%s' on exit", target->meta.path ? target->meta.path : "(null)"); - } - up_read(&g_global_table_rwsem); + spin_lock(&g_target_table_lock); + WARN_ON(!hash_empty(g_target_table)); + spin_unlock(&g_target_table_lock); } diff --git a/upatch-manage/patch_manage.h b/upatch-manage/patch_manage.h index 4ed63c9d..f1f8a5d1 100644 --- a/upatch-manage/patch_manage.h +++ b/upatch-manage/patch_manage.h @@ -28,16 +28,16 @@ struct inode; enum upatch_status; struct target_entity; -enum upatch_status upatch_status(const char *patch_file); +int upatch_load(const char *target_file, const char *patch_file); -int upatch_load(const char *patch_file, const char *binary_file); +int upatch_remove(const char *target_file, const char *patch_file); -int upatch_remove(const char *patch_file); +int upatch_active(const char *target_file, const char *patch_file); -int upatch_active(const char *patch_file); +int upatch_deactive(const char *target_file, const char *patch_file); -int upatch_deactive(const char *patch_file); +enum upatch_status upatch_status(const char *target_file, const char *patch_file); -void __exit report_global_table_populated(void); +void __exit check_target_table_populated(void); #endif // _UPATCH_MANAGE_PATCH_MANAGE_H diff --git a/upatch-manage/process_cache.c b/upatch-manage/process_cache.c new file mode 100644 index 00000000..61e4acc7 --- /dev/null +++ b/upatch-manage/process_cache.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * process related struct kmem cache + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "process_cache.h" + +#include "process_entity.h" + +struct kmem_cache *g_process_cache; +struct kmem_cache *g_patch_info_cache; +struct kmem_cache *g_jump_entry_cache; + +int __init process_cache_init(void) +{ + g_process_cache = kmem_cache_create( + "upatch_process_entity", + sizeof(struct process_entity), + 0, + SLAB_HWCACHE_ALIGN, + NULL + ); + if (unlikely(!g_process_cache)) { + pr_err("failed to create process entity cache\n"); + return -ENOMEM; + } + + g_patch_info_cache = kmem_cache_create( + "upatch_patch_info", + sizeof(struct patch_info), + 0, + SLAB_HWCACHE_ALIGN, + NULL + ); + if (unlikely(!g_patch_info_cache)) { + pr_err("failed to create patch info cache\n"); + return -ENOMEM; + } + + g_jump_entry_cache = kmem_cache_create( + "upatch_jump_entry", + sizeof(struct patch_jump_entry), + 0, + SLAB_HWCACHE_ALIGN, + NULL + ); + if (unlikely(!g_jump_entry_cache)) { + pr_err("failed to create patch jump entry cache\n"); + return -ENOMEM; + } + + return 0; +} + +void __exit process_cache_exit(void) +{ + kmem_cache_destroy(g_process_cache); + kmem_cache_destroy(g_patch_info_cache); + kmem_cache_destroy(g_jump_entry_cache); +} diff --git a/upatch-manage/process_cache.h b/upatch-manage/process_cache.h new file mode 100644 index 00000000..8002cdb7 --- /dev/null +++ b/upatch-manage/process_cache.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * process related struct kmem cache + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UPATCH_PROCESS_CACHE_H +#define _UPATCH_PROCESS_CACHE_H + +#include +#include + +extern struct kmem_cache *g_process_cache; +extern struct kmem_cache *g_patch_info_cache; +extern struct kmem_cache *g_jump_entry_cache; + +int __init process_cache_init(void); +void __exit process_cache_exit(void); + +#endif // _UPATCH_PROCESS_CACHE_H diff --git a/upatch-manage/process_entity.c b/upatch-manage/process_entity.c index f7cd36ab..0ade37e3 100644 --- a/upatch-manage/process_entity.c +++ b/upatch-manage/process_entity.c @@ -24,6 +24,8 @@ #include #include "kernel_compat.h" +#include "process_cache.h" + #include "patch_entity.h" #include "target_entity.h" @@ -32,6 +34,8 @@ #include "util.h" +/* --- Process life-cycle management --- */ + static int do_free_patch_memory(struct mm_struct *mm, unsigned long addr, size_t len) { struct vm_area_struct *vma; @@ -58,7 +62,7 @@ static int do_free_patch_memory(struct mm_struct *mm, unsigned long addr, size_t static void free_patch_memory(struct task_struct *task, struct patch_info *patch) { - pid_t pid = task_pid_nr(task); + pid_t pid = task_tgid_nr(task); struct mm_struct *mm; int ret; @@ -92,91 +96,173 @@ static void free_patch_memory(struct task_struct *task, struct patch_info *patch return; } -static void free_patch_info(struct patch_info *patch) +static void free_patch_info(struct patch_info *patch_info) { - struct pc_pair *pair; + struct patch_jump_entry *entry; struct hlist_node *tmp; int bkt; - if (unlikely(!patch)) { + if (unlikely(!patch_info)) { return; } - hash_for_each_safe(patch->pc_maps, bkt, tmp, pair, node) { - hash_del(&pair->node); - kfree(pair); + hash_for_each_safe(patch_info->jump_table, bkt, tmp, entry, node) { + hash_del(&entry->node); + kmem_cache_free(g_jump_entry_cache, entry); } - kfree(patch); + put_patch(patch_info->patch); + patch_info->patch = NULL; + + kmem_cache_free(g_patch_info_cache, patch_info); } -struct process_entity *new_process(struct target_entity *target) +static struct patch_info *find_patch_info_unlocked(struct process_entity *process, struct patch_entity *patch) { - if (unlikely(!target)) { + struct patch_info *patch_info; + struct patch_info *found = NULL; + + if (unlikely(!process || !patch)) { + return NULL; + } + + list_for_each_entry(patch_info, &process->patch_list, node) { + if (patch_info->patch == patch) { + found = patch_info; + break; + } + } + + return found; +} + +static bool is_patch_removable(pid_t pid, const void *page, void *context) +{ + static const size_t VALUE_NR = PAGE_SIZE / sizeof(unsigned long); + + const struct patch_info *patch = context; + + const bool check_text = (patch->text_len > 0); + const unsigned long text_start = patch->text_addr; + const unsigned long text_end = patch->text_addr + patch->text_len; + + const unsigned long *page_data = page; + size_t i; + + for (i = 0; i < VALUE_NR; i++) { + if (check_text && unlikely(page_data[i] >= text_start && page_data[i] < text_end)) { + log_err("process %d: found patch text 0x%lx on stack\n", pid, page_data[i]); + return false; + } + } + + return true; +} + +/* --- Public interface --- */ + +struct process_entity *new_process(struct task_struct *task) +{ + struct process_entity *process; + + if (unlikely(!task)) { return ERR_PTR(-EINVAL); } - struct process_entity *process = kzalloc(sizeof(struct process_entity), GFP_KERNEL); + process = kmem_cache_alloc(g_process_cache, GFP_ATOMIC); if (!process) { return ERR_PTR(-ENOMEM); } - process->pid = get_task_pid(current, PIDTYPE_TGID); - if (!process->pid) { - log_err("failed to get process %d task pid\n", task_tgid_nr(current)); - kfree(process); - return ERR_PTR(-EFAULT); - } - process->task = get_task_struct(current); + process->task = get_task_struct(task); + process->tgid = task_tgid_nr(task); + + spin_lock_init(&process->thread_lock); - mutex_init(&process->lock); + INIT_HLIST_NODE(&process->node); + INIT_LIST_HEAD(&process->pending_node); - process->latest_patch = NULL; - INIT_LIST_HEAD(&process->loaded_patches); - INIT_LIST_HEAD(&process->process_node); + INIT_LIST_HEAD(&process->patch_list); + process->patch_info = NULL; + kref_init(&process->kref); + + log_debug("new process %d\n", process->tgid); return process; } -void free_process(struct process_entity *process) +void release_process(struct kref *kref) { - pid_t pid; - struct patch_info *patch; + struct process_entity *process; + struct patch_info *patch_info; struct patch_info *tmp; - if (unlikely(!process)) { + if (unlikely(!kref)) { return; } - pid = task_pid_nr(process->task); + process = container_of(kref, struct process_entity, kref); + log_debug("free process %d\n", process->tgid); + + WARN_ON(spin_is_locked(&process->thread_lock)); - log_debug("free process %d\n", pid); - list_for_each_entry_safe(patch, tmp, &process->loaded_patches, node) { - list_del(&patch->node); - free_patch_memory(process->task, patch); - free_patch_info(patch); + WARN_ON(!hlist_unhashed(&process->node)); + WARN_ON(!list_empty(&process->pending_node)); + + list_for_each_entry_safe(patch_info, tmp, &process->patch_list, node) { + list_del_init(&patch_info->node); + free_patch_memory(process->task, patch_info); + free_patch_info(patch_info); } + process->patch_info = NULL; - put_pid(process->pid); put_task_struct(process->task); + process->task = NULL; + process->tgid = 0; - kfree(process); + kmem_cache_free(g_process_cache, process); } -struct patch_info *process_find_loaded_patch(struct process_entity *process, struct patch_entity *patch) +struct patch_info *process_switch_and_get_patch(struct process_entity *process, struct patch_entity *patch) { - struct patch_info *curr_patch; + struct patch_info *patch_info; + + BUG_ON(unlikely(!process || !patch)); - list_for_each_entry(curr_patch, &process->loaded_patches, node) { - if (curr_patch->patch == patch) { - return curr_patch; + if (likely(process->patch_info && process->patch_info->patch == patch)) { + return process->patch_info; + } + + patch_info = find_patch_info_unlocked(process, patch); + if (unlikely(!patch_info)) { + return NULL; + } + + process->patch_info = patch_info; + + return patch_info; +} + +unsigned long process_get_jump_addr(struct process_entity *process, unsigned long old_addr) +{ + struct patch_jump_entry *entry; + unsigned long jump_addr = 0; + + if (unlikely(!process || !process->patch_info)) { + return 0; + } + + hash_for_each_possible(process->patch_info->jump_table, entry, node, hash_long(old_addr, PATCH_FUNC_HASH_BITS)) { + if (entry->old_addr == old_addr) { + jump_addr = entry->new_addr; + break; } } - return NULL; + return jump_addr; } -int process_write_patch_info(struct process_entity *process, struct patch_entity *patch, struct patch_context *ctx) +int process_load_patch(struct process_entity *process, struct patch_entity *patch, struct patch_context *ctx) { struct upatch_function *funcs = (struct upatch_function *)ctx->func_shdr->sh_addr; size_t func_num = ctx->func_shdr->sh_size / (sizeof (struct upatch_function)); @@ -184,80 +270,67 @@ int process_write_patch_info(struct process_entity *process, struct patch_entity struct upatch_relocation *relas = (struct upatch_relocation *)ctx->rela_shdr->sh_addr; const char *strings = (const char *)ctx->string_shdr->sh_addr; - size_t i; - struct upatch_function *func; const char *func_name; + size_t i; - struct patch_info *info; - struct pc_pair *entry; + struct patch_info *patch_info; + struct patch_jump_entry *jump_entry; + + if (unlikely(!process || !patch || !ctx)) { + return -EINVAL; + } - info = kzalloc(sizeof(struct patch_info), GFP_KERNEL); - if (!info) { - log_err("failed to alloc patch info\n"); + patch_info = kmem_cache_alloc(g_patch_info_cache, GFP_ATOMIC); + if (!patch_info) { return -ENOMEM; } - hash_init(info->pc_maps); - for (i = 0; i < func_num; ++i) { - func = &funcs[i]; + patch_info->patch = get_patch(patch); + INIT_LIST_HEAD(&patch_info->node); - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) { - free_patch_info(info); - return -ENOMEM; - } + patch_info->text_addr = ctx->layout.base; + patch_info->text_len = ctx->layout.text_end; + + patch_info->rodata_addr = ctx->layout.base + ctx->layout.text_end; + patch_info->rodata_len = ctx->layout.ro_after_init_end - ctx->layout.text_end; + + hash_init(patch_info->jump_table); + for (i = 0; i < func_num; ++i) { func_name = strings + relas[i].name.r_addend; - entry->old_pc = funcs[i].old_addr + ctx->load_bias + ctx->target->load_offset; - entry->new_pc = funcs[i].new_addr; - hash_add(info->pc_maps, &entry->node, entry->old_pc); - log_debug("function: 0x%08lx -> 0x%08lx, name: '%s'\n", entry->old_pc, entry->new_pc, func_name); - } - info->patch = patch; + jump_entry = kmem_cache_alloc(g_jump_entry_cache, GFP_ATOMIC); + if (!jump_entry) { + free_patch_info(patch_info); + return -ENOMEM; + } - info->text_addr = ctx->layout.base; - info->text_len = ctx->layout.text_end; + INIT_HLIST_NODE(&jump_entry->node); + jump_entry->old_addr = funcs[i].old_addr + ctx->load_bias + ctx->target->load_offset; + jump_entry->new_addr = funcs[i].new_addr; - info->rodata_addr = ctx->layout.base + ctx->layout.text_end; - info->rodata_len = ctx->layout.ro_after_init_end - ctx->layout.text_end; + log_debug("process %d: old_addr=0x%08lx, new_addr=0x%08lx, func='%s'\n", + process->tgid, jump_entry->old_addr, jump_entry->new_addr, func_name); + hash_add(patch_info->jump_table, &jump_entry->node, hash_long(jump_entry->old_addr, PATCH_FUNC_HASH_BITS)); + } - list_add(&info->node, &process->loaded_patches); - process->latest_patch = info; + list_add(&patch_info->node, &process->patch_list); + process->patch_info = patch_info; return 0; } -static bool is_patch_removable(pid_t pid, const void *page, void *context) +void process_remove_patch(struct process_entity *process, struct patch_entity *patch) { - static const size_t VALUE_NR = PAGE_SIZE / sizeof(unsigned long); - - const struct patch_info *patch = (const struct patch_info *)context; - - const bool check_text = (patch->text_len > 0); - const unsigned long text_start = patch->text_addr; - const unsigned long text_end = patch->text_addr + patch->text_len; - - const bool check_rodata = (patch->rodata_len > 0); - const unsigned long rodata_start = patch->rodata_addr; - const unsigned long rodata_end = patch->rodata_addr + patch->rodata_len; - - const unsigned long *page_data = (const unsigned long *)page; - size_t i; - - for (i = 0; i < VALUE_NR; i++) { - if (check_text && unlikely(page_data[i] >= text_start && page_data[i] < text_end)) { - log_err("process %d: found patch text 0x%lx on stack\n", pid, page_data[i]); - return false; - } + struct patch_info *patch_info; - if (check_rodata && unlikely(page_data[i] >= rodata_start && page_data[i] < rodata_end)) { - log_err("process %d: found patch rodata 0x%lx on stack\n", pid, page_data[i]); - return false; - } + patch_info = find_patch_info_unlocked(process, patch); + if (unlikely(!patch_info)) { + return; } - return true; + list_del_init(&patch_info->node); + free_patch_info(patch_info); } int process_check_patch_on_stack(struct process_entity *process, struct patch_entity *patch) @@ -268,7 +341,7 @@ int process_check_patch_on_stack(struct process_entity *process, struct patch_en return -EINVAL; } - patch_info = process_find_loaded_patch(process, patch); + patch_info = find_patch_info_unlocked(process, patch); if (unlikely(!patch_info)) { return 0; } diff --git a/upatch-manage/process_entity.h b/upatch-manage/process_entity.h index 83231b53..8e8c71d5 100644 --- a/upatch-manage/process_entity.h +++ b/upatch-manage/process_entity.h @@ -22,8 +22,10 @@ #define _UPATCH_MANAGE_PROCESS_ENTITY_H #include -#include #include +#include +#include +#include #include @@ -34,13 +36,13 @@ struct patch_context; struct patch_entity; struct target_entity; -// we assume one patch will only modify less than 2^4 = 16 old funcs in target -#define FUNC_HASH_BITS 4 +#define PATCH_FUNC_HASH_BITS 6 // Single patch would have less than 2^6 = 64 funcs -struct pc_pair { - unsigned long old_pc; - unsigned long new_pc; - struct hlist_node node; // maintain pc pair in hash table +struct patch_jump_entry { + struct hlist_node node; + + unsigned long old_addr; + unsigned long new_addr; }; struct patch_info { @@ -48,40 +50,153 @@ struct patch_info { struct list_head node; unsigned long text_addr; - unsigned long rodata_addr; - size_t text_len; + + unsigned long rodata_addr; size_t rodata_len; - DECLARE_HASHTABLE(pc_maps, FUNC_HASH_BITS); + DECLARE_HASHTABLE(jump_table, PATCH_FUNC_HASH_BITS); }; // target may be loaded into different process // due to latency of uprobe handle, process may dealy patch loading struct process_entity { - struct pid *pid; - struct task_struct *task; - - // multi-thread may trap and run uprobe_handle, we only need one to load patch - struct mutex lock; - - // loaded but deactive patch will not free from VMA because the function of deactive patch may in call stack - // so we have to maintain all patches the process loaded - // For example we load and active p1, p2, p3, the patches list will be p3->p2->p1 - // when we want to active p2, we just look through this list and active p2 to avoid load p2 again - struct patch_info *latest_patch; // latest actived patch - struct list_head loaded_patches; // patch_info list head - struct list_head process_node; // target process list node + struct task_struct *task; // underlying task struct + pid_t tgid; + + spinlock_t thread_lock; // thread lock + + struct hlist_node node; // hash table node + struct list_head pending_node; // pending list node + + struct list_head patch_list; // all actived patches + struct patch_info *patch_info; // current actived patch info + + struct kref kref; }; -struct process_entity *new_process(struct target_entity *target); +/** + * @brief Create and initialize a new process entity for a given task. + * @param task The kernel task_struct to be wrapped by the new entity. + * This function will take its own reference to the task via + * get_task_struct(). + * + * @return On success, returns a pointer to the allocated process_entity with + * its reference count initialized to 1. + * On memory allocation failure, returns an ERR_PTR (e.g., ERR_PTR(-ENOMEM)). + * + * The caller owns the returned reference and is responsible for releasing it + * using put_process() when it is no longer needed. + */ +struct process_entity *new_process(struct task_struct *task); -void free_process(struct process_entity *process); +/** + * @brief Release process resources when refcount reaches zero + * @param kref: Reference counter + * + * Called automatically by kref_put(). + * Frees all process resources and disassociates from target. + */ +void release_process(struct kref *kref); -struct patch_info *process_find_loaded_patch(struct process_entity *process, struct patch_entity *patch); +/** + * @brief Acquire a reference to a process entity + * @param process: Process entity pointer + * @return Process entity with incremented refcount, or NULL if input is NULL + * + * Caller must balance with put_process(). + */ +static inline struct process_entity *get_process(struct process_entity *process) +{ + if (unlikely(!process)) { + return NULL; + } + + kref_get(&process->kref); + return process; +} + +/** + * @brief Release a process entity reference + * @param process: Process entity pointer + * + * Decrements refcount and triggers release_process() when reaching zero. + * Safe to call with NULL. + */ +static inline void put_process(struct process_entity *process) +{ + if (unlikely(!process)) { + return; + } + + kref_put(&process->kref, release_process); +} + +/** + * @brief Check if a process entity's underlying task is still alive. + * @param process: The process entity to check. + * @return Returns true if the task is considered alive by the kernel, + * false otherwise. + * + * Safe to call with NULL; it will be treated as not alive. + */ +static inline bool process_is_alive(struct process_entity *process) +{ + if (unlikely(!process || !process->task)) { + return false; + } + + return pid_alive(process->task); +} + +/** + * @brief Switch and get process actived patch to specific one + * @param process: Process entity (must not NULL) + * @param patch: Patch entity (must not NULL) + * @return Patch info pointer, NULL if not found + * + * Caller must hold thread lock. + */ +struct patch_info *process_switch_and_get_patch(struct process_entity *process, struct patch_entity *patch); -int process_write_patch_info(struct process_entity *process, struct patch_entity *patch, struct patch_context *ctx); +/** + * @brief Find function jump address in the process + * @param process: Process entity + * @param addr: Current function address (usually pc register) + * @return Jump address if found, 0 otherwise + * + * Caller must hold thread lock. + */ +unsigned long process_get_jump_addr(struct process_entity *process, unsigned long addr); + +/** + * @brief Load a patch to the process + * @param process: Process entity + * @param patch: Patch entity being applied + * @param ctx: Patch context + * @return 0 on success, negative error code on failure + * + * Caller must hold thread lock. + */ +int process_load_patch(struct process_entity *process, struct patch_entity *patch, struct patch_context *ctx); +/** + * @brief Remove patch is on the process + * @param process: Process entity + * @param patch: Patch entity to remove + * + * Caller must hold thread lock. + */ +void process_remove_patch(struct process_entity *process, struct patch_entity *patch); + +/** + * @brief Verify patch is not on process stack + * @param process: Process entity + * @param patch: Patch entity to verify + * @return 0 if safe to modify, -EBUSY if patch is on the stack + * + * Caller must hold thread lock. + */ int process_check_patch_on_stack(struct process_entity *process, struct patch_entity *patch); #endif // _UPATCH_MANAGE_PROCESS_ENTITY_H diff --git a/upatch-manage/stack_check.c b/upatch-manage/stack_check.c index 1a6776f6..4319450b 100644 --- a/upatch-manage/stack_check.c +++ b/upatch-manage/stack_check.c @@ -49,9 +49,9 @@ static int check_stack_pages(pid_t pid, struct page **pages, long count, stack_c return 0; } -static int check_thread_stack(pid_t pid, struct task_struct *thread, stack_check_fn check_fn, void *check_ctx) +static int check_thread_stack(struct task_struct *process, struct task_struct *thread, + stack_check_fn check_fn, void *check_ctx) { - pid_t tid; struct mm_struct *mm; unsigned long stack_pointer; @@ -67,6 +67,8 @@ static int check_thread_stack(pid_t pid, struct task_struct *thread, stack_check long page_count; int i; + pid_t tgid = task_tgid_nr(process); + pid_t pid = task_pid_nr(thread); int ret = 0; // skip if thread has no mm @@ -101,7 +103,7 @@ static int check_thread_stack(pid_t pid, struct task_struct *thread, stack_check } log_debug("process %d: thread %d stack at 0x%lx-0x%lx (%lu pages)\n", - pid, tid, stack_start, stack_end, stack_page_nr); + tgid, pid, stack_start, stack_end, stack_page_nr); stack_addr = stack_start; while (stack_addr < stack_end) { @@ -116,13 +118,13 @@ static int check_thread_stack(pid_t pid, struct task_struct *thread, stack_check page_count = get_user_pages_remote(mm, stack_addr, page_nr, FOLL_GET, stack_pages, NULL); if (unlikely(page_count < 0)) { ret = page_count; - log_err("process %d: failed to get stack pages at 0x%lx, ret=%d\n", pid, stack_addr, ret); + log_err("process %d: failed to get stack pages at 0x%lx, ret=%d\n", tgid, stack_addr, ret); break; } else if (page_count == 0) { - log_debug("process %d: skipped %lu unmapped pages\n", pid, page_nr); + log_debug("process %d: skipped %lu unmapped pages\n", tgid, page_nr); stack_addr += page_nr * PAGE_SIZE; } else { - ret = check_stack_pages(pid, stack_pages, page_count, check_fn, check_ctx); + ret = check_stack_pages(tgid, stack_pages, page_count, check_fn, check_ctx); for (i = 0; i < page_count; i++) { put_page(stack_pages[i]); } @@ -142,7 +144,6 @@ unlock_mm: int check_process_stack(struct task_struct *process, stack_check_fn check_fn, void *check_ctx) { - pid_t pid; struct task_struct *thread; int ret = 0; @@ -151,12 +152,9 @@ int check_process_stack(struct task_struct *process, stack_check_fn check_fn, vo return -EINVAL; } - pid = task_pid_nr(process); - rcu_read_lock(); for_each_thread(process, thread) { - log_debug("process %d: checking all thread stacks\n", pid); - ret = check_thread_stack(pid, thread, check_fn, check_ctx); + ret = check_thread_stack(process, thread, check_fn, check_ctx); if (ret) { break; } diff --git a/upatch-manage/symbol_resolve.c b/upatch-manage/symbol_resolve.c index 456bb2fc..4308a722 100644 --- a/upatch-manage/symbol_resolve.c +++ b/upatch-manage/symbol_resolve.c @@ -83,7 +83,7 @@ static unsigned long resolve_from_got(struct patch_context *ctx, const char *nam static unsigned long resolve_from_patch(struct patch_context *ctx, const char *name, Elf_Sym *sym) { - const struct target_metadata *elf = ctx->target; + const struct target_file *elf = ctx->target; unsigned long addr = 0; if (!elf) { @@ -107,7 +107,7 @@ static unsigned long resolve_from_patch(struct patch_context *ctx, const char *n /* handle external object, we need get it's address, used for R_X86_64_REX_GOTPCRELX */ static unsigned long resolve_from_rela_dyn(struct patch_context *ctx, const char *name, Elf_Sym *sym) { - const struct target_metadata *elf = ctx->target; + const struct target_file *elf = ctx->target; Elf_Sym *dynsym = elf->dynsym; Elf_Rela *rela_dyn = elf->rela_dyn; unsigned int i; @@ -150,7 +150,7 @@ static unsigned long resolve_from_rela_dyn(struct patch_context *ctx, const char static unsigned long resolve_from_rela_plt(struct patch_context *ctx, const char *name, Elf_Sym *sym) { - const struct target_metadata *elf = ctx->target; + const struct target_file *elf = ctx->target; Elf_Sym *dynsym = elf->dynsym; Elf_Rela *rela_plt = elf->rela_plt; unsigned int i; @@ -201,7 +201,7 @@ static unsigned long resolve_from_rela_plt(struct patch_context *ctx, const char // get symbol address from .dynsym static unsigned long resolve_from_dynsym(struct patch_context *ctx, const char *name) { - const struct target_metadata *elf = ctx->target; + const struct target_file *elf = ctx->target; Elf_Sym *dynsym = elf->dynsym; unsigned int i; const char *sym_name; @@ -653,7 +653,7 @@ static unsigned long resolve_from_vma_so(struct patch_context *ctx, const char * static unsigned long resolve_from_symtab(const struct patch_context *ctx, const char *name) { - const struct target_metadata *elf = ctx->target; + const struct target_file *elf = ctx->target; Elf_Sym *sym = elf->symtab; unsigned int i; const char *sym_name; diff --git a/upatch-manage/target_entity.c b/upatch-manage/target_entity.c index 8f90463a..6e3b7869 100644 --- a/upatch-manage/target_entity.c +++ b/upatch-manage/target_entity.c @@ -21,6 +21,9 @@ #include "target_entity.h" #include + +#include +#include #include #include "patch_entity.h" @@ -37,10 +40,23 @@ static const char *PLT_RELA_NAME = ".rela.plt"; static const char *PLT_NAME = ".plt"; static const char *GOT_NAME = ".got"; -static int resolve_target_sections(struct target_metadata *meta, struct file *target) +struct uprobe_record { + struct hlist_node node; + + loff_t offset; + long count; +}; + +/* --- Forward declarations --- */ + +static void destroy_target_file(struct target_file *target_file); + +/* --- Target life-cycle management --- */ + +static int resolve_file_sections(struct target_file *target_file, struct file *file) { - Elf_Shdr *shdrs = meta->shdrs; - Elf_Half shdr_num = meta->ehdr->e_shnum; + Elf_Shdr *shdrs = target_file->shdrs; + Elf_Half shdr_num = target_file->ehdr->e_shnum; Elf_Half i; Elf_Shdr *shdr; @@ -50,16 +66,16 @@ static int resolve_target_sections(struct target_metadata *meta, struct file *ta const char *sec_name; void *sec_data; - shdr = &shdrs[meta->ehdr->e_shstrndx]; - shstrtab = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + shdr = &shdrs[target_file->ehdr->e_shstrndx]; + shstrtab = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); if (IS_ERR(shstrtab)) { log_err("failed to read section '%s'\n", SHSTRTAB_NAME); return PTR_ERR(shstrtab); } shstrtab_len = shdr->sh_size; - meta->shstrtab = shstrtab; - meta->shstrtab_len = shstrtab_len; + target_file->shstrtab = shstrtab; + target_file->shstrtab_len = shstrtab_len; for (i = 1; i < shdr_num; i++) { shdr = &shdrs[i]; @@ -72,45 +88,45 @@ static int resolve_target_sections(struct target_metadata *meta, struct file *ta switch (shdr->sh_type) { case SHT_SYMTAB: - sec_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->symtab = sec_data; - meta->symtab_num = shdr->sh_size / sizeof(Elf_Sym); + sec_data = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + target_file->symtab = sec_data; + target_file->symtab_num = shdr->sh_size / sizeof(Elf_Sym); break; case SHT_STRTAB: if (strcmp(sec_name, STRTAB_NAME) == 0) { - sec_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->strtab = sec_data; - meta->strtab_len = shdr->sh_size; + sec_data = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + target_file->strtab = sec_data; + target_file->strtab_len = shdr->sh_size; } else if (strcmp(sec_name, DYNSTR_NAME) == 0) { - sec_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->dynstr = sec_data; - meta->dynstr_len = shdr->sh_size; + sec_data = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + target_file->dynstr = sec_data; + target_file->dynstr_len = shdr->sh_size; } break; case SHT_RELA: if (strcmp(sec_name, DYN_RELA_NAME) == 0) { - sec_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->rela_dyn = sec_data; - meta->rela_dyn_num = shdr->sh_size / sizeof(Elf_Rela); + sec_data = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + target_file->rela_dyn = sec_data; + target_file->rela_dyn_num = shdr->sh_size / sizeof(Elf_Rela); } else if (strcmp(sec_name, PLT_RELA_NAME) == 0) { - sec_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->rela_plt = sec_data; - meta->rela_plt_num = shdr->sh_size / sizeof(Elf_Rela); + sec_data = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + target_file->rela_plt = sec_data; + target_file->rela_plt_num = shdr->sh_size / sizeof(Elf_Rela); } break; case SHT_DYNAMIC: - sec_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->dynamic = sec_data; - meta->dynamic_num = shdr->sh_size / sizeof(Elf_Dyn); + sec_data = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + target_file->dynamic = sec_data; + target_file->dynamic_num = shdr->sh_size / sizeof(Elf_Dyn); break; case SHT_DYNSYM: - sec_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->dynsym = sec_data; - meta->dynsym_num = shdr->sh_size / sizeof(Elf_Sym); + sec_data = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + target_file->dynsym = sec_data; + target_file->dynsym_num = shdr->sh_size / sizeof(Elf_Sym); break; default: @@ -127,21 +143,21 @@ static int resolve_target_sections(struct target_metadata *meta, struct file *ta return 0; } -static int resolve_target_address(struct target_metadata *meta) +static int resolve_file_address(struct target_file *target_file) { Elf_Addr min_load_addr = ELF_ADDR_MAX; bool found_text_segment = false; - Elf_Ehdr *ehdr = meta->ehdr; - Elf_Phdr *phdrs = meta->phdrs; - Elf_Half phdr_num = meta->ehdr->e_phnum; + Elf_Ehdr *ehdr = target_file->ehdr; + Elf_Phdr *phdrs = target_file->phdrs; + Elf_Half phdr_num = target_file->ehdr->e_phnum; Elf_Phdr *phdr; - Elf_Shdr *shdrs = meta->shdrs; - Elf_Half shdr_num = meta->ehdr->e_shnum; + Elf_Shdr *shdrs = target_file->shdrs; + Elf_Half shdr_num = target_file->ehdr->e_shnum; Elf_Shdr *shdr; - const char *shstrtab = meta->shstrtab; + const char *shstrtab = target_file->shstrtab; const char *sec_name; Elf_Half i; @@ -155,9 +171,9 @@ static int resolve_target_address(struct target_metadata *meta) * Non-PIE executables (ET_EXEC) load at fixed addresses and don't need bias. */ if (ehdr->e_type == ET_DYN) { - meta->need_load_bias = true; + target_file->need_load_bias = true; } else { - meta->need_load_bias = false; + target_file->need_load_bias = false; } /* find minimum load virtual address */ @@ -183,16 +199,16 @@ static int resolve_target_address(struct target_metadata *meta) log_err("found multiple executable PT_LOAD segments\n"); return -ENOEXEC; } - meta->vma_offset = (phdr->p_vaddr - min_load_addr) & PAGE_MASK; - meta->load_offset = phdr->p_vaddr - min_load_addr - phdr->p_offset; + target_file->vma_offset = (phdr->p_vaddr - min_load_addr) & PAGE_MASK; + target_file->load_offset = phdr->p_vaddr - min_load_addr - phdr->p_offset; found_text_segment = true; } break; } case PT_TLS: - meta->tls_size = phdr->p_memsz; - meta->tls_align = phdr->p_align; + target_file->tls_size = phdr->p_memsz; + target_file->tls_align = phdr->p_align; break; default: @@ -207,7 +223,7 @@ static int resolve_target_address(struct target_metadata *meta) /* parse section headers */ for (Elf_Half i = 0; i < shdr_num; i++) { - if (meta->plt_addr && meta->got_addr) { + if (target_file->plt_addr && target_file->got_addr) { break; } @@ -218,368 +234,729 @@ static int resolve_target_address(struct target_metadata *meta) sec_name = shstrtab + shdr->sh_name; if ((shdr->sh_flags & (SHF_ALLOC|SHF_EXECINSTR)) == (SHF_ALLOC|SHF_EXECINSTR)) { - if (!meta->plt_addr && strcmp(sec_name, PLT_NAME) == 0) { - meta->plt_addr = shdr->sh_addr; - meta->plt_size = shdr->sh_size; + if (!target_file->plt_addr && strcmp(sec_name, PLT_NAME) == 0) { + target_file->plt_addr = shdr->sh_addr; + target_file->plt_size = shdr->sh_size; } } if ((shdr->sh_flags & (SHF_ALLOC|SHF_WRITE)) == (SHF_ALLOC|SHF_WRITE)) { - if (!meta->got_addr && strcmp(sec_name, GOT_NAME) == 0) { - meta->got_addr = shdr->sh_addr; - meta->got_size = shdr->sh_size; + if (!target_file->got_addr && strcmp(sec_name, GOT_NAME) == 0) { + target_file->got_addr = shdr->sh_addr; + target_file->got_size = shdr->sh_size; } } } log_debug("vma_offset: 0x%llx, load_offset: 0x%llx, plt_addr: 0x%llx, got_addr: 0x%llx\n", - meta->vma_offset, meta->load_offset, meta->plt_addr, meta->got_addr); + target_file->vma_offset, target_file->load_offset, target_file->plt_addr, target_file->got_addr); return 0; } -static void destroy_target_metadata(struct target_metadata *meta) -{ - KFREE_CLEAR(meta->path); - iput(meta->inode); - meta->inode = NULL; - - meta->size = 0; - - VFREE_CLEAR(meta->ehdr); - VFREE_CLEAR(meta->phdrs); - VFREE_CLEAR(meta->shdrs); - - VFREE_CLEAR(meta->symtab); - VFREE_CLEAR(meta->dynsym); - VFREE_CLEAR(meta->dynamic); - VFREE_CLEAR(meta->rela_dyn); - VFREE_CLEAR(meta->rela_plt); - - VFREE_CLEAR(meta->shstrtab); - VFREE_CLEAR(meta->strtab); - VFREE_CLEAR(meta->dynstr); - - meta->symtab_num = 0; - meta->dynamic_num = 0; - meta->dynsym_num = 0; - meta->rela_dyn_num = 0; - meta->rela_plt_num = 0; - - meta->shstrtab_len = 0; - meta->strtab_len = 0; - meta->dynstr_len = 0; - - meta->need_load_bias = false; - meta->vma_offset = 0; - meta->load_offset = 0; - - meta->tls_size = 0; - meta->tls_align = 0; - - meta->plt_addr = 0; - meta->got_addr = 0; - meta->plt_size = 0; - meta->got_size = 0; -} - -static int resolve_target_metadata(struct target_metadata *meta, const char *file_path) +static int resolve_target_file(struct target_file *target_file, struct file *file) { - struct file *file; int ret; - file = filp_open(file_path, O_RDONLY, 0); - if (IS_ERR(file)) { - log_err("failed to open '%s'\n", file_path); - ret = PTR_ERR(file); - file = NULL; - goto out; - } + rcu_read_lock(); + target_file->path = file_path(file, target_file->path_buff, PATH_MAX); + rcu_read_unlock(); - meta->path = kstrdup(file_path, GFP_KERNEL); - if (!meta->path) { - log_err("failed to alloc file path\n"); - ret = -ENOMEM; + if (unlikely(IS_ERR(target_file->path))) { + ret = PTR_ERR(target_file->path); + log_err("faild to get file path\n"); goto out; } - meta->inode = igrab(file_inode(file)); - if (!meta->inode) { - log_err("file '%s' inode is invalid\n", meta->path); + target_file->inode = igrab(file_inode(file)); + if (unlikely(!target_file->inode)) { + log_err("failed to get file inode\n"); ret = -ENOENT; goto out; } - meta->size = i_size_read(meta->inode); + target_file->size = i_size_read(target_file->inode); - meta->ehdr = vmalloc_read(file, 0, sizeof(Elf_Ehdr)); - if (IS_ERR(meta->ehdr)) { + target_file->ehdr = vmalloc_read(file, 0, sizeof(Elf_Ehdr)); + if (unlikely(IS_ERR(target_file->ehdr))) { log_err("failed to read elf header\n"); - ret = PTR_ERR(meta->ehdr); + ret = PTR_ERR(target_file->ehdr); goto out; } - if (!is_valid_target(meta->ehdr, meta->size)) { + if (unlikely(!is_valid_target(target_file->ehdr, target_file->size))) { log_err("invalid file format\n"); ret = -ENOEXEC; goto out; } - meta->phdrs = vmalloc_read(file, meta->ehdr->e_phoff, meta->ehdr->e_phentsize * meta->ehdr->e_phnum); - if (IS_ERR(meta->phdrs)) { + target_file->phdrs = vmalloc_read(file, target_file->ehdr->e_phoff, + target_file->ehdr->e_phentsize * target_file->ehdr->e_phnum); + if (unlikely(IS_ERR(target_file->phdrs))) { log_err("failed to read program header\n"); - ret = PTR_ERR(meta->phdrs); + ret = PTR_ERR(target_file->phdrs); goto out; } - meta->shdrs = vmalloc_read(file, meta->ehdr->e_shoff, meta->ehdr->e_shentsize * meta->ehdr->e_shnum); - if (IS_ERR(meta->shdrs)) { + target_file->shdrs = vmalloc_read(file, target_file->ehdr->e_shoff, + target_file->ehdr->e_shentsize * target_file->ehdr->e_shnum); + if (unlikely(IS_ERR(target_file->shdrs))) { log_err("failed to read section header\n"); - ret = PTR_ERR(meta->shdrs); + ret = PTR_ERR(target_file->shdrs); goto out; } - ret = resolve_target_sections(meta, file); - if (ret) { + ret = resolve_file_sections(target_file, file); + if (unlikely(ret)) { log_err("failed to resolve target sections\n"); goto out; } - ret = resolve_target_address(meta); - if (ret) { + ret = resolve_file_address(target_file); + if (unlikely(ret)) { log_err("failed to resolve target address\n"); goto out; } out: - if (file) { - filp_close(file, NULL); - } if (ret) { - destroy_target_metadata(meta); + destroy_target_file(target_file); } return ret; } -static int resolve_target_entity(struct target_entity *target, const char *file_path) +static void destroy_target_file(struct target_file *target_file) +{ + iput(target_file->inode); + + target_file->path = NULL; + target_file->inode = NULL; + target_file->size = 0; + + VFREE_CLEAR(target_file->ehdr); + VFREE_CLEAR(target_file->phdrs); + VFREE_CLEAR(target_file->shdrs); + + VFREE_CLEAR(target_file->symtab); + VFREE_CLEAR(target_file->dynsym); + VFREE_CLEAR(target_file->dynamic); + VFREE_CLEAR(target_file->rela_dyn); + VFREE_CLEAR(target_file->rela_plt); + + VFREE_CLEAR(target_file->shstrtab); + VFREE_CLEAR(target_file->strtab); + VFREE_CLEAR(target_file->dynstr); + + target_file->symtab_num = 0; + target_file->dynamic_num = 0; + target_file->dynsym_num = 0; + target_file->rela_dyn_num = 0; + target_file->rela_plt_num = 0; + + target_file->shstrtab_len = 0; + target_file->strtab_len = 0; + target_file->dynstr_len = 0; + + target_file->need_load_bias = false; + target_file->vma_offset = 0; + target_file->load_offset = 0; + + target_file->tls_size = 0; + target_file->tls_align = 0; + + target_file->plt_addr = 0; + target_file->got_addr = 0; + target_file->plt_size = 0; + target_file->got_size = 0; +} + +static int resolve_target(struct target_entity *target, struct file *file) { int ret; - ret = resolve_target_metadata(&target->meta, file_path); - if (ret) { + ret = resolve_target_file(&target->file, file); + if (unlikely(ret)) { return ret; } - INIT_HLIST_NODE(&target->table_node); + INIT_HLIST_NODE(&target->node); + target->is_deleting = false; + + hash_init(target->patches); + hash_init(target->uprobes); + mutex_init(&target->patch_lock); + + INIT_LIST_HEAD(&target->actived_patches); + spin_lock_init(&target->active_lock); - init_rwsem(&target->action_rwsem); - INIT_LIST_HEAD(&target->loaded_list); - INIT_LIST_HEAD(&target->actived_list); - INIT_LIST_HEAD(&target->func_list); + hash_init(target->processes); + spin_lock_init(&target->process_lock); - mutex_init(&target->process_mutex); - INIT_LIST_HEAD(&target->process_list); + kref_init(&target->kref); return 0; } -static void destroy_target_entity(struct target_entity *target) +static void destroy_target(struct target_entity *target) { struct process_entity *process; - struct process_entity *tmp; + struct hlist_node *tmp; + int bkt; + + WARN_ON(!hlist_unhashed(&target->node)); + target->is_deleting = false; + + destroy_target_file(&target->file); + + WARN_ON(mutex_is_locked(&target->patch_lock)); + WARN_ON(!hash_empty(target->patches)); + WARN_ON(!hash_empty(target->uprobes)); + hash_init(target->patches); + hash_init(target->uprobes); + + WARN_ON(spin_is_locked(&target->active_lock)); + WARN_ON(!list_empty(&target->actived_patches)); + + WARN_ON(spin_is_locked(&target->process_lock)); + hash_for_each_safe(target->processes, bkt, tmp, process, node) { + hash_del(&process->node); + put_process(process); + } + hash_init(target->processes); +} + +/* --- Patch management --- */ + +static inline struct patch_entity *find_patch_unlocked(const struct target_entity *target, const struct inode *inode) +{ + struct patch_entity *patch; + + hash_for_each_possible(target->patches, patch, node, hash_inode(inode, PATCH_HASH_BITS)) { + if (inode_equal(patch->file.inode, inode)) { + return patch; + } + } + + return NULL; +} + +static inline struct uprobe_record *find_function_unlocked(const struct target_entity *target, u64 offset) +{ + struct uprobe_record *uprobe; + + hash_for_each_possible(target->uprobes, uprobe, node, hash_64(offset, UPROBE_HASH_BITS)) { + if (uprobe->offset == offset) { + return uprobe; + } + } + + return NULL; +} + +static int register_uprobe_unlocked(struct target_entity *target, const struct upatch_function *func, + struct uprobe_consumer *uc) +{ + struct uprobe_record *uprobe; + int ret; + + uprobe = find_function_unlocked(target, func->old_addr); + if (uprobe) { + uprobe->count += 1; + return 0; + } + + uprobe = kmalloc(sizeof(*uprobe), GFP_KERNEL); + if (unlikely(!uprobe)) { + return -ENOMEM; + } + + INIT_HLIST_NODE(&uprobe->node); + uprobe->offset = func->old_addr; + uprobe->count = 1; + + ret = uprobe_register(target->file.inode, uprobe->offset, uc); + if (unlikely(ret)) { + log_err("failed to register uprobe on '%s', offset=0x%llx\n", target->file.path, uprobe->offset); + kfree(uprobe); + return ret; + } + + hash_add(target->uprobes, &uprobe->node, hash_64(func->old_addr, UPROBE_HASH_BITS)); + + return ret; +} + +static void unregister_uprobe_unlocked(struct target_entity *target, const struct upatch_function *func, + struct uprobe_consumer *uc) +{ + struct uprobe_record *uprobe; + + uprobe = find_function_unlocked(target, func->old_addr); + if (!uprobe) { + return; + } + + uprobe->count -= 1; + if (uprobe->count) { + return; // still has reference + } + + hash_del(&uprobe->node); + uprobe_unregister(target->file.inode, uprobe->offset, uc); + kfree(uprobe); +} + +static void do_unregister_patch_functions_unlocked(struct target_entity *target, const struct patch_entity *patch, + struct uprobe_consumer *uc, size_t count) +{ + const struct upatch_function *funcs = patch->file.funcs; + const char *strings = patch->file.strings; + + const struct upatch_function *func; + const char *name; + size_t i; + + if (count > patch->file.func_num) { + log_err("function count %zu exceeds %zu\n", count, patch->file.func_num); + return; + } + + log_debug("%s: unregister patch %s functions\n", target->file.path, patch->file.path); + for (i = 0; i < count; i++) { + func = &funcs[i]; + name = strings + func->name_off; + + log_debug("- function: offset=0x%08llx, size=0x%04llx, name='%s'\n", func->old_addr, func->old_size, name); + unregister_uprobe_unlocked(target, func, uc); + } +} + +static void unregister_patch_functions_unlocked(struct target_entity *target, const struct patch_entity *patch, + struct uprobe_consumer *uc) +{ + do_unregister_patch_functions_unlocked(target, patch, uc, patch->file.func_num); +} + +static int register_patch_functions_unlocked(struct target_entity *target, const struct patch_entity *patch, + struct uprobe_consumer *uc) +{ + const struct upatch_function *funcs = patch->file.funcs; + const char *strings = patch->file.strings; - destroy_target_metadata(&target->meta); + const struct upatch_function *func; + const char *name; + size_t i; - WARN_ON(!hlist_unhashed(&target->table_node)); + int ret = 0; - WARN_ON(rwsem_is_locked(&target->action_rwsem)); - WARN_ON(!list_empty(&target->loaded_list)); - WARN_ON(!list_empty(&target->actived_list)); - WARN_ON(!list_empty(&target->func_list)); - INIT_LIST_HEAD(&target->loaded_list); - INIT_LIST_HEAD(&target->actived_list); - INIT_LIST_HEAD(&target->func_list); + log_debug("%s: register patch %s functions\n", target->file.path, patch->file.path); + for (i = 0; i < patch->file.func_num; i++) { + func = &funcs[i]; + name = strings + func->name_off; - mutex_destroy(&target->process_mutex); - list_for_each_entry_safe(process, tmp, &target->process_list, process_node) { - list_del_init(&process->process_node); - free_process(process); + log_debug("+ function: offset=0x%08llx, size=0x%04llx, name='%s'\n", func->old_addr, func->old_size, name); + ret = register_uprobe_unlocked(target, func, uc); + if (ret) { + log_err("failed to register function '%s'\n", name); + do_unregister_patch_functions_unlocked(target, patch, uc, i); // we need rollback all changes + break; + } } - INIT_LIST_HEAD(&target->process_list); + + return ret; } -static inline struct target_function *target_get_function(struct target_entity *target, u64 addr) +/* --- Process management --- */ + +static inline struct process_entity *find_process_unlocked(const struct target_entity *target, pid_t pid) { - struct target_function *target_func; + struct process_entity *process; - list_for_each_entry(target_func, &target->func_list, func_node) { - if (target_func->addr == addr) { - return target_func; + hash_for_each_possible(target->processes, process, node, hash_32(pid, PROCESS_HASH_BITS)) { + if (process->tgid == pid) { + return process; } } return NULL; } -static inline struct process_entity *target_get_process(struct target_entity *target) +static int check_patch_removable(struct target_entity *target, struct patch_entity *patch) { - struct pid *current_pid = get_task_pid(current, PIDTYPE_TGID); struct process_entity *process; - struct process_entity *found = NULL; + int bkt; + + int ret = 0; + + spin_lock(&target->process_lock); - list_for_each_entry(process, &target->process_list, process_node) { - if (pid_nr(process->pid) == pid_nr(current_pid)) { - found = process; + hash_for_each(target->processes, bkt, process, node) { + ret = process_check_patch_on_stack(process, patch); + if (ret) { break; } } - put_pid(current_pid); - return found; + spin_unlock(&target->process_lock); + + return ret; } -/* public interface */ -struct target_entity *new_target_entity(const char *file_path) +static inline void remove_patch_on_all_process(struct target_entity *target, struct patch_entity *patch) +{ + struct process_entity *process; + int bkt; + + hash_for_each(target->processes, bkt, process, node) { + spin_lock(&process->thread_lock); + process_remove_patch(process, patch); + spin_unlock(&process->thread_lock); + } +} + +/* --- Public interface --- */ + +struct target_entity *load_target(struct file *file) { struct target_entity *target = NULL; int ret; - if (unlikely(!file_path)) { + if (unlikely(!file)) { return ERR_PTR(-EINVAL); } target = kzalloc(sizeof(struct target_entity), GFP_KERNEL); - if (!target) { - log_err("failed to alloc target entity\n"); + if (unlikely(!target)) { return ERR_PTR(-ENOMEM); } - ret = resolve_target_entity(target, file_path); - if (ret) { + ret = resolve_target(target, file); + if (unlikely(ret)) { kfree(target); return ERR_PTR(ret); } + log_debug("new target %s\n", target->file.path); return target; } -void free_target_entity(struct target_entity *target) +void release_target(struct kref *kref) { - if (unlikely(!target)) { + struct target_entity *target; + + if (unlikely(!kref)) { return; } - log_debug("free patch target '%s'\n", target->meta.path); - destroy_target_entity(target); + target = container_of(kref, struct target_entity, kref); + log_debug("free target %s\n", target->file.path); + + destroy_target(target); kfree(target); } -int target_add_function(struct target_entity *target, struct upatch_function *func, bool *need_register) +int target_load_patch(struct target_entity *target, const char *filename) { - struct target_function *target_func; + struct file *file; + struct patch_entity *existing; + struct patch_entity *patch; - if (!target || !func || !need_register) { + struct patch_entity *to_be_freed = NULL; + int ret = 0; + + if (unlikely(!target || !filename)) { return -EINVAL; } - *need_register = false; + /* --- Fast path: quick check if the patch is already exists --- */ - // get or alloc target function - target_func = target_get_function(target, func->old_addr); - if (!target_func) { - target_func = kzalloc(sizeof(*target_func), GFP_KERNEL); - if (!target_func) { - log_err("failed to alloc target function\n"); - return -ENOMEM; - } - target_func->addr = func->old_addr; - target_func->count = 0; - INIT_LIST_HEAD(&target_func->func_node); + /* Step 1: Open the file */ + file = filp_open(filename, O_RDONLY, 0); + if (unlikely(IS_ERR(file))) { + return PTR_ERR(file); + } + + mutex_lock(&target->patch_lock); - *need_register = true; - list_add(&target_func->func_node, &target->func_list); + /* Step 2: Check if the patch is already exists */ + existing = find_patch_unlocked(target, file_inode(file)); + if (unlikely(existing)) { + ret = -EEXIST; + goto unlock_out; } - target_func->count += 1; + mutex_unlock(&target->patch_lock); - return 0; + /* --- Slow path: load patch, check existence, insert patch table --- */ + + /* Step 3: Load patch from file */ + patch = load_patch(file); + if (unlikely(IS_ERR(patch))) { + ret = PTR_ERR(patch); + goto release_out; + } + patch->status = UPATCH_STATUS_DEACTIVED; + + mutex_lock(&target->patch_lock); + + /* Step 4: Re-check if the patch already exists (to handle race) */ + existing = find_patch_unlocked(target, patch->file.inode); + if (unlikely(existing)) { + ret = -EEXIST; + to_be_freed = patch; + goto unlock_out; + } + + /* Step 5: Insert the patch into patch table */ + hash_add(target->patches, &patch->node, hash_inode(patch->file.inode, PATCH_HASH_BITS)); + +unlock_out: + mutex_unlock(&target->patch_lock); + +release_out: + filp_close(file, NULL); + put_patch(to_be_freed); + + return ret; } -void target_remove_function(struct target_entity *target, struct upatch_function *func, bool *need_unregister) +int target_remove_patch(struct target_entity *target, struct inode *inode) { - struct target_function *target_func; + struct patch_entity *patch; - if (!target || !func || !need_unregister) { - return; + struct patch_entity *to_be_freed = NULL; + int ret = 0; + + if (unlikely(!target || !inode)) { + return -EINVAL; } - *need_unregister = false; + mutex_lock(&target->patch_lock); - target_func = target_get_function(target, func->old_addr); - if (!target_func) { - log_warn("target does not have function\n"); - return; + /* Step 1: Find patch from target patch table */ + patch = find_patch_unlocked(target, inode); + if (unlikely(!patch)) { + ret = -ENOENT; // patch does not exist + goto unlock_out; + } + + /* Step 2: Verify the patch status is what we expected */ + if (patch->status != UPATCH_STATUS_DEACTIVED) { + ret = -EPERM; + goto unlock_out; } - target_func->count -= 1; - if (!target_func->count) { - list_del_init(&target_func->func_node); - kfree(target_func); - *need_unregister = true; + /* Step 3: Check if the patch is removable */ + ret = check_patch_removable(target, patch); + if (unlikely(ret)) { + goto unlock_out; } + + /* Step 4: Remove the patch from all processes */ + remove_patch_on_all_process(target, patch); + + /* Step 5: Remove patch from target patch table & mark removable */ + hash_del(&patch->node); + to_be_freed = patch; + + /* Step 6: Update the patch status */ + patch->status = UPATCH_STATUS_NOT_APPLIED; + + /* + * We still have the ownership of the patch, since it was removed from target patch table and we didn't release it. + * Thus, we don't need increase it's reference. + */ +unlock_out: + mutex_unlock(&target->patch_lock); + + put_patch(to_be_freed); + return ret; } -void target_gather_exited_processes(struct target_entity *target, struct list_head *process_list) +int target_active_patch(struct target_entity *target, struct inode *inode, struct uprobe_consumer *uc) { - struct process_entity *process; - struct process_entity *tmp; + struct patch_entity *patch; - if (unlikely(!target || !process_list)) { - return; + int ret = 0; + + if (unlikely(!target || !inode || !uc)) { + return -EINVAL; } - list_for_each_entry_safe(process, tmp, &target->process_list, process_node) { - if (!pid_task(process->pid, PIDTYPE_TGID)) { - list_move(&process->process_node, process_list); - } + mutex_lock(&target->patch_lock); + + /* Step 1: Find patch from target patch table */ + patch = find_patch_unlocked(target, inode); + if (unlikely(!patch)) { + ret = -ENOENT; // patch does not exist + goto unlock_out; + } + + /* Step 2: Verify the patch status is what we expected */ + if (unlikely(patch->status != UPATCH_STATUS_DEACTIVED)) { + ret = -EPERM; + goto unlock_out; + } + + /* Step 3: Register the patch functions */ + ret = register_patch_functions_unlocked(target, patch, uc); + if (unlikely(ret)) { + goto unlock_out; + } + + /* Step 4: Insert the patch into target actived patch list */ + spin_lock(&target->active_lock); + list_add(&patch->actived_node, &target->actived_patches); + get_patch(patch); + spin_unlock(&target->active_lock); + + /* Step 5: Update the patch status */ + patch->status = UPATCH_STATUS_ACTIVED; + +unlock_out: + mutex_unlock(&target->patch_lock); + + return ret; +} + +int target_deactive_patch(struct target_entity *target, struct inode *inode, struct uprobe_consumer *uc) +{ + struct patch_entity *patch; + + struct patch_entity *to_be_freed = NULL; + int ret = 0; + + if (unlikely(!target || !inode || !uc)) { + return -EINVAL; + } + + mutex_lock(&target->patch_lock); + + /* Step 1: Find patch from target patch table */ + patch = find_patch_unlocked(target, inode); + if (unlikely(!patch)) { + ret = -ENOENT; // patch does not exist + goto unlock_out; + } + + /* Step 2: Verify the patch status is what we expected */ + if (unlikely(patch->status != UPATCH_STATUS_ACTIVED)) { + ret = -EPERM; + goto unlock_out; } + + /* Step 3: Register the patch functions */ + unregister_patch_functions_unlocked(target, patch, uc); + + /* Step 4: Remove the patch from target actived patch list */ + spin_lock(&target->active_lock); + list_del_init(&patch->actived_node); + to_be_freed = patch; + spin_unlock(&target->active_lock); + + /* Step 5: Update patch status */ + patch->status = UPATCH_STATUS_DEACTIVED; + +unlock_out: + mutex_unlock(&target->patch_lock); + + put_patch(to_be_freed); + return ret; +} + +enum upatch_status target_patch_status(struct target_entity *target, const struct inode *inode) +{ + enum upatch_status ret = UPATCH_STATUS_NOT_APPLIED; + struct patch_entity *patch; + + if (unlikely(!target || !inode)) { + return ret; + } + + mutex_lock(&target->patch_lock); + + /* Step 1: Find patch from target patch table */ + patch = find_patch_unlocked(target, inode); + if (unlikely(!patch)) { + goto unlock_out; // patch does not exist + } + + /* Step 2: Get patch status */ + ret = patch->status; + +unlock_out: + mutex_unlock(&target->patch_lock); + + return ret; +} + +struct patch_entity *target_get_actived_patch(struct target_entity *target) +{ + struct patch_entity *patch; + + spin_lock(&target->active_lock); + patch = get_patch(list_first_entry_or_null(&target->actived_patches, struct patch_entity, actived_node)); + spin_unlock(&target->active_lock); + + return patch; } -struct process_entity *target_get_or_create_process(struct target_entity *target) +struct process_entity *target_get_process(struct target_entity *target, struct task_struct *task) { - struct process_entity *process = NULL; + struct process_entity *process; + pid_t pid = task_tgid_nr(task); if (unlikely(!target)) { - return NULL; + return ERR_PTR(-EINVAL); } - process = target_get_process(target); + spin_lock(&target->process_lock); + + process = find_process_unlocked(target, pid); if (!process) { - log_debug("create process %d for '%s'\n", task_pid_nr(current), target->meta.path); - process = new_process(target); + log_debug("create process %d for '%s'\n", pid, target->file.path); + + process = new_process(task); if (IS_ERR(process)) { log_err("failed to create target process, ret=%d\n", (int)PTR_ERR(process)); - return NULL; + goto unlock_out; } - list_add(&process->process_node, &target->process_list); + + hash_add(target->processes, &process->node, hash_32(pid, PROCESS_HASH_BITS)); } + get_process(process); + +unlock_out: + spin_unlock(&target->process_lock); + return process; } -int target_check_patch_removable(struct target_entity *target, struct patch_entity *patch) +void target_cleanup_process(struct target_entity *target) { - struct process_entity *process = NULL; - int ret = 0; + struct process_entity *process; + struct process_entity *n; + struct hlist_node *tmp; + int bkt; - if (unlikely(!target || !patch)) { - return -EINVAL; + LIST_HEAD(pending_list); + + if (unlikely(!target)) { + return; } - list_for_each_entry(process, &target->process_list, process_node) { - ret = process_check_patch_on_stack(process, patch); - if (ret) { - break; + spin_lock(&target->process_lock); + + hash_for_each_safe(target->processes, bkt, tmp, process, node) { + if (!process_is_alive(process)) { + hash_del(&process->node); + list_add(&process->pending_node, &pending_list); } } - return ret; + spin_unlock(&target->process_lock); + + list_for_each_entry_safe(process, n, &pending_list, pending_node) { + list_del_init(&process->pending_node); + put_process(process); + } } diff --git a/upatch-manage/target_entity.h b/upatch-manage/target_entity.h index 62d530ac..7c0b96cd 100644 --- a/upatch-manage/target_entity.h +++ b/upatch-manage/target_entity.h @@ -22,13 +22,20 @@ #define _UPATCH_MANAGE_TARGET_ENTITY_H #include -#include -#include +#include + #include +#include +#include +#include #include #include +#define PATCH_HASH_BITS 4 // Single patch target would have less than 16 patches +#define UPROBE_HASH_BITS 7 // Single patch target would have less than 128 uprobes +#define PROCESS_HASH_BITS 4 // Single patch target would have less than 16 processes + #if defined(__x86_64__) #if defined(__CET__) || defined(__SHSTK__) #define PLT_ENTRY_SIZE 20 // PLT with CET @@ -44,16 +51,18 @@ #endif #define GOT_ENTRY_SIZE sizeof(uintptr_t) // GOT entry size is pointer size +struct file; struct inode; struct patch_entity; struct upatch_function; -/* target elf metadata */ -struct target_metadata { +/* Target file */ +struct target_file { + char path_buff[PATH_MAX]; + const char *path; struct inode *inode; - loff_t size; Elf_Ehdr *ehdr; @@ -93,79 +102,144 @@ struct target_metadata { size_t got_size; }; -/* target function record */ -struct target_function { - u64 addr; // target function address - size_t count; // target function patch count - struct list_head func_node; // target function list node -}; - +/* Target entity */ struct target_entity { - struct target_metadata meta; // target file metadata - struct hlist_node table_node; // global target hash table node + struct target_file file; // target file + + struct hlist_node node; // hash table node + bool is_deleting; // marker for deleting from hash table - struct rw_semaphore action_rwsem; // target action rw semaphore + DECLARE_HASHTABLE(patches, PATCH_HASH_BITS); // all loaded patches + DECLARE_HASHTABLE(uprobes, UPROBE_HASH_BITS); // all registered uprobes + struct mutex patch_lock; - struct list_head loaded_list; // target loaded patches - struct list_head actived_list; // target actived patches - struct list_head func_list; // target registered functions + struct list_head actived_patches; // actived patch list + spinlock_t active_lock; - struct mutex process_mutex; - struct list_head process_list; // all processes of the target + DECLARE_HASHTABLE(processes, PROCESS_HASH_BITS); // all running processes + spinlock_t process_lock; + + struct kref kref; }; -/* - * Load a target entity - * @param file_path: target file path - * @return target entity +/** + * @brief Load a new target file + * @param file: Target file struct pointer + * @return Newly allocated target entity with refcount=1, or NULL on failure + * + * Allocates and initializes a new taget entity structure with reference count 1. + * The caller is responsible for calling put_target() when done. */ -struct target_entity *new_target_entity(const char *file_path); +struct target_entity *load_target(struct file *file); -/* - * Free a target entity - * @param target: target entity - * @return void +/** + * @brief Release target resources when refcount reaches zero + * @param kref: Reference counter + * + * Called automatically by kref_put(). + * Frees all target resources and disassociates from target. */ -void free_target_entity(struct target_entity *target); +void release_target(struct kref *kref); -/* - * Add a patch function to target entity - * @param target: target entity - * @param func: patch function - * @param need_register: target offset needs register uprobe - * @return result +/** + * @brief Acquire a reference to a target entity + * @param target: Target entity pointer + * @return Target entity with incremented refcount, or NULL if input is NULL + * + * Caller must balance with put_target(). */ -int target_add_function(struct target_entity *target, struct upatch_function *func, bool *need_register); +static inline struct target_entity *get_target(struct target_entity *target) +{ + if (unlikely(!target)) { + return NULL; + } + + kref_get(&target->kref); + return target; +} + +/** + * @brief Release a target entity reference + * @param target: Target entity + * + * Decrements refcount and triggers release_target() when reaching zero. + * Safe to call with NULL. + */ +static inline void put_target(struct target_entity *target) +{ + if (unlikely(!target)) { + return; + } + + kref_put(&target->kref, release_target); +} + +/** + * @brief Load a patch to the target + * @param target Target entity + * @param patch Fully initialized patch entity + * @return 0 on success, negative error code on failure + */ +int target_load_patch(struct target_entity *target, const char *filename); -/* - * Remove a patch function from target entity - * @param target: target entity - * @param func: patch function - * @param need_unregister: target offset needs unregister uprobe - * @return result +/** + * @brief Remove a patch from the target + * @param target Target entity + * @param inode Patch inode + * @return 0 on success, negative error code on failure */ -void target_remove_function(struct target_entity *target, struct upatch_function *func, bool *need_unregister); +int target_remove_patch(struct target_entity *target, struct inode *inode); + +/** + * @brief Activate a patch on the target + * @param target Target entity + * @param inode Patch inode + * @param uc Uprobe consumer + * @return 0 on success, negative error code on failure + */ +int target_active_patch(struct target_entity *target, struct inode *inode, struct uprobe_consumer *uc); + +/** + * @brief Deactivate a patch on the target + * @param target Target entity + * @param inode Patch inode + * @param uc Uprobe consumer + * @return 0 on success, negative error code on failure + */ +int target_deactive_patch(struct target_entity *target, struct inode *inode, struct uprobe_consumer *uc); -/* - * Collect all exited process into a list - * @param target: target entity - * @param process_list: exited process list - * @return void +/** + * @brief Get patch status on the target + * @param target Target entity + * @param inode Patch inode + * @return Patch entity */ -void target_gather_exited_processes(struct target_entity *target, struct list_head *process_list); +enum upatch_status target_patch_status(struct target_entity *target, const struct inode *inode); -/* - * Get or create a process entity from target entity - * @param target: target entity - * @return process entity +/** + * @brief Get current actived patch on the target + * @param target Target entity + * @return Current actived patch entity or NULL if none exists + * + * The returned patch has its reference count incremented. + * Caller must call put_patch() when done. */ -struct process_entity *target_get_or_create_process(struct target_entity *target); +struct patch_entity *target_get_actived_patch(struct target_entity *target); -/* - * Check target process stack - * @param target: target entity - * @return result +/** + * @brief Get or create process entity + * @param target Target entity + * @param task Process task_struct + * @return Process entity with incremented refcount + * + * Caller must call put_process() when done. + */ +struct process_entity *target_get_process(struct target_entity *target, struct task_struct *task); + +/** + * @brief Cleanup exited processes of the target + * @param target Target entity */ -int target_check_patch_removable(struct target_entity *target, struct patch_entity *patch); +void target_cleanup_process(struct target_entity *target); #endif // _UPATCH_MANAGE_TARGET_ENTITY_H diff --git a/upatch-manage/util.h b/upatch-manage/util.h index e0a9f154..c3064498 100644 --- a/upatch-manage/util.h +++ b/upatch-manage/util.h @@ -30,6 +30,8 @@ #include #include +#include + static const char* MODULE_NAME = THIS_MODULE->name; #define log_err(fmt, args...) pr_err_ratelimited("%s: " fmt, MODULE_NAME, ##args) @@ -172,19 +174,43 @@ static inline const char *get_string_at(const char *strtab, size_t strtab_len, s return is_valid_str(strtab, strtab_len, offset) ? strtab + offset : NULL; } -static inline struct inode *get_path_inode(const char *file) +static inline struct inode *get_path_inode(const char *filename) { struct path path; struct inode *inode; - if (unlikely(!file || kern_path(file, LOOKUP_FOLLOW, &path))) { + if (unlikely(!filename || kern_path(filename, LOOKUP_FOLLOW, &path))) { return NULL; } - inode = igrab(path.dentry->d_inode); // will increase inode refcnt, need call iput after use path_put(&path); return inode; } +/** + * @brief Calculate hash value of an inode + * + * @param inode Pointer to the inode structure to hash (must not be NULL) + * @param bits Number of bits to use for the hash (must be between 1 and 31) + * @return Hashed value + */ +static inline unsigned long hash_inode(const struct inode *inode, unsigned int bits) +{ + return hash_long(inode->i_ino, bits) ^ hash_ptr(inode->i_sb, bits); +} + +/** + * @brief Compare two inodes for equality + * + * @param lhs First inode to compare + * @param rhs Second inode to compare + * @return True If both inodes are non-NULL and have matching i_ino and i_sb + * False If either inode is NULL or i_ino/i_sb don't match + */ +static inline bool inode_equal(const struct inode *lhs, const struct inode *rhs) +{ + return lhs && rhs && lhs->i_ino == rhs->i_ino && lhs->i_sb == rhs->i_sb; +} + #endif // _UPATCH_MANAGE_UTIL_H -- Gitee From 639114339da852a6896391e0b26bf85c5c4f5c06 Mon Sep 17 00:00:00 2001 From: renoseven Date: Mon, 4 Aug 2025 17:17:35 +0800 Subject: [PATCH 3/3] syscared: adapt upatch-manage changes Signed-off-by: renoseven --- syscared/src/patch/driver/upatch/mod.rs | 22 +++-- syscared/src/patch/driver/upatch/sys.rs | 125 ++++++++++++++++++------ 2 files changed, 105 insertions(+), 42 deletions(-) diff --git a/syscared/src/patch/driver/upatch/mod.rs b/syscared/src/patch/driver/upatch/mod.rs index 6cc04bae..28f20576 100644 --- a/syscared/src/patch/driver/upatch/mod.rs +++ b/syscared/src/patch/driver/upatch/mod.rs @@ -163,7 +163,7 @@ impl UserPatchDriver { } pub fn get_patch_status(&self, patch: &UserPatch) -> Result { - sys::get_patch_status(self.ioctl_fd(), &patch.patch_file).map_err(|e| { + sys::get_patch_status(self.ioctl_fd(), &patch.target_elf, &patch.patch_file).map_err(|e| { anyhow!( "Kpatch: Failed to get patch status, {}", e.to_string().to_lowercase() @@ -177,7 +177,7 @@ impl UserPatchDriver { "Upatch: Patch target '{}' is blocked", patch.target_elf.display(), ); - sys::load_patch(self.ioctl_fd(), &patch.patch_file, &patch.target_elf).map_err(|e| { + sys::load_patch(self.ioctl_fd(), &patch.target_elf, &patch.patch_file).map_err(|e| { anyhow!( "Upatch: Failed to load patch, {}", e.to_string().to_lowercase() @@ -186,7 +186,7 @@ impl UserPatchDriver { } pub fn remove_patch(&mut self, patch: &UserPatch) -> Result<()> { - sys::remove_patch(self.ioctl_fd(), &patch.patch_file).map_err(|e| { + sys::remove_patch(self.ioctl_fd(), &patch.target_elf, &patch.patch_file).map_err(|e| { anyhow!( "Upatch: Failed to remove patch, {}", e.to_string().to_lowercase() @@ -195,7 +195,7 @@ impl UserPatchDriver { } pub fn active_patch(&mut self, patch: &UserPatch) -> Result<()> { - sys::active_patch(self.ioctl_fd(), &patch.patch_file).map_err(|e| { + sys::active_patch(self.ioctl_fd(), &patch.target_elf, &patch.patch_file).map_err(|e| { anyhow!( "Upatch: Failed to active patch, {}", e.to_string().to_lowercase() @@ -207,12 +207,14 @@ impl UserPatchDriver { } pub fn deactive_patch(&mut self, patch: &UserPatch) -> Result<()> { - sys::deactive_patch(self.ioctl_fd(), &patch.patch_file).map_err(|e| { - anyhow!( - "Upatch: Failed to deactive patch, {}", - e.to_string().to_lowercase() - ) - })?; + sys::deactive_patch(self.ioctl_fd(), &patch.target_elf, &patch.patch_file).map_err( + |e| { + anyhow!( + "Upatch: Failed to deactive patch, {}", + e.to_string().to_lowercase() + ) + }, + )?; self.unregister_patch(patch); Ok(()) diff --git a/syscared/src/patch/driver/upatch/sys.rs b/syscared/src/patch/driver/upatch/sys.rs index 20d1086b..e7058695 100644 --- a/syscared/src/patch/driver/upatch/sys.rs +++ b/syscared/src/patch/driver/upatch/sys.rs @@ -29,37 +29,71 @@ mod ffi { pub const UPATCH_STATUS_ACTIVED: i32 = 3; #[repr(C)] - pub struct PatchLoadRequest { - pub patch_file: *const c_char, + pub struct UpatchIoctlRequest { pub target_elf: *const c_char, + pub patch_file: *const c_char, } ioctl_write_ptr!( ioctl_load_patch, UPATCH_MAGIC, UPATCH_LOAD, - PatchLoadRequest + UpatchIoctlRequest + ); + ioctl_write_ptr!( + ioctl_active_patch, + UPATCH_MAGIC, + UPATCH_ACTIVE, + UpatchIoctlRequest + ); + ioctl_write_ptr!( + ioctl_deactive_patch, + UPATCH_MAGIC, + UPATCH_DEACTIVE, + UpatchIoctlRequest + ); + ioctl_write_ptr!( + ioctl_remove_patch, + UPATCH_MAGIC, + UPATCH_REMOVE, + UpatchIoctlRequest + ); + ioctl_write_ptr!( + ioctl_get_patch_status, + UPATCH_MAGIC, + UPATCH_STATUS, + UpatchIoctlRequest ); - ioctl_write_ptr!(ioctl_active_patch, UPATCH_MAGIC, UPATCH_ACTIVE, c_char); - ioctl_write_ptr!(ioctl_deactive_patch, UPATCH_MAGIC, UPATCH_DEACTIVE, c_char); - ioctl_write_ptr!(ioctl_remove_patch, UPATCH_MAGIC, UPATCH_REMOVE, c_char); - ioctl_write_ptr!(ioctl_get_patch_status, UPATCH_MAGIC, UPATCH_STATUS, c_char); } -pub fn get_patch_status

(fd: RawFd, patch_file: P) -> std::io::Result +pub fn get_patch_status( + ioctl_dev: RawFd, + target_elf: P, + patch_file: Q, +) -> std::io::Result where P: AsRef, + Q: AsRef, { + let ioctl_fd = ioctl_dev.as_raw_fd(); + let target_elf = target_elf.as_ref(); let patch_file = patch_file.as_ref(); debug!( - "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", - fd, + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {{ {}, {} }} }}", + ioctl_fd, stringify!(UPATCH_STATUS), + target_elf.display(), patch_file.display(), ); + let target_cstr = CString::new(target_elf.as_os_str().as_bytes())?; let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; - let status_code = unsafe { ffi::ioctl_get_patch_status(fd, patch_cstr.as_ptr())? }; + let request = ffi::UpatchIoctlRequest { + target_elf: target_cstr.as_ptr(), + patch_file: patch_cstr.as_ptr(), + }; + + let status_code = unsafe { ffi::ioctl_get_patch_status(ioctl_dev, &request) }?; let status = match status_code { ffi::UPATCH_STATUS_NOT_APPLIED => PatchStatus::NotApplied, ffi::UPATCH_STATUS_DEACTIVED => PatchStatus::Deactived, @@ -70,27 +104,27 @@ where Ok(status) } -pub fn load_patch(ioctl_dev: RawFd, patch_file: Q, target_elf: P) -> std::io::Result<()> +pub fn load_patch(ioctl_dev: RawFd, target_elf: P, patch_file: Q) -> std::io::Result<()> where P: AsRef, Q: AsRef, { let ioctl_fd = ioctl_dev.as_raw_fd(); - let patch_file = patch_file.as_ref(); let target_elf = target_elf.as_ref(); + let patch_file = patch_file.as_ref(); debug!( "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {{ {}, {} }} }}", ioctl_fd, stringify!(UPATCH_LOAD), - patch_file.display(), target_elf.display(), + patch_file.display(), ); - let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; let target_cstr = CString::new(target_elf.as_os_str().as_bytes())?; - let request = ffi::PatchLoadRequest { - patch_file: patch_cstr.as_ptr(), + let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + let request = ffi::UpatchIoctlRequest { target_elf: target_cstr.as_ptr(), + patch_file: patch_cstr.as_ptr(), }; unsafe { ffi::ioctl_load_patch(ioctl_fd, &request)?; @@ -99,61 +133,88 @@ where Ok(()) } -pub fn remove_patch

(fd: RawFd, patch_file: P) -> std::io::Result<()> +pub fn active_patch(ioctl_dev: RawFd, target_elf: P, patch_file: Q) -> std::io::Result<()> where P: AsRef, + Q: AsRef, { + let ioctl_fd = ioctl_dev.as_raw_fd(); + let target_elf = target_elf.as_ref(); let patch_file = patch_file.as_ref(); debug!( - "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", - fd, - stringify!(UPATCH_REMOVE), + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {{ {}, {} }} }}", + ioctl_fd, + stringify!(UPATCH_ACTIVE), + target_elf.display(), patch_file.display(), ); + let target_cstr = CString::new(target_elf.as_os_str().as_bytes())?; let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + let request = ffi::UpatchIoctlRequest { + target_elf: target_cstr.as_ptr(), + patch_file: patch_cstr.as_ptr(), + }; unsafe { - ffi::ioctl_remove_patch(fd, patch_cstr.as_ptr())?; + ffi::ioctl_active_patch(ioctl_fd, &request)?; } Ok(()) } -pub fn active_patch

(fd: RawFd, patch_file: P) -> std::io::Result<()> +pub fn deactive_patch(ioctl_dev: RawFd, target_elf: P, patch_file: Q) -> std::io::Result<()> where P: AsRef, + Q: AsRef, { + let ioctl_fd = ioctl_dev.as_raw_fd(); + let target_elf = target_elf.as_ref(); let patch_file = patch_file.as_ref(); debug!( - "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", - fd, - stringify!(UPATCH_ACTIVE), + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {{ {}, {} }} }}", + ioctl_fd, + stringify!(UPATCH_DEACTIVE), + target_elf.display(), patch_file.display(), ); + let target_cstr = CString::new(target_elf.as_os_str().as_bytes())?; let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + let request = ffi::UpatchIoctlRequest { + target_elf: target_cstr.as_ptr(), + patch_file: patch_cstr.as_ptr(), + }; unsafe { - ffi::ioctl_active_patch(fd, patch_cstr.as_ptr())?; + ffi::ioctl_deactive_patch(ioctl_fd, &request)?; } Ok(()) } -pub fn deactive_patch

(fd: RawFd, patch_file: P) -> std::io::Result<()> +pub fn remove_patch(ioctl_dev: RawFd, target_elf: P, patch_file: Q) -> std::io::Result<()> where P: AsRef, + Q: AsRef, { + let ioctl_fd = ioctl_dev.as_raw_fd(); + let target_elf = target_elf.as_ref(); let patch_file = patch_file.as_ref(); debug!( - "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", - fd, - stringify!(UPATCH_DEACTIVE), + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {{ {}, {} }} }}", + ioctl_fd, + stringify!(UPATCH_REMOVE), + target_elf.display(), patch_file.display(), ); + let target_cstr = CString::new(target_elf.as_os_str().as_bytes())?; let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + let request = ffi::UpatchIoctlRequest { + target_elf: target_cstr.as_ptr(), + patch_file: patch_cstr.as_ptr(), + }; unsafe { - ffi::ioctl_deactive_patch(fd, patch_cstr.as_ptr())?; + ffi::ioctl_remove_patch(ioctl_fd, &request)?; } Ok(()) -- Gitee