Commit eabc33c6 authored by Zheng Yejian's avatar Zheng Yejian
Browse files

livepatch/ppc64: Implement livepatch without ftrace for ppc64be

hulk inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I8MGE6



---------------------------

Initially completed the livepatch for ppc64be, the call from the old
function to the new function using stub space.

We call from old func to new func, when return form new func, we
need to restore R2. The previous module relocation was by adding
an extra nop space after the call (bxxx) instruction to restore R2,
but it is impossible to use extra space here, because we will not
return after calling new func, so we need to use a trampoline space.

We will call new func in trampoline and then restore R2 when we return.
Please note that we can also use old func as trampoline as a solution,
but we are afraid that old func often does not have that much space to
store trampoline instruction fragments.

The trampoline can be implemented as global. However we need to
implement a trampoline for each function and improve its stack
check.

Our call chain to the new function looks like this:

CALLER
        old_func        |       old_func
                        |       -=> trampoline
                        |               -=> new_func

So we can't simply check that new_func, old_func and trampoline are
both possible on the stack.

Signed-off-by: default avatarCheng Jian <cj.chengjian@huawei.com>
Signed-off-by: default avatarDong Kai <dongkai11@huawei.com>
Signed-off-by: default avatarYe Weihua <yeweihua4@huawei.com>
Signed-off-by: default avatarZheng Yejian <zhengyejian1@huawei.com>
parent e3350aa0
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -257,7 +257,7 @@ config PPC
	select HAVE_KRETPROBES
	select HAVE_LD_DEAD_CODE_DATA_ELIMINATION if HAVE_OBJTOOL_MCOUNT && (!ARCH_USING_PATCHABLE_FUNCTION_ENTRY || (!CC_IS_GCC || GCC_VERSION >= 110100))
	select HAVE_LIVEPATCH_FTRACE		if HAVE_DYNAMIC_FTRACE_WITH_REGS
	select HAVE_LIVEPATCH_WO_FTRACE		if PPC32
	select HAVE_LIVEPATCH_WO_FTRACE		if (PPC64 && CPU_BIG_ENDIAN && PPC64_ELF_ABI_V1) || PPC32
	select HAVE_MOD_ARCH_SPECIFIC
	select HAVE_NMI				if PERF_EVENTS || (PPC64 && PPC_BOOK3S)
	select HAVE_OPTPROBES
+48 −2
Original line number Diff line number Diff line
@@ -23,16 +23,62 @@ static inline void klp_init_thread_info(struct task_struct *p) { }

#ifdef CONFIG_LIVEPATCH_WO_FTRACE

struct klp_func;

#ifdef CONFIG_PPC64
/*
 * use the livepatch stub to jump to the trampoline.
 * It is similar to stub, but does not need to save
 * and load R2.
 * struct ppc64_klp_bstub_entry
 */
struct ppc64_klp_bstub_entry {
	u32 jump[5];
	u32 magic;
	/* address for livepatch trampoline  */
	u64 trampoline;
};

struct ppc64_klp_btramp_entry {
	u32 jump[18];
	u32 magic;
	union {
		func_desc_t funcdata;
		unsigned long saved_entry[3];
	};
};

#define PPC64_INSN_SIZE	4
#define LJMP_INSN_SIZE	(sizeof(struct ppc64_klp_bstub_entry) / PPC64_INSN_SIZE)

/* STUB_MAGIC 0x73747562 "stub" */
#define BRANCH_STUB_MAGIC	0x73747563 /* stub + 1	*/
#define BRANCH_TRAMPOLINE_MAGIC 0x73747564 /* stub + 2	*/
void livepatch_branch_stub(void);
void livepatch_branch_stub_end(void);
void livepatch_branch_trampoline(void);
void livepatch_branch_trampoline_end(void);

int livepatch_create_branch(unsigned long pc, unsigned long trampoline,
			    unsigned long addr, struct module *me);
struct klp_object;
int arch_klp_init_func(struct klp_object *obj, struct klp_func *func);
void *arch_klp_mem_alloc(size_t size);
void arch_klp_mem_free(void *mem);
#else /* !CONFIG_PPC64 */
#define PPC32_INSN_SIZE	4
#define LJMP_INSN_SIZE	4
#endif /* CONFIG_PPC64 */

struct arch_klp_data {
	u32 old_insns[LJMP_INSN_SIZE];
#ifdef CONFIG_PPC64
	struct ppc64_klp_btramp_entry trampoline;
#endif
};

#define KLP_MAX_REPLACE_SIZE sizeof_field(struct arch_klp_data, old_insns)

struct klp_func;

/* kernel livepatch instruction barrier */
#define klp_smp_isb()  __smp_lwsync()
int arch_klp_patch_func(struct klp_func *func);
+3 −0
Original line number Diff line number Diff line
@@ -38,6 +38,9 @@ struct mod_arch_specific {
	/* For module function descriptor dereference */
	unsigned long start_opd;
	unsigned long end_opd;
#ifdef CONFIG_LIVEPATCH_WO_FTRACE
	unsigned long toc;
#endif
#else /* powerpc64 */
	/* Indices of PLT sections within module. */
	unsigned int core_plt_section;
+1 −0
Original line number Diff line number Diff line
@@ -211,6 +211,7 @@ obj-$(CONFIG_ALTIVEC) += vector.o
obj-$(CONFIG_PPC_OF_BOOT_TRAMPOLINE) += prom_init.o
obj64-$(CONFIG_PPC_OF_BOOT_TRAMPOLINE) += prom_entry_64.o
extra-$(CONFIG_PPC_OF_BOOT_TRAMPOLINE) += prom_init_check
obj64-$(CONFIG_LIVEPATCH_WO_FTRACE) += livepatch_tramp.o

obj-$(CONFIG_PPC64)		+= $(obj64-y)
obj-$(CONFIG_PPC32)		+= $(obj32-y)
+264 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * livepatch.c - powerpc-specific Kernel Live Patching Core
 *
 * Copyright (C) 2018  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 <http://www.gnu.org/licenses/>.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/moduleloader.h>
#include <linux/uaccess.h>
#include <linux/livepatch.h>
#include <linux/slab.h>
#include <linux/sizes.h>
#include <linux/kallsyms.h>
#include <asm/livepatch.h>
#include <asm/cacheflush.h>
#include <asm/code-patching.h>
#include <asm/elf.h>

int arch_klp_check_activeness_func(struct klp_func *func, int enable,
				   klp_add_func_t add_func, struct list_head *func_list)
{
	int ret;
	unsigned long func_addr, func_size;
	struct klp_func_node *func_node = NULL;

	func_node = func->func_node;
	/* Check func address in stack */
	if (enable) {
		if (func->patched || func->force == KLP_ENFORCEMENT)
			return 0;
		/*
		 * When enable, checking the currently
		 * active functions.
		 */
		if (list_empty(&func_node->func_stack)) {
			/*
			 * No patched on this function
			 * [ the origin one ]
			 */
			func_addr = (unsigned long)func->old_func;
			func_size = func->old_size;
		} else {
			/*
			 * Previously patched function
			 * [ the active one ]
			 */
			struct klp_func *prev;

			prev = list_first_or_null_rcu(&func_node->func_stack,
						      struct klp_func, stack_node);
			func_addr = ppc_function_entry((void *)prev->new_func);
			func_size = prev->new_size;
		}
		/*
		 * When preemption is disabled and the
		 * replacement area does not contain a jump
		 * instruction, the migration thread is
		 * scheduled to run stop machine only after the
		 * excution of instructions to be repalced is
		 * complete.
		 */
		if (IS_ENABLED(CONFIG_PREEMPTION) ||
		    IS_ENABLED(CONFIG_LIVEPATCH_BREAKPOINT_NO_STOP_MACHINE) ||
		    (func->force == KLP_NORMAL_FORCE) ||
		    arch_check_jump_insn(func_addr)) {
			ret = add_func(func_list, func_addr, func_size,
				       func->old_name, func->force);
			if (ret)
				return ret;
		}
	} else {
		/*
		 * When disable, check for the function itself
		 * which to be unpatched.
		 */
		func_addr = ppc_function_entry((void *)func->new_func);
		func_size = func->new_size;
		ret = add_func(func_list, func_addr,
			       func_size, func->old_name, 0);
		if (ret)
			return ret;
	}

	/*
	 * Check trampoline in stack
	 * new_func callchain:
	 *	old_func
	 *	-=> trampoline
	 *	    -=> new_func
	 * so, we should check all the func in the callchain
	 */
	if (func_addr != (unsigned long)func->old_func) {
#ifdef CONFIG_PREEMPTION
		/*
		 * No scheduling point in the replacement
		 * instructions. Therefore, when preemption is
		 * not enabled, atomic execution is performed
		 * and these instructions will not appear on
		 * the stack.
		 */
		func_addr = (unsigned long)func->old_func;
		func_size = func->old_size;
		ret = add_func(func_list, func_addr,
			       func_size, "OLD_FUNC", 0);
		if (ret)
			return ret;
#endif /* CONFIG_PREEMPTION */

		if (func_node->arch_data.trampoline.magic != BRANCH_TRAMPOLINE_MAGIC)
			return 0;

		func_addr = (unsigned long)&func_node->arch_data.trampoline;
		func_size = sizeof(struct ppc64_klp_btramp_entry);
		ret = add_func(func_list, func_addr,
				func_size, "trampoline", 0);
		if (ret)
			return ret;
	}
	return 0;
}

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, PPC64_INSN_SIZE);
		if (ret)
			break;
	}
	return ret;
}

static int do_patch(unsigned long pc, unsigned long new_addr,
		    struct arch_klp_data *arch_data, struct module *old_mod)
{
	int ret;

	ret = livepatch_create_branch(pc, (unsigned long)&arch_data->trampoline,
				      new_addr, old_mod);
	if (ret) {
		pr_err("create branch failed, ret=%d\n", ret);
		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;
}

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,
		       &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;
	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;
		}

		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 {
		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,
			 &func_node->arch_data, func->old_mod);
	}
}

int arch_klp_init_func(struct klp_object *obj, struct klp_func *func)
{
	unsigned long new_addr = (unsigned long)func->new_func;

	/*
	 * ABI v1 address is address of the OPD entry,
	 * which contains address of fn. ABI v2 An address
	 * is simply the address of the function.
	 *
	 * The function descriptor is in the data section. So
	 * If new_addr is in the code segment, we think it is
	 * a function address, if addr isn't in the code segment,
	 * we consider it to be a function descriptor.
	 */
	if (!is_module_text_address(new_addr)) {
		new_addr = (unsigned long)ppc_function_entry((void *)new_addr);
		if (!kallsyms_lookup_size_offset((unsigned long)new_addr,
			&func->new_size, NULL))
			return -ENOENT;
	}

	func->this_mod = __module_text_address(new_addr);
	if (!func->this_mod)
		return -EINVAL;

	func->new_func_descr.addr = new_addr;
	func->new_func_descr.toc = func->this_mod->arch.toc;
	func->new_func_descr.env = 0;
	func->new_func = (void *)&func->new_func_descr;

	return 0;
}

/*
 * Trampoline would be stored in the allocated memory and it need
 * executable permission, so ppc64 use 'module_alloc' but not 'kmalloc'.
 */
void *arch_klp_mem_alloc(size_t size)
{
	void *mem = module_alloc(size);

	if (mem)
		memset(mem, 0, size);  /* initially clear the memory */
	return mem;
}

void arch_klp_mem_free(void *mem)
{
	module_memfree(mem);
}
Loading