diff options
author | Rabin Vincent <rabin@rab.in> | 2014-04-24 17:28:57 -0400 |
---|---|---|
committer | Kees Cook <keescook@chromium.org> | 2014-10-16 17:38:53 -0400 |
commit | ab0615e2d6fb074764a3e4d05f1326fa2fdb4627 (patch) | |
tree | 50f160291b6102404b8482c29247099eaf1e68a8 | |
parent | 99b4ac9afce4129323b5b4c7002a942a9489914c (diff) |
arm: use fixmap for text patching when text is RO
Use fixmaps for text patching when the kernel text is read-only,
inspired by x86. This makes jump labels and kprobes work with the
currently available CONFIG_DEBUG_SET_MODULE_RONX and the upcoming
CONFIG_DEBUG_RODATA options.
Signed-off-by: Rabin Vincent <rabin@rab.in>
[kees: fixed up for merge with "arm: use generic fixmap.h"]
[kees: added parse acquire/release annotations to pass C=1 builds]
[kees: always use stop_machine to keep TLB flushing local]
Signed-off-by: Kees Cook <keescook@chromium.org>
Acked-by: Nicolas Pitre <nico@linaro.org>
-rw-r--r-- | arch/arm/include/asm/fixmap.h | 4 | ||||
-rw-r--r-- | arch/arm/kernel/jump_label.c | 2 | ||||
-rw-r--r-- | arch/arm/kernel/patch.c | 92 | ||||
-rw-r--r-- | arch/arm/kernel/patch.h | 12 |
4 files changed, 89 insertions, 21 deletions
diff --git a/arch/arm/include/asm/fixmap.h b/arch/arm/include/asm/fixmap.h index 714606f70425..0415eae1df27 100644 --- a/arch/arm/include/asm/fixmap.h +++ b/arch/arm/include/asm/fixmap.h | |||
@@ -11,6 +11,10 @@ enum fixed_addresses { | |||
11 | FIX_KMAP_BEGIN, | 11 | FIX_KMAP_BEGIN, |
12 | FIX_KMAP_END = FIX_KMAP_BEGIN + (KM_TYPE_NR * NR_CPUS) - 1, | 12 | FIX_KMAP_END = FIX_KMAP_BEGIN + (KM_TYPE_NR * NR_CPUS) - 1, |
13 | 13 | ||
14 | /* Support writing RO kernel text via kprobes, jump labels, etc. */ | ||
15 | FIX_TEXT_POKE0, | ||
16 | FIX_TEXT_POKE1, | ||
17 | |||
14 | __end_of_fixed_addresses | 18 | __end_of_fixed_addresses |
15 | }; | 19 | }; |
16 | 20 | ||
diff --git a/arch/arm/kernel/jump_label.c b/arch/arm/kernel/jump_label.c index 4ce4f789446d..afeeb9ea6f43 100644 --- a/arch/arm/kernel/jump_label.c +++ b/arch/arm/kernel/jump_label.c | |||
@@ -19,7 +19,7 @@ static void __arch_jump_label_transform(struct jump_entry *entry, | |||
19 | insn = arm_gen_nop(); | 19 | insn = arm_gen_nop(); |
20 | 20 | ||
21 | if (is_static) | 21 | if (is_static) |
22 | __patch_text(addr, insn); | 22 | __patch_text_early(addr, insn); |
23 | else | 23 | else |
24 | patch_text(addr, insn); | 24 | patch_text(addr, insn); |
25 | } | 25 | } |
diff --git a/arch/arm/kernel/patch.c b/arch/arm/kernel/patch.c index 07314af47733..5038960e3c55 100644 --- a/arch/arm/kernel/patch.c +++ b/arch/arm/kernel/patch.c | |||
@@ -1,8 +1,11 @@ | |||
1 | #include <linux/kernel.h> | 1 | #include <linux/kernel.h> |
2 | #include <linux/spinlock.h> | ||
2 | #include <linux/kprobes.h> | 3 | #include <linux/kprobes.h> |
4 | #include <linux/mm.h> | ||
3 | #include <linux/stop_machine.h> | 5 | #include <linux/stop_machine.h> |
4 | 6 | ||
5 | #include <asm/cacheflush.h> | 7 | #include <asm/cacheflush.h> |
8 | #include <asm/fixmap.h> | ||
6 | #include <asm/smp_plat.h> | 9 | #include <asm/smp_plat.h> |
7 | #include <asm/opcodes.h> | 10 | #include <asm/opcodes.h> |
8 | 11 | ||
@@ -13,21 +16,77 @@ struct patch { | |||
13 | unsigned int insn; | 16 | unsigned int insn; |
14 | }; | 17 | }; |
15 | 18 | ||
16 | void __kprobes __patch_text(void *addr, unsigned int insn) | 19 | static DEFINE_SPINLOCK(patch_lock); |
20 | |||
21 | static void __kprobes *patch_map(void *addr, int fixmap, unsigned long *flags) | ||
22 | __acquires(&patch_lock) | ||
23 | { | ||
24 | unsigned int uintaddr = (uintptr_t) addr; | ||
25 | bool module = !core_kernel_text(uintaddr); | ||
26 | struct page *page; | ||
27 | |||
28 | if (module && IS_ENABLED(CONFIG_DEBUG_SET_MODULE_RONX)) | ||
29 | page = vmalloc_to_page(addr); | ||
30 | else if (!module && IS_ENABLED(CONFIG_DEBUG_RODATA)) | ||
31 | page = virt_to_page(addr); | ||
32 | else | ||
33 | return addr; | ||
34 | |||
35 | if (flags) | ||
36 | spin_lock_irqsave(&patch_lock, *flags); | ||
37 | else | ||
38 | __acquire(&patch_lock); | ||
39 | |||
40 | set_fixmap(fixmap, page_to_phys(page)); | ||
41 | |||
42 | return (void *) (__fix_to_virt(fixmap) + (uintaddr & ~PAGE_MASK)); | ||
43 | } | ||
44 | |||
45 | static void __kprobes patch_unmap(int fixmap, unsigned long *flags) | ||
46 | __releases(&patch_lock) | ||
47 | { | ||
48 | clear_fixmap(fixmap); | ||
49 | |||
50 | if (flags) | ||
51 | spin_unlock_irqrestore(&patch_lock, *flags); | ||
52 | else | ||
53 | __release(&patch_lock); | ||
54 | } | ||
55 | |||
56 | void __kprobes __patch_text_real(void *addr, unsigned int insn, bool remap) | ||
17 | { | 57 | { |
18 | bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL); | 58 | bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL); |
59 | unsigned int uintaddr = (uintptr_t) addr; | ||
60 | bool twopage = false; | ||
61 | unsigned long flags; | ||
62 | void *waddr = addr; | ||
19 | int size; | 63 | int size; |
20 | 64 | ||
65 | if (remap) | ||
66 | waddr = patch_map(addr, FIX_TEXT_POKE0, &flags); | ||
67 | else | ||
68 | __acquire(&patch_lock); | ||
69 | |||
21 | if (thumb2 && __opcode_is_thumb16(insn)) { | 70 | if (thumb2 && __opcode_is_thumb16(insn)) { |
22 | *(u16 *)addr = __opcode_to_mem_thumb16(insn); | 71 | *(u16 *)waddr = __opcode_to_mem_thumb16(insn); |
23 | size = sizeof(u16); | 72 | size = sizeof(u16); |
24 | } else if (thumb2 && ((uintptr_t)addr & 2)) { | 73 | } else if (thumb2 && (uintaddr & 2)) { |
25 | u16 first = __opcode_thumb32_first(insn); | 74 | u16 first = __opcode_thumb32_first(insn); |
26 | u16 second = __opcode_thumb32_second(insn); | 75 | u16 second = __opcode_thumb32_second(insn); |
27 | u16 *addrh = addr; | 76 | u16 *addrh0 = waddr; |
77 | u16 *addrh1 = waddr + 2; | ||
78 | |||
79 | twopage = (uintaddr & ~PAGE_MASK) == PAGE_SIZE - 2; | ||
80 | if (twopage && remap) | ||
81 | addrh1 = patch_map(addr + 2, FIX_TEXT_POKE1, NULL); | ||
82 | |||
83 | *addrh0 = __opcode_to_mem_thumb16(first); | ||
84 | *addrh1 = __opcode_to_mem_thumb16(second); | ||
28 | 85 | ||
29 | addrh[0] = __opcode_to_mem_thumb16(first); | 86 | if (twopage && addrh1 != addr + 2) { |
30 | addrh[1] = __opcode_to_mem_thumb16(second); | 87 | flush_kernel_vmap_range(addrh1, 2); |
88 | patch_unmap(FIX_TEXT_POKE1, NULL); | ||
89 | } | ||
31 | 90 | ||
32 | size = sizeof(u32); | 91 | size = sizeof(u32); |
33 | } else { | 92 | } else { |
@@ -36,10 +95,16 @@ void __kprobes __patch_text(void *addr, unsigned int insn) | |||
36 | else | 95 | else |
37 | insn = __opcode_to_mem_arm(insn); | 96 | insn = __opcode_to_mem_arm(insn); |
38 | 97 | ||
39 | *(u32 *)addr = insn; | 98 | *(u32 *)waddr = insn; |
40 | size = sizeof(u32); | 99 | size = sizeof(u32); |
41 | } | 100 | } |
42 | 101 | ||
102 | if (waddr != addr) { | ||
103 | flush_kernel_vmap_range(waddr, twopage ? size / 2 : size); | ||
104 | patch_unmap(FIX_TEXT_POKE0, &flags); | ||
105 | } else | ||
106 | __release(&patch_lock); | ||
107 | |||
43 | flush_icache_range((uintptr_t)(addr), | 108 | flush_icache_range((uintptr_t)(addr), |
44 | (uintptr_t)(addr) + size); | 109 | (uintptr_t)(addr) + size); |
45 | } | 110 | } |
@@ -60,16 +125,5 @@ void __kprobes patch_text(void *addr, unsigned int insn) | |||
60 | .insn = insn, | 125 | .insn = insn, |
61 | }; | 126 | }; |
62 | 127 | ||
63 | if (cache_ops_need_broadcast()) { | 128 | stop_machine(patch_text_stop_machine, &patch, NULL); |
64 | stop_machine(patch_text_stop_machine, &patch, cpu_online_mask); | ||
65 | } else { | ||
66 | bool straddles_word = IS_ENABLED(CONFIG_THUMB2_KERNEL) | ||
67 | && __opcode_is_thumb32(insn) | ||
68 | && ((uintptr_t)addr & 2); | ||
69 | |||
70 | if (straddles_word) | ||
71 | stop_machine(patch_text_stop_machine, &patch, NULL); | ||
72 | else | ||
73 | __patch_text(addr, insn); | ||
74 | } | ||
75 | } | 129 | } |
diff --git a/arch/arm/kernel/patch.h b/arch/arm/kernel/patch.h index b4731f2dac38..77e054c2f6cd 100644 --- a/arch/arm/kernel/patch.h +++ b/arch/arm/kernel/patch.h | |||
@@ -2,6 +2,16 @@ | |||
2 | #define _ARM_KERNEL_PATCH_H | 2 | #define _ARM_KERNEL_PATCH_H |
3 | 3 | ||
4 | void patch_text(void *addr, unsigned int insn); | 4 | void patch_text(void *addr, unsigned int insn); |
5 | void __patch_text(void *addr, unsigned int insn); | 5 | void __patch_text_real(void *addr, unsigned int insn, bool remap); |
6 | |||
7 | static inline void __patch_text(void *addr, unsigned int insn) | ||
8 | { | ||
9 | __patch_text_real(addr, insn, true); | ||
10 | } | ||
11 | |||
12 | static inline void __patch_text_early(void *addr, unsigned int insn) | ||
13 | { | ||
14 | __patch_text_real(addr, insn, false); | ||
15 | } | ||
6 | 16 | ||
7 | #endif | 17 | #endif |