Commit | Line | Data |
---|---|---|
620a53d5 SS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * functions to patch RO kernel text during runtime | |
4 | * | |
5 | * Copyright (c) 2019 Sven Schnelle <svens@stackframe.org> | |
6 | */ | |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <linux/spinlock.h> | |
10 | #include <linux/kprobes.h> | |
11 | #include <linux/mm.h> | |
12 | #include <linux/stop_machine.h> | |
13 | ||
14 | #include <asm/cacheflush.h> | |
15 | #include <asm/fixmap.h> | |
16 | #include <asm/patch.h> | |
17 | ||
18 | struct patch { | |
19 | void *addr; | |
20 | unsigned int insn; | |
21 | }; | |
22 | ||
ea5a8c62 | 23 | static void __kprobes *patch_map(void *addr, int fixmap) |
620a53d5 SS |
24 | { |
25 | unsigned long uintaddr = (uintptr_t) addr; | |
26 | bool module = !core_kernel_text(uintaddr); | |
27 | struct page *page; | |
28 | ||
29 | if (module && IS_ENABLED(CONFIG_STRICT_MODULE_RWX)) | |
30 | page = vmalloc_to_page(addr); | |
31 | else if (!module && IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) | |
32 | page = virt_to_page(addr); | |
33 | else | |
34 | return addr; | |
35 | ||
36 | set_fixmap(fixmap, page_to_phys(page)); | |
37 | ||
38 | return (void *) (__fix_to_virt(fixmap) + (uintaddr & ~PAGE_MASK)); | |
39 | } | |
40 | ||
ea5a8c62 | 41 | static void __kprobes patch_unmap(int fixmap) |
620a53d5 SS |
42 | { |
43 | clear_fixmap(fixmap); | |
44 | } | |
45 | ||
46 | void __kprobes __patch_text(void *addr, unsigned int insn) | |
47 | { | |
620a53d5 SS |
48 | void *waddr = addr; |
49 | int size; | |
50 | ||
ea5a8c62 | 51 | waddr = patch_map(addr, FIX_TEXT_POKE0); |
620a53d5 SS |
52 | *(u32 *)waddr = insn; |
53 | size = sizeof(u32); | |
54 | flush_kernel_vmap_range(waddr, size); | |
ea5a8c62 | 55 | patch_unmap(FIX_TEXT_POKE0); |
620a53d5 SS |
56 | flush_icache_range((uintptr_t)(addr), |
57 | (uintptr_t)(addr) + size); | |
58 | } | |
59 | ||
60 | static int __kprobes patch_text_stop_machine(void *data) | |
61 | { | |
62 | struct patch *patch = data; | |
63 | ||
64 | __patch_text(patch->addr, patch->insn); | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
69 | void __kprobes patch_text(void *addr, unsigned int insn) | |
70 | { | |
71 | struct patch patch = { | |
72 | .addr = addr, | |
73 | .insn = insn, | |
74 | }; | |
75 | ||
76 | stop_machine_cpuslocked(patch_text_stop_machine, &patch, NULL); | |
77 | } |