Commit 583e6e55 authored by Zheng Yejian's avatar Zheng Yejian
Browse files

livepatch/x86: Support livepatch without ftrace

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


CVE: NA

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

support livepatch without ftrace for x86_64

supported now:
        livepatch relocation when init_patch after load_module;
        instruction patched when enable;
	activeness function check;
	enforcing the patch stacking principle;

x86_64 use variable length instruction, so there's no need to consider
extra implementation for long jumps.

Signed-off-by: default avatarCheng Jian <cj.chengjian@huawei.com>
Signed-off-by: default avatarLi Bin <huawei.libin@huawei.com>
Signed-off-by: default avatarWang ShaoBo <bobo.shaobowang@huawei.com>
Signed-off-by: default avatarYe Weihua <yeweihua4@huawei.com>
Signed-off-by: default avatarZheng Yejian <zhengyejian1@huawei.com>
parent b0d1bf27
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -502,9 +502,9 @@ CONFIG_LEGACY_VSYSCALL_XONLY=y
# CONFIG_CMDLINE_BOOL is not set
CONFIG_MODIFY_LDT_SYSCALL=y
# CONFIG_STRICT_SIGALTSTACK_SIZE is not set
CONFIG_HAVE_LIVEPATCH_FTRACE=y
CONFIG_HAVE_LIVEPATCH_WO_FTRACE=y
CONFIG_LIVEPATCH=y
CONFIG_LIVEPATCH_FTRACE=y
CONFIG_LIVEPATCH_WO_FTRACE=y
# end of Processor type and features

CONFIG_FUNCTION_PADDING_CFI=11
+33 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * livepatch.h - x86-specific Kernel Live Patching Core
 *
 * Copyright (C) 2023 Huawei.
 */

#ifndef _ASM_X86_LIVEPATCH_H
#define _ASM_X86_LIVEPATCH_H

#ifdef CONFIG_LIVEPATCH_WO_FTRACE

#define JMP_E9_INSN_SIZE 5
struct arch_klp_data {
	unsigned char old_insns[JMP_E9_INSN_SIZE];
};

#define KLP_MAX_REPLACE_SIZE sizeof_field(struct arch_klp_data, old_insns)

struct klp_func;

#define klp_smp_isb()
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);
void arch_klp_code_modify_prepare(void);
void arch_klp_code_modify_post_process(void);

#endif /* CONFIG_LIVEPATCH_WO_FTRACE */

#endif /* _ASM_X86_LIVEPATCH_H */
+1 −0
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ obj-$(CONFIG_SMP) += setup_percpu.o
obj-$(CONFIG_X86_MPPARSE)	+= mpparse.o
obj-y				+= apic/
obj-$(CONFIG_X86_REBOOTFIXUPS)	+= reboot_fixups_32.o
obj-$(CONFIG_LIVEPATCH_WO_FTRACE) += livepatch.o
obj-$(CONFIG_DYNAMIC_FTRACE)	+= ftrace.o
obj-$(CONFIG_FUNCTION_TRACER)	+= ftrace_$(BITS).o
obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o
+289 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * livepatch.c - x86-specific Kernel Live Patching Core
 *
 * Copyright (C) 2023 Huawei Inc.
 *
 * 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/kallsyms.h>
#include <linux/livepatch.h>
#include <linux/stacktrace.h>
#include <linux/memory.h>
#include <asm/text-patching.h>
#include <asm/stacktrace.h>
#include <asm/set_memory.h>
#include <asm/insn.h>

#include <linux/slab.h>
#include <asm/nops.h>
#include <asm/sections.h>
#include <linux/kprobes.h>

/*
 * The instruction set on x86 is CISC.
 * The instructions of call in same segment are 11101000(direct),
 * 11111111(register indirect) and 11111111(memory indirect).
 * The instructions of call in other segment are 10011010(direct),
 * 11111111(indirect).
 */
static bool is_jump_insn(u8 *insn)
{
	if ((insn[0] == 0xE8) || (insn[0] == 0x9a))
		return true;
	else if ((insn[0] == 0xFF) && ((insn[1] & 0x30) == 0x10))
		return true;
	return false;
}

bool arch_check_jump_insn(unsigned long func_addr)
{
	int len = JMP_E9_INSN_SIZE;
	struct insn insn;
	u8 *addr = (u8 *)func_addr;

	do {
		if (is_jump_insn(addr))
			return true;
		insn_init(&insn, addr, MAX_INSN_SIZE, 1);
		insn_get_length(&insn);
		if (!insn.length || !insn_complete(&insn))
			return true;
		len -= insn.length;
		addr += insn.length;
	} while (len > 0);

	return false;
}

static void klp_print_stack_trace(void *trace_ptr, int trace_len)
{
	int i;
#ifdef CONFIG_ARCH_STACKWALK
	unsigned long *trace = trace_ptr;
#else
	struct stack_trace *trace = trace_ptr;
#endif

	pr_err("Call Trace:\n");
#ifdef CONFIG_ARCH_STACKWALK
	for (i = 0; i < trace_len; i++) {
		pr_err("[<%pK>] %pS\n",
		(void *)trace[i],
		(void *)trace[i]);
	}
#else
	for (i = 0; i < trace->nr_entries; i++) {
		pr_err("[<%pK>] %pS\n",
		(void *)trace->entries[i],
		(void *)trace->entries[i]);
	}
#endif

}

#ifdef MAX_STACK_ENTRIES
#undef MAX_STACK_ENTRIES
#endif
#define MAX_STACK_ENTRIES  100

static int klp_check_stack(void *trace_ptr, int trace_len,
			   bool (*fn)(void *, int *, unsigned long), void *data)
{
#ifdef CONFIG_ARCH_STACKWALK
	unsigned long *trace = trace_ptr;
#else
	struct stack_trace *trace = trace_ptr;
#endif
	unsigned long address;
	int i, ret;

#ifdef CONFIG_ARCH_STACKWALK
	for (i = 0; i < trace_len; i++) {
		address = trace[i];
#else
	for (i = 0; i < trace->nr_entries; i++) {
		address = trace->entries[i];
#endif
		if (!fn(data, &ret, address)) {
#ifdef CONFIG_ARCH_STACKWALK
			klp_print_stack_trace(trace_ptr, trace_len);
#else
			klp_print_stack_trace(trace_ptr, 0);
#endif
			return ret;
		}
	}

	return 0;
}

static int check_task_calltrace(struct task_struct *t,
				bool (*fn)(void *, int *, unsigned long),
				void *data)
{
	int ret = 0;
	static unsigned long trace_entries[MAX_STACK_ENTRIES];
#ifdef CONFIG_ARCH_STACKWALK
	int trace_len;
#else
	struct stack_trace trace;
#endif

#ifdef CONFIG_ARCH_STACKWALK
	ret = stack_trace_save_tsk_reliable(t, trace_entries, MAX_STACK_ENTRIES);
	if (ret < 0) {
		pr_err("%s:%d has an unreliable stack, ret=%d\n",
		       t->comm, t->pid, ret);
		return ret;
	}
	trace_len = ret;
	ret = klp_check_stack(trace_entries, trace_len, fn, data);
#else
	trace.skip = 0;
	trace.nr_entries = 0;
	trace.max_entries = MAX_STACK_ENTRIES;
	trace.entries = trace_entries;
	ret = save_stack_trace_tsk_reliable(t, &trace);
	if (ret) {
		pr_err("%s: %s:%d has an unreliable stack, ret=%d\n",
		       __func__, t->comm, t->pid, ret);
		return ret;
	}
	ret = klp_check_stack(&trace, 0, fn, data);
#endif
	if (ret) {
		pr_err("%s:%d check stack failed, ret=%d\n",
		       t->comm, t->pid, ret);
		return ret;
	}
	return 0;
}

static int do_check_calltrace(bool (*fn)(void *, int *, unsigned long), void *data)
{
	int ret = 0;
	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, fn, data);
		if (ret)
			return ret;
	}
	for_each_online_cpu(cpu) {
		ret = check_task_calltrace(idle_task(cpu), fn, data);
		if (ret)
			return ret;
	}
	return 0;
}

int arch_klp_check_calltrace(bool (*check_func)(void *, int *, unsigned long), void *data)
{
	return do_check_calltrace(check_func, data);
}

void arch_klp_code_modify_prepare(void)
	__acquires(&text_mutex)
{
	mutex_lock(&text_mutex);
}

void arch_klp_code_modify_post_process(void)
	__releases(&text_mutex)
{
	text_poke_sync();
	mutex_unlock(&text_mutex);
}

long arch_klp_save_old_code(struct arch_klp_data *arch_data, void *old_func)
{
	long ret;

	/* Prevent text modification */
	mutex_lock(&text_mutex);
	ret = copy_from_kernel_nofault(arch_data->old_insns,
			old_func, JMP_E9_INSN_SIZE);
	mutex_unlock(&text_mutex);

	return ret;
}

static void klp_patch_text(void *dst, const void *src, int len)
{
	if (len <= 1)
		return;
	/* skip breakpoint at first */
	text_poke(dst + 1, src + 1, len - 1);
	/*
	 * Avoid compile optimization, make sure that instructions
	 * except first breakpoint has been patched.
	 */
	barrier();
	/* update jmp opcode */
	text_poke(dst, src, 1);
}

static void *klp_jmp_code(unsigned long ip, unsigned long addr)
{
	return text_gen_insn(JMP32_INSN_OPCODE, (void *)ip, (void *)addr);
}

int arch_klp_patch_func(struct klp_func *func)
{
	struct klp_func_node *func_node;
	unsigned long ip, new_addr;
	unsigned char *new;

	func_node = func->func_node;
	ip = (unsigned long)func->old_func;
	list_add_rcu(&func->stack_node, &func_node->func_stack);
	new_addr = (unsigned long)func->new_func;
	/* replace the text with the new text */
	new = (unsigned char *)klp_jmp_code(ip, new_addr);
	klp_patch_text((void *)ip, (const void *)new, JMP_E9_INSN_SIZE);
	return 0;
}

void arch_klp_unpatch_func(struct klp_func *func)
{
	struct klp_func_node *func_node;
	struct klp_func *next_func;
	unsigned long ip, new_addr;
	void *new;

	func_node = func->func_node;
	ip = (unsigned long)func_node->old_func;
	list_del_rcu(&func->stack_node);
	if (list_empty(&func_node->func_stack)) {
		new = func_node->arch_data.old_insns;
	} else {
		next_func = list_first_or_null_rcu(&func_node->func_stack,
						struct klp_func, stack_node);

		new_addr = (unsigned long)next_func->new_func;
		new = klp_jmp_code(ip, new_addr);
	}

	/* replace the text with the new text */
	klp_patch_text((void *)ip, (const void *)new, JMP_E9_INSN_SIZE);
}
+67 −22
Original line number Diff line number Diff line
@@ -17,11 +17,17 @@

#if IS_ENABLED(CONFIG_LIVEPATCH)

#include <asm/livepatch.h>

/* task patch states */
#define KLP_UNDEFINED	-1
#define KLP_UNPATCHED	 0
#define KLP_PATCHED	 1

#define KLP_NORMAL_FORCE	0
#define KLP_ENFORCEMENT		1
#define KLP_STACK_OPTIMIZE	2

/**
 * struct klp_func - function structure for live patching
 * @old_name:	name of the function to be patched
@@ -65,6 +71,7 @@ struct klp_func {
	 * in kallsyms for the given object is used.
	 */
	unsigned long old_sympos;
	int force; /* Only used in the solution without ftrace */

	/* internal */
	void *old_func;
@@ -72,16 +79,14 @@ struct klp_func {
	struct list_head node;
	struct list_head stack_node;
	unsigned long old_size, new_size;
#ifdef CONFIG_LIVEPATCH_FTRACE
	bool nop;
#endif
	bool nop; /* Not used in the solution without ftrace */
	bool patched;
#ifdef CONFIG_LIVEPATCH_FTRACE
	bool transition;
#endif
	void *func_node; /* Only used in the solution without ftrace */
};

#ifdef CONFIG_LIVEPATCH_FTRACE
struct klp_object;

/**
@@ -105,7 +110,6 @@ struct klp_callbacks {
	void (*post_unpatch)(struct klp_object *obj);
	bool post_unpatch_enabled;
};
#endif /* CONFIG_LIVEPATCH_FTRACE */

/**
 * struct klp_object - kernel object structure for live patching
@@ -124,22 +128,17 @@ struct klp_object {
	/* external */
	const char *name;
	struct klp_func *funcs;
#ifdef CONFIG_LIVEPATCH_FTRACE
	struct klp_callbacks callbacks;
#endif
	struct klp_callbacks callbacks; /* Not used in the solution without ftrace */

	/* internal */
	struct kobject kobj;
	struct list_head func_list;
	struct list_head node;
	struct module *mod;
#ifdef CONFIG_LIVEPATCH_FTRACE
	bool dynamic;
#endif
	bool dynamic; /* Not used in the solution without ftrace */
	bool patched;
};

#ifdef CONFIG_LIVEPATCH_FTRACE
/**
 * struct klp_state - state of the system modified by the livepatch
 * @id:		system state identifier (non-zero)
@@ -151,7 +150,6 @@ struct klp_state {
	unsigned int version;
	void *data;
};
#endif /* CONFIG_LIVEPATCH_FTRACE */

/**
 * struct klp_patch - patch structure for live patching
@@ -171,20 +169,16 @@ struct klp_patch {
	/* external */
	struct module *mod;
	struct klp_object *objs;
#ifdef CONFIG_LIVEPATCH_FTRACE
	struct klp_state *states;
	bool replace;
#endif
	struct klp_state *states; /* Not used in the solution without ftrace */
	bool replace; /* Not used in the solution without ftrace */

	/* internal */
	struct list_head list;
	struct kobject kobj;
	struct list_head obj_list;
	bool enabled;
#ifdef CONFIG_LIVEPATCH_FTRACE
	bool forced;
#endif
	struct work_struct free_work;
	bool forced; /* Not used in the solution without ftrace */
	struct work_struct free_work; /* Not used in the solution without ftrace */
	struct completion finish;
};

@@ -208,9 +202,9 @@ struct klp_patch {
#define klp_for_each_func(obj, func)	\
	list_for_each_entry(func, &obj->func_list, node)

#ifdef CONFIG_LIVEPATCH_FTRACE
int klp_enable_patch(struct klp_patch *);

#ifdef CONFIG_LIVEPATCH_FTRACE
/* Called from the module loader during module coming/going states */
int klp_module_coming(struct module *mod);
void klp_module_going(struct module *mod);
@@ -249,6 +243,57 @@ struct klp_state *klp_get_prev_state(unsigned long id);

#else /* !CONFIG_LIVEPATCH_FTRACE */

struct klp_func_node {
	struct list_head node;
	struct list_head func_stack;
	void *old_func;
	struct arch_klp_data arch_data;
};

static inline
int klp_compare_address(unsigned long pc, unsigned long func_addr,
			const char *func_name, unsigned long check_size)
{
	if (pc >= func_addr && pc < func_addr + check_size) {
		pr_warn("func %s is in use!\n", func_name);
		/* Return -EAGAIN for next retry */
		return -EAGAIN;
	}
	return 0;
}

typedef int (*klp_add_func_t)(struct list_head *func_list,
			       unsigned long func_addr, unsigned long func_size,
			       const char *func_name, int force);

struct walk_stackframe_args {
	void *data;
	int ret;
	bool (*check_func)(void *data, int *ret, unsigned long pc);
};

#ifndef klp_smp_isb
#define klp_smp_isb()
#endif

#define KLP_MIGRATION_NAME_PREFIX	"migration/"
static inline bool klp_is_migration_thread(const char *task_name)
{
	/*
	 * current on other CPU
	 * we call this in stop_machine, so the current
	 * of each CPUs is migration, just compare the
	 * task_comm here, because we can't get the
	 * cpu_curr(task_cpu(t))). This assumes that no
	 * other thread will pretend to be a stopper via
	 * task_comm.
	 */
	return !strncmp(task_name, KLP_MIGRATION_NAME_PREFIX,
			sizeof(KLP_MIGRATION_NAME_PREFIX) - 1);
}

int klp_register_patch(struct klp_patch *patch);
int klp_unregister_patch(struct klp_patch *patch);
static inline int klp_module_coming(struct module *mod) { return 0; }
static inline void klp_module_going(struct module *mod) {}
static inline bool klp_patch_pending(struct task_struct *task) { return false; }
Loading