Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/alternative: Implement .retpoline_sites support

Rewrite retpoline thunk call sites to be indirect calls for
spectre_v2=off. This ensures spectre_v2=off is as near to a
RETPOLINE=n build as possible.

This is the replacement for objtool writing alternative entries to
ensure the same and achieves feature-parity with the previous
approach.

One noteworthy feature is that it relies on the thunks to be in
machine order to compute the register index.

Specifically, this does not yet address the Jcc __x86_indirect_thunk_*
calls generated by clang, a future patch will add this.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: Borislav Petkov <bp@suse.de>
Acked-by: Josh Poimboeuf <jpoimboe@redhat.com>
Tested-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/r/20211026120310.232495794@infradead.org

+150 -5
+4
arch/um/kernel/um_arch.c
··· 421 421 os_check_bugs(); 422 422 } 423 423 424 + void apply_retpolines(s32 *start, s32 *end) 425 + { 426 + } 427 + 424 428 void apply_alternatives(struct alt_instr *start, struct alt_instr *end) 425 429 { 426 430 }
+1
arch/x86/include/asm/alternative.h
··· 75 75 76 76 extern void alternative_instructions(void); 77 77 extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end); 78 + extern void apply_retpolines(s32 *start, s32 *end); 78 79 79 80 struct module; 80 81
+137 -4
arch/x86/kernel/alternative.c
··· 29 29 #include <asm/io.h> 30 30 #include <asm/fixmap.h> 31 31 #include <asm/paravirt.h> 32 + #include <asm/asm-prototypes.h> 32 33 33 34 int __read_mostly alternatives_patched; 34 35 ··· 114 113 } 115 114 } 116 115 116 + extern s32 __retpoline_sites[], __retpoline_sites_end[]; 117 117 extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; 118 118 extern s32 __smp_locks[], __smp_locks_end[]; 119 119 void text_poke_early(void *addr, const void *opcode, size_t len); ··· 223 221 * "noinline" to cause control flow change and thus invalidate I$ and 224 222 * cause refetch after modification. 225 223 */ 226 - static void __init_or_module noinline optimize_nops(struct alt_instr *a, u8 *instr) 224 + static void __init_or_module noinline optimize_nops(u8 *instr, size_t len) 227 225 { 228 226 struct insn insn; 229 227 int i = 0; ··· 241 239 * optimized. 242 240 */ 243 241 if (insn.length == 1 && insn.opcode.bytes[0] == 0x90) 244 - i += optimize_nops_range(instr, a->instrlen, i); 242 + i += optimize_nops_range(instr, len, i); 245 243 else 246 244 i += insn.length; 247 245 248 - if (i >= a->instrlen) 246 + if (i >= len) 249 247 return; 250 248 } 251 249 } ··· 333 331 text_poke_early(instr, insn_buff, insn_buff_sz); 334 332 335 333 next: 336 - optimize_nops(a, instr); 334 + optimize_nops(instr, a->instrlen); 337 335 } 338 336 } 337 + 338 + #if defined(CONFIG_RETPOLINE) && defined(CONFIG_STACK_VALIDATION) 339 + 340 + /* 341 + * CALL/JMP *%\reg 342 + */ 343 + static int emit_indirect(int op, int reg, u8 *bytes) 344 + { 345 + int i = 0; 346 + u8 modrm; 347 + 348 + switch (op) { 349 + case CALL_INSN_OPCODE: 350 + modrm = 0x10; /* Reg = 2; CALL r/m */ 351 + break; 352 + 353 + case JMP32_INSN_OPCODE: 354 + modrm = 0x20; /* Reg = 4; JMP r/m */ 355 + break; 356 + 357 + default: 358 + WARN_ON_ONCE(1); 359 + return -1; 360 + } 361 + 362 + if (reg >= 8) { 363 + bytes[i++] = 0x41; /* REX.B prefix */ 364 + reg -= 8; 365 + } 366 + 367 + modrm |= 0xc0; /* Mod = 3 */ 368 + modrm += reg; 369 + 370 + bytes[i++] = 0xff; /* opcode */ 371 + bytes[i++] = modrm; 372 + 373 + return i; 374 + } 375 + 376 + /* 377 + * Rewrite the compiler generated retpoline thunk calls. 378 + * 379 + * For spectre_v2=off (!X86_FEATURE_RETPOLINE), rewrite them into immediate 380 + * indirect instructions, avoiding the extra indirection. 381 + * 382 + * For example, convert: 383 + * 384 + * CALL __x86_indirect_thunk_\reg 385 + * 386 + * into: 387 + * 388 + * CALL *%\reg 389 + * 390 + */ 391 + static int patch_retpoline(void *addr, struct insn *insn, u8 *bytes) 392 + { 393 + retpoline_thunk_t *target; 394 + int reg, i = 0; 395 + 396 + target = addr + insn->length + insn->immediate.value; 397 + reg = target - __x86_indirect_thunk_array; 398 + 399 + if (WARN_ON_ONCE(reg & ~0xf)) 400 + return -1; 401 + 402 + /* If anyone ever does: CALL/JMP *%rsp, we're in deep trouble. */ 403 + BUG_ON(reg == 4); 404 + 405 + if (cpu_feature_enabled(X86_FEATURE_RETPOLINE)) 406 + return -1; 407 + 408 + i = emit_indirect(insn->opcode.bytes[0], reg, bytes); 409 + if (i < 0) 410 + return i; 411 + 412 + for (; i < insn->length;) 413 + bytes[i++] = BYTES_NOP1; 414 + 415 + return i; 416 + } 417 + 418 + /* 419 + * Generated by 'objtool --retpoline'. 420 + */ 421 + void __init_or_module noinline apply_retpolines(s32 *start, s32 *end) 422 + { 423 + s32 *s; 424 + 425 + for (s = start; s < end; s++) { 426 + void *addr = (void *)s + *s; 427 + struct insn insn; 428 + int len, ret; 429 + u8 bytes[16]; 430 + u8 op1, op2; 431 + 432 + ret = insn_decode_kernel(&insn, addr); 433 + if (WARN_ON_ONCE(ret < 0)) 434 + continue; 435 + 436 + op1 = insn.opcode.bytes[0]; 437 + op2 = insn.opcode.bytes[1]; 438 + 439 + switch (op1) { 440 + case CALL_INSN_OPCODE: 441 + case JMP32_INSN_OPCODE: 442 + break; 443 + 444 + default: 445 + WARN_ON_ONCE(1); 446 + continue; 447 + } 448 + 449 + len = patch_retpoline(addr, &insn, bytes); 450 + if (len == insn.length) { 451 + optimize_nops(bytes, len); 452 + text_poke_early(addr, bytes, len); 453 + } 454 + } 455 + } 456 + 457 + #else /* !RETPOLINES || !CONFIG_STACK_VALIDATION */ 458 + 459 + void __init_or_module noinline apply_retpolines(s32 *start, s32 *end) { } 460 + 461 + #endif /* CONFIG_RETPOLINE && CONFIG_STACK_VALIDATION */ 339 462 340 463 #ifdef CONFIG_SMP 341 464 static void alternatives_smp_lock(const s32 *start, const s32 *end, ··· 768 641 * call with the direct call. 769 642 */ 770 643 apply_paravirt(__parainstructions, __parainstructions_end); 644 + 645 + /* 646 + * Rewrite the retpolines, must be done before alternatives since 647 + * those can rewrite the retpoline thunks. 648 + */ 649 + apply_retpolines(__retpoline_sites, __retpoline_sites_end); 771 650 772 651 /* 773 652 * Then patch alternatives, such that those paravirt calls that are in
+8 -1
arch/x86/kernel/module.c
··· 251 251 struct module *me) 252 252 { 253 253 const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL, 254 - *para = NULL, *orc = NULL, *orc_ip = NULL; 254 + *para = NULL, *orc = NULL, *orc_ip = NULL, 255 + *retpolines = NULL; 255 256 char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; 256 257 257 258 for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) { ··· 268 267 orc = s; 269 268 if (!strcmp(".orc_unwind_ip", secstrings + s->sh_name)) 270 269 orc_ip = s; 270 + if (!strcmp(".retpoline_sites", secstrings + s->sh_name)) 271 + retpolines = s; 271 272 } 272 273 274 + if (retpolines) { 275 + void *rseg = (void *)retpolines->sh_addr; 276 + apply_retpolines(rseg, rseg + retpolines->sh_size); 277 + } 273 278 if (alt) { 274 279 /* patch .altinstructions */ 275 280 void *aseg = (void *)alt->sh_addr;