diff options
author | Jessica Yu <jeyu@redhat.com> | 2016-03-22 20:03:18 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2016-04-01 09:00:11 -0400 |
commit | 425595a7fc2096ab46c741b5ed5372c5ab5bbeac (patch) | |
tree | 9cf12348ba352e7679be0aadf370aa0a7bc5509e | |
parent | f31e0960f395df0c9197de53674365e2a28a129b (diff) |
livepatch: reuse module loader code to write relocations
Reuse module loader code to write relocations, thereby eliminating the need
for architecture specific relocation code in livepatch. Specifically, reuse
the apply_relocate_add() function in the module loader to write relocations
instead of duplicating functionality in livepatch's arch-dependent
klp_write_module_reloc() function.
In order to accomplish this, livepatch modules manage their own relocation
sections (marked with the SHF_RELA_LIVEPATCH section flag) and
livepatch-specific symbols (marked with SHN_LIVEPATCH symbol section
index). To apply livepatch relocation sections, livepatch symbols
referenced by relocs are resolved and then apply_relocate_add() is called
to apply those relocations.
In addition, remove x86 livepatch relocation code and the s390
klp_write_module_reloc() function stub. They are no longer needed since
relocation work has been offloaded to module loader.
Lastly, mark the module as a livepatch module so that the module loader
canappropriately identify and initialize it.
Signed-off-by: Jessica Yu <jeyu@redhat.com>
Reviewed-by: Miroslav Benes <mbenes@suse.cz>
Acked-by: Josh Poimboeuf <jpoimboe@redhat.com>
Acked-by: Heiko Carstens <heiko.carstens@de.ibm.com> # for s390 changes
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r-- | arch/s390/include/asm/livepatch.h | 7 | ||||
-rw-r--r-- | arch/x86/include/asm/livepatch.h | 2 | ||||
-rw-r--r-- | arch/x86/kernel/Makefile | 1 | ||||
-rw-r--r-- | arch/x86/kernel/livepatch.c | 70 | ||||
-rw-r--r-- | include/linux/livepatch.h | 20 | ||||
-rw-r--r-- | kernel/livepatch/core.c | 148 | ||||
-rw-r--r-- | samples/livepatch/livepatch-sample.c | 1 |
7 files changed, 95 insertions, 154 deletions
diff --git a/arch/s390/include/asm/livepatch.h b/arch/s390/include/asm/livepatch.h index d5427c78b1b3..2c1213785892 100644 --- a/arch/s390/include/asm/livepatch.h +++ b/arch/s390/include/asm/livepatch.h | |||
@@ -24,13 +24,6 @@ static inline int klp_check_compiler_support(void) | |||
24 | return 0; | 24 | return 0; |
25 | } | 25 | } |
26 | 26 | ||
27 | static inline int klp_write_module_reloc(struct module *mod, unsigned long | ||
28 | type, unsigned long loc, unsigned long value) | ||
29 | { | ||
30 | /* not supported yet */ | ||
31 | return -ENOSYS; | ||
32 | } | ||
33 | |||
34 | static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip) | 27 | static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip) |
35 | { | 28 | { |
36 | regs->psw.addr = ip; | 29 | regs->psw.addr = ip; |
diff --git a/arch/x86/include/asm/livepatch.h b/arch/x86/include/asm/livepatch.h index 7e68f9558552..a7f9181f63f3 100644 --- a/arch/x86/include/asm/livepatch.h +++ b/arch/x86/include/asm/livepatch.h | |||
@@ -32,8 +32,6 @@ static inline int klp_check_compiler_support(void) | |||
32 | #endif | 32 | #endif |
33 | return 0; | 33 | return 0; |
34 | } | 34 | } |
35 | int klp_write_module_reloc(struct module *mod, unsigned long type, | ||
36 | unsigned long loc, unsigned long value); | ||
37 | 35 | ||
38 | static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip) | 36 | static inline void klp_arch_set_pc(struct pt_regs *regs, unsigned long ip) |
39 | { | 37 | { |
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index b1b78ffe01d0..c5e9a5cf976b 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile | |||
@@ -67,7 +67,6 @@ obj-$(CONFIG_X86_MPPARSE) += mpparse.o | |||
67 | obj-y += apic/ | 67 | obj-y += apic/ |
68 | obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups_32.o | 68 | obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups_32.o |
69 | obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o | 69 | obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o |
70 | obj-$(CONFIG_LIVEPATCH) += livepatch.o | ||
71 | obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o | 70 | obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o |
72 | obj-$(CONFIG_FTRACE_SYSCALLS) += ftrace.o | 71 | obj-$(CONFIG_FTRACE_SYSCALLS) += ftrace.o |
73 | obj-$(CONFIG_X86_TSC) += trace_clock.o | 72 | obj-$(CONFIG_X86_TSC) += trace_clock.o |
diff --git a/arch/x86/kernel/livepatch.c b/arch/x86/kernel/livepatch.c deleted file mode 100644 index 92fc1a51f994..000000000000 --- a/arch/x86/kernel/livepatch.c +++ /dev/null | |||
@@ -1,70 +0,0 @@ | |||
1 | /* | ||
2 | * livepatch.c - x86-specific Kernel Live Patching Core | ||
3 | * | ||
4 | * Copyright (C) 2014 Seth Jennings <sjenning@redhat.com> | ||
5 | * Copyright (C) 2014 SUSE | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * as published by the Free Software Foundation; either version 2 | ||
10 | * of the License, or (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | |||
21 | #include <linux/module.h> | ||
22 | #include <linux/uaccess.h> | ||
23 | #include <asm/elf.h> | ||
24 | #include <asm/livepatch.h> | ||
25 | |||
26 | /** | ||
27 | * klp_write_module_reloc() - write a relocation in a module | ||
28 | * @mod: module in which the section to be modified is found | ||
29 | * @type: ELF relocation type (see asm/elf.h) | ||
30 | * @loc: address that the relocation should be written to | ||
31 | * @value: relocation value (sym address + addend) | ||
32 | * | ||
33 | * This function writes a relocation to the specified location for | ||
34 | * a particular module. | ||
35 | */ | ||
36 | int klp_write_module_reloc(struct module *mod, unsigned long type, | ||
37 | unsigned long loc, unsigned long value) | ||
38 | { | ||
39 | size_t size = 4; | ||
40 | unsigned long val; | ||
41 | unsigned long core = (unsigned long)mod->core_layout.base; | ||
42 | unsigned long core_size = mod->core_layout.size; | ||
43 | |||
44 | switch (type) { | ||
45 | case R_X86_64_NONE: | ||
46 | return 0; | ||
47 | case R_X86_64_64: | ||
48 | val = value; | ||
49 | size = 8; | ||
50 | break; | ||
51 | case R_X86_64_32: | ||
52 | val = (u32)value; | ||
53 | break; | ||
54 | case R_X86_64_32S: | ||
55 | val = (s32)value; | ||
56 | break; | ||
57 | case R_X86_64_PC32: | ||
58 | val = (u32)(value - loc); | ||
59 | break; | ||
60 | default: | ||
61 | /* unsupported relocation type */ | ||
62 | return -EINVAL; | ||
63 | } | ||
64 | |||
65 | if (loc < core || loc >= core + core_size) | ||
66 | /* loc does not point to any symbol inside the module */ | ||
67 | return -EINVAL; | ||
68 | |||
69 | return probe_kernel_write((void *)loc, &val, size); | ||
70 | } | ||
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index bd830d590465..0933ca47791c 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h | |||
@@ -65,27 +65,8 @@ struct klp_func { | |||
65 | }; | 65 | }; |
66 | 66 | ||
67 | /** | 67 | /** |
68 | * struct klp_reloc - relocation structure for live patching | ||
69 | * @loc: address where the relocation will be written | ||
70 | * @sympos: position in kallsyms to disambiguate symbols (optional) | ||
71 | * @type: ELF relocation type | ||
72 | * @name: name of the referenced symbol (for lookup/verification) | ||
73 | * @addend: offset from the referenced symbol | ||
74 | * @external: symbol is either exported or within the live patch module itself | ||
75 | */ | ||
76 | struct klp_reloc { | ||
77 | unsigned long loc; | ||
78 | unsigned long sympos; | ||
79 | unsigned long type; | ||
80 | const char *name; | ||
81 | int addend; | ||
82 | int external; | ||
83 | }; | ||
84 | |||
85 | /** | ||
86 | * struct klp_object - kernel object structure for live patching | 68 | * struct klp_object - kernel object structure for live patching |
87 | * @name: module name (or NULL for vmlinux) | 69 | * @name: module name (or NULL for vmlinux) |
88 | * @relocs: relocation entries to be applied at load time | ||
89 | * @funcs: function entries for functions to be patched in the object | 70 | * @funcs: function entries for functions to be patched in the object |
90 | * @kobj: kobject for sysfs resources | 71 | * @kobj: kobject for sysfs resources |
91 | * @mod: kernel module associated with the patched object | 72 | * @mod: kernel module associated with the patched object |
@@ -95,7 +76,6 @@ struct klp_reloc { | |||
95 | struct klp_object { | 76 | struct klp_object { |
96 | /* external */ | 77 | /* external */ |
97 | const char *name; | 78 | const char *name; |
98 | struct klp_reloc *relocs; | ||
99 | struct klp_func *funcs; | 79 | struct klp_func *funcs; |
100 | 80 | ||
101 | /* internal */ | 81 | /* internal */ |
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index d68fbf63b083..eb5db6e837aa 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c | |||
@@ -28,6 +28,8 @@ | |||
28 | #include <linux/list.h> | 28 | #include <linux/list.h> |
29 | #include <linux/kallsyms.h> | 29 | #include <linux/kallsyms.h> |
30 | #include <linux/livepatch.h> | 30 | #include <linux/livepatch.h> |
31 | #include <linux/elf.h> | ||
32 | #include <linux/moduleloader.h> | ||
31 | #include <asm/cacheflush.h> | 33 | #include <asm/cacheflush.h> |
32 | 34 | ||
33 | /** | 35 | /** |
@@ -204,75 +206,109 @@ static int klp_find_object_symbol(const char *objname, const char *name, | |||
204 | return -EINVAL; | 206 | return -EINVAL; |
205 | } | 207 | } |
206 | 208 | ||
207 | /* | 209 | static int klp_resolve_symbols(Elf_Shdr *relasec, struct module *pmod) |
208 | * external symbols are located outside the parent object (where the parent | ||
209 | * object is either vmlinux or the kmod being patched). | ||
210 | */ | ||
211 | static int klp_find_external_symbol(struct module *pmod, const char *name, | ||
212 | unsigned long *addr) | ||
213 | { | 210 | { |
214 | const struct kernel_symbol *sym; | 211 | int i, cnt, vmlinux, ret; |
215 | 212 | char objname[MODULE_NAME_LEN]; | |
216 | /* first, check if it's an exported symbol */ | 213 | char symname[KSYM_NAME_LEN]; |
217 | preempt_disable(); | 214 | char *strtab = pmod->core_kallsyms.strtab; |
218 | sym = find_symbol(name, NULL, NULL, true, true); | 215 | Elf_Rela *relas; |
219 | if (sym) { | 216 | Elf_Sym *sym; |
220 | *addr = sym->value; | 217 | unsigned long sympos, addr; |
221 | preempt_enable(); | ||
222 | return 0; | ||
223 | } | ||
224 | preempt_enable(); | ||
225 | 218 | ||
226 | /* | 219 | /* |
227 | * Check if it's in another .o within the patch module. This also | 220 | * Since the field widths for objname and symname in the sscanf() |
228 | * checks that the external symbol is unique. | 221 | * call are hard-coded and correspond to MODULE_NAME_LEN and |
222 | * KSYM_NAME_LEN respectively, we must make sure that MODULE_NAME_LEN | ||
223 | * and KSYM_NAME_LEN have the values we expect them to have. | ||
224 | * | ||
225 | * Because the value of MODULE_NAME_LEN can differ among architectures, | ||
226 | * we use the smallest/strictest upper bound possible (56, based on | ||
227 | * the current definition of MODULE_NAME_LEN) to prevent overflows. | ||
229 | */ | 228 | */ |
230 | return klp_find_object_symbol(pmod->name, name, 0, addr); | 229 | BUILD_BUG_ON(MODULE_NAME_LEN < 56 || KSYM_NAME_LEN != 128); |
230 | |||
231 | relas = (Elf_Rela *) relasec->sh_addr; | ||
232 | /* For each rela in this klp relocation section */ | ||
233 | for (i = 0; i < relasec->sh_size / sizeof(Elf_Rela); i++) { | ||
234 | sym = pmod->core_kallsyms.symtab + ELF_R_SYM(relas[i].r_info); | ||
235 | if (sym->st_shndx != SHN_LIVEPATCH) { | ||
236 | pr_err("symbol %s is not marked as a livepatch symbol", | ||
237 | strtab + sym->st_name); | ||
238 | return -EINVAL; | ||
239 | } | ||
240 | |||
241 | /* Format: .klp.sym.objname.symname,sympos */ | ||
242 | cnt = sscanf(strtab + sym->st_name, | ||
243 | ".klp.sym.%55[^.].%127[^,],%lu", | ||
244 | objname, symname, &sympos); | ||
245 | if (cnt != 3) { | ||
246 | pr_err("symbol %s has an incorrectly formatted name", | ||
247 | strtab + sym->st_name); | ||
248 | return -EINVAL; | ||
249 | } | ||
250 | |||
251 | /* klp_find_object_symbol() treats a NULL objname as vmlinux */ | ||
252 | vmlinux = !strcmp(objname, "vmlinux"); | ||
253 | ret = klp_find_object_symbol(vmlinux ? NULL : objname, | ||
254 | symname, sympos, &addr); | ||
255 | if (ret) | ||
256 | return ret; | ||
257 | |||
258 | sym->st_value = addr; | ||
259 | } | ||
260 | |||
261 | return 0; | ||
231 | } | 262 | } |
232 | 263 | ||
233 | static int klp_write_object_relocations(struct module *pmod, | 264 | static int klp_write_object_relocations(struct module *pmod, |
234 | struct klp_object *obj) | 265 | struct klp_object *obj) |
235 | { | 266 | { |
236 | int ret = 0; | 267 | int i, cnt, ret = 0; |
237 | unsigned long val; | 268 | const char *objname, *secname; |
238 | struct klp_reloc *reloc; | 269 | char sec_objname[MODULE_NAME_LEN]; |
270 | Elf_Shdr *sec; | ||
239 | 271 | ||
240 | if (WARN_ON(!klp_is_object_loaded(obj))) | 272 | if (WARN_ON(!klp_is_object_loaded(obj))) |
241 | return -EINVAL; | 273 | return -EINVAL; |
242 | 274 | ||
243 | if (WARN_ON(!obj->relocs)) | 275 | objname = klp_is_module(obj) ? obj->name : "vmlinux"; |
244 | return -EINVAL; | ||
245 | 276 | ||
246 | module_disable_ro(pmod); | 277 | module_disable_ro(pmod); |
278 | /* For each klp relocation section */ | ||
279 | for (i = 1; i < pmod->klp_info->hdr.e_shnum; i++) { | ||
280 | sec = pmod->klp_info->sechdrs + i; | ||
281 | secname = pmod->klp_info->secstrings + sec->sh_name; | ||
282 | if (!(sec->sh_flags & SHF_RELA_LIVEPATCH)) | ||
283 | continue; | ||
247 | 284 | ||
248 | for (reloc = obj->relocs; reloc->name; reloc++) { | 285 | /* |
249 | /* discover the address of the referenced symbol */ | 286 | * Format: .klp.rela.sec_objname.section_name |
250 | if (reloc->external) { | 287 | * See comment in klp_resolve_symbols() for an explanation |
251 | if (reloc->sympos > 0) { | 288 | * of the selected field width value. |
252 | pr_err("non-zero sympos for external reloc symbol '%s' is not supported\n", | 289 | */ |
253 | reloc->name); | 290 | cnt = sscanf(secname, ".klp.rela.%55[^.]", sec_objname); |
254 | ret = -EINVAL; | 291 | if (cnt != 1) { |
255 | goto out; | 292 | pr_err("section %s has an incorrectly formatted name", |
256 | } | 293 | secname); |
257 | ret = klp_find_external_symbol(pmod, reloc->name, &val); | 294 | ret = -EINVAL; |
258 | } else | 295 | break; |
259 | ret = klp_find_object_symbol(obj->name, | 296 | } |
260 | reloc->name, | 297 | |
261 | reloc->sympos, | 298 | if (strcmp(objname, sec_objname)) |
262 | &val); | 299 | continue; |
300 | |||
301 | ret = klp_resolve_symbols(sec, pmod); | ||
263 | if (ret) | 302 | if (ret) |
264 | goto out; | 303 | break; |
265 | 304 | ||
266 | ret = klp_write_module_reloc(pmod, reloc->type, reloc->loc, | 305 | ret = apply_relocate_add(pmod->klp_info->sechdrs, |
267 | val + reloc->addend); | 306 | pmod->core_kallsyms.strtab, |
268 | if (ret) { | 307 | pmod->klp_info->symndx, i, pmod); |
269 | pr_err("relocation failed for symbol '%s' at 0x%016lx (%d)\n", | 308 | if (ret) |
270 | reloc->name, val, ret); | 309 | break; |
271 | goto out; | ||
272 | } | ||
273 | } | 310 | } |
274 | 311 | ||
275 | out: | ||
276 | module_enable_ro(pmod); | 312 | module_enable_ro(pmod); |
277 | return ret; | 313 | return ret; |
278 | } | 314 | } |
@@ -703,11 +739,9 @@ static int klp_init_object_loaded(struct klp_patch *patch, | |||
703 | struct klp_func *func; | 739 | struct klp_func *func; |
704 | int ret; | 740 | int ret; |
705 | 741 | ||
706 | if (obj->relocs) { | 742 | ret = klp_write_object_relocations(patch->mod, obj); |
707 | ret = klp_write_object_relocations(patch->mod, obj); | 743 | if (ret) |
708 | if (ret) | 744 | return ret; |
709 | return ret; | ||
710 | } | ||
711 | 745 | ||
712 | klp_for_each_func(obj, func) { | 746 | klp_for_each_func(obj, func) { |
713 | ret = klp_find_object_symbol(obj->name, func->old_name, | 747 | ret = klp_find_object_symbol(obj->name, func->old_name, |
@@ -842,6 +876,12 @@ int klp_register_patch(struct klp_patch *patch) | |||
842 | { | 876 | { |
843 | int ret; | 877 | int ret; |
844 | 878 | ||
879 | if (!is_livepatch_module(patch->mod)) { | ||
880 | pr_err("module %s is not marked as a livepatch module", | ||
881 | patch->mod->name); | ||
882 | return -EINVAL; | ||
883 | } | ||
884 | |||
845 | if (!klp_initialized()) | 885 | if (!klp_initialized()) |
846 | return -ENODEV; | 886 | return -ENODEV; |
847 | 887 | ||
diff --git a/samples/livepatch/livepatch-sample.c b/samples/livepatch/livepatch-sample.c index fb8c8614e728..e34f871e69b1 100644 --- a/samples/livepatch/livepatch-sample.c +++ b/samples/livepatch/livepatch-sample.c | |||
@@ -89,3 +89,4 @@ static void livepatch_exit(void) | |||
89 | module_init(livepatch_init); | 89 | module_init(livepatch_init); |
90 | module_exit(livepatch_exit); | 90 | module_exit(livepatch_exit); |
91 | MODULE_LICENSE("GPL"); | 91 | MODULE_LICENSE("GPL"); |
92 | MODULE_INFO(livepatch, "Y"); | ||