|
18 | 18 | #include <linux/mmu_context.h>
|
19 | 19 | #include <linux/bsearch.h>
|
20 | 20 | #include <linux/sync_core.h>
|
| 21 | +#include <linux/moduleloader.h> |
| 22 | +#include <linux/cleanup.h> |
21 | 23 | #include <asm/text-patching.h>
|
22 | 24 | #include <asm/alternative.h>
|
23 | 25 | #include <asm/sections.h>
|
|
31 | 33 | #include <asm/paravirt.h>
|
32 | 34 | #include <asm/asm-prototypes.h>
|
33 | 35 | #include <asm/cfi.h>
|
| 36 | +#include <asm/set_memory.h> |
34 | 37 |
|
35 | 38 | int __read_mostly alternatives_patched;
|
36 | 39 |
|
@@ -399,6 +402,123 @@ static int emit_indirect(int op, int reg, u8 *bytes)
|
399 | 402 |
|
400 | 403 | #ifdef CONFIG_MITIGATION_ITS
|
401 | 404 |
|
| 405 | +static struct module *its_mod; |
| 406 | +static void *its_page; |
| 407 | +static unsigned int its_offset; |
| 408 | + |
| 409 | +/* Initialize a thunk with the "jmp *reg; int3" instructions. */ |
| 410 | +static void *its_init_thunk(void *thunk, int reg) |
| 411 | +{ |
| 412 | + u8 *bytes = thunk; |
| 413 | + int i = 0; |
| 414 | + |
| 415 | + if (reg >= 8) { |
| 416 | + bytes[i++] = 0x41; /* REX.B prefix */ |
| 417 | + reg -= 8; |
| 418 | + } |
| 419 | + bytes[i++] = 0xff; |
| 420 | + bytes[i++] = 0xe0 + reg; /* jmp *reg */ |
| 421 | + bytes[i++] = 0xcc; |
| 422 | + |
| 423 | + return thunk; |
| 424 | +} |
| 425 | + |
| 426 | +void its_init_mod(struct module *mod) |
| 427 | +{ |
| 428 | + if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS)) |
| 429 | + return; |
| 430 | + |
| 431 | + mutex_lock(&text_mutex); |
| 432 | + its_mod = mod; |
| 433 | + its_page = NULL; |
| 434 | +} |
| 435 | + |
| 436 | +void its_fini_mod(struct module *mod) |
| 437 | +{ |
| 438 | + if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS)) |
| 439 | + return; |
| 440 | + |
| 441 | + WARN_ON_ONCE(its_mod != mod); |
| 442 | + |
| 443 | + its_mod = NULL; |
| 444 | + its_page = NULL; |
| 445 | + mutex_unlock(&text_mutex); |
| 446 | + |
| 447 | + for (int i = 0; i < mod->its_num_pages; i++) { |
| 448 | + void *page = mod->its_page_array[i]; |
| 449 | + set_memory_ro((unsigned long)page, 1); |
| 450 | + set_memory_x((unsigned long)page, 1); |
| 451 | + } |
| 452 | +} |
| 453 | + |
| 454 | +void its_free_mod(struct module *mod) |
| 455 | +{ |
| 456 | + if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS)) |
| 457 | + return; |
| 458 | + |
| 459 | + for (int i = 0; i < mod->its_num_pages; i++) { |
| 460 | + void *page = mod->its_page_array[i]; |
| 461 | + module_memfree(page); |
| 462 | + } |
| 463 | + kfree(mod->its_page_array); |
| 464 | +} |
| 465 | + |
| 466 | +DEFINE_FREE(its_execmem, void *, if (_T) module_memfree(_T)); |
| 467 | + |
| 468 | +static void *its_alloc(void) |
| 469 | +{ |
| 470 | + void *page __free(its_execmem) = module_alloc(PAGE_SIZE); |
| 471 | + |
| 472 | + if (!page) |
| 473 | + return NULL; |
| 474 | + |
| 475 | + if (its_mod) { |
| 476 | + void *tmp = krealloc(its_mod->its_page_array, |
| 477 | + (its_mod->its_num_pages+1) * sizeof(void *), |
| 478 | + GFP_KERNEL); |
| 479 | + if (!tmp) |
| 480 | + return NULL; |
| 481 | + |
| 482 | + its_mod->its_page_array = tmp; |
| 483 | + its_mod->its_page_array[its_mod->its_num_pages++] = page; |
| 484 | + } |
| 485 | + |
| 486 | + return no_free_ptr(page); |
| 487 | +} |
| 488 | + |
| 489 | +static void *its_allocate_thunk(int reg) |
| 490 | +{ |
| 491 | + int size = 3 + (reg / 8); |
| 492 | + void *thunk; |
| 493 | + |
| 494 | + if (!its_page || (its_offset + size - 1) >= PAGE_SIZE) { |
| 495 | + its_page = its_alloc(); |
| 496 | + if (!its_page) { |
| 497 | + pr_err("ITS page allocation failed\n"); |
| 498 | + return NULL; |
| 499 | + } |
| 500 | + memset(its_page, INT3_INSN_OPCODE, PAGE_SIZE); |
| 501 | + its_offset = 32; |
| 502 | + } |
| 503 | + |
| 504 | + /* |
| 505 | + * If the indirect branch instruction will be in the lower half |
| 506 | + * of a cacheline, then update the offset to reach the upper half. |
| 507 | + */ |
| 508 | + if ((its_offset + size - 1) % 64 < 32) |
| 509 | + its_offset = ((its_offset - 1) | 0x3F) + 33; |
| 510 | + |
| 511 | + thunk = its_page + its_offset; |
| 512 | + its_offset += size; |
| 513 | + |
| 514 | + set_memory_rw((unsigned long)its_page, 1); |
| 515 | + thunk = its_init_thunk(thunk, reg); |
| 516 | + set_memory_ro((unsigned long)its_page, 1); |
| 517 | + set_memory_x((unsigned long)its_page, 1); |
| 518 | + |
| 519 | + return thunk; |
| 520 | +} |
| 521 | + |
402 | 522 | static int __emit_trampoline(void *addr, struct insn *insn, u8 *bytes,
|
403 | 523 | void *call_dest, void *jmp_dest)
|
404 | 524 | {
|
@@ -446,9 +566,13 @@ static int __emit_trampoline(void *addr, struct insn *insn, u8 *bytes,
|
446 | 566 |
|
447 | 567 | static int emit_its_trampoline(void *addr, struct insn *insn, int reg, u8 *bytes)
|
448 | 568 | {
|
449 |
| - return __emit_trampoline(addr, insn, bytes, |
450 |
| - __x86_indirect_its_thunk_array[reg], |
451 |
| - __x86_indirect_its_thunk_array[reg]); |
| 569 | + u8 *thunk = __x86_indirect_its_thunk_array[reg]; |
| 570 | + u8 *tmp = its_allocate_thunk(reg); |
| 571 | + |
| 572 | + if (tmp) |
| 573 | + thunk = tmp; |
| 574 | + |
| 575 | + return __emit_trampoline(addr, insn, bytes, thunk, thunk); |
452 | 576 | }
|
453 | 577 |
|
454 | 578 | /* Check if an indirect branch is at ITS-unsafe address */
|
|
0 commit comments