diff --git a/upatch-manage/Makefile b/upatch-manage/Makefile index 66ad4280e8d747c7ea3215f53e19463fed97e367..b3c3cca3820bbe0c9639af46746d6c8f73b822db 100644 --- a/upatch-manage/Makefile +++ b/upatch-manage/Makefile @@ -22,7 +22,7 @@ obj-m += ${MODULE_NAME}.o ${MODULE_NAME}-objs := main.o ioctl_dev.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 -${MODULE_NAME}-objs += patch_manage.o kernel_compat.o util.o +${MODULE_NAME}-objs += patch_manage.o kernel_compat.o ifeq ($(ARCH), arm64) ${MODULE_NAME}-objs += arch/$(ARCH)/insn.o diff --git a/upatch-manage/arch/arm/patch_load.c b/upatch-manage/arch/arm/patch_load.c deleted file mode 100644 index afe09591586883e121e1a4d90f36daef6925a78b..0000000000000000000000000000000000000000 --- a/upatch-manage/arch/arm/patch_load.c +++ /dev/null @@ -1,470 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * setup jmp table and do relocation in arm - * 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. - */ - -#ifdef __arm__ - -#include -#include - -#include "../../util.h" -#include "../../patch_load.h" -#include "../patch_load.h" - -#ifndef R_ARM_TLS_DESC -#define R_ARM_TLS_DESC 13 -#endif - -#ifndef R_ARM_GLOB_DAT -#define R_ARM_GLOB_DAT 21 -#endif - -#ifndef R_ARM_JUMP_SLOT -#define R_ARM_JUMP_SLOT 22 -#endif - -#ifndef R_ARM_GOTOFF32 -#define R_ARM_GOTOFF32 24 -#endif -#ifndef R_ARM_GOTPC -#define R_ARM_GOTPC 25 -#endif -#ifndef R_ARM_GOT32 -#define R_ARM_GOT32 26 -#endif -#ifndef R_ARM_TLS_GOTDESC -#define R_ARM_TLS_GOTDESC 90 -#endif -#ifndef R_ARM_GOT_ABS -#define R_ARM_GOT_ABS 95 -#endif -#ifndef R_ARM_GOT_PREL -#define R_ARM_GOT_PREL 96 -#endif -#ifndef R_ARM_GOT_BREL12 -#define R_ARM_GOT_BREL12 97 -#endif -#ifndef R_ARM_GOTOFF12 -#define R_ARM_GOTOFF12 98 -#endif -#ifndef R_ARM_GOTRELAX -#define R_ARM_GOTRELAX 99 -#endif -#ifndef R_ARM_TLS_GD32 -#define R_ARM_TLS_GD32 104 -#endif -#ifndef R_ARM_TLS_LDM32 -#define R_ARM_TLS_LDM32 105 -#endif -#ifndef R_ARM_TLS_IE32 -#define R_ARM_TLS_IE32 107 -#endif -#ifndef R_ARM_TLS_IE12GP -#define R_ARM_TLS_IE12GP 111 -#endif -#ifndef R_ARM_THM_GOT_BREL12 -#define R_ARM_THM_GOT_BREL12 131 -#endif - -/* arm32 PC = crr_addr+8 - * r12 is temp data storage register, no need to restore - * 0: ldr r12, [pc] - * 4: mov pc, r12 //bx r12 - * 8: -*/ -#define ARM_JUMP_TABLE_JMP_1 0xE59FC000 -#define ARM_JUMP_TABLE_JMP_2 0xE1A0F00C - -/* For IFUNC(indirect function), the symbol value is point to the resolve function - * We should call resolve func and get the real func address in x0 - * For armv7 and sparc64, we need to pass hwcap as first arg to resolve function - * 0x00 push {r0, r1, r2, lr} save arg for IFUNC, like memcpy - * 0x04 ldr r12, [pc, #0x14] load resolve func addr - 0x08 ldr r0, [pc, #0x14] load hwcap - * 0xoc mov lr, pc save return addr 0x14 - * 0x10 mov pc, r12 jmp to resolve func - * 0x14 mov r12, r0 get the real addr of IFUNC - * 0x18 pop {r0, r1, r2, lr} restore arg for IFUNC - * 0x1c mov pc, r12 jmp to real IFUNC - * 0x20 addr[0] - * 0x24 hwcap - */ - -#define ARM_CALL_IFUNC_1 0xE92D4007 -#define ARM_CALL_IFUNC_2 0xE59FC014 -#define ARM_CALL_IFUNC_3 0xE59F0014 -#define ARM_CALL_IFUNC_4 0xE1A0E00F -#define ARM_CALL_IFUNC_5 0xE1A0F00C -#define ARM_CALL_IFUNC_6 0xE1A0C000 -#define ARM_CALL_IFUNC_7 0xE8BD4007 -#define ARM_CALL_IFUNC_8 0xE1A0F00C - -enum arm_reloc_op { - RELOC_OP_NONE, - RELOC_OP_ABS, - RELOC_OP_PREL, -}; - -unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc) -{ - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; - unsigned int index = table->cur; - int entry_num = is_ifunc ? IFUNC_JMP_ENTRY_NUM : NORMAL_JMP_ENTRY_NUM; - if (table->cur + entry_num > table->max) { - log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", - table->cur, table->max, entry_num); - return 0; - } - - if (is_ifunc) { - jmp[index] = ARM_CALL_IFUNC_1; - jmp[index + 1] = ARM_CALL_IFUNC_2; - jmp[index + 2] = ARM_CALL_IFUNC_3; - jmp[index + 3] = ARM_CALL_IFUNC_4; - jmp[index + 4] = ARM_CALL_IFUNC_5; - jmp[index + 5] = ARM_CALL_IFUNC_6; - jmp[index + 6] = ARM_CALL_IFUNC_7; - jmp[index + 7] = ARM_CALL_IFUNC_8; - jmp[index + 8] = jmp_addr; - jmp[index + 9] = ELF_HWCAP; - } else { - jmp[index] = ARM_JUMP_TABLE_JMP_1; - jmp[index + 1] = ARM_JUMP_TABLE_JMP_2; - jmp[index + 2] = jmp_addr; - } - table->cur += entry_num; - - return info->layout.base + table->off + index * JMP_ENTRY_SIZE; -} - -unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr) -{ - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; - unsigned int index = table->cur; - unsigned long entry_addr = info->layout.base + table->off + index * JMP_ENTRY_SIZE; - int entry_num = NORMAL_JMP_ENTRY_NUM; - if (table->cur + entry_num > table->max) { - log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", - table->cur, table->max, entry_num); - return 0; - } - - jmp[index] = jmp_addr; - jmp[index + 1] = tls_addr; - table->cur += entry_num; - - log_debug("\tsetup got table 0x%lx -> 0x%lx, tls_addr=0x%lx\n", entry_addr, jmp_addr, tls_addr); - - return entry_addr; -} - -unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr) -{ - unsigned long jmp_addr; - unsigned long tls_addr = 0xffffffff; - unsigned long elf_addr = 0; - - if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { - log_err("copy address failed\n"); - goto out; - } - - if (r_type == R_ARM_TLS_DESC && - copy_from_user((void *)&tls_addr, addr + sizeof(unsigned long), sizeof(unsigned long))) { - log_err("copy address failed\n"); - goto out; - } - - if (r_type == R_ARM_TLS_DESC) { - elf_addr = setup_got_table(info, jmp_addr, tls_addr); - } else { - elf_addr = setup_jmp_table(info, jmp_addr, 0); - } - - log_debug("jump: 0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, jmp_addr, tls_addr); - -out: - return elf_addr; -} - -unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr) -{ - unsigned long jmp_addr; - unsigned long tls_addr = 0xffffffff; - unsigned long elf_addr = 0; - - if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { - log_err("copy address failed\n"); - goto out; - } - - if (r_type == R_ARM_TLS_DESC && - copy_from_user((void *)&tls_addr, addr + sizeof(unsigned long), sizeof(unsigned long))) { - log_err("copy address failed\n"); - goto out; - } - - elf_addr = setup_got_table(info, jmp_addr, tls_addr); - -out: - return elf_addr; -} - -static u32 calc_reloc(enum arm_reloc_op op, u32 *place, u32 S) -{ - s32 sval = 0; - switch (op) { - case RELOC_OP_ABS: - // S + A - sval = S; - break; - case RELOC_OP_PREL: - // S + A - P - sval = S - (u32)place; - break; - default: - log_err("\tunknown relocation operation %d\n", op); - break; - } - - log_debug("\tS + A = 0x%x, P = 0x%x, X = 0x%x\n", S, (u32)place, sval); - return sval; -} - -int apply_relocate_add(struct upatch_info *info, unsigned int relsec) -{ - Elf_Shdr *shdrs = info->shdrs; - const char *strtab = info->strtab; - unsigned int symindex = info->index.sym; - unsigned int i; - Elf_Sym *sym; - char const *sym_name; - u32 *reloc_place; - u32 *ureloc_place; - u32 sym_addr; - u32 got; - u32 tmp; - s32 result; - Elf_Rel *rel = (void *)shdrs[relsec].sh_addr; - u32 got_vaddr = 0; - struct jmp_table *table; - unsigned int reloc_sec = shdrs[relsec].sh_info; - - // sh_addr = kdest, is the section start in hot patch kalloc memory - // sh_addralign = dest, is the section start in VMA pole - u32 sec_kaddr = shdrs[reloc_sec].sh_addr; - u32 sec_vaddr = shdrs[reloc_sec].sh_addralign; - - log_debug("sec_kaddr = 0x%x sec_vaddr = 0x%x\n", sec_kaddr, sec_vaddr); - - for (i = 0; i < shdrs[relsec].sh_size / sizeof(*rel); i++) { - /* relocP corresponds to P in the kernel space */ - reloc_place = (void *)sec_kaddr + rel[i].r_offset; - /* urelocP corresponds to P in user spcace */ - ureloc_place = (void *)sec_vaddr + rel[i].r_offset; - /* sym is the ELF symbol we're referring to */ - sym = (Elf_Sym *)shdrs[symindex].sh_addr + ELF_R_SYM(rel[i].r_info); - sym_name = strtab + sym->st_name; - - sym_addr = sym->st_value; - log_debug("'%s'\t type %d r_offset=0x%x, st_value=0x%x\n", - sym_name, (int)ELF_R_TYPE(rel[i].r_info), rel[i].r_offset, sym->st_value); - log_debug("\t(S + A) = 0x%x \tP(kernel) = 0x%x \tP(user) = 0x%x\n", - sym_addr, (u32)reloc_place, (u32)ureloc_place); - log_debug("\t(before) *reloc_place = 0x%x\n", *reloc_place); - - table = &info->layout.table; - got_vaddr = info->layout.base + table->off; - - switch (ELF_R_TYPE(rel[i].r_info)) { - case R_ARM_NONE: - break; - case R_ARM_ABS32: - case R_ARM_TARGET1: // (S + A) | T - result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); - *reloc_place += result; - break; - - case R_ARM_PC24: - case R_ARM_CALL: - case R_ARM_JUMP24: - if (sym_addr & 3) { - log_err("unsupported interworking call (ARM -> Thumb)\n"); - return -ENOEXEC; - } - result = __mem_to_opcode_arm(*reloc_place); - result = (result & 0x00ffffff) << 2; - if (result & 0x02000000) - result -= 0x04000000; - - // L = S + A - P (A = 0) - result += sym_addr - (u32)ureloc_place; - /* - * Route through a PLT entry if 'offset' exceeds the - * supported range. Note that 'offset + loc + 8' - * contains the absolute jump target, i.e., - * @sym + addend, corrected for the +8 PC bias. - */ - if (IS_ENABLED(CONFIG_ARM_MODULE_PLTS) && - (result <= (s32)0xfe000000 || result >= (s32)0x02000000)) { - result = setup_jmp_table(info, result + (u32)ureloc_place + 8, false) - - (u32)ureloc_place - 8; - if (!result) { - goto overflow; - } - log_warn("setup jmp table for PLT in arm! result = 0x%x\n", result); - } - - // check if plt addr still outside of 32MB range - if (result <= (s32)0xfe000000 || result >= (s32)0x02000000) { - log_err("setup jmp table outside of 32MB range for result = 0x%x\n", result); - goto overflow; - } - - result >>= 2; - result &= 0x00ffffff; - - *reloc_place &= __opcode_to_mem_arm(0xff000000); - *reloc_place |= __opcode_to_mem_arm(result); - break; - - case R_ARM_V4BX: - /* Preserve Rm and the condition code. Alter - * other bits to re-code instruction as - * MOV PC,Rm. - */ - *reloc_place &= __opcode_to_mem_arm(0xf000000f); - *reloc_place |= __opcode_to_mem_arm(0x01a0f000); - break; - - case R_ARM_PREL31: /* sign extend */ - result = (*(s32 *)reloc_place << 1) >> 1; - result += calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); - if (result >= 0x40000000 || result < -0x40000000) { - goto overflow; - } - *reloc_place &= 0x80000000; - *reloc_place |= (result & 0x7fffffff); - break; - - case R_ARM_REL32: // ((S + A) | T) - P, T is 0 because we don't have thumb mode - result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); - *reloc_place += result; - break; - - case R_ARM_MOVW_ABS_NC: // (S + A) | T - case R_ARM_MOVT_ABS: // S + A - case R_ARM_MOVW_PREL_NC: // ((S + A) | T) - P - case R_ARM_MOVT_PREL: // (S + A) - P - result = tmp = __mem_to_opcode_arm(*reloc_place); - result = ((result & 0xf0000) >> 4) | (result & 0xfff); - result = (result ^ 0x8000) - 0x8000; - - result += sym_addr; // S - if (ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVT_PREL || - ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVW_PREL_NC) - result -= (u32)ureloc_place; // - P - if (ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVT_ABS || - ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVT_PREL) - result >>= 16; - - tmp &= 0xfff0f000; - tmp |= ((result & 0xf000) << 4) | (result & 0x0fff); - - *reloc_place = __opcode_to_mem_arm(tmp); - break; - - // The relocation above is implement based on Linux kernel arch/arm/kernel/module.c - // The relocation below is implement based on LLVM - case R_ARM_GLOB_DAT: - case R_ARM_JUMP_SLOT: // (S + A) | T - result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); - *reloc_place += result; - break; - - case R_ARM_GOTPC: // R_ARM_BASE_PREL B(S) + A - P - // B(S) is the start address of .got section, which is got_vaddr - log_debug("\t(GOT) got_vaddr = 0x%x\n", got_vaddr); - result = calc_reloc(RELOC_OP_PREL, ureloc_place, got_vaddr); - *reloc_place += result; - break; - - case R_ARM_GOT32: // R_ARM_GOT_BREL GOT(S) + A - GOT_ORG - // GOT_ORG is the start address of got table, which is got_vaddr - log_debug("\t(GOT) got_vaddr = 0x%x\n", got_vaddr); - got = get_or_setup_got_entry(info, sym); - if (got == 0) { - goto overflow; - } - result = calc_reloc(RELOC_OP_PREL, (u32 *)got_vaddr, got); - *reloc_place += result; - break; - - case R_ARM_GOT_PREL: // GOT(S) + A -P - log_debug("\t(GOT) got_vaddr = 0x%x\n", got_vaddr); - got = get_or_setup_got_entry(info, sym); - if (got == 0) { - goto overflow; - } - result = calc_reloc(RELOC_OP_PREL, ureloc_place, got); - *reloc_place += result; - break; - - default: - log_debug("\tunsupported REL relocation: %u\n", ELF_R_TYPE(rel[i].r_info)); - return -ENOEXEC; - } - log_debug("\t(after) *reloc_place = 0x%x, result = 0x%x\n", *reloc_place, result); - } - return 0; - -overflow: - log_err("\toverflow in relocation type %d val %x reloc 0x%x\n", - (int)ELF_R_TYPE(rel[i].r_info), sym_addr, result); - return -ENOEXEC; -} - -bool is_got_rela_type(int type) -{ - switch (type) { - case R_ARM_GOTOFF32: - case R_ARM_GOT32: - case R_ARM_TLS_GOTDESC: - case R_ARM_GOT_ABS: - case R_ARM_GOT_PREL: - case R_ARM_GOT_BREL12: - case R_ARM_GOTOFF12: - case R_ARM_GOTRELAX: - case R_ARM_TLS_GD32: - case R_ARM_TLS_LDM32: - case R_ARM_TLS_IE32: - case R_ARM_TLS_IE12GP: - case R_ARM_THM_GOT_BREL12: - return true; - break; - default: - return false; - break; - } - return false; -} - -#endif /* __arm__ */ diff --git a/upatch-manage/arch/arm64/patch_load.c b/upatch-manage/arch/arm64/patch_load.c index a13dedd7f6f2e472888349edb91d0aac5b94c487..f11558ba3d0091f46a4f412d966f21fc4aa30a1f 100644 --- a/upatch-manage/arch/arm64/patch_load.c +++ b/upatch-manage/arch/arm64/patch_load.c @@ -127,10 +127,10 @@ enum aarch64_reloc_op { RELOC_OP_PAGE, }; -unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc) +unsigned long setup_jmp_table(struct patch_context *ctx, unsigned long jmp_addr, bool is_ifunc) { - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; + struct jmp_table *table = &ctx->layout.table; + unsigned long *jmp = ctx->layout.kbase + table->off; unsigned int index = table->cur; int entry_num = is_ifunc ? IFUNC_JMP_ENTRY_NUM : NORMAL_JMP_ENTRY_NUM; if (table->cur + entry_num > table->max) { @@ -151,14 +151,14 @@ unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, } table->cur += entry_num; - return info->layout.base + table->off + index * JMP_ENTRY_SIZE; + return ctx->layout.base + table->off + index * JMP_ENTRY_SIZE; } -static unsigned long setup_jmp_table_with_plt(struct upatch_info *info, +static unsigned long setup_jmp_table_with_plt(struct patch_context *ctx, unsigned long jmp_addr, unsigned long plt_addr) { - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; + struct jmp_table *table = &ctx->layout.table; + unsigned long *jmp = ctx->layout.kbase + table->off; unsigned int index = table->cur; int entry_num = PLT_JMP_ENTRY_NUM; if (table->cur + entry_num > table->max) { @@ -173,15 +173,15 @@ static unsigned long setup_jmp_table_with_plt(struct upatch_info *info, jmp[index + 3] = plt_addr; table->cur += entry_num; - return info->layout.base + table->off + index * JMP_ENTRY_SIZE; + return ctx->layout.base + table->off + index * JMP_ENTRY_SIZE; } -unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr) +unsigned long setup_got_table(struct patch_context *ctx, unsigned long jmp_addr, unsigned long tls_addr) { - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; + struct jmp_table *table = &ctx->layout.table; + unsigned long *jmp = ctx->layout.kbase + table->off; unsigned int index = table->cur; - unsigned long entry_addr = info->layout.base + table->off + index * JMP_ENTRY_SIZE; + unsigned long entry_addr = ctx->layout.base + table->off + index * JMP_ENTRY_SIZE; int entry_num = NORMAL_JMP_ENTRY_NUM; if (table->cur + entry_num > table->max) { log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", @@ -199,7 +199,7 @@ unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, return entry_addr; } -unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +unsigned long insert_plt_table(struct patch_context *ctx, unsigned long r_type, void __user *addr) { unsigned long jmp_addr; unsigned long tls_addr = 0xffffffff; @@ -217,9 +217,9 @@ unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, v } if (r_type == R_AARCH64_TLSDESC) - elf_addr = setup_got_table(info, jmp_addr, tls_addr); + elf_addr = setup_got_table(ctx, jmp_addr, tls_addr); else - elf_addr = setup_jmp_table_with_plt(info, jmp_addr, (unsigned long)(uintptr_t)addr); + elf_addr = setup_jmp_table_with_plt(ctx, jmp_addr, (unsigned long)(uintptr_t)addr); log_debug("jump: 0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, jmp_addr, tls_addr); @@ -228,24 +228,24 @@ out: return elf_addr; } -static unsigned long search_insert_plt_table(struct upatch_info *info, +static unsigned long search_insert_plt_table(struct patch_context *ctx, unsigned long jmp_addr, unsigned long plt_addr) { - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; + struct jmp_table *table = &ctx->layout.table; + unsigned long *jmp = ctx->layout.kbase + table->off; unsigned int i = 0; for (i = 0; i < table->max; ++i) { if (jmp[i] != jmp_addr) { continue; } - return info->layout.base + table->off + i * JMP_ENTRY_SIZE; + return ctx->layout.base + table->off + i * JMP_ENTRY_SIZE; } - return setup_jmp_table_with_plt(info, jmp_addr, plt_addr); + return setup_jmp_table_with_plt(ctx, jmp_addr, plt_addr); } -unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +unsigned long insert_got_table(struct patch_context *ctx, unsigned long r_type, void __user *addr) { unsigned long jmp_addr; unsigned long tls_addr = 0xffffffff; @@ -262,7 +262,7 @@ unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, v goto out; } - elf_addr = setup_got_table(info, jmp_addr, tls_addr); + elf_addr = setup_got_table(ctx, jmp_addr, tls_addr); out: return elf_addr; @@ -319,11 +319,11 @@ static inline u32 insert_insn_imm(enum aarch64_insn_imm_type imm_type, void *pla return new_insn; } -int apply_relocate_add(struct upatch_info *info, unsigned int relsec) +int apply_relocate_add(struct patch_context *ctx, unsigned int relsec) { - Elf_Shdr *shdrs = info->shdrs; - const char *strtab = info->strtab; - unsigned int symindex = info->index.sym; + Elf_Shdr *shdrs = ctx->shdrs; + Elf_Sym *symtab = (void *)ctx->symtab_shdr->sh_addr; + const char *strtab = (void *)ctx->strtab_shdr->sh_addr; unsigned int i; Elf_Sym *sym; char const *sym_name; @@ -332,7 +332,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) u64 sym_addr; u64 got; s64 result; - u64 got_start = info->layout.base + info->layout.table.off; + u64 got_start = ctx->layout.base + ctx->layout.table.off; Elf_Rela *rel = (void *)shdrs[relsec].sh_addr; unsigned int reloc_sec = shdrs[relsec].sh_info; @@ -351,7 +351,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) ureloc_place = (void *)sec_vaddr + rel[i].r_offset; /* sym is the ELF symbol we're referring to */ - sym = (Elf_Sym *)shdrs[symindex].sh_addr + ELF_R_SYM(rel[i].r_info); + sym = &symtab[ELF_R_SYM(rel[i].r_info)]; sym_name = strtab + sym->st_name; /* src corresponds to (S + A) */ @@ -484,7 +484,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) if (result < -(s64)BIT(27) || result >= (s64)BIT(27)) { log_warn("\tR_AARCH64_CALL26 overflow: result = 0x%llx, uloc = 0x%lx, val = 0x%llx\n", result, (unsigned long)(uintptr_t)ureloc_place, sym_addr); - sym_addr = search_insert_plt_table(info, sym_addr, (u64)&sym_addr); + sym_addr = search_insert_plt_table(ctx, sym_addr, (u64)&sym_addr); log_warn("\tR_AARCH64_CALL26 overflow: plt.addr = 0x%llx\n", sym_addr); if (!sym_addr) { goto overflow; @@ -496,7 +496,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) *(__le32 *)reloc_place = cpu_to_le32(result); break; case R_AARCH64_ADR_GOT_PAGE: - got = get_or_setup_got_entry(info, sym); + got = get_or_setup_got_entry(ctx, sym); if (got == 0) { goto overflow; } @@ -509,7 +509,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) *(__le32 *)reloc_place = cpu_to_le32(result); break; case R_AARCH64_LD64_GOT_LO12_NC: - got = get_or_setup_got_entry(info, sym); + got = get_or_setup_got_entry(ctx, sym); if (got == 0) { goto overflow; } @@ -521,7 +521,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) *(__le32 *)reloc_place = cpu_to_le32(result); break; case R_AARCH64_LD64_GOTPAGE_LO15: - got = get_or_setup_got_entry(info, sym); + got = get_or_setup_got_entry(ctx, sym); if (got == 0) { goto overflow; } @@ -536,7 +536,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) *(__le32 *)reloc_place = cpu_to_le32(result); break; case R_AARCH64_TLSLE_ADD_TPREL_HI12: - result = ALIGN(TCB_SIZE, info->running_elf.meta->tls_align) + sym_addr; + result = ALIGN(TCB_SIZE, ctx->target->tls_align) + sym_addr; if (result < 0 || result >= BIT(24)) { goto overflow; } @@ -545,7 +545,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) *(__le32 *)reloc_place = cpu_to_le32(result); break; case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: - result = ALIGN(TCB_SIZE, info->running_elf.meta->tls_align) + sym_addr; + result = ALIGN(TCB_SIZE, ctx->target->tls_align) + sym_addr; result = extract_insn_imm(result, 12, 0); result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); *(__le32 *)reloc_place = cpu_to_le32(result); diff --git a/upatch-manage/arch/patch_load.h b/upatch-manage/arch/patch_load.h index 1441392beb7c59d68156d1d88584df99b9e23d4f..912511e596dc03fe1ac2bf94f2c733ecd93804de 100644 --- a/upatch-manage/arch/patch_load.h +++ b/upatch-manage/arch/patch_load.h @@ -11,6 +11,7 @@ #define _ARCH_PATCH_LOAD_H #include "../patch_load.h" +#include "../patch_entity.h" #include "../target_entity.h" #include "../util.h" @@ -55,15 +56,15 @@ struct upatch_jmp_table_entry { #define JMP_TABLE_ENTRY_MAX_SIZE (JMP_ENTRY_SIZE * IFUNC_JMP_ENTRY_NUM) -unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr); +unsigned long insert_plt_table(struct patch_context *ctx, unsigned long r_type, void __user *addr); // write jmp addr in jmp table in text section, return the real jmp entry address in VMA -unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc); +unsigned long setup_jmp_table(struct patch_context *ctx, unsigned long jmp_addr, bool is_ifunc); -unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr); +unsigned long insert_got_table(struct patch_context *ctx, unsigned long r_type, void __user *addr); -unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr); +unsigned long setup_got_table(struct patch_context *ctx, unsigned long jmp_addr, unsigned long tls_addr); -int apply_relocate_add(struct upatch_info *info, unsigned int relsec); +int apply_relocate_add(struct patch_context *ctx, unsigned int relsec); #endif /* _ARCH_PATCH_LOAD_H */ diff --git a/upatch-manage/arch/x86/patch_load.c b/upatch-manage/arch/x86/patch_load.c index b6767a7b1941f790dd962bc459c2d0a4a49111b7..9d3823a22b76acc99c3644170332c8ff2e233102 100644 --- a/upatch-manage/arch/x86/patch_load.c +++ b/upatch-manage/arch/x86/patch_load.c @@ -70,10 +70,10 @@ #define X86_64_CALL_IFUNC_1 0x00000715FF525657 #define X86_64_CALL_IFUNC_2 0x9090E0FF5F5E5A00 -unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc) +unsigned long setup_jmp_table(struct patch_context *ctx, unsigned long jmp_addr, bool is_ifunc) { - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; + struct jmp_table *table = &ctx->layout.table; + unsigned long *jmp = ctx->layout.kbase + table->off; unsigned int index = table->cur; int entry_num = is_ifunc ? IFUNC_JMP_ENTRY_NUM : NORMAL_JMP_ENTRY_NUM; if (table->cur + entry_num > table->max) { @@ -92,7 +92,7 @@ unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, } table->cur += entry_num; - return info->layout.base + table->off + index * JMP_ENTRY_SIZE; + return ctx->layout.base + table->off + index * JMP_ENTRY_SIZE; } /* @@ -101,12 +101,12 @@ unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, * GOT only need record address and resolve it by [got_addr]. * To simplify design, use same table for both jmp table and GOT. */ -unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr) +unsigned long setup_got_table(struct patch_context *ctx, unsigned long jmp_addr, unsigned long tls_addr) { - struct jmp_table *table = &info->layout.table; - unsigned long *jmp = info->layout.kbase + table->off; + struct jmp_table *table = &ctx->layout.table; + unsigned long *jmp = ctx->layout.kbase + table->off; unsigned int index = table->cur; - unsigned long entry_addr = info->layout.base + table->off + index * JMP_ENTRY_SIZE; + unsigned long entry_addr = ctx->layout.base + table->off + index * JMP_ENTRY_SIZE; if (table->cur + NORMAL_JMP_ENTRY_NUM > table->max) { log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", table->cur, table->max, NORMAL_JMP_ENTRY_NUM); @@ -123,7 +123,7 @@ unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, return entry_addr; } -unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +unsigned long insert_plt_table(struct patch_context *ctx, unsigned long r_type, void __user *addr) { unsigned long jmp_addr; unsigned long elf_addr = 0; @@ -133,7 +133,7 @@ unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, v goto out; } - elf_addr = setup_jmp_table(info, jmp_addr, false); + elf_addr = setup_jmp_table(ctx, jmp_addr, false); log_debug("PLT: 0x%lx -> 0x%lx\n", elf_addr, jmp_addr); @@ -142,7 +142,7 @@ out: } -unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +unsigned long insert_got_table(struct patch_context *ctx, unsigned long r_type, void __user *addr) { unsigned long jmp_addr; unsigned long tls_addr = 0xffffffff; @@ -163,17 +163,17 @@ unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, v goto out; } - elf_addr = setup_got_table(info, jmp_addr, tls_addr); + elf_addr = setup_got_table(ctx, jmp_addr, tls_addr); out: return elf_addr; } -int apply_relocate_add(struct upatch_info *info, unsigned int relsec) +int apply_relocate_add(struct patch_context *ctx, unsigned int relsec) { - Elf_Shdr *shdrs = info->shdrs; - const char *strtab = info->strtab; - unsigned int symindex = info->index.sym; + Elf_Shdr *shdrs = ctx->shdrs; + Elf_Sym *symtab = (void *)ctx->symtab_shdr->sh_addr; + const char *strtab = (void *)ctx->strtab_shdr->sh_addr; unsigned int i; Elf_Rela *rel = (void *)shdrs[relsec].sh_addr; Elf_Sym *sym; @@ -206,7 +206,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) /* This is the symbol it is referring to. Note that all undefined symbols have been resolved. */ - sym = (Elf_Sym *)shdrs[symindex].sh_addr + ELF_R_SYM(rel[i].r_info); + sym = &symtab[ELF_R_SYM(rel[i].r_info)]; name = strtab + sym->st_name; /* src corresponds to (S + A) */ @@ -250,7 +250,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) case R_X86_64_GOTPCRELX: case R_X86_64_REX_GOTPCRELX: /* get GOT address */ - got = get_or_setup_got_entry(info, sym); + got = get_or_setup_got_entry(ctx, sym); if (got == 0) { goto overflow; } @@ -274,7 +274,7 @@ int apply_relocate_add(struct upatch_info *info, unsigned int relsec) memcpy(reloc_place, &sym_addr, sizeof(u64)); break; case R_X86_64_TPOFF32: - tls_size = ALIGN(info->running_elf.meta->tls_size, info->running_elf.meta->tls_align); + tls_size = ALIGN(ctx->target->tls_size, ctx->target->tls_align); // %fs + val - tls_size if (sym_addr >= tls_size) { goto overflow; diff --git a/upatch-manage/ioctl_dev.c b/upatch-manage/ioctl_dev.c index aa154a24b74507e57220a0cba66ec6258c9d62f0..23881a15bab341242773c3b5576fb004f02b9fbf 100644 --- a/upatch-manage/ioctl_dev.c +++ b/upatch-manage/ioctl_dev.c @@ -126,7 +126,7 @@ static int ioctl_get_patch_status(void __user * user_addr) } ret = upatch_status(patch); - log_debug("patch '%s' is %s\n", patch, patch_status(ret)); + log_debug("patch '%s' is %s\n", patch, patch_status_str(ret)); vfree(patch); return ret; diff --git a/upatch-manage/main.c b/upatch-manage/main.c index 6935647d62a2263529af11eb00696fc5b48b0130..7263a7565221d83e0be8c0453ea7d32101792535 100644 --- a/upatch-manage/main.c +++ b/upatch-manage/main.c @@ -65,8 +65,8 @@ static int __init upatch_module_init(void) */ static void __exit upatch_module_exit(void) { - verify_patch_empty_on_exit(); - verify_target_empty_on_exit(); + report_patch_table_populated(); + report_target_table_populated(); kernel_compat_exit(); ioctl_device_exit(); diff --git a/upatch-manage/patch_entity.c b/upatch-manage/patch_entity.c index b58be38f12489891b61c7c4b728e5245c085d895..4d5832bf1d756eff793ef6ad63a08d3def8980d7 100644 --- a/upatch-manage/patch_entity.c +++ b/upatch-manage/patch_entity.c @@ -26,195 +26,257 @@ #include "patch_load.h" #include "util.h" -static const char *UPATCH_STRINGS_NAME = ".upatch.strings"; -static const char *UPATCH_FUNCS_NAME = ".upatch.funcs"; -static const char *RELA_TEXT_NAME = ".rela.text."; -static const char *REL_TEXT_NAME = ".rel.text."; +#define PATCH_TABLE_HASH_BITS 4 -DEFINE_HASHTABLE(g_patches, PATCHES_HASH_BITS); +static const char *SYMTAB_NAME = ".symtab"; +static const char *TEXT_RELA_NAME = ".rela.text."; + +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"; + +DEFINE_HASHTABLE(g_patch_table, PATCH_TABLE_HASH_BITS); DEFINE_MUTEX(g_patch_table_lock); -static void free_patch_meta(struct upatch_metadata *meta) +static inline void count_und_symbol(struct patch_metadata *meta, Elf_Sym *symtab, size_t count) { - VFREE_CLEAR(meta->patch_buff); - meta->func_count = 0; - meta->patch_size = 0; + size_t i; + + for (i = 1; i < count; i++) { + if (symtab[i].st_shndx == SHN_UNDEF) { + meta->und_sym_num++; + } + } } -int search_got_rela_entry(struct file *patch, struct upatch_metadata *meta, Elf_Shdr *shdr) +static inline void count_got_reloc(struct patch_metadata *meta, Elf_Rela *relas, size_t count) { - int type; - int ret = 0; - unsigned int i; - Elf_Rela *rel = vmalloc_read(patch, shdr->sh_offset, shdr->sh_size); - if (IS_ERR(rel)) { - ret = PTR_ERR(rel); - log_err("failed to read section '%s'\n", RELA_TEXT_NAME); - return ret; - } + size_t i; - for (i = 0; i < shdr->sh_size / sizeof(*rel); i++) { - type = ELF_R_TYPE(rel[i].r_info); - if (is_got_rela_type(type)) { - meta->got_rela_cnt++; + for (i = 0; i < count; i++) { + if (is_got_rela_type(ELF_R_TYPE(relas[i].r_info))) { + meta->got_reloc_num++; } } +} - VFREE_CLEAR(rel); - return ret; +static void clear_patch_metadata(struct patch_metadata *meta) +{ + VFREE_CLEAR(meta->file_buff); + meta->file_size = 0; + + meta->symtab_index = 0; + meta->strtab_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 init_patch_meta(struct upatch_metadata *meta, struct file *patch) +static int resolve_patch_metadata(struct patch_metadata *meta, struct file *patch) { int ret = 0; - Elf_Ehdr *ehdr; // elf header - Elf_Shdr *shdrs; // section headers - char *shstrtab; // .shstrtab + loff_t file_size; + Elf_Ehdr *ehdr; + Elf_Shdr *shdrs; + Elf_Half shdr_num; + Elf_Half i; Elf_Shdr *shdr; - char *sh_name; - void *sh_data; - int sh_idx; - void *hdr; - size_t i; - Elf_Sym *symtab; - unsigned int symnum; - - meta->patch_size = i_size_read(file_inode(patch)); - hdr = vmalloc_read(patch, 0, meta->patch_size); - if (IS_ERR(hdr)) { - ret = PTR_ERR(hdr); - log_err("read patch file for entity failed. ret=%d\n", ret); - return ret; - } - meta->patch_buff = hdr; - // elf header - ehdr = hdr; + const char *shstrtab; + size_t shstrtab_size; - if (!is_elf_valid(ehdr, meta->patch_size, true)) { - ret = -EINVAL; - log_err("invalid patch format\n"); + const char *sec_name; + void *sec_data; + + struct upatch_relocation *relas = NULL; + size_t rela_num = 0; + + meta->file_size = i_size_read(file_inode(patch)); + meta->file_buff = vmalloc_read(patch, 0, meta->file_size); + if (IS_ERR(meta->file_buff)) { + ret = PTR_ERR(meta->file_buff); + log_err("failed to read file, len=0x%llx\n", meta->file_size); + goto fail; + } + + ehdr = meta->file_buff; + if (!is_valid_patch(ehdr, meta->file_size)) { + ret = -ENOEXEC; + log_err("invalid file format\n"); goto fail; } + meta->shstrtab_index = ehdr->e_shstrndx; + + file_size = meta->file_size; + shdrs = meta->file_buff + ehdr->e_shoff; + shdr_num = ehdr->e_shnum; + shstrtab = meta->file_buff + shdrs[meta->shstrtab_index].sh_offset; + shstrtab_size = shdrs[meta->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) { + ret = -ENOEXEC; + log_err("invalid section name, index=%u\n", i); + goto fail; + } - // section headers - shdrs = hdr + ehdr->e_shoff; - - // section header string table - shdr = &shdrs[ehdr->e_shstrndx]; - shstrtab = hdr + shdr->sh_offset; - - // resolve section headers - for (sh_idx = 1; sh_idx < ehdr->e_shnum; sh_idx++) { - shdr = &shdrs[sh_idx]; - sh_name = shstrtab + shdr->sh_name; - sh_data = hdr + shdr->sh_offset; - - if (shdr->sh_type == SHT_SYMTAB) { - meta->index.sym = sh_idx; - meta->index.str = shdrs[sh_idx].sh_link; - symtab = sh_data; - symnum = shdr->sh_size / sizeof(Elf_Sym); - } else if (strcmp(sh_name, UPATCH_STRINGS_NAME) == 0) { - meta->strings = sh_data; - } else if (strcmp(sh_name, UPATCH_FUNCS_NAME) == 0) { - shdr->sh_entsize = sizeof(struct upatch_function); - meta->func_count = shdr->sh_size / shdr->sh_entsize; - meta->funcs = sh_data; - } else if ((shdr->sh_type == SHT_RELA && !strncmp(sh_name, RELA_TEXT_NAME, strlen(RELA_TEXT_NAME))) || - (shdr->sh_type == SHT_REL && !strncmp(sh_name, REL_TEXT_NAME, strlen(REL_TEXT_NAME)))) { - if (search_got_rela_entry(patch, meta, shdr)) { - goto fail; - } + sec_name = shstrtab + shdr->sh_name; // no need check + if (shdr->sh_type != SHT_NOBITS && shdr->sh_offset + shdr->sh_size > file_size) { + log_err("section '%s' offset overflow, index=%u\n", sec_name, i); + ret = -ENOEXEC; + goto fail; + } + + sec_data = meta->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); + } 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; + } + break; + + case SHT_SYMTAB: + if (shdr->sh_entsize != sizeof(Elf_Sym)) { + log_err("invalid section '%s' entity size\n", sec_name); + ret = -ENOEXEC; + goto fail; + } + if (shdr->sh_link > shdr_num) { + ret = -ENOEXEC; + log_err("invalid section '%s' string table index\n", sec_name); + goto fail; + } + 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); + } + break; + + case SHT_RELA: + if (shdr->sh_entsize != sizeof(Elf_Rela)) { + log_err("invalid section '%s' entity size\n", sec_name); + ret = -ENOEXEC; + goto fail; + } + if (strcmp(sec_name, UPATCH_FUNCS_RELA_NAME) == 0) { + meta->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); + } + break; + + default: + break; } } - if (!meta->index.sym) { - log_err("patch has no symbols (stripped?)\n"); - ret = -EINVAL; + if (!meta->symtab_index || !meta->strtab_index) { + log_err("patch contains no symbol\n"); + ret = -ENOEXEC; goto fail; } - - if (meta->func_count == 0) { - log_err("patch has no .upatch.funcs\n"); - ret = -EINVAL; + if (!meta->func_index || !meta->funcs || !meta->func_num) { + log_err("patch contains no function\n"); + ret = -ENOEXEC; + goto fail; + } + if (!meta->rela_index || !meta->string_index || + !relas || !meta->strings || + !rela_num || !meta->string_len || + meta->func_num != rela_num) { + log_err("invalid patch format\n"); + ret = -ENOEXEC; goto fail; } - // search UND symbol number - for (i = 1; i < symnum; i++) { - if (symtab[i].st_shndx == SHN_UNDEF) - meta->und_count++; + for (i = 0; i < rela_num; i++) { + meta->funcs[i].name_off = relas[i].name.r_addend; } return 0; fail: - free_patch_meta(meta); + clear_patch_metadata(meta); return ret; } -static int init_grab_patch(struct patch_entity *patch, const char *file_path) +static int resolve_patch_entity(struct patch_entity *patch, const char *file_path) { int ret = 0; - struct file *file = NULL; + + struct file *file; INIT_HLIST_NODE(&patch->node); INIT_LIST_HEAD(&patch->patch_node); INIT_LIST_HEAD(&patch->actived_node); - // open patch file file = filp_open(file_path, O_RDONLY, 0); if (IS_ERR(file)) { - log_err("failed to open file '%s'\n", file_path); + log_err("failed to open '%s'\n", file_path); return PTR_ERR(file); } patch->inode = igrab(file_inode(file)); if (!patch->inode) { - pr_err("%s: failed to grab inode of '%s'\n", __func__, file_path); + log_err("file '%s' inode is invalid\n", file_path); ret = -ENOENT; - goto fail; + goto out; } patch->path = kstrdup(file_path, GFP_KERNEL); if (!patch->path) { ret = -ENOMEM; iput(patch->inode); - goto fail; + log_err("faild to alloc filename\n"); + goto out; } - // resolve patch metadata - ret = init_patch_meta(&patch->meta, file); - if (ret != 0) { + ret = resolve_patch_metadata(&patch->meta, file); + if (ret) { iput(patch->inode); KFREE_CLEAR(patch->path); - log_err("failed to resolve patch meta, ret=%d\n", ret); - goto fail; + goto out; } patch->status = UPATCH_STATUS_DEACTIVED; -fail: +out: filp_close(file, NULL); return ret; } -struct patch_entity *get_patch_entity_from_inode(struct inode *inode) +static struct patch_entity *get_patch_entity_by_inode(struct inode *inode) { struct patch_entity *patch; struct patch_entity *found = NULL; mutex_lock(&g_patch_table_lock); - hash_for_each_possible(g_patches, patch, node, inode->i_ino) { + hash_for_each_possible(g_patch_table, patch, node, inode->i_ino) { if (patch->inode == inode) { found = patch; break; } } - mutex_unlock(&g_patch_table_lock); return found; } @@ -222,33 +284,21 @@ struct patch_entity *get_patch_entity_from_inode(struct inode *inode) /* public interface */ struct patch_entity *get_patch_entity(const char *path) { - struct inode *inode; struct patch_entity *patch; + struct inode *inode; - inode = path_inode(path); - if (IS_ERR(inode)) { - return NULL; - } - - inode = igrab(inode); + inode = get_path_inode(path); if (!inode) { - pr_err("%s: Failed to grab inode of %s\n", __func__, path); + log_err("failed to get '%s' inode\n", path); return NULL; } - patch = get_patch_entity_from_inode(inode); + patch = get_patch_entity_by_inode(inode); iput(inode); return patch; } -static inline void insert_patch(struct patch_entity *patch) -{ - mutex_lock(&g_patch_table_lock); - hash_add(g_patches, &patch->node, patch->inode->i_ino); - mutex_unlock(&g_patch_table_lock); -} - struct patch_entity *new_patch_entity(const char *file_path) { int ret = 0; @@ -260,14 +310,16 @@ struct patch_entity *new_patch_entity(const char *file_path) return ERR_PTR(-ENOMEM); } - ret = init_grab_patch(patch, file_path); + ret = resolve_patch_entity(patch, file_path); if (ret) { - log_err("failed to init patch '%s', ret=%d\n", file_path, ret); kfree(patch); return ERR_PTR(ret); } - insert_patch(patch); + mutex_lock(&g_patch_table_lock); + hash_add(g_patch_table, &patch->node, patch->inode->i_ino); + mutex_unlock(&g_patch_table_lock); + return patch; } @@ -281,7 +333,7 @@ void free_patch_entity(struct patch_entity *patch) iput(patch->inode); KFREE_CLEAR(patch->path); - free_patch_meta(&patch->meta); + clear_patch_metadata(&patch->meta); list_del(&patch->actived_node); list_del(&patch->patch_node); @@ -290,15 +342,15 @@ void free_patch_entity(struct patch_entity *patch) kfree(patch); } -void __exit verify_patch_empty_on_exit(void) +void __exit report_patch_table_populated(void) { struct patch_entity *patch; int bkt; mutex_lock(&g_patch_table_lock); - hash_for_each(g_patches, bkt, patch, node) { - log_err("found patch '%s' (%s) on exit", - patch->path ? patch->path : "(null)", patch_status(patch->status)); + hash_for_each(g_patch_table, bkt, patch, node) { + log_warn("found patch '%s' (%s) on exit", + patch->path ? patch->path : "(null)", patch_status_str(patch->status)); } mutex_unlock(&g_patch_table_lock); } diff --git a/upatch-manage/patch_entity.h b/upatch-manage/patch_entity.h index 2a08cb40e0d187760a88e48f8477a6e5874844c6..02242d36818ad4e908a1199d2c6bfa09d9db96c2 100644 --- a/upatch-manage/patch_entity.h +++ b/upatch-manage/patch_entity.h @@ -22,16 +22,12 @@ #define _UPATCH_MANAGE_PATCH_ENTITY_H #include -#include - #include #include struct inode; struct target_entity; -#define PATCHES_HASH_BITS 4 - /* Patch status */ enum upatch_status { UPATCH_STATUS_NOT_APPLIED = 1, @@ -39,12 +35,12 @@ enum upatch_status { UPATCH_STATUS_ACTIVED }; -static inline const char *patch_status(int status) +static inline const char *patch_status_str(int status) { static const char *STATUS_STR[] = {"NOT_APPLIED", "DEACTIVED", "ACTIVED"}; if (status < UPATCH_STATUS_NOT_APPLIED || status > UPATCH_STATUS_ACTIVED) { - return "INVALID_STATUS"; + return "UNKNOWN"; } return STATUS_STR[status - 1]; @@ -81,35 +77,41 @@ struct upatch_function { #endif /* Patch metadata */ -struct upatch_metadata { - struct upatch_function *funcs; // this should vmalloc, if not, relocation of new_addr may fail - struct upatch_relocation *relas; // .rela.upatch.funcs - char *strings; // .upatch.strings +struct patch_metadata { + void *file_buff; + loff_t file_size; + + Elf_Half shstrtab_index; // section '.shstrtab' index + Elf_Half symtab_index; // section '.symtab' index + Elf_Half strtab_index; // section '.strtab' index - struct { - unsigned int sym, str; - } index; + 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_count; - size_t und_count; // UND symbol count - size_t got_rela_cnt; // relocation type need add got table cnt + struct upatch_function *funcs; // patch function table + const char *strings; // patch string table - void *patch_buff; - size_t patch_size; + 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 { - char *path; // patch file path - struct inode *inode; // patch file inode + const char *path; // patch file path + struct inode *inode; // patch file inode + + struct patch_metadata meta; // patch elf metadata + struct target_entity *target; // patch target - struct upatch_metadata meta; // patch metadata - struct target_entity *target; // target file inode - enum upatch_status status; // patch status + enum upatch_status status; // patch status - struct hlist_node node; // all patches store in hash table - struct list_head patch_node; // patch node in target entity - struct list_head actived_node; // actived patch in target entity + struct hlist_node node; // all patches store in hash table + struct list_head patch_node; // patch node in target entity + struct list_head actived_node; // actived patch in target entity }; struct patch_entity *get_patch_entity(const char *patch_file); @@ -118,6 +120,6 @@ struct patch_entity *new_patch_entity(const char *patch_file); void free_patch_entity(struct patch_entity *patch); -void __exit verify_patch_empty_on_exit(void); +void __exit report_patch_table_populated(void); #endif // _UPATCH_MANAGE_PATCH_ENTITY_H diff --git a/upatch-manage/patch_load.c b/upatch-manage/patch_load.c index 799353f841866e1296b7e69bb54df6e80c3e1fad..297bdebb07c5e19c1d175e80b677a6fa8eabd381 100644 --- a/upatch-manage/patch_load.c +++ b/upatch-manage/patch_load.c @@ -38,110 +38,67 @@ #define ARCH_SHF_SMALL 0 #endif -static int setup_load_info(struct upatch_info *info, struct patch_entity *patch) +static void layout_sections(struct patch_context *ctx) { - info->len = patch->meta.patch_size; - info->ehdr = vmalloc(info->len); - if (!info->ehdr) { - log_err("failed to vmalloc upatch info, len=%ld\n", info->len); - return -ENOMEM; - } - - // read whole patch into kernel temporarily - memcpy(info->ehdr, patch->meta.patch_buff, info->len); - - info->shdrs = (void *)info->ehdr + info->ehdr->e_shoff; - info->shshdrtab = (void *)info->ehdr + info->shdrs[info->ehdr->e_shstrndx].sh_offset; - info->index.sym = patch->meta.index.sym; - info->index.str = patch->meta.index.str; - info->und_cnt = patch->meta.und_count; - info->got_rela_cnt = patch->meta.got_rela_cnt; - info->strtab = (char *)info->ehdr + info->shdrs[info->index.str].sh_offset; - log_debug("symbol '%d', type UND, got_rela=%d\n", info->und_cnt, info->got_rela_cnt); - - return 0; -} - -static int rewrite_section_headers(struct upatch_info *info) -{ - unsigned int i; - - /* This should always be true, but let's be sure. */ - info->shdrs[0].sh_addr = 0; - info->shdrs[0].sh_addralign = 0; - - for (i = 1; i < info->ehdr->e_shnum; i++) { - Elf_Shdr *shdr = &info->shdrs[i]; - if (shdr->sh_type != SHT_NOBITS && info->len < shdr->sh_offset + shdr->sh_size) { - log_err("section was truncated, index=%u, len=%lu\n", i, info->len); - return -ENOEXEC; - } - - /* Mark all sections sh_addr with their address in the - temporary image. */ - shdr->sh_addr = (size_t)info->ehdr + shdr->sh_offset; - } - - return 0; -} - -static long align_size_add_sh_size(unsigned int *size, Elf_Shdr *sechdr) -{ - long ret; - - ret = ALIGN(*size, sechdr->sh_addralign ?: 1); - *size = ret + sechdr->sh_size; - return ret; -} - -static void layout_jmptable(struct upatch_layout *layout, struct upatch_info *info) -{ - unsigned long start_off; - layout->table.cur = 0; - layout->table.max = info->und_cnt * JMP_TABLE_ENTRY_MAX_SIZE + info->got_rela_cnt * JMP_TABLE_GOT_ENTRY_SIZE; - start_off = ALIGN(layout->size, sizeof(unsigned long)); - - layout->table.off = start_off; - layout->size = start_off + layout->table.max; - log_debug("\t\t%-20s \t0x%lx - 0x%x max size %d\n", "jmptable", start_off, layout->size, layout->table.max); -} - -static void layout_sections(struct upatch_layout *layout, struct upatch_info *info) -{ - static unsigned long const masks[][2] = { + static const unsigned long SECTION_FLAGS[][2] = { /* NOTE: all executable code must be the first section * in this array; otherwise modify the text_size * finder in the two loops below */ - { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL }, - { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL }, + { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL }, + { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL }, { SHF_RO_AFTER_INIT | SHF_ALLOC, ARCH_SHF_SMALL }, - { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL }, - { ARCH_SHF_SMALL | SHF_ALLOC, 0 } + { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL }, + { ARCH_SHF_SMALL | SHF_ALLOC, 0 } }; - unsigned int m; - unsigned int i; - for (i = 0; i < info->ehdr->e_shnum; i++) { - info->shdrs[i].sh_entsize = ~0UL; + size_t und_sym_num = ctx->patch->und_sym_num; + size_t got_reloc_num = ctx->patch->got_reloc_num; + + struct patch_layout *layout = &ctx->layout; + Elf_Half m; + + Elf_Shdr *shdrs = ctx->shdrs; + Elf_Half shdr_num = ctx->ehdr->e_shnum; + Elf_Half i; + Elf_Shdr *shdr; + + const char *shstrtab = ctx->buff + ctx->shstrtab_shdr->sh_offset; + const char *sec_name; + + for (i = 1; i < shdr_num; i++) { + shdr = &shdrs[i]; + /* set all section address to their address of patch image */ + shdr->sh_addr = (Elf_Addr)ctx->buff + shdr->sh_offset; + /* set all section entity size to invalid value */ + shdr->sh_entsize = ~0UL; } - log_debug("upatch section allocation order:\n"); - for (m = 0; m < ARRAY_SIZE(masks); ++m) { - for (i = 0; i < info->ehdr->e_shnum; ++i) { - Elf_Shdr *s = &info->shdrs[i]; - const char *sname = info->shshdrtab + s->sh_name; + log_debug("patch layout:\n"); + for (m = 0; m < ARRAY_SIZE(SECTION_FLAGS); ++m) { + for (i = 1; i < shdr_num; i++) { + shdr = &shdrs[i]; - if ((s->sh_flags & masks[m][0]) != masks[m][0] || (s->sh_flags & masks[m][1]) || s->sh_entsize != ~0UL) { + if ((shdr->sh_flags & SECTION_FLAGS[m][0]) != SECTION_FLAGS[m][0] || + (shdr->sh_flags & SECTION_FLAGS[m][1]) || + shdr->sh_entsize != ~0UL) { continue; } - s->sh_entsize = align_size_add_sh_size(&layout->size, s); - log_debug("type[%02d] %-20s \tend at 0x%x\n", m, sname, layout->size); + + sec_name = shstrtab + shdr->sh_name; + shdr->sh_entsize = ALIGN(layout->size, shdr->sh_addralign ?: 1); + layout->size = shdr->sh_entsize + shdr->sh_size; + + log_debug("type[%02d] %-20s \tend at 0x%x\n", m, sec_name, layout->size); } switch (m) { case 0: /* executable */ - layout_jmptable(layout, info); - layout->size = PAGE_ALIGN(layout->size); + layout->table.off = ALIGN(layout->size, sizeof(unsigned long)); + layout->table.cur = 0; + layout->table.max = und_sym_num * JMP_TABLE_ENTRY_MAX_SIZE + got_reloc_num * JMP_TABLE_GOT_ENTRY_SIZE; + layout->size = PAGE_ALIGN(layout->table.off + layout->table.max); layout->text_end = layout->size; + log_debug("\t\t%-20s \t0x%lx - 0x%x max size %d\n", "jmptable", + layout->table.off, layout->size, layout->table.max); break; case 1: /* RO: text and ro-data */ layout->size = PAGE_ALIGN(layout->size); @@ -160,13 +117,69 @@ static void layout_sections(struct upatch_layout *layout, struct upatch_info *in } } +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.file_buff; + loff_t file_size = patch->meta.file_size; + + ctx->target = &target->meta; + ctx->patch = &patch->meta; + ctx->load_bias = vma_start - target->meta.vma_offset; + log_debug("process %d: vma_start=0x%lx, load_bias=0x%lx\n", task_pid_nr(current), vma_start, ctx->load_bias); + + // alloc & copy whole patch into kernel temporarily + ctx->buff = vmalloc(file_size); + if (!ctx->buff) { + log_err("failed to vmalloc upatch info, len=0x%llx\n", file_size); + return -ENOMEM; + } + memcpy(ctx->buff, file_buff, file_size); + + 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->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]; + + // alloc patch sections + layout_sections(ctx); + + // read process plt & got + ctx->plt = vmalloc_copy_user((void __user *)ctx->load_bias, ctx->target->plt_addr, ctx->target->plt_size); + if (IS_ERR(ctx->plt)) { + ctx->plt = NULL; + } + + ctx->got = vmalloc_copy_user((void __user *)ctx->load_bias, ctx->target->got_addr, ctx->target->got_size); + if (IS_ERR(ctx->got)) { + ctx->got = NULL; + } + + return 0; +} + +static void clear_load_info(struct patch_context *ctx) +{ + VFREE_CLEAR(ctx->buff); + VFREE_CLEAR(ctx->plt); + VFREE_CLEAR(ctx->got); + KFREE_CLEAR(ctx->layout.kbase); +} + // we don't use the VMA after the patched code because we may use the VMA that heap will reuse // we search before the patched code to find a empty VMA -static unsigned long get_upatch_hole(unsigned long start, unsigned long size) +static unsigned long find_vma_hole(unsigned long start, unsigned long size) { + struct mm_struct *mm = current->mm; + unsigned long search = start; struct vm_area_struct *vma; - struct mm_struct *mm = current->mm; mmap_read_lock(mm); vma = find_vma_intersection(mm, search, search + size); @@ -176,82 +189,62 @@ static unsigned long get_upatch_hole(unsigned long start, unsigned long size) } mmap_read_unlock(mm); - log_debug("find hole at 0x%lx - 0x%lx\n", search, search + size); + log_debug("patch address: 0x%lx - 0x%lx\n", search, search + size); return search; } -/* alloc memory in userspace */ -static unsigned long find_vma_hole_and_vmmap(unsigned long vma_start, unsigned long size) +static int do_alloc_patch_memory(struct patch_context *ctx) { - unsigned long mem_addr; - unsigned long addr = get_upatch_hole(vma_start, size); - if (!addr) { - log_err("cannot find hole start in %ld in pid %d\n", vma_start, task_pid_nr(current)); - return 0; + /* find patch location & alloc patch from load bias */ + unsigned long base_addr = ctx->load_bias; + unsigned long layout_size = ctx->layout.size; // must be page-aligned + + unsigned long vma_hole; + unsigned long user_addr; + unsigned long kern_addr; + + vma_hole = find_vma_hole(base_addr, layout_size); + if (!vma_hole) { + log_err("failed to find vma hole, addr=0x%lx, len=0x%lx\n", base_addr, layout_size); + return -EFAULT; } - mem_addr = vm_mmap(NULL, addr, size, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, 0); - if (IS_ERR((void *)mem_addr)) { - log_err("mmap process memory failed with %ld\n", PTR_ERR((void *)mem_addr)); - return 0; + user_addr = vm_mmap(NULL, vma_hole, layout_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (IS_ERR_VALUE(user_addr)) { + log_err("failed to patch memory in userspace\n"); + return -ENOMEM; } /* If the address applied for by the hot patch is too far away from the VMA address of the target file * and the relocation type (such as the jump instruction) with distance restriction is used during relocation * we can put the jump instruction into the jmp table to avoid the jump distance restriction */ - if (vma_start - mem_addr >= PATCH_LOAD_RANGE_LIMIT) { - log_warn("patch hole 0x%lx is 0x%lx far from the code start 0x%lx\n", - mem_addr, vma_start - mem_addr, vma_start); + if (base_addr - user_addr >= PATCH_LOAD_RANGE_LIMIT) { + log_warn("patch address range exceeds limit, addr=0x%lx, limit=0x%lx\n", user_addr, PATCH_LOAD_RANGE_LIMIT); } - return mem_addr; -} - -static int alloc_mem_for_upatch(struct upatch_info *info, struct upatch_layout *layout) -{ - /* find moudle location from code start place */ - unsigned long vma_start = info->running_elf.vma_start_addr; - - layout->base = find_vma_hole_and_vmmap(vma_start, layout->size); - if (!layout->base) - return -ENOMEM; - - layout->kbase = kzalloc(layout->size, GFP_KERNEL); - if (!layout->kbase) { - vm_munmap(layout->base, layout->size); - layout->base = 0; + kern_addr = (unsigned long)kzalloc(layout_size, GFP_KERNEL); + if (!kern_addr) { + log_err("failed to alloc patch memory\n"); + vm_munmap(user_addr, layout_size); + ctx->layout.base = 0; return -ENOMEM; } - log_debug("kbase 0x%lx base 0x%lx\n", (unsigned long)(uintptr_t)layout->kbase, layout->base); - + ctx->layout.kbase = (void *)kern_addr; + ctx->layout.base = user_addr; + log_debug("kbase: 0x%lx, base: 0x%lx\n", kern_addr, user_addr); return 0; } -static void clear_load_info(struct upatch_info *info) -{ - VFREE_CLEAR(info->ehdr); - KFREE_CLEAR(info->layout.kbase); -} - -void parse_vma_flags(char *buf, unsigned long flags) -{ - buf[0] = (flags & VM_READ) ? 'r' : '-'; - buf[1] = (flags & VM_WRITE) ? 'w' : '-'; - buf[2] = (flags & VM_EXEC) ? 'x' : '-'; - buf[3] = '\0'; -} - static void print_vma_info(void) { struct mm_struct *mm = current->mm; - struct vm_area_struct *vma; struct upatch_vma_iter vma_iter; - char prot[4]; - char *path; - char buf[256]; + struct vm_area_struct *vma; + char prot[5]; + char buff[256]; + const char *file_path; if (!mm) { log_debug("cannot find memory descriptor\n"); @@ -260,65 +253,71 @@ static void print_vma_info(void) log_debug("virtual memory address:\n"); mmap_read_lock(mm); - upatch_vma_iter_init(&vma_iter, mm); - while ((vma = upatch_vma_next(&vma_iter))) { - if (vma->vm_file) { - path = d_path(&vma->vm_file->f_path, buf, sizeof(buf)); - } else { - path = ""; + { + upatch_vma_iter_init(&vma_iter, mm); + while ((vma = upatch_vma_next(&vma_iter))) { + /* get file path or empty string */ + file_path = vma->vm_file ? d_path(&vma->vm_file->f_path, buff, sizeof(buff)) : ""; + /* parse protection flags */ + prot[0] = (vma->vm_flags & VM_READ) ? 'r' : '-'; + prot[1] = (vma->vm_flags & VM_WRITE) ? 'w' : '-'; + prot[2] = (vma->vm_flags & VM_EXEC) ? 'x' : '-'; + prot[3] = (vma->vm_flags & VM_SHARED) ? 's' : 'p'; + prot[4] = '\0'; + /* print vma info */ + log_debug("0x%lx-0x%lx\t%s\t%s\n", vma->vm_start, vma->vm_end, prot, file_path); } - parse_vma_flags(prot, vma->vm_flags); - log_debug("0x%lx-0x%lx %s %s\n", vma->vm_start, vma->vm_end, prot, path); } mmap_read_unlock(mm); } -static int alloc_layout(struct upatch_layout *layout, struct upatch_info *info) +static int alloc_patch_memory(struct patch_context *ctx) { - char *name; - int i; - int ret; + struct patch_layout *layout = &ctx->layout; + + Elf_Shdr *shdrs = ctx->shdrs; + Elf_Half shdr_num = ctx->ehdr->e_shnum; + Elf_Half i; + Elf_Shdr *shdr; + + const char *shstrtab = ctx->buff + ctx->shstrtab_shdr->sh_offset; + const char *sec_name; + + unsigned long dest; + unsigned long kdest; /* Do the allocs. */ - ret = alloc_mem_for_upatch(info, layout); + int ret = do_alloc_patch_memory(ctx); if (ret) { - log_err("failed to alloc upatch process memory, ret=%d\n", ret); return ret; } /* Transfer each section which specifies SHF_ALLOC */ log_debug("final section addresses:\n"); - for (i = 0; i < info->ehdr->e_shnum; i++) { - unsigned long dest; - uintptr_t kdest; - Elf_Shdr *shdr = &info->shdrs[i]; + for (i = 1; i < shdr_num; i++) { + shdr = &shdrs[i]; if (!(shdr->sh_flags & SHF_ALLOC)) { continue; } - name = info->shshdrtab + shdr->sh_name; - // sh_entsize is set to this section layout start offset in 'layout_sections' + sec_name = shstrtab + shdr->sh_name; // no need check dest = layout->base + shdr->sh_entsize; - kdest = (uintptr_t)layout->kbase + shdr->sh_entsize; + kdest = (unsigned long)layout->kbase + shdr->sh_entsize; - if (shdr->sh_type != SHT_NOBITS) + if (shdr->sh_type != SHT_NOBITS) { memcpy((void *)kdest, (void *)shdr->sh_addr, shdr->sh_size); - - if (!strcmp(name, ".upatch.funcs")) { - info->upatch_func_sec = shdr; } - - /* Update sh_addr to point to copy in image. */ + /* update sh_addr to point to copy in image. */ shdr->sh_addr = (unsigned long)kdest; /* overuse this attr to record user address */ shdr->sh_addralign = dest; log_debug("sec[%02d] %-20s \t0x%lx -> 0x%lx size 0x%zx\n", - i, name, (unsigned long)kdest, dest, (size_t)shdr->sh_size); + i, sec_name, (unsigned long)kdest, dest, (size_t)shdr->sh_size); } - log_debug("patch vma layout:\n"); + log_debug("patch layout:\n"); log_debug("\ttext \t\t\t0x%lx size 0x%x\n", layout->base, layout->text_end); log_debug("\trodata \t\t\t0x%lx size 0x%x\n", layout->base + layout->text_end, layout->ro_end - layout->text_end); @@ -328,212 +327,193 @@ static int alloc_layout(struct upatch_layout *layout, struct upatch_info *info) layout->base + layout->ro_after_init_end, layout->size - layout->ro_after_init_end); print_vma_info(); - - if (!info->upatch_func_sec) { - log_err("cannot find '.upatch_func' section\n"); - return -1; - } return 0; } -static int layout_and_allocate(struct upatch_info *info) +static int simplify_symbols(struct patch_context *ctx) { - int err; + Elf_Shdr *shdrs = ctx->shdrs; + Elf_Half shdr_num = ctx->ehdr->e_shnum; + const char *shstrtab = (void *)ctx->shstrtab_shdr->sh_addr; - layout_sections(&info->layout, info); + Elf_Sym *symtab = (void *)ctx->symtab_shdr->sh_addr; + size_t sym_num = ctx->symtab_shdr->sh_size / sizeof(Elf_Sym); - err = alloc_layout(&info->layout, info); - if (err) { - return err; - } + const char *strtab = (void *)ctx->strtab_shdr->sh_addr; - return 0; -} + size_t i; + Elf_Sym *sym; -static int simplify_symbols(const struct upatch_info *info) -{ - Elf_Shdr *symsec = &info->shdrs[info->index.sym]; - Elf_Sym *sym = (void *)symsec->sh_addr; - unsigned long secbase; - unsigned int i; - int ret = 0; - unsigned long elf_addr; - - for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) { - const char *name; - - if (ELF_ST_TYPE(sym[i].st_info) == STT_SECTION && sym[i].st_shndx < info->ehdr->e_shnum) { - name = info->shshdrtab + info->shdrs[sym[i].st_shndx].sh_name; + const char *sym_name; + + for (i = 1; i < sym_num; i++) { + sym = &symtab[i]; + + if (ELF_ST_TYPE(sym->st_info) == STT_SECTION && sym->st_shndx < shdr_num) { + sym_name = shstrtab + shdrs[sym->st_shndx].sh_name; } else { - name = info->strtab + sym[i].st_name; + sym_name = strtab + sym->st_name; } - switch (sym[i].st_shndx) { + switch (sym->st_shndx) { case SHN_COMMON: - log_err("common symbol '%s' is not supported\n", name); - ret = -ENOEXEC; - break; + log_err("common symbol '%s' is not supported\n", sym_name); + return -ENOEXEC; case SHN_ABS: break; case SHN_UNDEF: - elf_addr = resolve_symbol(&info->running_elf, name, sym[i]); - if (!elf_addr) { + sym->st_value = resolve_symbol(ctx, sym_name, sym); + if (!sym->st_value) { return -ENOEXEC; } - sym[i].st_value = elf_addr; - log_debug("resolved external symbol '%s' at 0x%lx\n", - name, (unsigned long)sym[i].st_value); + log_debug("resolved external symbol '%s' at 0x%lx\n", sym_name, (unsigned long)sym->st_value); break; case SHN_LIVEPATCH: - sym[i].st_value += info->running_elf.vma_start_addr; - log_debug("resolved livepatch symbol '%s' at 0x%lx\n", - name, (unsigned long)sym[i].st_value); + sym->st_value += ctx->load_bias; + log_debug("resolved livepatch symbol '%s' at 0x%lx\n", sym_name, (unsigned long)sym->st_value); break; default: - /* use real address to calculate secbase */ - secbase = info->shdrs[sym[i].st_shndx].sh_addralign; - sym[i].st_value += secbase; - log_debug("resolved normal symbol '%s' -> 0x%lx\n", - name, (unsigned long)sym[i].st_value); + /* use userspace address */ + sym->st_value += shdrs[sym->st_shndx].sh_addralign; + log_debug("resolved normal symbol '%s' -> 0x%lx\n", sym_name, (unsigned long)sym->st_value); break; } } - return ret; + return 0; } -static int apply_relocations(struct upatch_info *info) +static int apply_relocations(struct patch_context *ctx) { - unsigned int i; - int err = 0; + int ret; + + Elf_Shdr *shdrs = ctx->shdrs; + Elf_Half shdr_num = ctx->ehdr->e_shnum; + Elf_Half i; + Elf_Shdr *shdr; + + const char *shstrtab = (void *)ctx->shstrtab_shdr->sh_addr; + const char *sec_name; /* Now do relocations. */ - for (i = 1; i < info->ehdr->e_shnum; i++) { - unsigned int infosec = info->shdrs[i].sh_info; - const char *name = info->shshdrtab + info->shdrs[i].sh_name; + for (i = 1; i < shdr_num; i++) { + shdr = &shdrs[i]; - /* Not a valid relocation section? */ - if (infosec >= info->ehdr->e_shnum) { + if (shdr->sh_type != SHT_REL && shdr->sh_type != SHT_RELA) { continue; } - /* Don't bother with non-allocated sections */ - if (!(info->shdrs[infosec].sh_flags & SHF_ALLOC)) { + /* not a valid relocation section? */ + if (shdr->sh_info >= shdr_num) { continue; } - if (info->shdrs[i].sh_type == SHT_REL || info->shdrs[i].sh_type == SHT_RELA) { - log_debug("do relocations for %s\n", name); - err = apply_relocate_add(info, i); + /* don't bother with non-allocated sections */ + if (!(shdrs[shdr->sh_info].sh_flags & SHF_ALLOC)) { + continue; } - if (err) { - break; + sec_name = shstrtab + shdr->sh_name; + log_debug("do relocations for %s\n", sec_name); + ret = apply_relocate_add(ctx, i); + if (ret) { + return ret; } } - return err; + + return 0; } -static int copy_layout_into_vma(struct upatch_layout *layout) +static int write_patch_to_user(const struct patch_context *ctx) { - log_debug("mov content from 0x%lx to 0x%lx with 0x%x\n", + const struct patch_layout *layout = &ctx->layout; + + log_debug("write patch image, src=0x%lx, dst=0x%lx, len=0x%x\n", (unsigned long)layout->kbase, layout->base, layout->size); if (copy_to_user((void *)layout->base, layout->kbase, layout->size)) { - log_err("copy_to_user from 0x%lx to 0x%lx with 0x%x failed\n", + log_err("failed to write patch image, src=0x%lx, dst=0x%lx, len=0x%x\n", (unsigned long)layout->kbase, layout->base, layout->size); - return -EPERM; + return -EFAULT; } + return 0; } -static int frob_text(const struct upatch_layout *layout) +static int set_memory_privileges(const struct patch_context *ctx) { - unsigned long addr = (unsigned long)layout->base; - size_t text_size = layout->text_end; + const struct patch_layout *layout = &ctx->layout; + + unsigned long addr; + size_t size; int ret; - ret = upatch_mprotect(addr, text_size, PROT_READ | PROT_EXEC); + /* text */ + addr = layout->base; + size = layout->text_end; + ret = upatch_mprotect(addr, size, PROT_READ | PROT_EXEC); if (ret) { - log_err("failed to set text memory previliage to r-x, ret=%d\n", ret); + log_err("failed to set text memory privilege to r-x, ret=%d\n", ret); return ret; } - return 0; -} - -static int frob_rodata(const struct upatch_layout *layout) -{ - unsigned long ro_start = (unsigned long)layout->base + layout->text_end; - size_t ro_size = layout->ro_end - layout->text_end; - int ret; - - unsigned long ro_after_init_start = (unsigned long)layout->base + layout->ro_end; - size_t ro_after_init_size = layout->ro_after_init_end - layout->ro_end; - - ret = upatch_mprotect(ro_start, ro_size, PROT_READ); + /* rodata */ + addr = layout->base + layout->text_end; + size = layout->ro_end - layout->text_end; + ret = upatch_mprotect(addr, size, PROT_READ); if (ret) { - log_err("failed to set rodata memory previliage to r--, ret=%d\n", ret); + log_err("failed to set rodata memory privilege to r--, ret=%d\n", ret); return ret; } - ret = upatch_mprotect(ro_after_init_start, ro_after_init_size, PROT_READ); + /* ro_after_init */ + addr = layout->base + layout->ro_end; + size = layout->ro_after_init_end - layout->ro_end; + ret = upatch_mprotect(addr, size, PROT_READ); if (ret) { - log_err("failed to set ro_after_init memory previliage to r--, ret=%d\n", ret); + log_err("failed to set ro_after_init memory privilege to r--, ret=%d\n", ret); return ret; } return 0; } -static int set_memory_previliage(struct upatch_layout *layout) +static int set_execution_map(const struct patch_context *ctx, + struct process_entity *process, struct patch_entity *patch) { - int ret; + 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)); - ret = frob_text(layout); - if (ret) { - return ret; - } + struct upatch_relocation *relas = (struct upatch_relocation *)ctx->rela_shdr->sh_addr; + const char *strings = (const char *)ctx->string_shdr->sh_addr; - ret = frob_rodata(layout); - if (ret) { - return ret; - } - - return 0; -} + size_t i; + struct upatch_function *func; + const char *func_name; -// create old_pc - new_pc maps -static int create_relocated_pc_maps(struct process_entity *process, struct upatch_info *load_info, - struct patch_entity *patch) -{ - unsigned int num; - struct upatch_function *funcs; - unsigned int i; struct patch_info *info; - struct pc_pair *pp; - - funcs = (struct upatch_function *)load_info->upatch_func_sec->sh_addr; - num = load_info->upatch_func_sec->sh_size / (sizeof (struct upatch_function)); + struct pc_pair *entry; info = kzalloc(sizeof(struct patch_info), GFP_KERNEL); if (!info) { - log_err("malloc patch_info failed!\n"); + log_err("failed to alloc patch info\n"); return -ENOMEM; } hash_init(info->pc_maps); - for (i = 0; i < num; ++i) { - pp = kmalloc(sizeof(*pp), GFP_KERNEL); - if (!pp) { + for (i = 0; i < func_num; ++i) { + func = &funcs[i]; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { free_patch_info(info); return -ENOMEM; } - pp->old_pc = funcs[i].old_addr + - load_info->running_elf.vma_start_addr + - load_info->running_elf.meta->code_virt_offset; - pp->new_pc = funcs[i].new_addr; - hash_add(info->pc_maps, &pp->node, pp->old_pc); - log_debug("function: 0x%08lx -> 0x%08lx\n", pp->old_pc, pp->new_pc); + + 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); } list_add(&info->list, &process->loaded_patches); @@ -545,91 +525,80 @@ static int create_relocated_pc_maps(struct process_entity *process, struct upatc /* The main idea is from insmod */ int upatch_resolve(struct target_entity *target, struct patch_entity *patch, struct process_entity *process, - unsigned long target_code_start) + unsigned long vma_start) { - struct upatch_info info; - int err; - - memset(&info, 0, sizeof(info)); - - info.running_elf.vma_start_addr = target_code_start - target->meta.code_vma_offset; - log_debug("process %d: vma_start=0x%lx, code_start=0x%lx\n", - task_pid_nr(current), info.running_elf.vma_start_addr, target_code_start); - - info.running_elf.meta = &target->meta; - info.running_elf.load_info = &info; - - err = setup_load_info(&info, patch); - if (err) { - goto fail; - } + struct patch_context context = { 0 }; + int ret; - /* update section address */ - err = rewrite_section_headers(&info); - if (err) { + ret = init_load_info(&context, patch, target, vma_start); + if (ret) { goto fail; } - err = layout_and_allocate(&info); - if (err) { + ret = alloc_patch_memory(&context); + if (ret) { goto fail; } /* Fix up syms, so that st_value is a pointer to location. */ - err = simplify_symbols(&info); - if (err) { + ret = simplify_symbols(&context); + if (ret) { goto fail; } /* upatch new address will be updated */ - err = apply_relocations(&info); - if (err) { + ret = apply_relocations(&context); + if (ret) { goto fail; } - err = copy_layout_into_vma(&info.layout); - if (err) { + ret = write_patch_to_user(&context); + if (ret) { goto fail; } - err = set_memory_previliage(&info.layout); - if (err) { + ret = set_memory_privileges(&context); + if (ret) { goto fail; } - err = create_relocated_pc_maps(process, &info, patch); - if (err) { + ret = set_execution_map(&context, process, patch); + if (ret) { goto fail; } log_debug("patch load successfully\n"); - clear_load_info(&info); + clear_load_info(&context); return 0; fail: - if (info.layout.base) { - vm_munmap(info.layout.base, info.layout.size); - info.layout.base = 0; + if (context.layout.base) { + vm_munmap(context.layout.base, context.layout.size); + context.layout.base = 0; } - clear_load_info(&info); - return err; + clear_load_info(&context); + return ret; } -static inline bool is_addr_in_got_table(struct upatch_layout *layout, u64 addr) +static inline bool is_addr_in_got(struct patch_context *ctx, Elf_Addr addr) { - unsigned long table_start = layout->base + layout->table.off; - unsigned long table_end = table_start + layout->table.max; - return addr >= table_start && addr < table_end; + unsigned long got_start = ctx->load_bias + ctx->target->got_addr; + unsigned long got_end = got_start + ctx->target->got_size; + + unsigned long jmp_table_start = ctx->layout.base + ctx->layout.table.off; + unsigned long jmp_table_end = jmp_table_start + ctx->layout.table.max; + + return (addr >= got_start && addr < got_end) || (addr >= jmp_table_start && addr < jmp_table_end); } -unsigned long get_or_setup_got_entry(struct upatch_info *info, Elf_Sym *sym) +unsigned long get_or_setup_got_entry(struct patch_context *ctx, Elf_Sym *sym) { unsigned long got; - if (sym->st_shndx == SHN_UNDEF && is_addr_in_got_table(&info->layout, sym->st_value)) { + if (sym->st_shndx == SHN_UNDEF && is_addr_in_got(ctx, sym->st_value)) { got = sym->st_value; } else { - got = setup_got_table(info, sym->st_value, 0); + got = setup_got_table(ctx, sym->st_value, 0); } return got; diff --git a/upatch-manage/patch_load.h b/upatch-manage/patch_load.h index 2d1faf8d5ad0b5f07ea111bf95d43d65c7e32528..bbe00caae53adf5b5a08c818ed409334dfebb9fd 100644 --- a/upatch-manage/patch_load.h +++ b/upatch-manage/patch_load.h @@ -26,10 +26,11 @@ struct target_entity; struct target_metadata; + struct patch_entity; -struct process_entity; +struct patch_metadata; -struct upatch_info; +struct process_entity; struct jmp_table { unsigned long off; @@ -38,50 +39,47 @@ struct jmp_table { }; /* memory layout for module */ -struct upatch_layout { - void* kbase; // kmalloc patch, will be relocated and copy to base - unsigned long base; // VMA in user space - unsigned int size; // Total size - unsigned int text_end; // The size of the executable code and jmp table - unsigned int ro_end; // Size of RO section of the module (text+rodata) - unsigned int ro_after_init_end; // Size of RO after init section +struct patch_layout { + void* kbase; // patch image in kernelspace + unsigned long base; // patch in userspace + unsigned int size; // patch total size + unsigned int text_end; // patch executable code & jump table size + unsigned int ro_end; // patch read-only section size (text+rodata) + unsigned int ro_after_init_end; // patch read-only after init section size struct jmp_table table; }; -struct running_elf { - struct target_metadata *meta; - - // target vma start addr, the first vma could not be the text in LLVM - unsigned long vma_start_addr; +// when load patch, patch need resolve in different process +struct patch_context { + const struct target_metadata *target; + const struct patch_metadata *patch; + unsigned long load_bias; - struct upatch_info *load_info; -}; + void *buff; // patch image in kernelspace + struct patch_layout layout; -// when load patch, patch need resolve in different process -struct upatch_info { - unsigned long len; Elf_Ehdr *ehdr; Elf_Shdr *shdrs; - Elf_Shdr *upatch_func_sec; - char *shshdrtab, *strtab; - unsigned int und_cnt, got_rela_cnt; - struct { - unsigned int sym, str; - } index; - /* memory layout for patch */ - struct upatch_layout layout; + Elf_Shdr *shstrtab_shdr; + Elf_Shdr *symtab_shdr; + Elf_Shdr *strtab_shdr; + + Elf_Shdr *func_shdr; + Elf_Shdr *rela_shdr; + Elf_Shdr *string_shdr; - struct running_elf running_elf; + void *plt; + uintptr_t *got; }; int upatch_resolve(struct target_entity *target, struct patch_entity *patch, struct process_entity *process, - unsigned long target_code_start); + unsigned long vma_start); bool is_got_rela_type(int type); // All UND symbol have already been set up got table in resolve_symbol.c // Except thoese GLOBAL OBJECT in target found in resolve_from_target_sym -unsigned long get_or_setup_got_entry(struct upatch_info *info, Elf_Sym *sym); +unsigned long get_or_setup_got_entry(struct patch_context *ctx, Elf_Sym *sym); #endif // _UPATCH_IOCTL_PATCH_LOAD_H diff --git a/upatch-manage/patch_manage.c b/upatch-manage/patch_manage.c index 9a66e90459a121dfe4fae6ed1fa838e242cb32b4..47d883b49c59d8ae5626a05e93d384268f0fe448 100644 --- a/upatch-manage/patch_manage.c +++ b/upatch-manage/patch_manage.c @@ -93,7 +93,7 @@ static struct target_entity *get_target_from_pc(unsigned long pc, unsigned long } get_file(target_file); - target = get_target_entity_from_inode(file_inode(target_file)); + target = get_target_entity_by_inode(file_inode(target_file)); fput(target_file); if (!target) { @@ -207,7 +207,7 @@ static int target_register_function(struct target_entity *target, loff_t offset, } // find if this target have func changed in offset - list_for_each_entry(off, &target->off_head, list) { + list_for_each_entry(off, &target->offset_node, list) { if (off->offset == offset) { find = true; break; @@ -236,7 +236,7 @@ static int target_register_function(struct target_entity *target, loff_t offset, return ret; } - list_add(&off->list, &target->off_head); + list_add(&off->list, &target->offset_node); } func_node->func = func; @@ -252,7 +252,7 @@ static void unregister_function_uprobe(struct target_entity *target, loff_t offs struct patched_func_node *tmp = NULL; bool find = false; - list_for_each_entry(off, &target->off_head, list) { + list_for_each_entry(off, &target->offset_node, list) { if (off->offset == offset) { find = true; break; @@ -285,7 +285,7 @@ static void target_unregister_functions(struct target_entity *target, struct pat struct upatch_function *func = NULL; size_t i = 0; loff_t offset = 0; - char *name = NULL; + const char *name = NULL; log_debug("unregister patch '%s' functions:\n", target->path); for (i = 0; i < count; i++) { @@ -293,8 +293,7 @@ static void target_unregister_functions(struct target_entity *target, struct pat offset = func->old_addr; name = patch->meta.strings + func->name_off; - log_debug("- function: offset=0x%08llx, size=0x%08zx, name='%s'\n", - offset, (size_t)func->old_size, name); + log_debug("- function: offset=0x%08llx, size=0x%04llx, name='%s'\n", offset, func->old_size, name); unregister_function_uprobe(target, offset, func); } } @@ -307,18 +306,17 @@ static int do_active_patch(struct target_entity *target, struct patch_entity *pa int ret = 0; size_t i = 0; loff_t offset; - char *name = NULL; + const char *name = NULL; log_debug("register target '%s' functions:\n", target->path); down_write(&target->patch_lock); - for (i = 0; i < patch->meta.func_count; i++) { + for (i = 0; i < patch->meta.func_num; i++) { func = &funcs[i]; offset = func->old_addr; name = patch->meta.strings + func->name_off; - log_debug("+ function: offset=0x%08llx, size=0x%zx, name='%s'\n", - offset, (size_t)func->old_size, name); + log_debug("+ function: offset=0x%08llx, size=0x%04llx, name='%s'\n", offset, func->old_size, name); ret = target_register_function(target, offset, func); if (ret) { log_err("failed to register function '%s', ret=%d\n", name, ret); @@ -364,7 +362,7 @@ static void do_deactive_patch(struct patch_entity *patch) down_write(&target->patch_lock); - target_unregister_functions(target, patch, funcs, patch->meta.func_count); + target_unregister_functions(target, patch, funcs, patch->meta.func_num); target_remove_actived_patch(target, patch); patch->status = UPATCH_STATUS_DEACTIVED; @@ -399,7 +397,7 @@ int upatch_load(const char *patch_file, const char *target_path) patch = new_patch_entity(patch_file); if (IS_ERR(patch)) { - log_err("failed to init patch '%s'\n", patch_file); + log_err("failed to load patch '%s'\n", patch_file); return PTR_ERR(patch); } @@ -408,7 +406,7 @@ int upatch_load(const char *patch_file, const char *target_path) target = new_target_entity(target_path); if (IS_ERR(target)) { free_patch_entity(patch); - log_err("failed to init patch target '%s'\n", target_path); + log_err("failed to load target '%s'\n", target_path); return PTR_ERR(target); } } @@ -513,7 +511,7 @@ void target_unregister_uprobes(struct target_entity *target) struct patched_offset *tmp_off = NULL; log_debug("unregister '%s' (inode: %lu) uprobes:", target->path, target->inode->i_ino); - list_for_each_entry_safe(off, tmp_off, &target->off_head, list) { + list_for_each_entry_safe(off, tmp_off, &target->offset_node, list) { log_debug("unregister offset 0x%llx\n", off->offset); uprobe_unregister(target->inode, off->offset, &patch_consumer); list_del(&off->list); diff --git a/upatch-manage/symbol_resolve.c b/upatch-manage/symbol_resolve.c index bd230e2a1e292b7eaf8cf6a2334993cdf6004cf3..392bbf1c3755e15f85108abbfeb166401cc4da3c 100644 --- a/upatch-manage/symbol_resolve.c +++ b/upatch-manage/symbol_resolve.c @@ -35,10 +35,45 @@ static inline bool is_same_name(const char *name, const char *name2) return strcmp(name, name2) == 0; } -static unsigned long resolve_from_patch(const struct running_elf *relf, - const char *name, Elf_Sym *patch_sym) +static unsigned long search_process_got(struct patch_context *ctx, unsigned long addr) { - const struct target_metadata *elf = relf->meta; + uintptr_t *got = ctx->got; + size_t got_num = ctx->target->got_size / GOT_ENTRY_SIZE; + size_t i; + + if (unlikely(!got || !got_num)) { + return 0; + } + + for (i = 0; i < got_num; i++) { + if (unlikely(!got[i])) { + continue; + } + if (got[i] == addr) { + return ctx->load_bias + ctx->target->got_addr + (i * GOT_ENTRY_SIZE); + } + } + + return 0; +} + +static unsigned long resolve_from_got(struct patch_context *ctx, const char *name, Elf_Sym *sym) +{ + unsigned long sym_addr = ctx->load_bias + sym->st_value; + unsigned long addr; + + addr = search_process_got(ctx, sym_addr); + if (!addr) { + return 0; + } + + log_debug("found symbol '%s' from '.got' at 0x%lx\n", name, addr); + return addr; +} + +static unsigned long resolve_from_patch(struct patch_context *ctx, const char *name, Elf_Sym *patch_sym) +{ + const struct target_metadata *elf = ctx->target; unsigned long elf_addr = 0; if (!elf) { @@ -49,21 +84,20 @@ static unsigned long resolve_from_patch(const struct running_elf *relf, return 0; } - elf_addr = relf->vma_start_addr + patch_sym->st_value; + elf_addr = ctx->load_bias + patch_sym->st_value; log_debug("found symbol '%s' from patch at 0x%lx\n", name, elf_addr); return elf_addr; } /* handle external object, we need get it's address, used for R_X86_64_REX_GOTPCRELX */ -static unsigned long resolve_from_rela_dyn(const struct running_elf *relf, - const char *name, Elf_Sym *patch_sym) +static unsigned long resolve_from_rela_dyn(struct patch_context *ctx, const char *name, Elf_Sym *patch_sym) { - const struct target_metadata *elf = relf->meta; + const struct target_metadata *elf = ctx->target; Elf_Sym *dynsym = elf->dynsym; Elf_Rela *rela_dyn = elf->rela_dyn; unsigned int i; - char *sym_name; + const char *sym_name; void __user *sym_addr; unsigned long elf_addr; @@ -71,7 +105,7 @@ static unsigned long resolve_from_rela_dyn(const struct running_elf *relf, return 0; } - for (i = 0; i < elf->num.rela_dyn; i++) { + for (i = 0; i < elf->rela_dyn_num; i++) { unsigned long sym_idx = ELF_R_SYM(rela_dyn[i].r_info); if (!sym_idx) { continue; @@ -85,8 +119,8 @@ static unsigned long resolve_from_rela_dyn(const struct running_elf *relf, } /* for executable file, r_offset is virtual address of GOT table */ - sym_addr = (void *)(relf->vma_start_addr + rela_dyn[i].r_offset); - elf_addr = insert_got_table(relf->load_info, ELF_R_TYPE(rela_dyn[i].r_info), sym_addr); + sym_addr = (void *)(ctx->load_bias + rela_dyn[i].r_offset); + elf_addr = insert_got_table(ctx, ELF_R_TYPE(rela_dyn[i].r_info), sym_addr); log_debug("found symbol '%s' from '.rela.dyn' at 0x%lx, ret=0x%lx\n", sym_name, (unsigned long)sym_addr, elf_addr); @@ -96,14 +130,13 @@ static unsigned long resolve_from_rela_dyn(const struct running_elf *relf, return 0; } -static unsigned long resolve_from_rela_plt(const struct running_elf *relf, - const char *name, Elf_Sym *patch_sym) +static unsigned long resolve_from_rela_plt(struct patch_context *ctx, const char *name, Elf_Sym *patch_sym) { - const struct target_metadata *elf = relf->meta; + const struct target_metadata *elf = ctx->target; Elf_Sym *dynsym = elf->dynsym; Elf_Rela *rela_plt = elf->rela_plt; unsigned int i; - char *sym_name; + const char *sym_name; void __user *sym_addr; unsigned long elf_addr = 0; @@ -111,7 +144,7 @@ static unsigned long resolve_from_rela_plt(const struct running_elf *relf, return 0; } - for (i = 0; i < elf->num.rela_plt; i++) { + for (i = 0; i < elf->rela_plt_num; i++) { unsigned long sym_idx = ELF_R_SYM(rela_plt[i].r_info); unsigned long sym_type = ELF_ST_TYPE(dynsym[sym_idx].st_info); if (!sym_idx) { @@ -130,8 +163,8 @@ static unsigned long resolve_from_rela_plt(const struct running_elf *relf, } /* for executable file, r_offset is virtual address of PLT table */ - sym_addr = (void *)(relf->vma_start_addr + rela_plt[i].r_offset); - elf_addr = insert_plt_table(relf->load_info, ELF_R_TYPE(rela_plt[i].r_info), sym_addr); + sym_addr = (void *)(ctx->load_bias + rela_plt[i].r_offset); + elf_addr = insert_plt_table(ctx, ELF_R_TYPE(rela_plt[i].r_info), sym_addr); if (!elf_addr) { return 0; } @@ -144,12 +177,12 @@ static unsigned long resolve_from_rela_plt(const struct running_elf *relf, } // get symbol address from .dynsym -static unsigned long resolve_from_dynsym(const struct running_elf *relf, const char *name) +static unsigned long resolve_from_dynsym(struct patch_context *ctx, const char *name) { - const struct target_metadata *elf = relf->meta; + const struct target_metadata *elf = ctx->target; Elf_Sym *dynsym = elf->dynsym; unsigned int i; - char *sym_name; + const char *sym_name; void __user *sym_addr; unsigned long elf_addr = 0; @@ -157,7 +190,7 @@ static unsigned long resolve_from_dynsym(const struct running_elf *relf, const c return 0; } - for (i = 0; i < elf->num.dynsym; i++) { + for (i = 0; i < elf->dynsym_num; i++) { /* only need the st_value that is not 0 */ if (dynsym[i].st_value == 0) { continue; @@ -170,8 +203,8 @@ static unsigned long resolve_from_dynsym(const struct running_elf *relf, const c continue; } - sym_addr = (void *)(relf->vma_start_addr + dynsym[i].st_value); - elf_addr = insert_got_table(relf->load_info, 0, sym_addr); + sym_addr = (void *)(ctx->load_bias + dynsym[i].st_value); + elf_addr = insert_got_table(ctx, 0, sym_addr); log_debug("found symbol '%s' from '.dynsym' at 0x%lx, ret=0x%lx\n", sym_name, (unsigned long)sym_addr, elf_addr); return elf_addr; @@ -499,7 +532,7 @@ out: } // Check if the current VMA is the text segment of shared object and is not the patched target itself -static bool is_vma_other_so_text(const struct running_elf *relf, struct vm_area_struct *vma) +static bool is_vma_other_so_text(struct patch_context *ctx, struct vm_area_struct *vma) { const char *file_path; bool is_so; @@ -517,7 +550,7 @@ static bool is_vma_other_so_text(const struct running_elf *relf, struct vm_area_ return false; } - if (strcmp(file_path, relf->meta->file_name) == 0) { + if (strcmp(file_path, ctx->target->file_name) == 0) { return false; } @@ -548,7 +581,7 @@ static unsigned long search_base_addr(struct vm_area_struct *vma) // Search all loaded so in current VMA, read the symbol table of so and find symbol offset // Then combine with the so loaded base address, we can get the symbol loaded address -static unsigned long resolve_from_vma_so(const struct running_elf *relf, const char *symbol_name) +static unsigned long resolve_from_vma_so(struct patch_context *ctx, const char *symbol_name) { struct vm_area_struct *vma; struct mm_struct *mm = current->mm; @@ -566,7 +599,7 @@ static unsigned long resolve_from_vma_so(const struct running_elf *relf, const c mmap_read_lock(mm); upatch_vma_iter_init(&vmi, mm); while ((vma = upatch_vma_next(&vmi))) { - if (!is_vma_other_so_text(relf, vma)) { + if (!is_vma_other_so_text(ctx, vma)) { continue; } @@ -579,9 +612,9 @@ static unsigned long resolve_from_vma_so(const struct running_elf *relf, const c base_addr = search_base_addr(vma); sym_addr += base_addr; if ((type & STT_FUNC) || (type & STT_IFUNC)) { - elf_addr = setup_jmp_table(relf->load_info, sym_addr, type == STT_IFUNC); + elf_addr = setup_jmp_table(ctx, sym_addr, type == STT_IFUNC); } else { - elf_addr = setup_got_table(relf->load_info, sym_addr, 0); + elf_addr = setup_got_table(ctx, sym_addr, 0); } log_debug("found symbol '%s' from shared object at 0x%lx (base 0x%lx), ret=0x%lx\n", symbol_name, sym_addr, base_addr, elf_addr); @@ -592,26 +625,26 @@ static unsigned long resolve_from_vma_so(const struct running_elf *relf, const c return elf_addr; } -static unsigned long resolve_from_symtab(const struct running_elf *relf, const char *name) +static unsigned long resolve_from_symtab(const struct patch_context *ctx, const char *name) { - const struct target_metadata *elf = relf->meta; + const struct target_metadata *elf = ctx->target; Elf_Sym *sym = elf->symtab; unsigned int i; - char *sym_name; + const char *sym_name; unsigned long elf_addr; if (!sym || !elf->strtab) { return 0; } - for (i = 0; i < elf->num.symtab; i++) { + for (i = 0; i < elf->symtab_num; i++) { if (sym[i].st_shndx == SHN_UNDEF) { continue; } sym_name = elf->strtab + sym[i].st_name; if (is_same_name(sym_name, name)) { - elf_addr = relf->vma_start_addr + sym[i].st_value; + elf_addr = ctx->load_bias + sym[i].st_value; log_debug("found symbol '%s' from '.symtab' at 0x%lx\n", name, elf_addr); return elf_addr; } @@ -626,33 +659,36 @@ static unsigned long resolve_from_symtab(const struct running_elf *relf, const c * 2. use address from PLT/GOT, problems are: * 3. read symbol from library that is loaded into VMA for the new called sym in .so */ -unsigned long resolve_symbol(const struct running_elf *relf, const char *name, Elf_Sym patch_sym) +unsigned long resolve_symbol(struct patch_context *ctx, const char *name, Elf_Sym *patch_sym) { unsigned long elf_addr = 0; if (!elf_addr) { - elf_addr = resolve_from_vma_so(relf, name); + elf_addr = resolve_from_vma_so(ctx, name); + } + + if (!elf_addr) { + elf_addr = resolve_from_rela_plt(ctx, name, patch_sym); } if (!elf_addr) { - elf_addr = resolve_from_rela_plt(relf, name, &patch_sym); + elf_addr = resolve_from_rela_dyn(ctx, name, patch_sym); } - /* resolve from got */ if (!elf_addr) { - elf_addr = resolve_from_rela_dyn(relf, name, &patch_sym); + elf_addr = resolve_from_got(ctx, name, patch_sym); } if (!elf_addr) { - elf_addr = resolve_from_dynsym(relf, name); + elf_addr = resolve_from_dynsym(ctx, name); } if (!elf_addr) { - elf_addr = resolve_from_symtab(relf, name); + elf_addr = resolve_from_symtab(ctx, name); } if (!elf_addr) { - elf_addr = resolve_from_patch(relf, name, &patch_sym); + elf_addr = resolve_from_patch(ctx, name, patch_sym); } if (!elf_addr) { diff --git a/upatch-manage/symbol_resolve.h b/upatch-manage/symbol_resolve.h index 4a0ad47c99d570779414e899d45e01b556212ca3..0582189ae17189b448aacc90e586c1f28ae7934c 100644 --- a/upatch-manage/symbol_resolve.h +++ b/upatch-manage/symbol_resolve.h @@ -49,8 +49,8 @@ # endif #endif -struct running_elf; +struct patch_context; -unsigned long resolve_symbol(const struct running_elf *relf, const char *name, Elf_Sym patch_sym); +unsigned long resolve_symbol(struct patch_context *ctx, const char *name, Elf_Sym *patch_sym); #endif // _UPATCH_SYMBOL_RESOLVE_H diff --git a/upatch-manage/target_entity.c b/upatch-manage/target_entity.c index e201b0e65fd272f19d7205c03630cfc382c1c23f..7026ef93912243fa66a6cbecb11d5a0856efb75f 100644 --- a/upatch-manage/target_entity.c +++ b/upatch-manage/target_entity.c @@ -28,238 +28,298 @@ #include "patch_manage.h" #include "util.h" +#define TARGET_TABLE_HASH_BITS 4 #define ELF_ADDR_MAX UINT_MAX -#define SHSTRTAB_NAME ".shstrtab" -#define STRTAB_NAME ".strtab" -#define DYNSTR_NAME ".dynstr" -#define DYN_RELA_NAME ".rela.dyn" -#define PLT_RELA_NAME ".rela.plt" -#define DYN_REL_NAME ".rel.dyn" -#define PLT_REL_NAME ".rel.plt" +static const char *SHSTRTAB_NAME = ".shstrtab"; +static const char *STRTAB_NAME = ".strtab"; +static const char *DYNSTR_NAME = ".dynstr"; +static const char *DYN_RELA_NAME = ".rela.dyn"; +static const char *PLT_RELA_NAME = ".rela.plt"; +static const char *PLT_NAME = ".plt"; +static const char *GOT_NAME = ".got"; -DEFINE_HASHTABLE(g_targets, TARGETS_HASH_BITS); +DEFINE_HASHTABLE(g_target_table, TARGET_TABLE_HASH_BITS); DEFINE_MUTEX(g_target_table_lock); -static void free_elf_meta(struct target_metadata *meta) +static void clear_target_metadata(struct target_metadata *meta) { KFREE_CLEAR(meta->file_name); + + 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->dynstr); + + 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; } -static int parse_target_load_addr(struct target_metadata *meta, struct file *target) +static int resolve_target_sections(struct target_metadata *meta, struct file *target) { - Elf_Ehdr elf_header; - Elf_Phdr *phdr = NULL; - Elf_Addr vma_base_addr = ELF_ADDR_MAX; - int size; - int i; - loff_t pos; - int ret; - - meta->len = i_size_read(file_inode(target)); - - ret = kernel_read(target, &elf_header, sizeof(elf_header), 0); - if (ret != sizeof(elf_header)) { - log_err("failed to read elf header, ret=%d\n", ret); - ret = -ENOEXEC; - goto out; - } + Elf_Shdr *shdrs = meta->shdrs; + Elf_Half shdr_num = meta->ehdr->e_shnum; + Elf_Half i; + Elf_Shdr *shdr; - size = sizeof(Elf_Phdr) * elf_header.e_phnum; - phdr = kmalloc(size, GFP_KERNEL); - if (!phdr) { - log_err("failed to kmalloc program headers\n"); - ret = -ENOMEM; - goto out; - } + const char *shstrtab; + size_t shstrtab_len; - pos = elf_header.e_phoff; - ret = kernel_read(target, phdr, size, &pos); - if (ret < 0) { - log_err("failed to read program headers, ret=%d\n", ret); - ret = -ENOEXEC; - goto out; + const char *sec_name; + void *sec_data; + + shdr = &shdrs[meta->ehdr->e_shstrndx]; + shstrtab = vmalloc_read(target, 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; + + for (i = 1; i < shdr_num; i++) { + shdr = &shdrs[i]; - for (i = 0; i < elf_header.e_phnum; i++) { - if (phdr[i].p_type == PT_LOAD) { - vma_base_addr = min(vma_base_addr, phdr[i].p_vaddr); + sec_name = get_string_at(shstrtab, shstrtab_len, shdr->sh_name); + if (sec_name == NULL) { + log_err("invalid section name, index=%u\n", i); + return -ENOEXEC; } - } - for (i = 0; i < elf_header.e_phnum; i++) { - if ((phdr[i].p_type == PT_LOAD) && (phdr[i].p_flags & PF_X)) { - if (meta->code_vma_offset) { - log_err("found multiple executable PT_LOAD segments (expected one)\n"); - ret = -ENOEXEC; - goto out; - } - meta->code_vma_offset = (phdr[i].p_vaddr - vma_base_addr) & PAGE_MASK; - meta->code_virt_offset = phdr[i].p_vaddr - vma_base_addr - phdr[i].p_offset; + 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); + 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; + } 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; + } + 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); + } 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); + } + 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); + 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); + break; + + default: + // do nothing + break; + } + + if (IS_ERR(sec_data)) { + log_err("failed to read section '%s'\n", sec_name); + return PTR_ERR(sec_data); } } - ret = 0; -out: - KFREE_CLEAR(phdr); - return ret; + return 0; } -static int process_section_header(struct file *target, Elf_Shdr *shdr, char *sh_name, struct target_metadata *meta) +static int resolve_target_address(struct target_metadata *meta) { - void *sh_data = NULL; - int ret = 0; + Elf_Addr min_load_addr = ELF_ADDR_MAX; + bool found_text_segment = false; + + Elf_Phdr *phdrs = meta->phdrs; + Elf_Half phdr_num = meta->ehdr->e_phnum; + Elf_Phdr *phdr; - if (shdr->sh_type == SHT_SYMTAB) { - sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->symtab = sh_data; - meta->num.symtab = shdr->sh_size / sizeof(Elf_Sym); - } else if (strcmp(sh_name, STRTAB_NAME) == 0) { - sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->strtab = sh_data; - } else if (strcmp(sh_name, DYNSTR_NAME) == 0) { - sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->dynstr = sh_data; - } else if (strcmp(sh_name, DYN_RELA_NAME) == 0 || strcmp(sh_name, DYN_REL_NAME) == 0) { - sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->rela_dyn = sh_data; - meta->num.rela_dyn = shdr->sh_size / sizeof(Elf_Rela); - } else if (strcmp(sh_name, PLT_RELA_NAME) == 0 || strcmp(sh_name, PLT_REL_NAME) == 0) { - sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->rela_plt = sh_data; - meta->num.rela_plt = shdr->sh_size / sizeof(Elf_Rela); - } else if (shdr->sh_type == SHT_DYNAMIC) { - sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->dynamic = sh_data; - } else if (shdr->sh_type == SHT_DYNSYM) { - sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - meta->dynsym = sh_data; - meta->num.dynsym = shdr->sh_size / sizeof(Elf_Sym); + Elf_Shdr *shdrs = meta->shdrs; + Elf_Half shdr_num = meta->ehdr->e_shnum; + Elf_Shdr *shdr; + + const char *shstrtab = meta->shstrtab; + const char *sec_name; + + Elf_Half i; + + /* find minimum load virtual address */ + for (i = 0; i < phdr_num; i++) { + phdr = &phdrs[i]; + if (phdr->p_type == PT_LOAD) { + min_load_addr = min(min_load_addr, phdr->p_vaddr); + } } + if (min_load_addr == ELF_ADDR_MAX) { + log_err("cannot find any PT_LOAD segment\n"); + return -ENOEXEC; + } + + /* parse program headers */ + for (i = 0; i < phdr_num; i++) { + phdr = &phdrs[i]; + + switch (phdr->p_type) { + case PT_LOAD: { + if (phdr->p_flags & PF_X) { + if (found_text_segment) { + 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; + found_text_segment = true; + } + break; + } + + case PT_TLS: + meta->tls_size = phdr->p_memsz; + meta->tls_align = phdr->p_align; + break; - if (IS_ERR_VALUE(sh_data)) { - ret = PTR_ERR(sh_data); - log_err("failed to read section '%s'\n", sh_name); + default: + break; + } } - return ret; -} -static int init_target_meta(struct target_metadata *meta, struct file *target) -{ - int ret; + if (!found_text_segment) { + log_err("no executable PT_LOAD segment\n"); + return -ENOEXEC; + } - Elf_Ehdr *ehdr = NULL; // elf header - Elf_Shdr *shdrs = NULL; // section headers - char *shstrtab = NULL; // section string table - Elf_Phdr *phdrs = NULL; + /* parse section headers */ + for (Elf_Half i = 0; i < shdr_num; i++) { + if (meta->plt_addr && meta->got_addr) { + break; + } - Elf_Shdr *shdr; - Elf_Half i; - char *sh_name; + shdr = &shdrs[i]; + if (shdr->sh_type != SHT_PROGBITS) { + continue; + } + + 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 ((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; + } + } + } - const unsigned char *base_name; + 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); + return 0; +} - meta->file_name = NULL; +static int resolve_target_metadata(struct target_metadata *meta, struct file *target) +{ + int ret = 0; - // Check if target dentry are valid before accessing if (!target->f_path.dentry) { - log_err("Invalid target file pointer or dentry\n"); + log_err("invalid file dentry\n"); ret = -EINVAL; goto out; } - base_name = target->f_path.dentry->d_name.name; - - meta->file_name = kstrdup(base_name, GFP_KERNEL); + meta->file_name = kstrdup(target->f_path.dentry->d_name.name, GFP_KERNEL); if (!meta->file_name) { - log_err("Failed to allocate memory for filename\n"); + log_err("failed to alloc filename\n"); ret = -ENOMEM; goto out; } + meta->file_size = i_size_read(file_inode(target)); - ret = parse_target_load_addr(meta, target); - if (ret) { + meta->ehdr = vmalloc_read(target, 0, sizeof(Elf_Ehdr)); + if (IS_ERR(meta->ehdr)) { + ret = PTR_ERR(meta->ehdr); + log_err("failed to read elf header\n"); goto out; } - // read elf header - ret = kernel_read(target, &meta->ehdr, sizeof(Elf_Ehdr), 0); - if (ret != sizeof(Elf_Ehdr)) { + if (!is_valid_target(meta->ehdr, meta->file_size)) { ret = -ENOEXEC; - log_err("read elf header failed ret=%d\n", ret); + log_err("invalid file format\n"); goto out; } - ehdr = &meta->ehdr; - if (!is_elf_valid(ehdr, i_size_read(file_inode(target)), false)) { - ret = -ENOEXEC; - log_err("invalid target format\n"); + meta->phdrs = vmalloc_read(target, meta->ehdr->e_phoff, meta->ehdr->e_phentsize * meta->ehdr->e_phnum); + if (IS_ERR(meta->phdrs)) { + ret = PTR_ERR(meta->phdrs); + log_err("failed to read program header\n"); goto out; } - // read section headers - shdrs = vmalloc_read(target, ehdr->e_shoff, ehdr->e_shentsize * ehdr->e_shnum); - if (IS_ERR(shdrs)) { - ret = PTR_ERR(shdrs); + meta->shdrs = vmalloc_read(target, meta->ehdr->e_shoff, meta->ehdr->e_shentsize * meta->ehdr->e_shnum); + if (IS_ERR(meta->shdrs)) { + ret = PTR_ERR(meta->shdrs); log_err("failed to read section header\n"); goto out; } - // read section header string table - shdr = &shdrs[ehdr->e_shstrndx]; - shstrtab = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); - if (IS_ERR(shstrtab)) { - ret = PTR_ERR(shstrtab); - log_err("failed to read '%s' section\n", SHSTRTAB_NAME); + ret = resolve_target_sections(meta, target); + if (ret) { + log_err("failed to resolve target sections\n"); goto out; } - // resolve section headers - for (i = 1; i < ehdr->e_shnum; i++) { - shdr = &shdrs[i]; - sh_name = shstrtab + shdr->sh_name; - - ret = process_section_header(target, shdr, sh_name, meta); - if (ret) - goto out; - } - - phdrs = vmalloc_read(target, ehdr->e_phoff, ehdr->e_phentsize * ehdr->e_phnum); - if (IS_ERR(phdrs)) { - ret = PTR_ERR(phdrs); - log_err("failed to read program header\n"); + ret = resolve_target_address(meta); + if (ret) { + log_err("failed to resolve target address\n"); goto out; } - for (i = 0; i < ehdr->e_phnum; i++) { - if (phdrs[i].p_type == PT_TLS) { - meta->tls_size = phdrs[i].p_memsz; - meta->tls_align = phdrs[i].p_align; - log_debug("Found TLS size = %zd, align = %zd\n", - (size_t)meta->tls_size, (size_t)meta->tls_align); - break; - } - } - ret = 0; + return 0; out: - if (ret != 0) { - free_elf_meta(meta); - } - VFREE_CLEAR(shdrs); - VFREE_CLEAR(shstrtab); - VFREE_CLEAR(phdrs); + clear_target_metadata(meta); return ret; } -static int init_grab_target(struct target_entity *target, const char *file_path) +static int resolve_target_entity(struct target_entity *target, const char *file_path) { int ret = 0; struct file *file = NULL; @@ -267,59 +327,56 @@ static int init_grab_target(struct target_entity *target, const char *file_path) init_rwsem(&target->patch_lock); mutex_init(&target->process_lock); INIT_HLIST_NODE(&target->node); - INIT_LIST_HEAD(&target->off_head); + INIT_LIST_HEAD(&target->offset_node); INIT_LIST_HEAD(&target->all_patch_list); INIT_LIST_HEAD(&target->actived_patch_list); INIT_LIST_HEAD(&target->process_head); - // open target file file = filp_open(file_path, O_RDONLY, 0); // open file by inode if (IS_ERR(file)) { - log_err("failed to open file '%s'\n", file_path); + log_err("failed to open '%s'\n", file_path); return PTR_ERR(file); } target->inode = igrab(file_inode(file)); if (!target->inode) { - pr_err("%s: Failed to grab inode of %s\n", __func__, file_path); + log_err("file '%s' inode is invalid\n", file_path); ret = -ENOENT; - goto fail; + goto out; } target->path = kstrdup(file_path, GFP_KERNEL); if (!target->path) { iput(target->inode); ret = -ENOMEM; - goto fail; + log_err("faild to alloc filename\n"); + goto out; } - // resolve elf meta - ret = init_target_meta(&target->meta, file); + ret = resolve_target_metadata(&target->meta, file); if (ret != 0) { iput(target->inode); KFREE_CLEAR(target->path); - log_err("failed to resolve elf meta\n"); - goto fail; + goto out; } -fail: +out: filp_close(file, NULL); return ret; } -struct target_entity *get_target_entity_from_inode(struct inode *inode) +struct target_entity *get_target_entity_by_inode(struct inode *inode) { struct target_entity *target; struct target_entity *found = NULL; mutex_lock(&g_target_table_lock); - hash_for_each_possible(g_targets, target, node, inode->i_ino) { + hash_for_each_possible(g_target_table, target, node, inode->i_ino) { if (target->inode == inode) { found = target; break; } } - mutex_unlock(&g_target_table_lock); return found; } @@ -327,30 +384,25 @@ struct target_entity *get_target_entity_from_inode(struct inode *inode) /* public interface */ struct target_entity *get_target_entity(const char *path) { - struct inode *inode = path_inode(path); struct target_entity *target; + struct inode *inode; - log_debug("start to get target_entity for %s\n", path); - - if (IS_ERR(inode)) { - return NULL; - } - - inode = igrab(inode); + inode = get_path_inode(path); if (!inode) { - pr_err("failed to grab inode of %s\n", path); + log_err("failed to get '%s' inode\n", path); return NULL; } - target = get_target_entity_from_inode(inode); + target = get_target_entity_by_inode(inode); iput(inode); + return target; } static void insert_target(struct target_entity *target) { mutex_lock(&g_target_table_lock); - hash_add(g_targets, &target->node, target->inode->i_ino); + hash_add(g_target_table, &target->node, target->inode->i_ino); mutex_unlock(&g_target_table_lock); } @@ -371,9 +423,8 @@ struct target_entity *new_target_entity(const char *file_path) return ERR_PTR(-ENOMEM); } - ret = init_grab_target(target, file_path); + ret = resolve_target_entity(target, file_path); if (ret != 0) { - log_err("failed to init patch target '%s', ret=%d\n", file_path, ret); kfree(target); return ERR_PTR(ret); } @@ -393,7 +444,7 @@ void free_target_entity(struct target_entity *target) log_debug("free patch target '%s'\n", target->path); down_write(&target->patch_lock); - list_for_each_entry(off, &target->off_head, list) { + list_for_each_entry(off, &target->offset_node, list) { log_err("found uprobe in 0x%lx\n", (unsigned long)off->offset); } @@ -413,7 +464,7 @@ void free_target_entity(struct target_entity *target) iput(target->inode); KFREE_CLEAR(target->path); - free_elf_meta(&target->meta); + clear_target_metadata(&target->meta); hash_del(&target->node); target_unregister_uprobes(target); @@ -428,30 +479,13 @@ bool is_target_has_patch(const struct target_entity *target) return !list_empty(&target->all_patch_list); } -bool upatch_binary_has_addr(const struct target_entity *target, loff_t offset) -{ - struct patched_offset *addr = NULL; - - if (!target) { - return false; - } - - list_for_each_entry(addr, &target->off_head, list) { - if (addr->offset == offset) { - return true; - } - } - - return false; -} - -void __exit verify_target_empty_on_exit(void) +void __exit report_target_table_populated(void) { struct target_entity *target; int bkt; mutex_lock(&g_target_table_lock); - hash_for_each(g_targets, bkt, target, node) { + hash_for_each(g_target_table, bkt, target, node) { log_err("found target '%s' on exit", target->path ? target->path : "(null)"); } mutex_unlock(&g_target_table_lock); diff --git a/upatch-manage/target_entity.h b/upatch-manage/target_entity.h index 5166e407badd80673ec578a42a507d525ad58ca4..f97e8c83b2711c47adfda42e4599bb0effd4e279 100644 --- a/upatch-manage/target_entity.h +++ b/upatch-manage/target_entity.h @@ -28,67 +28,96 @@ #include #include -#define TARGETS_HASH_BITS 4 +#if defined(__x86_64__) + #if defined(__CET__) || defined(__SHSTK__) + #define PLT_ENTRY_SIZE 20 // PLT with CET + #else + #define PLT_ENTRY_SIZE 16 + #endif +#elif defined(__aarch64__) + #if defined(__ARM_PAC) || defined(__ARM_FEATURE_PAC_DEFAULT) + #define PLT_ENTRY_SIZE 20 // PLT with PAC + #else + #define PLT_ENTRY_SIZE 16 + #endif +#endif +#define GOT_ENTRY_SIZE sizeof(uintptr_t) // GOT entry size is pointer size struct inode; - struct upatch_function; /* target elf metadata */ struct target_metadata { - unsigned long len; // file len - - Elf_Ehdr ehdr; + const char *file_name; + loff_t file_size; - char *file_name; + Elf_Ehdr *ehdr; + Elf_Phdr *phdrs; + Elf_Shdr *shdrs; - // tables Elf_Sym *symtab; Elf_Sym *dynsym; Elf_Dyn *dynamic; Elf_Rela *rela_dyn; Elf_Rela *rela_plt; - char *strtab; - char *dynstr; - // table entity number - struct { - unsigned int symtab, rela_dyn, rela_plt, dynsym; - } num; + const char *shstrtab; + const char *strtab; + const char *dynstr; + + size_t symtab_num; + size_t dynsym_num; + size_t dynamic_num; + size_t rela_dyn_num; + size_t rela_plt_num; + + size_t shstrtab_len; + size_t strtab_len; + size_t dynstr_len; + + Elf_Addr vma_offset; // .text page-aligned offset from the minimum load address + Elf_Addr load_offset; // .text load segment vma - offset Elf_Addr tls_size; Elf_Addr tls_align; - // In LLVM, offset != virtaddr, target VMA start != vma_code_start - offset - // code_vma_offset = VirtAddr round down to PAGE_SIZE - // The target VMA start = vma_code_start - code_vma_offset - Elf_Addr code_vma_offset; - - // code LOAD segment VirtAddr - offset - unsigned long code_virt_offset; + Elf_Addr plt_addr; + Elf_Addr got_addr; + size_t plt_size; + size_t got_size; }; struct target_entity { - char *path; - struct inode *inode; + const char *path; // patch file path + struct inode *inode; // target file inode - struct target_metadata meta; // store target elf info + struct target_metadata meta; // target file elf data - // there is only one thread to call active/deactive so we don't need to lock - struct list_head off_head; // list of file offset of active patch function for struct patched_offset + /* + * there is only one thread to call active / deactive + * we don't need a lock + */ + struct list_head offset_node; // list of file offset of active patch function for struct patched_offset struct hlist_node node; // all target store in hash table - // all patches related to this target, including active and deactive patches - // don't need lock. only load_patch, remove_patch, rmmod upatch_manage will read/write this list - // uprobe_handle will not use this list, and we limit there is only one thread to manage patch + /* + * all patches related to this target, including active and deactive patches + * don't need lock. only load_patch, remove_patch, rmmod upatch_manage will read/write this list + * uprobe_handle will not use this list, and we limit there is only one thread to manage patch + */ struct list_head all_patch_list; - // active patch list need lock, uprobe handle will read it, active method will write it + /* + * active patch list need lock + * uprobe handle will read it, active method will write it + */ struct rw_semaphore patch_lock; struct list_head actived_patch_list; - // target ELF may run in different process (so) - // every process will have a active patch + /* + * target ELF may run in different process, such as a dynamic object + * every process will have a actived patch + */ struct mutex process_lock; // uprobe handle will call free_process, so we need lock struct list_head process_head; }; @@ -113,7 +142,7 @@ struct patched_func_node { */ struct target_entity *get_target_entity(const char* target_path); -struct target_entity *get_target_entity_from_inode(struct inode *inode); +struct target_entity *get_target_entity_by_inode(struct inode *inode); /* * Load a target entity @@ -137,14 +166,6 @@ void free_target_entity(struct target_entity *target); */ bool is_target_has_patch(const struct target_entity *target); -/* - * Check if a target offset has been patched - * @param target: target entity - * @param offset: target offset - * @return result - */ -bool upatch_binary_has_addr(const struct target_entity *target, loff_t offset); - -void __exit verify_target_empty_on_exit(void); +void __exit report_target_table_populated(void); #endif // _UPATCH_MANAGE_TARGET_ENTITY_H diff --git a/upatch-manage/util.c b/upatch-manage/util.c deleted file mode 100644 index 1bb3f5b74c6699e609667ad6a8c7323ff624bcb8..0000000000000000000000000000000000000000 --- a/upatch-manage/util.c +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * provide utils - * 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 "util.h" - -#include -#include -#include -#include -#include -#include - -#include "patch_entity.h" - -bool is_elf_valid(Elf_Ehdr *ehdr, size_t len, bool is_patch) -{ - if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { - log_err("elf magic wrong!\n"); - return false; - } - - if (!elf_check_arch(ehdr)) { - log_err("elf_check_arch failed, e_machine = %d\n", ehdr->e_machine); - return false; - } - - if (!ehdr->e_shoff || !ehdr->e_shnum || !ehdr->e_shentsize) { - log_err("file don't have section\n"); - return false; - } - - if (ehdr->e_shentsize != sizeof(Elf_Shdr)) { - log_err("e_shentsize is %d not %zu\n", ehdr->e_shentsize, sizeof(Elf_Shdr)); - return false; - } - - if (ehdr->e_shstrndx > (ehdr->e_shnum - 1)) { - log_err("e_shstrndx = %d, greater than e_shnum = %d\n", - ehdr->e_shstrndx, ehdr->e_shnum); - return false; - } - - if (len < sizeof(Elf_Ehdr) || ehdr->e_shoff >= len || - ehdr->e_shnum * sizeof(Elf_Shdr) > len - ehdr->e_shoff) { - log_err("len is %ld, not suitable with e_shnum %d and e_shoff %lld\n", - (long int)len, ehdr->e_shnum, (long long)ehdr->e_shoff); - return false; - } - - if (is_patch) { - if (ehdr->e_type != ET_REL) { - log_err("patch is not REL format\n"); - return false; - } - } else { - if ((ehdr->e_type != ET_EXEC) && (ehdr->e_type != ET_DYN)) { - log_err("file is not exe or so\n"); - return false; - } - - if (!ehdr->e_phoff || !ehdr->e_phnum || !ehdr->e_phentsize) { - log_err("file don't have program header\n"); - return false; - } - } - - return true; -} - -struct inode *path_inode(const char *file) -{ - struct path path; - struct inode *inode; - int ret = 0; - - ret = kern_path(file, LOOKUP_FOLLOW, &path); - if (ret) { - log_err("%s: cannot get inode of %s\n", __func__, file); - return ERR_PTR(ret); - } - - inode = path.dentry->d_inode; - if (!inode) { - log_err("%s: path inode is NULL, path = %s\n", __func__, file); - return ERR_PTR(-ENOENT); - } - path_put(&path); - return inode; -} - -void *vmalloc_read(struct file *file, loff_t offset, size_t len) -{ - void *buff = NULL; - ssize_t read = 0; - - if (!len) { - return ERR_PTR(-EINVAL); - } - - buff = vmalloc(len); - if (!buff) { - return ERR_PTR(-ENOMEM); - } - - read = kernel_read(file, buff, len, &offset); - if (read < 0) { - vfree(buff); - return ERR_PTR(read); - } - if (read != len) { - vfree(buff); - return ERR_PTR(-EIO); - } - - return buff; -} \ No newline at end of file diff --git a/upatch-manage/util.h b/upatch-manage/util.h index d44b0e791343463bb34fee555d54d688509db17d..280a71d02c0cc9ac6a711b129598ffd5779bcff6 100644 --- a/upatch-manage/util.h +++ b/upatch-manage/util.h @@ -22,12 +22,13 @@ #define _UPATCH_MANAGE_UTIL_H #include +#include + +#include +#include +#include #include #include -#include - -#include -#include static const char* MODULE_NAME = THIS_MODULE->name; @@ -43,38 +44,148 @@ static const char* MODULE_NAME = THIS_MODULE->name; * @param len: read length * @return buffer pointer */ -void *vmalloc_read(struct file *file, loff_t offset, size_t len); +static inline void *vmalloc_read(struct file *file, loff_t offset, size_t len) +{ + void *buff; + ssize_t ret; + + if (!len) { + return ERR_PTR(-EINVAL); + } + + buff = vmalloc(len); + if (unlikely(!buff)) { + return ERR_PTR(-ENOMEM); + } + + ret = kernel_read(file, buff, len, &offset); + if (unlikely(ret < 0)) { + vfree(buff); + return ERR_PTR(ret); + } + + if (unlikely(ret != len)) { + vfree(buff); + return ERR_PTR(-EIO); + } + + return buff; +} + +static inline void *vmalloc_copy_user(void __user *addr, size_t offset, size_t size) +{ + void __user *uaddr; + void *kaddr; + + if (unlikely(!addr || !size)) { + return ERR_PTR(-EINVAL); + } + + if (unlikely(check_add_overflow((unsigned long)addr, offset, (unsigned long*)&uaddr))) { + return ERR_PTR(-EINVAL); + } + + if (unlikely(!access_ok(uaddr, size))) { + return ERR_PTR(-EFAULT); + } + + kaddr = vmalloc(size); + if (unlikely(!kaddr)) { + return ERR_PTR(-ENOMEM); + } + + if (unlikely(copy_from_user(kaddr, uaddr, size))) { + vfree(kaddr); + return ERR_PTR(-EFAULT); + } + + return kaddr; +} /* - * Free kalloc() allocated memory safely + * Free valloc() allocated memory safely * @param addr: memory address * @return void */ -static inline void kfree_safe(const void *addr) +static inline void vfree_safe(const void *addr) { - if (addr) { - kfree(addr); + if (likely(addr && !IS_ERR(addr))) { + vfree(addr); } } -#define KFREE_CLEAR(ptr) do { kfree_safe(ptr); ptr = NULL; } while (0) +#define VFREE_CLEAR(ptr) do { vfree_safe((ptr)); (ptr) = NULL; } while (0) /* - * Free valloc() allocated memory safely + * Free kalloc() allocated memory safely * @param addr: memory address * @return void */ -static inline void vfree_safe(const void *addr) +static inline void kfree_safe(const void *addr) { - if (addr) { - vfree(addr); + if (likely(addr && !IS_ERR(addr))) { + kfree(addr); + } +} + +#define KFREE_CLEAR(ptr) do { kfree_safe((ptr)); (ptr) = NULL; } while (0) + +static inline bool is_valid_elf(Elf_Ehdr *ehdr, size_t len) +{ + return ehdr && len >= sizeof(Elf_Ehdr) && + ehdr->e_ident[EI_MAG0] == ELFMAG0 && ehdr->e_ident[EI_MAG1] == ELFMAG1 && + ehdr->e_ident[EI_MAG2] == ELFMAG2 && ehdr->e_ident[EI_MAG3] == ELFMAG3 && + elf_check_arch(ehdr) && + ehdr->e_shoff && ehdr->e_shoff < len && + ehdr->e_shnum && ehdr->e_shstrndx < ehdr->e_shnum && ehdr->e_shentsize == sizeof(Elf_Shdr) && + ehdr->e_shnum * ehdr->e_shentsize <= len - ehdr->e_shoff && + (!ehdr->e_phoff || (ehdr->e_phoff < len && ehdr->e_phnum && ehdr->e_phentsize == sizeof(Elf_Phdr) && + ehdr->e_phnum * ehdr->e_phentsize <= len - ehdr->e_phoff)); +} + +static inline bool is_valid_patch(Elf_Ehdr *ehdr, size_t len) +{ + return is_valid_elf(ehdr, len) && (ehdr->e_type == ET_REL); +} + +static inline bool is_valid_target(Elf_Ehdr *ehdr, size_t len) +{ + return is_valid_elf(ehdr, len) && ((ehdr->e_type == ET_EXEC) || (ehdr->e_type == ET_DYN)) && ehdr->e_phnum; +} + +static inline bool is_valid_str(const char *strtab, size_t strtab_len, size_t offset) +{ + const char *start; + size_t remain_len; + + if (unlikely(offset == 0 || offset >= strtab_len - 1)) { + return false; } + + start = strtab + offset; + remain_len = strtab_len - offset; + return memchr(start, '\0', remain_len) != NULL; +} + +static inline const char *get_string_at(const char *strtab, size_t strtab_len, size_t offset) +{ + return is_valid_str(strtab, strtab_len, offset) ? strtab + offset : NULL; } -#define VFREE_CLEAR(ptr) do { vfree_safe(ptr); ptr = NULL; } while (0) +static inline struct inode *get_path_inode(const char *file) +{ + struct path path; + struct inode *inode; + + if (unlikely(!file || kern_path(file, LOOKUP_FOLLOW, &path))) { + return NULL; + } -bool is_elf_valid(Elf_Ehdr *ehdr, size_t len, bool is_patch); + inode = path.dentry->d_inode; + path_put(&path); -struct inode *path_inode(const char *file); + // will increase inode refcnt, need call iput after use + return igrab(inode); +} #endif // _UPATCH_MANAGE_UTIL_H