Unverified Commit 34397912 authored by openeuler-ci-bot's avatar openeuler-ci-bot Committed by Gitee
Browse files

!4084 riscv64: initial LIVEPATCH_WO_FTRACE support

Merge Pull Request from: @laokz 
 
This is a rebased commit of 2536#PR per the 6.6 kernel, see https://gitee.com/openeuler/kernel/pulls/2536

Build log at https://build.tarsier-infra.com/package/show/home:laokz:branches:openEuler:24.03/kernel

For 6.6 kernel, I tried apply and test the patch, it worked, such as livepatch-sample.c.

The PR already reported at kernel SIG 2023-11-10 conference, please see https://etherpad.openeuler.org/p/Kernel-meetings#L897

To adapt RISC-V specific feature, the PR touched one core code to let architecture to determine whether to do livepatch symbol relocation or not. For arches not having this issue, the changed code looks as a nop. Please see the code. 
 
Link:https://gitee.com/openeuler/kernel/pulls/4084

 

Reviewed-by: default avatarMingzheng Xing <xingmingzheng@iscas.ac.cn>
Signed-off-by: default avatarXie XiuQi <xiexiuqi@huawei.com>
parents ad0e1bdf 5e290522
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -163,6 +163,7 @@ config RISCV
	select TRACE_IRQFLAGS_SUPPORT
	select UACCESS_MEMCPY if !MMU
	select ZONE_DMA32 if 64BIT
	select HAVE_LIVEPATCH_WO_FTRACE

config CLANG_SUPPORTS_DYNAMIC_FTRACE
	def_bool CC_IS_CLANG
@@ -926,3 +927,4 @@ endmenu # "CPU Power Management"
source "arch/riscv/kvm/Kconfig"

source "drivers/acpi/Kconfig"
source "kernel/livepatch/Kconfig"
+6 −0
Original line number Diff line number Diff line
@@ -7383,3 +7383,9 @@ CONFIG_ARCH_USE_MEMTEST=y
# end of Kernel hacking

# CONFIG_KWORKER_NUMA_AFFINITY is not set

# enable openEuler livepatch
CONFIG_LIVEPATCH=y
CONFIG_LIVEPATCH_WO_FTRACE=y
CONFIG_SAMPLES=y
CONFIG_SAMPLE_LIVEPATCH=m
+67 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0
 *
 * Copyright (C) 2014-2023, Huawei.
 *	Author: Li Bin <huawei.libin@huawei.com>
 *	Author: Cheng Jian <cj.chengjian@huawei.com>
 *	Author: laokz <zhangkai@iscas.ac.cn>
 *
 * livepatch.h - riscv64-specific Kernel Live Patching Core
 *
 * 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/>.
 */

#ifndef _ASM_RISCV64_LIVEPATCH_H
#define _ASM_RISCV64_LIVEPATCH_H

#include <linux/module.h>
#include <linux/livepatch.h>
#include <asm/cacheflush.h>

/*
 * For arbitrary kernel address(PC offset might over/underflow +-2G), we
 * need store the new address and then use at least 3 instructors to jump
 * to. And with the alignment, we need 6 u32 space. Here C extension can't
 * help because it lacks scratch registers.
 *   0. 0x00000f97 // auipc t6,0x0
 *   1. 0x010fbf83 // ld    t6,16(t6) # t6 --> 4,5 new_addr
 *   2. 0x000f8067 // jr    t6
 *   3. not used
 *   4,5. new_addr
 */
#define LJMP_INSN_SIZE 6
struct arch_klp_data {
	u32 old_insns[LJMP_INSN_SIZE];
	/*
	 * Saved opcode at the entry of the old func (which maybe replaced
	 * with breakpoint).
	 */
	u32 saved_opcode;
};

#define KLP_MAX_REPLACE_SIZE sizeof_field(struct arch_klp_data, old_insns)

struct klp_func;

#define klp_smp_isb()  local_flush_icache_all()
int arch_klp_patch_func(struct klp_func *func);
void arch_klp_unpatch_func(struct klp_func *func);
long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func);
bool arch_check_jump_insn(unsigned long func_addr);
int arch_klp_check_calltrace(bool (*check_func)(void *, int *, unsigned long), void *data);
int arch_klp_add_breakpoint(struct arch_klp_data *arch_data, void *old_func);
void arch_klp_remove_breakpoint(struct arch_klp_data *arch_data, void *old_func);
int arch_klp_module_check_calltrace(void *data);
bool arch_klp_skip_resolve(unsigned int type);

#endif /* _ASM_RISCV64_LIVEPATCH_H */
+1 −0
Original line number Diff line number Diff line
@@ -103,3 +103,4 @@ obj-$(CONFIG_COMPAT) += compat_vdso/

obj-$(CONFIG_64BIT)		+= pi/
obj-$(CONFIG_ACPI)		+= acpi.o
obj-$(CONFIG_LIVEPATCH_WO_FTRACE)	+= livepatch.o
+262 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * livepatch.c - riscv64-specific Kernel Live Patching Core
 *
 * Copyright (C) 2014 Li Bin <huawei.libin@huawei.com>
 * Copyright (C) 2023 Zheng Yejian <zhengyejian1@huawei.com>
 * Copyright (C) 2023 laokz <zhangkai@iscas.ac.cn>
 *
 * 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/>.
 */

/* NOTE, only little-endian machine supported now. */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/uaccess.h>
#include <linux/livepatch.h>
#include <linux/stacktrace.h>
#include <linux/sched/debug.h>
#include <asm/livepatch.h>
#include <asm/patch.h>

#define CHECK_JUMP_RANGE LJMP_INSN_SIZE
#define LJMP_INSN0       0x00000f97
#define LJMP_INSN1       0x010fbf83
#define LJMP_INSN2       0x000f8067
#define NEW_ADDR_INDEX   4
#define EBREAK_INSN      0x00100073

/*
 * Check if any call instructor in the replace area.
 *
 * 31    20   19   15   14 12   11   7   6     0
 *             imm            |   rd   | 1101111    jal  (rd != 0)
 *    imm   |   rs1   |  000  |   rd   | 1100111    jalr (rd != 0)
 *
 * 15 13   12  11    7   6    2   1  0
 *  001  |         imm          |  01       c.jal
 *  100  | 1 |   rs1   | 00000  |  10       c.jalr (rs1 != 0)
 */
bool arch_check_jump_insn(unsigned long func_addr)
{
	unsigned long start = func_addr;
	unsigned long end = func_addr + CHECK_JUMP_RANGE * 4;
	short *i16;
	int *i32;

	while (start < end) {
		if ((*(char *)start & 0x3) == 3) { /* RV32I */
			i32 = (int *)start;      /* jal && rd != 0 */
			if (((*i32 & 0x7f) == 0x6f && (*i32 & 0xf80) != 0) ||
						/* jalr && rd != 0 */
			    ((*i32 & 0x707f) == 0x67 && (*i32 & 0xf80) != 0))
				return true;
			start += 4;
		} else { /* RVC */
			i16 = (short *)start;
			if ((*i16 & 0xe003) == 0x2001 ||                  /* c.jal */
			   ((*i16 & 0xf07f) == 0x9002 && *i16 != 0x9002)) /* c.jalr */
				return true;
			start += 2;
		}
	}
	return false;
}

static bool klp_check_jump_func(void *ws_args, unsigned long pc)
{
	struct walk_stackframe_args *args = ws_args;

	return args->check_func(args->data, &args->ret, pc);
}

static int check_task_calltrace(struct task_struct *t,
				struct walk_stackframe_args *args,
				bool (*fn)(void *, unsigned long))
{
	arch_stack_walk(fn, args, t, NULL);
	if (args->ret) {
		pr_info("PID: %d Comm: %.20s\n", t->pid, t->comm);
		show_stack(t, NULL, KERN_INFO);
		return args->ret;
	}
	return 0;
}

static int do_check_calltrace(struct walk_stackframe_args *args,
			      bool (*fn)(void *, unsigned long))
{
	int ret;
	struct task_struct *g, *t;
	unsigned int cpu;

	for_each_process_thread(g, t) {
		if (klp_is_migration_thread(t->comm))
			continue;
		ret = check_task_calltrace(t, args, fn);
		if (ret)
			return ret;
	}
	for_each_online_cpu(cpu) {
		ret = check_task_calltrace(idle_task(cpu), args, fn);
		if (ret)
			return ret;
	}
	return 0;
}

int arch_klp_check_calltrace(bool (*check_func)(void *, int *, unsigned long), void *data)
{
	struct walk_stackframe_args args = {
		.data = data,
		.ret = 0,
		.check_func = check_func,
	};

	return do_check_calltrace(&args, klp_check_jump_func);
}

long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func)
{
	long ret = copy_from_kernel_nofault(arch_data->old_insns, old_func, KLP_MAX_REPLACE_SIZE);

	if (ret)
		return ret;

	/* assume only we can set breakpoint to this patient */
	if (arch_data->old_insns[0] == EBREAK_INSN)
		arch_data->old_insns[0] = arch_data->saved_opcode;
	return 0;
}

static bool check_module_calltrace(void *ws_args, unsigned long pc)
{
	struct walk_stackframe_args *args = ws_args;
	struct module *mod = args->data;

	if (within_module_core(pc, mod)) {
		pr_err("module %s is in use!\n", mod->name);
		args->ret = -EBUSY;
		return false;
	}
	return true;
}

int arch_klp_module_check_calltrace(void *data)
{
	struct walk_stackframe_args args = {
		.data = data,
		.ret = 0
	};

	return do_check_calltrace(&args, check_module_calltrace);
}

int arch_klp_add_breakpoint(struct arch_klp_data *arch_data, void *old_func)
{
	u32 insn = EBREAK_INSN;

	arch_data->saved_opcode = *(u32 *)old_func;
	return patch_text(old_func, &insn, 1);
}

void arch_klp_remove_breakpoint(struct arch_klp_data *arch_data, void *old_func)
{
	patch_text(old_func, &arch_data->saved_opcode, 1);
}

static int do_patch(unsigned long pc, unsigned long new_addr)
{
	u32 insns[LJMP_INSN_SIZE] = {LJMP_INSN0, LJMP_INSN1, LJMP_INSN2};
	unsigned long *l = (unsigned long *)&insns[NEW_ADDR_INDEX];
	int ret;

	*l = new_addr;

	ret = patch_text_nosync((void *)pc, (void *)insns, KLP_MAX_REPLACE_SIZE);
	if (ret) {
		pr_err("patch instruction 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 = patch_text_nosync(func_node->old_func,
			(void *)func_node->arch_data.old_insns, KLP_MAX_REPLACE_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);
		if (WARN_ON(!next_func))
			return;
		do_patch(pc, (unsigned long)next_func->new_func);
	}
}

/*
 * Per the RISC-V psABI:
 *
 *   In the linker relaxation optimization, we introduce a concept
 *   called relocation group; a relocation group consists of 1)
 *   relocations associated with the same target symbol and can be
 *   applied with the same relaxation, or 2) relocations with the
 *   linkage relationship (e.g. `R_RISCV_PCREL_LO12_S` linked with
 *   a `R_RISCV_PCREL_HI20`); all relocations in a single group must
 *   be present in the same section, otherwise will split into another
 *   relocation group.
 *
 * When patches reference external non-exported globals, their
 * R_RISCV_PCREL_HI20/R_RISCV_PCREL_LO12_I relocations target the same
 * symbol and must live in the same section.
 *
 *   R_RISCV_PCREL_HI20 entry should be moved to .klp.rela.xxx section
 *                      when making patch, and when loading livepatch
 *                      core will resolve the target symbol address
 *   R_RISCV_PCREL_LO12_I should also be moved to .klp.rela.xxx section
 *                      when making patch, but when loading livepatch
 *                      core MUST ignore it because R_RISCV_PCREL_LO12_I
 *                      indeed is just a link to the R_RISCV_PCREL_HI20
 */
bool arch_klp_skip_resolve(unsigned int type)
{
	return (type == R_RISCV_PCREL_LO12_I || type == R_RISCV_PCREL_LO12_S);
}
Loading