// SPDX-License-Identifier: GPL-2.0-or-later /* * livepatch.c - powerpc-specific Kernel Live Patching Core * * Copyright (C) 2018 Huawei Technologies Co., Ltd. * Copyright (C) 2023 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, see . */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include static inline bool offset_in_range(unsigned long pc, unsigned long addr, long range) { long offset = addr - pc; return (offset >= -range && offset < range); } long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func) { long ret; int i; for (i = 0; i < LJMP_INSN_SIZE; i++) { ret = copy_from_kernel_nofault(&arch_data->old_insns[i], ((u32 *)old_func) + i, PPC32_INSN_SIZE); if (ret) break; } return ret; } static int do_patch(unsigned long pc, unsigned long new_addr) { int ret; u32 insns[LJMP_INSN_SIZE]; if (offset_in_range(pc, new_addr, SZ_32M)) { ppc_inst_t instr; create_branch(&instr, (ppc_inst_t *)pc, new_addr, 0); ret = patch_instruction((ppc_inst_t *)pc, instr); if (ret) { pr_err("patch instruction small range failed, ret=%d\n", ret); return -EPERM; } } 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; ret = klp_patch_text((u32 *)pc, insns, LJMP_INSN_SIZE); if (ret) { pr_err("patch instruction large range failed, ret=%d\n", ret); return -EPERM; } } 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; int ret; func_node = func->func_node; pc = (unsigned long)func_node->old_func; list_del_rcu(&func->stack_node); if (list_empty(&func_node->func_stack)) { ret = klp_patch_text((u32 *)pc, func_node->arch_data.old_insns, LJMP_INSN_SIZE); if (ret) { pr_err("restore instruction failed, ret=%d\n", ret); return; } } else { next_func = list_first_or_null_rcu(&func_node->func_stack, struct klp_func, stack_node); do_patch(pc, (unsigned long)next_func->new_func); } }