/* * livepatch.c - x86-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 . */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include /* Apply per-object alternatives. Based on x86 module_finalize() */ void arch_klp_init_object_loaded(struct klp_patch *patch, struct klp_object *obj) { int cnt; struct klp_modinfo *info; Elf_Shdr *s, *alt = NULL, *para = NULL; void *aseg, *pseg; const char *objname; char sec_objname[MODULE_NAME_LEN]; char secname[KSYM_NAME_LEN]; info = patch->mod->klp_info; objname = obj->name ? obj->name : "vmlinux"; /* See livepatch core code for BUILD_BUG_ON() explanation */ BUILD_BUG_ON(MODULE_NAME_LEN < 56 || KSYM_NAME_LEN != 128); for (s = info->sechdrs; s < info->sechdrs + info->hdr.e_shnum; s++) { /* Apply per-object .klp.arch sections */ cnt = sscanf(info->secstrings + s->sh_name, ".klp.arch.%55[^.].%127s", sec_objname, secname); if (cnt != 2) continue; if (strcmp(sec_objname, objname)) continue; if (!strcmp(".altinstructions", secname)) alt = s; if (!strcmp(".parainstructions", secname)) para = s; } if (alt) { aseg = (void *) alt->sh_addr; apply_alternatives(aseg, aseg + alt->sh_size); } if (para) { pseg = (void *) para->sh_addr; apply_paravirt(pseg, pseg + para->sh_size); } } #ifdef CONFIG_LIVEPATCH_WO_FTRACE static inline int klp_compare_address(unsigned long stack_addr, unsigned long func_addr, unsigned long func_size, const char *func_name) { if (stack_addr >= func_addr && stack_addr < func_addr + func_size) { pr_err("func %s is in use!\n", func_name); return -EBUSY; } return 0; } static int klp_check_stack_func(struct klp_func *func, struct stack_trace *trace, int enable) { unsigned long func_addr, func_size, address; const char *func_name; int i; for (i = 0; i < trace->nr_entries; i++) { address = trace->entries[i]; if (enable) { if (func->force) continue; func_addr = func->old_addr; func_size = func->old_size; } else { func_addr = (unsigned long)func->new_func; func_size = func->new_size; } func_name = func->old_name; if (klp_compare_address(address, func_addr, func_size, func_name)) return -EAGAIN; } return 0; } static void klp_print_stack_trace(struct stack_trace *trace) { int i; pr_err("Call Trace:\n"); for (i = 0; i < trace->nr_entries; i++) { pr_err("[<%pK>] %pS\n", (void *)trace->entries[i], (void *)trace->entries[i]); } } #ifdef MAX_STACK_ENTRIES #undef MAX_STACK_ENTRIES #endif #define MAX_STACK_ENTRIES 100 /* * Determine whether it's safe to transition the task to the target patch state * by looking for any to-be-patched or to-be-unpatched functions on its stack. */ static int klp_check_stack(struct task_struct *task, struct klp_patch *patch, int enable) { static unsigned long entries[MAX_STACK_ENTRIES]; struct stack_trace trace; struct klp_object *obj; struct klp_func *func; int ret; trace.skip = 0; trace.nr_entries = 0; trace.max_entries = MAX_STACK_ENTRIES; trace.entries = entries; ret = save_stack_trace_tsk_reliable(task, &trace); WARN_ON_ONCE(ret == -ENOSYS); if (ret) { pr_info("%s: %s:%d has an unreliable stack\n", __func__, task->comm, task->pid); return ret; } klp_for_each_object(patch, obj) { klp_for_each_func(obj, func) { ret = klp_check_stack_func(func, &trace, enable); if (ret) { pr_info("%s: %s:%d is sleeping on function %s\n", __func__, task->comm, task->pid, func->old_name); klp_print_stack_trace(&trace); return ret; } } } return 0; } int klp_check_calltrace(struct klp_patch *patch, int enable) { struct task_struct *g, *t; int ret = 0; for_each_process_thread(g, t) { ret = klp_check_stack(t, patch, enable); if (ret) goto out; } out: return ret; } #include #include #include #define JMP_E9_INSN_SIZE 5 union klp_code_union { char code[JMP_E9_INSN_SIZE]; struct { unsigned char e9; int offset; } __packed; }; struct klp_func_node { struct list_head node; struct list_head func_stack; unsigned long old_addr; unsigned char old_code[JMP_E9_INSN_SIZE]; }; static LIST_HEAD(klp_func_list); static struct klp_func_node *klp_find_func_node(unsigned long old_addr) { struct klp_func_node *func_node; list_for_each_entry(func_node, &klp_func_list, node) { if (func_node->old_addr == old_addr) return func_node; } return NULL; } int arch_klp_init_func(struct klp_object *obj, struct klp_func *func) { return 0; } void arch_klp_free_func(struct klp_object *obj, struct klp_func *limit) { } static int klp_calc_offset(long pc, long addr) { return (int)(addr - pc); } static unsigned char *klp_jmp_code(unsigned long ip, unsigned long addr) { static union klp_code_union calc; calc.e9 = 0xe9; calc.offset = klp_calc_offset(ip + JMP_E9_INSN_SIZE, addr); return calc.code; } static unsigned char *klp_old_code(unsigned char *code) { static union klp_code_union old_code; strncpy(old_code.code, code, JMP_E9_INSN_SIZE); return old_code.code; } void arch_klp_code_modify_prepare(void) { set_kernel_text_rw(); set_all_modules_text_rw(); } void arch_klp_code_modify_post_process(void) { set_all_modules_text_ro(); set_kernel_text_ro(); } static inline int within(unsigned long addr, unsigned long start, unsigned long end) { return addr >= start && addr < end; } static unsigned long text_ip_addr(unsigned long ip) { if (within(ip, (unsigned long)_text, (unsigned long)_etext)) ip = (unsigned long)__va(__pa_symbol(ip)); return ip; } int arch_klp_patch_func(struct klp_func *func) { struct klp_func_node *func_node; unsigned long ip, new_addr; const unsigned char *new; func_node = klp_find_func_node(func->old_addr); ip = func->old_addr; if (!func_node) { func_node = kzalloc(sizeof(*func_node), GFP_ATOMIC); if (!func_node) return -ENOMEM; INIT_LIST_HEAD(&func_node->func_stack); func_node->old_addr = func->old_addr; probe_kernel_read(func_node->old_code, (void *)ip, JMP_E9_INSN_SIZE); list_add_rcu(&func_node->node, &klp_func_list); } list_add_rcu(&func->stack_node, &func_node->func_stack); new_addr = (unsigned long)func->new_func; new = klp_jmp_code(ip, new_addr); ip = text_ip_addr(ip); if (probe_kernel_write((void *)ip, new, JMP_E9_INSN_SIZE)) return -EPERM; sync_core(); 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; const unsigned char *new; func_node = klp_find_func_node(func->old_addr); ip = func_node->old_addr; if (list_is_singular(&func_node->func_stack)) { list_del_rcu(&func->stack_node); list_del_rcu(&func_node->node); new = klp_old_code(func_node->old_code); kfree(func_node); } 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; new = klp_jmp_code(ip, new_addr); } ip = text_ip_addr(ip); probe_kernel_write((void *)ip, new, JMP_E9_INSN_SIZE); sync_core(); } #endif