From: Zheng Yejian zhengyejian1@huawei.com
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/I53WZ9
--------------------------------
Codes related to patching text in 'arch_klp_patch_func' and 'arch_klp_unpatch_func' are duplicate, we can reduce them.
And There is issue in arm/arm64 that 'offset' between pc and new function address is out of valid range is NOT considered if MODULE_PLTS is not enabled (CONFIG_ARM_MODULE_PLTS in arm, CONFIG_ARM_MODULE_PLTS in arm64). We fix it by always checking that 'offset'.
Fixes: 2fa9f353c118 livepatch/arm: Support livepatch without ftrace Fixes: e429c61d12bf livepatch/arm64: Support livepatch without ftrace Suggested-by: Xu Kuohai xukuohai@huawei.com Signed-off-by: Zheng Yejian zhengyejian1@huawei.com Reviewed-by: Kuohai Xu xukuohai@huawei.com Signed-off-by: Zheng Zengkai zhengzengkai@huawei.com --- arch/arm/kernel/livepatch.c | 73 ++++++++++++--------------- arch/arm64/kernel/livepatch.c | 79 ++++++++++++------------------ arch/powerpc/kernel/livepatch_32.c | 58 +++++++--------------- arch/powerpc/kernel/livepatch_64.c | 58 +++++++++------------- 4 files changed, 103 insertions(+), 165 deletions(-)
diff --git a/arch/arm/kernel/livepatch.c b/arch/arm/kernel/livepatch.c index 4b07e73ad37b..d9eae1dd9744 100644 --- a/arch/arm/kernel/livepatch.c +++ b/arch/arm/kernel/livepatch.c @@ -370,22 +370,15 @@ long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func) return ret; }
-int arch_klp_patch_func(struct klp_func *func) +static int do_patch(unsigned long pc, unsigned long new_addr) { - struct klp_func_node *func_node; - unsigned long pc, new_addr; u32 insn; -#ifdef CONFIG_ARM_MODULE_PLTS - int i; - u32 insns[LJMP_INSN_SIZE]; -#endif
- func_node = func->func_node; - list_add_rcu(&func->stack_node, &func_node->func_stack); - pc = (unsigned long)func->old_func; - new_addr = (unsigned long)func->new_func; -#ifdef CONFIG_ARM_MODULE_PLTS if (!offset_in_range(pc, new_addr, SZ_32M)) { +#ifdef CONFIG_ARM_MODULE_PLTS + int i; + u32 insns[LJMP_INSN_SIZE]; + /* * [0] LDR PC, [PC+8] * [4] nop @@ -397,28 +390,44 @@ int arch_klp_patch_func(struct klp_func *func)
for (i = 0; i < LJMP_INSN_SIZE; i++) __patch_text(((u32 *)pc) + i, insns[i]); - +#else + /* + * When offset from 'new_addr' to 'pc' is out of SZ_32M range but + * CONFIG_ARM_MODULE_PLTS not enabled, we should stop patching. + */ + pr_err("new address out of range\n"); + return -EFAULT; +#endif } else { insn = arm_gen_branch(pc, new_addr); __patch_text((void *)pc, insn); } -#else - insn = arm_gen_branch(pc, new_addr); - __patch_text((void *)pc, insn); -#endif - return 0; }
+int arch_klp_patch_func(struct klp_func *func) +{ + struct klp_func_node *func_node; + int ret; + + func_node = func->func_node; + list_add_rcu(&func->stack_node, &func_node->func_stack); + ret = do_patch((unsigned long)func->old_func, (unsigned long)func->new_func); + if (ret) + list_del_rcu(&func->stack_node); + return ret; +} + void arch_klp_unpatch_func(struct klp_func *func) { struct klp_func_node *func_node; struct klp_func *next_func; - unsigned long pc, new_addr; - u32 insn; + unsigned long pc; #ifdef CONFIG_ARM_MODULE_PLTS int i; u32 insns[LJMP_INSN_SIZE]; +#else + u32 insn; #endif
func_node = func->func_node; @@ -439,29 +448,7 @@ void arch_klp_unpatch_func(struct klp_func *func) next_func = list_first_or_null_rcu(&func_node->func_stack, struct klp_func, stack_node);
- new_addr = (unsigned long)next_func->new_func; -#ifdef CONFIG_ARM_MODULE_PLTS - if (!offset_in_range(pc, new_addr, SZ_32M)) { - /* - * [0] LDR PC, [PC+8] - * [4] nop - * [8] new_addr_to_jump - */ - insns[0] = __opcode_to_mem_arm(0xe59ff000); - insns[1] = __opcode_to_mem_arm(0xe320f000); - insns[2] = new_addr; - - for (i = 0; i < LJMP_INSN_SIZE; i++) - __patch_text(((u32 *)pc) + i, insns[i]); - - } else { - insn = arm_gen_branch(pc, new_addr); - __patch_text((void *)pc, insn); - } -#else - insn = arm_gen_branch(pc, new_addr); - __patch_text((void *)pc, insn); -#endif + do_patch(pc, (unsigned long)next_func->new_func); } }
diff --git a/arch/arm64/kernel/livepatch.c b/arch/arm64/kernel/livepatch.c index 2c292008440c..4e4ed4a65244 100644 --- a/arch/arm64/kernel/livepatch.c +++ b/arch/arm64/kernel/livepatch.c @@ -349,60 +349,63 @@ long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func) return ret; }
-int arch_klp_patch_func(struct klp_func *func) +static int do_patch(unsigned long pc, unsigned long new_addr) { - struct klp_func_node *func_node; - unsigned long pc, new_addr; u32 insn; -#ifdef CONFIG_ARM64_MODULE_PLTS - int i; - u32 insns[LJMP_INSN_SIZE]; -#endif
- func_node = func->func_node; - list_add_rcu(&func->stack_node, &func_node->func_stack); - pc = (unsigned long)func->old_func; - new_addr = (unsigned long)func->new_func; -#ifdef CONFIG_ARM64_MODULE_PLTS if (offset_in_range(pc, new_addr, SZ_128M)) { insn = aarch64_insn_gen_branch_imm(pc, new_addr, - AARCH64_INSN_BRANCH_NOLINK); + AARCH64_INSN_BRANCH_NOLINK); if (aarch64_insn_patch_text_nosync((void *)pc, insn)) - goto ERR_OUT; + return -EPERM; } else { +#ifdef CONFIG_ARM64_MODULE_PLTS + int i; + u32 insns[LJMP_INSN_SIZE]; + insns[0] = cpu_to_le32(0x92800010 | (((~new_addr) & 0xffff)) << 5); insns[1] = cpu_to_le32(0xf2a00010 | (((new_addr >> 16) & 0xffff)) << 5); insns[2] = cpu_to_le32(0xf2c00010 | (((new_addr >> 32) & 0xffff)) << 5); insns[3] = cpu_to_le32(0xd61f0200); for (i = 0; i < LJMP_INSN_SIZE; i++) { if (aarch64_insn_patch_text_nosync(((u32 *)pc) + i, insns[i])) - goto ERR_OUT; + return -EPERM; } - } #else - insn = aarch64_insn_gen_branch_imm(pc, new_addr, - AARCH64_INSN_BRANCH_NOLINK); - - if (aarch64_insn_patch_text_nosync((void *)pc, insn)) - goto ERR_OUT; + /* + * When offset from 'new_addr' to 'pc' is out of SZ_128M range but + * CONFIG_ARM64_MODULE_PLTS not enabled, we should stop patching. + */ + pr_err("new address out of range\n"); + return -EFAULT; #endif + } return 0; +}
-ERR_OUT: - list_del_rcu(&func->stack_node); +int arch_klp_patch_func(struct klp_func *func) +{ + struct klp_func_node *func_node; + int ret;
- return -EPERM; + func_node = func->func_node; + list_add_rcu(&func->stack_node, &func_node->func_stack); + ret = do_patch((unsigned long)func->old_func, (unsigned long)func->new_func); + if (ret) + list_del_rcu(&func->stack_node); + return ret; }
void arch_klp_unpatch_func(struct klp_func *func) { struct klp_func_node *func_node; struct klp_func *next_func; - unsigned long pc, new_addr; - u32 insn; + unsigned long pc; #ifdef CONFIG_ARM64_MODULE_PLTS int i; u32 insns[LJMP_INSN_SIZE]; +#else + u32 insn; #endif
func_node = func->func_node; @@ -430,29 +433,7 @@ void arch_klp_unpatch_func(struct klp_func *func) struct klp_func, stack_node); if (WARN_ON(!next_func)) return; - - new_addr = (unsigned long)next_func->new_func; -#ifdef CONFIG_ARM64_MODULE_PLTS - if (offset_in_range(pc, new_addr, SZ_128M)) { - insn = aarch64_insn_gen_branch_imm(pc, new_addr, - AARCH64_INSN_BRANCH_NOLINK); - - aarch64_insn_patch_text_nosync((void *)pc, insn); - } else { - insns[0] = cpu_to_le32(0x92800010 | (((~new_addr) & 0xffff)) << 5); - insns[1] = cpu_to_le32(0xf2a00010 | (((new_addr >> 16) & 0xffff)) << 5); - insns[2] = cpu_to_le32(0xf2c00010 | (((new_addr >> 32) & 0xffff)) << 5); - insns[3] = cpu_to_le32(0xd61f0200); - for (i = 0; i < LJMP_INSN_SIZE; i++) - aarch64_insn_patch_text_nosync(((u32 *)pc) + i, - insns[i]); - } -#else - insn = aarch64_insn_gen_branch_imm(pc, new_addr, - AARCH64_INSN_BRANCH_NOLINK); - - aarch64_insn_patch_text_nosync((void *)pc, insn); -#endif + do_patch(pc, (unsigned long)next_func->new_func); } }
diff --git a/arch/powerpc/kernel/livepatch_32.c b/arch/powerpc/kernel/livepatch_32.c index 99acabd730e0..3b5c9b121c6f 100644 --- a/arch/powerpc/kernel/livepatch_32.c +++ b/arch/powerpc/kernel/livepatch_32.c @@ -392,24 +392,19 @@ long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func) return ret; }
-int arch_klp_patch_func(struct klp_func *func) +static int do_patch(unsigned long pc, unsigned long new_addr) { - struct klp_func_node *func_node; - unsigned long pc, new_addr; - long ret; + int ret; int i; u32 insns[LJMP_INSN_SIZE];
- func_node = func->func_node; - list_add_rcu(&func->stack_node, &func_node->func_stack); - pc = (unsigned long)func->old_func; - new_addr = (unsigned long)func->new_func; if (offset_in_range(pc, new_addr, SZ_32M)) { struct ppc_inst instr;
create_branch(&instr, (struct ppc_inst *)pc, new_addr, 0); - if (patch_instruction((struct ppc_inst *)pc, instr)) - goto ERR_OUT; + ret = patch_instruction((struct ppc_inst *)pc, instr); + if (ret) + return -EPERM; } else { /* * lis r12,sym@ha @@ -426,23 +421,30 @@ int arch_klp_patch_func(struct klp_func *func) ret = patch_instruction((struct ppc_inst *)(((u32 *)pc) + i), ppc_inst(insns[i])); if (ret) - goto ERR_OUT; + return -EPERM; } } - return 0; +}
-ERR_OUT: - list_del_rcu(&func->stack_node); +int arch_klp_patch_func(struct klp_func *func) +{ + struct klp_func_node *func_node; + int ret;
- return -EPERM; + func_node = func->func_node; + list_add_rcu(&func->stack_node, &func_node->func_stack); + ret = do_patch((unsigned long)func->old_func, (unsigned long)func->new_func); + if (ret) + list_del_rcu(&func->stack_node); + return ret; }
void arch_klp_unpatch_func(struct klp_func *func) { struct klp_func_node *func_node; struct klp_func *next_func; - unsigned long pc, new_addr; + unsigned long pc; u32 insns[LJMP_INSN_SIZE]; int i;
@@ -461,29 +463,7 @@ void arch_klp_unpatch_func(struct klp_func *func) list_del_rcu(&func->stack_node); next_func = list_first_or_null_rcu(&func_node->func_stack, struct klp_func, stack_node); - - new_addr = (unsigned long)next_func->new_func; - if (offset_in_range(pc, new_addr, SZ_32M)) { - struct ppc_inst instr; - - create_branch(&instr, (struct ppc_inst *)pc, new_addr, 0); - patch_instruction((struct ppc_inst *)pc, instr); - } else { - /* - * lis r12,sym@ha - * addi r12,r12,sym@l - * mtctr r12 - * bctr - */ - insns[0] = 0x3d800000 + ((new_addr + 0x8000) >> 16); - insns[1] = 0x398c0000 + (new_addr & 0xffff); - insns[2] = 0x7d8903a6; - insns[3] = 0x4e800420; - - for (i = 0; i < LJMP_INSN_SIZE; i++) - patch_instruction((struct ppc_inst *)(((u32 *)pc) + i), - ppc_inst(insns[i])); - } + do_patch(pc, (unsigned long)next_func->new_func); } }
diff --git a/arch/powerpc/kernel/livepatch_64.c b/arch/powerpc/kernel/livepatch_64.c index b319675afd4c..f3cd2ee66efa 100644 --- a/arch/powerpc/kernel/livepatch_64.c +++ b/arch/powerpc/kernel/livepatch_64.c @@ -439,43 +439,44 @@ long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func) return ret; }
-int arch_klp_patch_func(struct klp_func *func) +static int do_patch(unsigned long pc, unsigned long new_addr, + struct arch_klp_data *arch_data, struct module *old_mod) { - struct klp_func_node *func_node; - unsigned long pc, new_addr; - long ret; - - func_node = func->func_node; - list_add_rcu(&func->stack_node, &func_node->func_stack); + int ret;
- pc = (unsigned long)func->old_func; - new_addr = (unsigned long)func->new_func; - ret = livepatch_create_branch(pc, (unsigned long)&func_node->arch_data.trampoline, - new_addr, func->old_mod); + ret = livepatch_create_branch(pc, (unsigned long)&arch_data->trampoline, + new_addr, old_mod); if (ret) - goto ERR_OUT; - flush_icache_range((unsigned long)pc, - (unsigned long)pc + LJMP_INSN_SIZE * PPC64_INSN_SIZE); - + return -EPERM; + flush_icache_range(pc, pc + LJMP_INSN_SIZE * PPC64_INSN_SIZE); pr_debug("[%s %d] old = 0x%lx/0x%lx/%pS, new = 0x%lx/0x%lx/%pS\n", __func__, __LINE__, pc, ppc_function_entry((void *)pc), (void *)pc, new_addr, ppc_function_entry((void *)new_addr), (void *)ppc_function_entry((void *)new_addr)); - return 0; +}
-ERR_OUT: - list_del_rcu(&func->stack_node); +int arch_klp_patch_func(struct klp_func *func) +{ + struct klp_func_node *func_node; + int ret;
- return -EPERM; + func_node = func->func_node; + list_add_rcu(&func->stack_node, &func_node->func_stack); + ret = do_patch((unsigned long)func->old_func, + (unsigned long)func->new_func, + &func_node->arch_data, func->old_mod); + if (ret) + list_del_rcu(&func->stack_node); + return ret; }
void arch_klp_unpatch_func(struct klp_func *func) { struct klp_func_node *func_node; struct klp_func *next_func; - unsigned long pc, new_addr; + unsigned long pc; u32 insns[LJMP_INSN_SIZE]; int i;
@@ -492,25 +493,14 @@ void arch_klp_unpatch_func(struct klp_func *func) ppc_inst(insns[i]));
pr_debug("[%s %d] restore insns at 0x%lx\n", __func__, __LINE__, pc); + flush_icache_range(pc, pc + LJMP_INSN_SIZE * PPC64_INSN_SIZE); } else { list_del_rcu(&func->stack_node); next_func = list_first_or_null_rcu(&func_node->func_stack, struct klp_func, stack_node); - new_addr = (unsigned long)next_func->new_func; - - livepatch_create_branch(pc, (unsigned long)&func_node->arch_data.trampoline, - new_addr, func->old_mod); - - pr_debug("[%s %d] old = 0x%lx/0x%lx/%pS, new = 0x%lx/0x%lx/%pS\n", - __func__, __LINE__, - pc, ppc_function_entry((void *)pc), (void *)pc, - new_addr, ppc_function_entry((void *)new_addr), - (void *)ppc_function_entry((void *)new_addr)); - + do_patch(pc, (unsigned long)next_func->new_func, + &func_node->arch_data, func->old_mod); } - - flush_icache_range((unsigned long)pc, - (unsigned long)pc + LJMP_INSN_SIZE * PPC64_INSN_SIZE); }
/* return 0 if the func can be patched */