aboutsummaryrefslogtreecommitdiffstats
path: root/arch/i386
diff options
context:
space:
mode:
authorAndi Kleen <ak@suse.de>2007-07-22 05:12:31 -0400
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-07-22 14:03:37 -0400
commit19d36ccdc34f5ed444f8a6af0cbfdb6790eb1177 (patch)
tree39942efb826f2793692da60b04fc0e7b015fa23d /arch/i386
parentf51c94528a9bc73504928926ca4d791a2b7ddd7c (diff)
x86: Fix alternatives and kprobes to remap write-protected kernel text
Reenable kprobes and alternative patching when the kernel text is write protected by DEBUG_RODATA Add a general utility function to change write protected text. The new function remaps the code using vmap to write it and takes care of CPU synchronization. It also does CLFLUSH to make icache recovery faster. There are some limitations on when the function can be used, see the comment. This is a newer version that also changes the paravirt_ops code. text_poke also supports multi byte patching now. Contains bug fixes from Zach Amsden and suggestions from Mathieu Desnoyers. Cc: Jan Beulich <jbeulich@novell.com> Cc: Jeremy Fitzhardinge <jeremy@goop.org> Cc: Mathieu Desnoyers <compudj@krystal.dyndns.org> Cc: Zach Amsden <zach@vmware.com> Signed-off-by: Andi Kleen <ak@suse.de> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'arch/i386')
-rw-r--r--arch/i386/kernel/alternative.c40
-rw-r--r--arch/i386/kernel/kprobes.c9
-rw-r--r--arch/i386/kernel/paravirt.c17
-rw-r--r--arch/i386/mm/init.c14
4 files changed, 53 insertions, 27 deletions
diff --git a/arch/i386/kernel/alternative.c b/arch/i386/kernel/alternative.c
index 0695be538de5..206ea2ca63cc 100644
--- a/arch/i386/kernel/alternative.c
+++ b/arch/i386/kernel/alternative.c
@@ -2,8 +2,12 @@
2#include <linux/sched.h> 2#include <linux/sched.h>
3#include <linux/spinlock.h> 3#include <linux/spinlock.h>
4#include <linux/list.h> 4#include <linux/list.h>
5#include <linux/kprobes.h>
6#include <linux/mm.h>
7#include <linux/vmalloc.h>
5#include <asm/alternative.h> 8#include <asm/alternative.h>
6#include <asm/sections.h> 9#include <asm/sections.h>
10#include <asm/pgtable.h>
7 11
8#ifdef CONFIG_HOTPLUG_CPU 12#ifdef CONFIG_HOTPLUG_CPU
9static int smp_alt_once; 13static int smp_alt_once;
@@ -150,7 +154,7 @@ static void nop_out(void *insns, unsigned int len)
150 unsigned int noplen = len; 154 unsigned int noplen = len;
151 if (noplen > ASM_NOP_MAX) 155 if (noplen > ASM_NOP_MAX)
152 noplen = ASM_NOP_MAX; 156 noplen = ASM_NOP_MAX;
153 memcpy(insns, noptable[noplen], noplen); 157 text_poke(insns, noptable[noplen], noplen);
154 insns += noplen; 158 insns += noplen;
155 len -= noplen; 159 len -= noplen;
156 } 160 }
@@ -202,7 +206,7 @@ static void alternatives_smp_lock(u8 **start, u8 **end, u8 *text, u8 *text_end)
202 continue; 206 continue;
203 if (*ptr > text_end) 207 if (*ptr > text_end)
204 continue; 208 continue;
205 **ptr = 0xf0; /* lock prefix */ 209 text_poke(*ptr, ((unsigned char []){0xf0}), 1); /* add lock prefix */
206 }; 210 };
207} 211}
208 212
@@ -360,10 +364,6 @@ void apply_paravirt(struct paravirt_patch_site *start,
360 /* Pad the rest with nops */ 364 /* Pad the rest with nops */
361 nop_out(p->instr + used, p->len - used); 365 nop_out(p->instr + used, p->len - used);
362 } 366 }
363
364 /* Sync to be conservative, in case we patched following
365 * instructions */
366 sync_core();
367} 367}
368extern struct paravirt_patch_site __start_parainstructions[], 368extern struct paravirt_patch_site __start_parainstructions[],
369 __stop_parainstructions[]; 369 __stop_parainstructions[];
@@ -406,3 +406,31 @@ void __init alternative_instructions(void)
406 apply_paravirt(__parainstructions, __parainstructions_end); 406 apply_paravirt(__parainstructions, __parainstructions_end);
407 local_irq_restore(flags); 407 local_irq_restore(flags);
408} 408}
409
410/*
411 * Warning:
412 * When you use this code to patch more than one byte of an instruction
413 * you need to make sure that other CPUs cannot execute this code in parallel.
414 * Also no thread must be currently preempted in the middle of these instructions.
415 * And on the local CPU you need to be protected again NMI or MCE handlers
416 * seeing an inconsistent instruction while you patch.
417 */
418void __kprobes text_poke(void *oaddr, unsigned char *opcode, int len)
419{
420 u8 *addr = oaddr;
421 if (!pte_write(*lookup_address((unsigned long)addr))) {
422 struct page *p[2] = { virt_to_page(addr), virt_to_page(addr+PAGE_SIZE) };
423 addr = vmap(p, 2, VM_MAP, PAGE_KERNEL);
424 if (!addr)
425 return;
426 addr += ((unsigned long)oaddr) % PAGE_SIZE;
427 }
428 memcpy(addr, opcode, len);
429 sync_core();
430 /* Not strictly needed, but can speed CPU recovery up. Ignore cross cacheline
431 case. */
432 if (cpu_has_clflush)
433 asm("clflush (%0) " :: "r" (oaddr) : "memory");
434 if (addr != oaddr)
435 vunmap(addr);
436}
diff --git a/arch/i386/kernel/kprobes.c b/arch/i386/kernel/kprobes.c
index dde828a333c3..448a50b1324c 100644
--- a/arch/i386/kernel/kprobes.c
+++ b/arch/i386/kernel/kprobes.c
@@ -35,6 +35,7 @@
35#include <asm/cacheflush.h> 35#include <asm/cacheflush.h>
36#include <asm/desc.h> 36#include <asm/desc.h>
37#include <asm/uaccess.h> 37#include <asm/uaccess.h>
38#include <asm/alternative.h>
38 39
39void jprobe_return_end(void); 40void jprobe_return_end(void);
40 41
@@ -169,16 +170,12 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
169 170
170void __kprobes arch_arm_kprobe(struct kprobe *p) 171void __kprobes arch_arm_kprobe(struct kprobe *p)
171{ 172{
172 *p->addr = BREAKPOINT_INSTRUCTION; 173 text_poke(p->addr, ((unsigned char []){BREAKPOINT_INSTRUCTION}), 1);
173 flush_icache_range((unsigned long) p->addr,
174 (unsigned long) p->addr + sizeof(kprobe_opcode_t));
175} 174}
176 175
177void __kprobes arch_disarm_kprobe(struct kprobe *p) 176void __kprobes arch_disarm_kprobe(struct kprobe *p)
178{ 177{
179 *p->addr = p->opcode; 178 text_poke(p->addr, &p->opcode, 1);
180 flush_icache_range((unsigned long) p->addr,
181 (unsigned long) p->addr + sizeof(kprobe_opcode_t));
182} 179}
183 180
184void __kprobes arch_remove_kprobe(struct kprobe *p) 181void __kprobes arch_remove_kprobe(struct kprobe *p)
diff --git a/arch/i386/kernel/paravirt.c b/arch/i386/kernel/paravirt.c
index 53f07a8275e3..79c167fcaee9 100644
--- a/arch/i386/kernel/paravirt.c
+++ b/arch/i386/kernel/paravirt.c
@@ -124,20 +124,28 @@ unsigned paravirt_patch_ignore(unsigned len)
124 return len; 124 return len;
125} 125}
126 126
127struct branch {
128 unsigned char opcode;
129 u32 delta;
130} __attribute__((packed));
131
127unsigned paravirt_patch_call(void *target, u16 tgt_clobbers, 132unsigned paravirt_patch_call(void *target, u16 tgt_clobbers,
128 void *site, u16 site_clobbers, 133 void *site, u16 site_clobbers,
129 unsigned len) 134 unsigned len)
130{ 135{
131 unsigned char *call = site; 136 unsigned char *call = site;
132 unsigned long delta = (unsigned long)target - (unsigned long)(call+5); 137 unsigned long delta = (unsigned long)target - (unsigned long)(call+5);
138 struct branch b;
133 139
134 if (tgt_clobbers & ~site_clobbers) 140 if (tgt_clobbers & ~site_clobbers)
135 return len; /* target would clobber too much for this site */ 141 return len; /* target would clobber too much for this site */
136 if (len < 5) 142 if (len < 5)
137 return len; /* call too long for patch site */ 143 return len; /* call too long for patch site */
138 144
139 *call++ = 0xe8; /* call */ 145 b.opcode = 0xe8; /* call */
140 *(unsigned long *)call = delta; 146 b.delta = delta;
147 BUILD_BUG_ON(sizeof(b) != 5);
148 text_poke(call, (unsigned char *)&b, 5);
141 149
142 return 5; 150 return 5;
143} 151}
@@ -150,8 +158,9 @@ unsigned paravirt_patch_jmp(void *target, void *site, unsigned len)
150 if (len < 5) 158 if (len < 5)
151 return len; /* call too long for patch site */ 159 return len; /* call too long for patch site */
152 160
153 *jmp++ = 0xe9; /* jmp */ 161 b.opcode = 0xe9; /* jmp */
154 *(unsigned long *)jmp = delta; 162 b.delta = delta;
163 text_poke(call, (unsigned char *)&b, 5);
155 164
156 return 5; 165 return 5;
157} 166}
diff --git a/arch/i386/mm/init.c b/arch/i386/mm/init.c
index e1a9a805c445..c3b9905af2d5 100644
--- a/arch/i386/mm/init.c
+++ b/arch/i386/mm/init.c
@@ -800,17 +800,9 @@ void mark_rodata_ro(void)
800 unsigned long start = PFN_ALIGN(_text); 800 unsigned long start = PFN_ALIGN(_text);
801 unsigned long size = PFN_ALIGN(_etext) - start; 801 unsigned long size = PFN_ALIGN(_etext) - start;
802 802
803#ifndef CONFIG_KPROBES 803 change_page_attr(virt_to_page(start),
804#ifdef CONFIG_HOTPLUG_CPU 804 size >> PAGE_SHIFT, PAGE_KERNEL_RX);
805 /* It must still be possible to apply SMP alternatives. */ 805 printk("Write protecting the kernel text: %luk\n", size >> 10);
806 if (num_possible_cpus() <= 1)
807#endif
808 {
809 change_page_attr(virt_to_page(start),
810 size >> PAGE_SHIFT, PAGE_KERNEL_RX);
811 printk("Write protecting the kernel text: %luk\n", size >> 10);
812 }
813#endif
814 start += size; 806 start += size;
815 size = (unsigned long)__end_rodata - start; 807 size = (unsigned long)__end_rodata - start;
816 change_page_attr(virt_to_page(start), 808 change_page_attr(virt_to_page(start),