diff options
Diffstat (limited to 'arch/x86/kernel/alternative.c')
-rw-r--r-- | arch/x86/kernel/alternative.c | 125 |
1 files changed, 107 insertions, 18 deletions
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index de7353c0ce9c..f65ab8b014c4 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c | |||
@@ -7,6 +7,8 @@ | |||
7 | #include <linux/mm.h> | 7 | #include <linux/mm.h> |
8 | #include <linux/vmalloc.h> | 8 | #include <linux/vmalloc.h> |
9 | #include <linux/memory.h> | 9 | #include <linux/memory.h> |
10 | #include <linux/stop_machine.h> | ||
11 | #include <linux/slab.h> | ||
10 | #include <asm/alternative.h> | 12 | #include <asm/alternative.h> |
11 | #include <asm/sections.h> | 13 | #include <asm/sections.h> |
12 | #include <asm/pgtable.h> | 14 | #include <asm/pgtable.h> |
@@ -192,7 +194,7 @@ static void __init_or_module add_nops(void *insns, unsigned int len) | |||
192 | } | 194 | } |
193 | 195 | ||
194 | extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; | 196 | extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; |
195 | extern u8 *__smp_locks[], *__smp_locks_end[]; | 197 | extern s32 __smp_locks[], __smp_locks_end[]; |
196 | static void *text_poke_early(void *addr, const void *opcode, size_t len); | 198 | static void *text_poke_early(void *addr, const void *opcode, size_t len); |
197 | 199 | ||
198 | /* Replace instructions with better alternatives for this CPU type. | 200 | /* Replace instructions with better alternatives for this CPU type. |
@@ -205,13 +207,14 @@ void __init_or_module apply_alternatives(struct alt_instr *start, | |||
205 | struct alt_instr *end) | 207 | struct alt_instr *end) |
206 | { | 208 | { |
207 | struct alt_instr *a; | 209 | struct alt_instr *a; |
208 | char insnbuf[MAX_PATCH_LEN]; | 210 | u8 insnbuf[MAX_PATCH_LEN]; |
209 | 211 | ||
210 | DPRINTK("%s: alt table %p -> %p\n", __func__, start, end); | 212 | DPRINTK("%s: alt table %p -> %p\n", __func__, start, end); |
211 | for (a = start; a < end; a++) { | 213 | for (a = start; a < end; a++) { |
212 | u8 *instr = a->instr; | 214 | u8 *instr = a->instr; |
213 | BUG_ON(a->replacementlen > a->instrlen); | 215 | BUG_ON(a->replacementlen > a->instrlen); |
214 | BUG_ON(a->instrlen > sizeof(insnbuf)); | 216 | BUG_ON(a->instrlen > sizeof(insnbuf)); |
217 | BUG_ON(a->cpuid >= NCAPINTS*32); | ||
215 | if (!boot_cpu_has(a->cpuid)) | 218 | if (!boot_cpu_has(a->cpuid)) |
216 | continue; | 219 | continue; |
217 | #ifdef CONFIG_X86_64 | 220 | #ifdef CONFIG_X86_64 |
@@ -223,6 +226,8 @@ void __init_or_module apply_alternatives(struct alt_instr *start, | |||
223 | } | 226 | } |
224 | #endif | 227 | #endif |
225 | memcpy(insnbuf, a->replacement, a->replacementlen); | 228 | memcpy(insnbuf, a->replacement, a->replacementlen); |
229 | if (*insnbuf == 0xe8 && a->replacementlen == 5) | ||
230 | *(s32 *)(insnbuf + 1) += a->replacement - a->instr; | ||
226 | add_nops(insnbuf + a->replacementlen, | 231 | add_nops(insnbuf + a->replacementlen, |
227 | a->instrlen - a->replacementlen); | 232 | a->instrlen - a->replacementlen); |
228 | text_poke_early(instr, insnbuf, a->instrlen); | 233 | text_poke_early(instr, insnbuf, a->instrlen); |
@@ -231,37 +236,41 @@ void __init_or_module apply_alternatives(struct alt_instr *start, | |||
231 | 236 | ||
232 | #ifdef CONFIG_SMP | 237 | #ifdef CONFIG_SMP |
233 | 238 | ||
234 | static void alternatives_smp_lock(u8 **start, u8 **end, u8 *text, u8 *text_end) | 239 | static void alternatives_smp_lock(const s32 *start, const s32 *end, |
240 | u8 *text, u8 *text_end) | ||
235 | { | 241 | { |
236 | u8 **ptr; | 242 | const s32 *poff; |
237 | 243 | ||
238 | mutex_lock(&text_mutex); | 244 | mutex_lock(&text_mutex); |
239 | for (ptr = start; ptr < end; ptr++) { | 245 | for (poff = start; poff < end; poff++) { |
240 | if (*ptr < text) | 246 | u8 *ptr = (u8 *)poff + *poff; |
241 | continue; | 247 | |
242 | if (*ptr > text_end) | 248 | if (!*poff || ptr < text || ptr >= text_end) |
243 | continue; | 249 | continue; |
244 | /* turn DS segment override prefix into lock prefix */ | 250 | /* turn DS segment override prefix into lock prefix */ |
245 | text_poke(*ptr, ((unsigned char []){0xf0}), 1); | 251 | if (*ptr == 0x3e) |
252 | text_poke(ptr, ((unsigned char []){0xf0}), 1); | ||
246 | }; | 253 | }; |
247 | mutex_unlock(&text_mutex); | 254 | mutex_unlock(&text_mutex); |
248 | } | 255 | } |
249 | 256 | ||
250 | static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end) | 257 | static void alternatives_smp_unlock(const s32 *start, const s32 *end, |
258 | u8 *text, u8 *text_end) | ||
251 | { | 259 | { |
252 | u8 **ptr; | 260 | const s32 *poff; |
253 | 261 | ||
254 | if (noreplace_smp) | 262 | if (noreplace_smp) |
255 | return; | 263 | return; |
256 | 264 | ||
257 | mutex_lock(&text_mutex); | 265 | mutex_lock(&text_mutex); |
258 | for (ptr = start; ptr < end; ptr++) { | 266 | for (poff = start; poff < end; poff++) { |
259 | if (*ptr < text) | 267 | u8 *ptr = (u8 *)poff + *poff; |
260 | continue; | 268 | |
261 | if (*ptr > text_end) | 269 | if (!*poff || ptr < text || ptr >= text_end) |
262 | continue; | 270 | continue; |
263 | /* turn lock prefix into DS segment override prefix */ | 271 | /* turn lock prefix into DS segment override prefix */ |
264 | text_poke(*ptr, ((unsigned char []){0x3E}), 1); | 272 | if (*ptr == 0xf0) |
273 | text_poke(ptr, ((unsigned char []){0x3E}), 1); | ||
265 | }; | 274 | }; |
266 | mutex_unlock(&text_mutex); | 275 | mutex_unlock(&text_mutex); |
267 | } | 276 | } |
@@ -272,8 +281,8 @@ struct smp_alt_module { | |||
272 | char *name; | 281 | char *name; |
273 | 282 | ||
274 | /* ptrs to lock prefixes */ | 283 | /* ptrs to lock prefixes */ |
275 | u8 **locks; | 284 | const s32 *locks; |
276 | u8 **locks_end; | 285 | const s32 *locks_end; |
277 | 286 | ||
278 | /* .text segment, needed to avoid patching init code ;) */ | 287 | /* .text segment, needed to avoid patching init code ;) */ |
279 | u8 *text; | 288 | u8 *text; |
@@ -390,6 +399,27 @@ void alternatives_smp_switch(int smp) | |||
390 | mutex_unlock(&smp_alt); | 399 | mutex_unlock(&smp_alt); |
391 | } | 400 | } |
392 | 401 | ||
402 | /* Return 1 if the address range is reserved for smp-alternatives */ | ||
403 | int alternatives_text_reserved(void *start, void *end) | ||
404 | { | ||
405 | struct smp_alt_module *mod; | ||
406 | const s32 *poff; | ||
407 | u8 *text_start = start; | ||
408 | u8 *text_end = end; | ||
409 | |||
410 | list_for_each_entry(mod, &smp_alt_modules, next) { | ||
411 | if (mod->text > text_end || mod->text_end < text_start) | ||
412 | continue; | ||
413 | for (poff = mod->locks; poff < mod->locks_end; poff++) { | ||
414 | const u8 *ptr = (const u8 *)poff + *poff; | ||
415 | |||
416 | if (text_start <= ptr && text_end > ptr) | ||
417 | return 1; | ||
418 | } | ||
419 | } | ||
420 | |||
421 | return 0; | ||
422 | } | ||
393 | #endif | 423 | #endif |
394 | 424 | ||
395 | #ifdef CONFIG_PARAVIRT | 425 | #ifdef CONFIG_PARAVIRT |
@@ -552,3 +582,62 @@ void *__kprobes text_poke(void *addr, const void *opcode, size_t len) | |||
552 | local_irq_restore(flags); | 582 | local_irq_restore(flags); |
553 | return addr; | 583 | return addr; |
554 | } | 584 | } |
585 | |||
586 | /* | ||
587 | * Cross-modifying kernel text with stop_machine(). | ||
588 | * This code originally comes from immediate value. | ||
589 | */ | ||
590 | static atomic_t stop_machine_first; | ||
591 | static int wrote_text; | ||
592 | |||
593 | struct text_poke_params { | ||
594 | void *addr; | ||
595 | const void *opcode; | ||
596 | size_t len; | ||
597 | }; | ||
598 | |||
599 | static int __kprobes stop_machine_text_poke(void *data) | ||
600 | { | ||
601 | struct text_poke_params *tpp = data; | ||
602 | |||
603 | if (atomic_dec_and_test(&stop_machine_first)) { | ||
604 | text_poke(tpp->addr, tpp->opcode, tpp->len); | ||
605 | smp_wmb(); /* Make sure other cpus see that this has run */ | ||
606 | wrote_text = 1; | ||
607 | } else { | ||
608 | while (!wrote_text) | ||
609 | cpu_relax(); | ||
610 | smp_mb(); /* Load wrote_text before following execution */ | ||
611 | } | ||
612 | |||
613 | flush_icache_range((unsigned long)tpp->addr, | ||
614 | (unsigned long)tpp->addr + tpp->len); | ||
615 | return 0; | ||
616 | } | ||
617 | |||
618 | /** | ||
619 | * text_poke_smp - Update instructions on a live kernel on SMP | ||
620 | * @addr: address to modify | ||
621 | * @opcode: source of the copy | ||
622 | * @len: length to copy | ||
623 | * | ||
624 | * Modify multi-byte instruction by using stop_machine() on SMP. This allows | ||
625 | * user to poke/set multi-byte text on SMP. Only non-NMI/MCE code modifying | ||
626 | * should be allowed, since stop_machine() does _not_ protect code against | ||
627 | * NMI and MCE. | ||
628 | * | ||
629 | * Note: Must be called under get_online_cpus() and text_mutex. | ||
630 | */ | ||
631 | void *__kprobes text_poke_smp(void *addr, const void *opcode, size_t len) | ||
632 | { | ||
633 | struct text_poke_params tpp; | ||
634 | |||
635 | tpp.addr = addr; | ||
636 | tpp.opcode = opcode; | ||
637 | tpp.len = len; | ||
638 | atomic_set(&stop_machine_first, 1); | ||
639 | wrote_text = 0; | ||
640 | stop_machine(stop_machine_text_poke, (void *)&tpp, NULL); | ||
641 | return addr; | ||
642 | } | ||
643 | |||