diff options
author | Andi Kleen <ak@suse.de> | 2007-08-10 16:31:03 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-08-11 18:58:13 -0400 |
commit | ab144f5ec64c42218a555ec1dbde6b60cf2982d6 (patch) | |
tree | e3a4532e1db116e87060c9b18f4cfbf6258fdba3 /arch/i386/kernel/alternative.c | |
parent | d3f3c9346979bfa074c64eac5fc3ed5bba4f40ed (diff) |
i386: Make patching more robust, fix paravirt issue
Commit 19d36ccdc34f5ed444f8a6af0cbfdb6790eb1177 "x86: Fix alternatives
and kprobes to remap write-protected kernel text" uses code which is
being patched for patching.
In particular, paravirt_ops does patching in two stages: first it
calls paravirt_ops.patch, then it fills any remaining instructions
with nop_out(). nop_out calls text_poke() which calls
lookup_address() which calls pgd_val() (aka paravirt_ops.pgd_val):
that call site is one of the places we patch.
If we always do patching as one single call to text_poke(), we only
need make sure we're not patching the memcpy in text_poke itself.
This means the prototype to paravirt_ops.patch needs to change, to
marshal the new code into a buffer rather than patching in place as it
does now. It also means all patching goes through text_poke(), which
is known to be safe (apply_alternatives is also changed to make a
single patch).
AK: fix compilation on x86-64 (bad rusty!)
AK: fix boot on x86-64 (sigh)
AK: merged with other patches
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'arch/i386/kernel/alternative.c')
-rw-r--r-- | arch/i386/kernel/alternative.c | 33 |
1 files changed, 21 insertions, 12 deletions
diff --git a/arch/i386/kernel/alternative.c b/arch/i386/kernel/alternative.c index c85598acb8fd..27a6b0c9a7cc 100644 --- a/arch/i386/kernel/alternative.c +++ b/arch/i386/kernel/alternative.c | |||
@@ -11,6 +11,8 @@ | |||
11 | #include <asm/mce.h> | 11 | #include <asm/mce.h> |
12 | #include <asm/nmi.h> | 12 | #include <asm/nmi.h> |
13 | 13 | ||
14 | #define MAX_PATCH_LEN (255-1) | ||
15 | |||
14 | #ifdef CONFIG_HOTPLUG_CPU | 16 | #ifdef CONFIG_HOTPLUG_CPU |
15 | static int smp_alt_once; | 17 | static int smp_alt_once; |
16 | 18 | ||
@@ -148,7 +150,8 @@ static unsigned char** find_nop_table(void) | |||
148 | 150 | ||
149 | #endif /* CONFIG_X86_64 */ | 151 | #endif /* CONFIG_X86_64 */ |
150 | 152 | ||
151 | static void nop_out(void *insns, unsigned int len) | 153 | /* Use this to add nops to a buffer, then text_poke the whole buffer. */ |
154 | static void add_nops(void *insns, unsigned int len) | ||
152 | { | 155 | { |
153 | unsigned char **noptable = find_nop_table(); | 156 | unsigned char **noptable = find_nop_table(); |
154 | 157 | ||
@@ -156,7 +159,7 @@ static void nop_out(void *insns, unsigned int len) | |||
156 | unsigned int noplen = len; | 159 | unsigned int noplen = len; |
157 | if (noplen > ASM_NOP_MAX) | 160 | if (noplen > ASM_NOP_MAX) |
158 | noplen = ASM_NOP_MAX; | 161 | noplen = ASM_NOP_MAX; |
159 | text_poke(insns, noptable[noplen], noplen); | 162 | memcpy(insns, noptable[noplen], noplen); |
160 | insns += noplen; | 163 | insns += noplen; |
161 | len -= noplen; | 164 | len -= noplen; |
162 | } | 165 | } |
@@ -174,15 +177,15 @@ extern u8 *__smp_locks[], *__smp_locks_end[]; | |||
174 | void apply_alternatives(struct alt_instr *start, struct alt_instr *end) | 177 | void apply_alternatives(struct alt_instr *start, struct alt_instr *end) |
175 | { | 178 | { |
176 | struct alt_instr *a; | 179 | struct alt_instr *a; |
177 | u8 *instr; | 180 | char insnbuf[MAX_PATCH_LEN]; |
178 | int diff; | ||
179 | 181 | ||
180 | DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end); | 182 | DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end); |
181 | for (a = start; a < end; a++) { | 183 | for (a = start; a < end; a++) { |
184 | u8 *instr = a->instr; | ||
182 | BUG_ON(a->replacementlen > a->instrlen); | 185 | BUG_ON(a->replacementlen > a->instrlen); |
186 | BUG_ON(a->instrlen > sizeof(insnbuf)); | ||
183 | if (!boot_cpu_has(a->cpuid)) | 187 | if (!boot_cpu_has(a->cpuid)) |
184 | continue; | 188 | continue; |
185 | instr = a->instr; | ||
186 | #ifdef CONFIG_X86_64 | 189 | #ifdef CONFIG_X86_64 |
187 | /* vsyscall code is not mapped yet. resolve it manually. */ | 190 | /* vsyscall code is not mapped yet. resolve it manually. */ |
188 | if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) { | 191 | if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) { |
@@ -191,9 +194,10 @@ void apply_alternatives(struct alt_instr *start, struct alt_instr *end) | |||
191 | __FUNCTION__, a->instr, instr); | 194 | __FUNCTION__, a->instr, instr); |
192 | } | 195 | } |
193 | #endif | 196 | #endif |
194 | memcpy(instr, a->replacement, a->replacementlen); | 197 | memcpy(insnbuf, a->replacement, a->replacementlen); |
195 | diff = a->instrlen - a->replacementlen; | 198 | add_nops(insnbuf + a->replacementlen, |
196 | nop_out(instr + a->replacementlen, diff); | 199 | a->instrlen - a->replacementlen); |
200 | text_poke(instr, insnbuf, a->instrlen); | ||
197 | } | 201 | } |
198 | } | 202 | } |
199 | 203 | ||
@@ -215,16 +219,18 @@ static void alternatives_smp_lock(u8 **start, u8 **end, u8 *text, u8 *text_end) | |||
215 | static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end) | 219 | static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end) |
216 | { | 220 | { |
217 | u8 **ptr; | 221 | u8 **ptr; |
222 | char insn[1]; | ||
218 | 223 | ||
219 | if (noreplace_smp) | 224 | if (noreplace_smp) |
220 | return; | 225 | return; |
221 | 226 | ||
227 | add_nops(insn, 1); | ||
222 | for (ptr = start; ptr < end; ptr++) { | 228 | for (ptr = start; ptr < end; ptr++) { |
223 | if (*ptr < text) | 229 | if (*ptr < text) |
224 | continue; | 230 | continue; |
225 | if (*ptr > text_end) | 231 | if (*ptr > text_end) |
226 | continue; | 232 | continue; |
227 | nop_out(*ptr, 1); | 233 | text_poke(*ptr, insn, 1); |
228 | }; | 234 | }; |
229 | } | 235 | } |
230 | 236 | ||
@@ -351,6 +357,7 @@ void apply_paravirt(struct paravirt_patch_site *start, | |||
351 | struct paravirt_patch_site *end) | 357 | struct paravirt_patch_site *end) |
352 | { | 358 | { |
353 | struct paravirt_patch_site *p; | 359 | struct paravirt_patch_site *p; |
360 | char insnbuf[MAX_PATCH_LEN]; | ||
354 | 361 | ||
355 | if (noreplace_paravirt) | 362 | if (noreplace_paravirt) |
356 | return; | 363 | return; |
@@ -358,13 +365,15 @@ void apply_paravirt(struct paravirt_patch_site *start, | |||
358 | for (p = start; p < end; p++) { | 365 | for (p = start; p < end; p++) { |
359 | unsigned int used; | 366 | unsigned int used; |
360 | 367 | ||
361 | used = paravirt_ops.patch(p->instrtype, p->clobbers, p->instr, | 368 | BUG_ON(p->len > MAX_PATCH_LEN); |
362 | p->len); | 369 | used = paravirt_ops.patch(p->instrtype, p->clobbers, insnbuf, |
370 | (unsigned long)p->instr, p->len); | ||
363 | 371 | ||
364 | BUG_ON(used > p->len); | 372 | BUG_ON(used > p->len); |
365 | 373 | ||
366 | /* Pad the rest with nops */ | 374 | /* Pad the rest with nops */ |
367 | nop_out(p->instr + used, p->len - used); | 375 | add_nops(insnbuf + used, p->len - used); |
376 | text_poke(p->instr, insnbuf, p->len); | ||
368 | } | 377 | } |
369 | } | 378 | } |
370 | extern struct paravirt_patch_site __start_parainstructions[], | 379 | extern struct paravirt_patch_site __start_parainstructions[], |