Note: needs to be in a section distinct from Retpolines such that the Retpoline RET substitution cannot possibly use immediate jumps. ORC unwinding for zen_untrain_ret() and __x86_return_thunk() is a little tricky but works due to the fact that zen_untrain_ret() doesn't have any stack ops and as such will emit a single ORC entry at the start (+0x3f). Meanwhile, unwinding an IP, including the __x86_return_thunk() one (+0x40) will search for the largest ORC entry smaller or equal to the IP, these will find the one ORC entry (+0x3f) and all works. [ Alexandre: SVM part. ] [ bp: Build fix, massages. ]
Unified · Split
| @@ -96,6 +96,7 @@ SYM_CODE_START(entry_SYSCALL_64) · 95 unmodified lines | ||
| 96 | 96 | |
| 97 | 97 | SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL) |
| 98 | 98 | ANNOTATE_NOENDBR |
| 99 | + UNTRAIN_RET | |
| 99 | 100 | |
| 100 | 101 | /* Construct struct pt_regs on stack */ |
| 101 | 102 | pushq $__USER_DS /* pt_regs->ss */ |
| @@ -717,6 +718,7 @@ native_irq_return_ldt: · 615 unmodified lines | ||
| 717 | 718 | pushq %rdi /* Stash user RDI */ |
| 718 | 719 | swapgs /* to kernel GS */ |
| 719 | 720 | SWITCH_TO_KERNEL_CR3 scratch_reg=%rdi /* to kernel CR3 */ |
| 721 | + UNTRAIN_RET | |
| 720 | 722 | |
| 721 | 723 | movq PER_CPU_VAR(espfix_waddr), %rdi |
| 722 | 724 | movq %rax, (0*8)(%rdi) /* user RAX */ |
| @@ -911,6 +913,7 @@ SYM_CODE_START_LOCAL(paranoid_entry) · 188 unmodified lines | ||
| 911 | 913 | * be retrieved from a kernel internal table. |
| 912 | 914 | */ |
| 913 | 915 | SAVE_AND_SWITCH_TO_KERNEL_CR3 scratch_reg=%rax save_reg=%r14 |
| 916 | + UNTRAIN_RET | |
| 914 | 917 | |
| 915 | 918 | /* |
| 916 | 919 | * Handling GSBASE depends on the availability of FSGSBASE. |
| @@ -1020,6 +1023,7 @@ SYM_CODE_START_LOCAL(error_entry) · 103 unmodified lines | ||
| 1020 | 1023 | FENCE_SWAPGS_USER_ENTRY |
| 1021 | 1024 | /* We have user CR3. Change to kernel CR3. */ |
| 1022 | 1025 | SWITCH_TO_KERNEL_CR3 scratch_reg=%rax |
| 1026 | + UNTRAIN_RET | |
| 1023 | 1027 | |
| 1024 | 1028 | leaq 8(%rsp), %rdi /* arg0 = pt_regs pointer */ |
| 1025 | 1029 | .Lerror_entry_from_usermode_after_swapgs: |
| @@ -1072,6 +1076,7 @@ SYM_CODE_START_LOCAL(error_entry) · 46 unmodified lines | ||
| 1072 | 1076 | swapgs |
| 1073 | 1077 | FENCE_SWAPGS_USER_ENTRY |
| 1074 | 1078 | SWITCH_TO_KERNEL_CR3 scratch_reg=%rax |
| 1079 | + UNTRAIN_RET | |
| 1075 | 1080 | |
| 1076 | 1081 | /* |
| 1077 | 1082 | * Pretend that the exception came from user mode: set up pt_regs |
| @@ -1167,6 +1172,7 @@ SYM_CODE_START(asm_exc_nmi) · 89 unmodified lines | ||
| 1167 | 1172 | movq %rsp, %rdx |
| 1168 | 1173 | movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp |
| 1169 | 1174 | UNWIND_HINT_IRET_REGS base=%rdx offset=8 |
| 1175 | + UNTRAIN_RET | |
| 1170 | 1176 | pushq 5*8(%rdx) /* pt_regs->ss */ |
| 1171 | 1177 | pushq 4*8(%rdx) /* pt_regs->rsp */ |
| 1172 | 1178 | pushq 3*8(%rdx) /* pt_regs->flags */ |
| @@ -14,6 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | #include <asm/irqflags.h> |
| 15 | 15 | #include <asm/asm.h> |
| 16 | 16 | #include <asm/smap.h> |
| 17 | +#include <asm/nospec-branch.h> | |
| 17 | 18 | #include <linux/linkage.h> |
| 18 | 19 | #include <linux/err.h> |
| 19 | 20 | |
| @@ -72,6 +73,7 @@ SYM_CODE_START(entry_SYSENTER_compat) · 52 unmodified lines | ||
| 72 | 73 | pushq $__USER32_CS /* pt_regs->cs */ |
| 73 | 74 | pushq $0 /* pt_regs->ip = 0 (placeholder) */ |
| 74 | 75 | SYM_INNER_LABEL(entry_SYSENTER_compat_after_hwframe, SYM_L_GLOBAL) |
| 76 | + UNTRAIN_RET | |
| 75 | 77 | |
| 76 | 78 | /* |
| 77 | 79 | * User tracing code (ptrace or signal handlers) might assume that |
| @@ -190,6 +192,7 @@ SYM_CODE_START(entry_SYSCALL_compat) · 112 unmodified lines | ||
| 190 | 192 | |
| 191 | 193 | SYM_INNER_LABEL(entry_SYSCALL_compat_safe_stack, SYM_L_GLOBAL) |
| 192 | 194 | ANNOTATE_NOENDBR |
| 195 | + UNTRAIN_RET | |
| 193 | 196 | |
| 194 | 197 | /* Construct struct pt_regs on stack */ |
| 195 | 198 | pushq $__USER32_DS /* pt_regs->ss */ |
| @@ -332,6 +335,7 @@ SYM_CODE_START(entry_INT80_compat) · 136 unmodified lines | ||
| 332 | 335 | pushq 0*8(%rax) /* regs->orig_ax */ |
| 333 | 336 | .Lint80_keep_stack: |
| 334 | 337 | |
| 338 | + UNTRAIN_RET | |
| 335 | 339 | PUSH_AND_CLEAR_REGS rax=$-ENOSYS |
| 336 | 340 | UNWIND_HINT_REGS |
| 337 | 341 | |
| @@ -301,6 +301,7 @@ · 300 unmodified lines | ||
| 301 | 301 | #define X86_FEATURE_RETPOLINE (11*32+12) /* "" Generic Retpoline mitigation for Spectre variant 2 */ |
| 302 | 302 | #define X86_FEATURE_RETPOLINE_LFENCE (11*32+13) /* "" Use LFENCE for Spectre variant 2 */ |
| 303 | 303 | #define X86_FEATURE_RETHUNK (11*32+14) /* "" Use REturn THUNK */ |
| 304 | +#define X86_FEATURE_UNRET (11*32+15) /* "" AMD BTB untrain return */ | |
| 304 | 305 | |
| 305 | 306 | /* Intel-defined CPU features, CPUID level 0x00000007:1 (EAX), word 12 */ |
| 306 | 307 | #define X86_FEATURE_AVX_VNNI (12*32+ 4) /* AVX VNNI instructions */ |
| @@ -55,7 +55,8 @@ · 54 unmodified lines | ||
| 55 | 55 | #else |
| 56 | 56 | # define DISABLE_RETPOLINE ((1 << (X86_FEATURE_RETPOLINE & 31)) | \ |
| 57 | 57 | (1 << (X86_FEATURE_RETPOLINE_LFENCE & 31)) | \ |
| 58 | − (1 << (X86_FEATURE_RETHUNK & 31))) | |
| 58 | + (1 << (X86_FEATURE_RETHUNK & 31)) | \ | |
| 59 | + (1 << (X86_FEATURE_UNRET & 31))) | |
| 59 | 60 | #endif |
| 60 | 61 | |
| 61 | 62 | #ifdef CONFIG_INTEL_IOMMU_SVM |
| @@ -112,6 +112,22 @@ · 111 unmodified lines | ||
| 112 | 112 | #endif |
| 113 | 113 | .endm |
| 114 | 114 | |
| 115 | +/* | |
| 116 | + * Mitigate RETBleed for AMD/Hygon Zen uarch. Requires KERNEL CR3 because the | |
| 117 | + * return thunk isn't mapped into the userspace tables (then again, AMD | |
| 118 | + * typically has NO_MELTDOWN). | |
| 119 | + * | |
| 120 | + * Doesn't clobber any registers but does require a stable stack. | |
| 121 | + * | |
| 122 | + * As such, this must be placed after every *SWITCH_TO_KERNEL_CR3 at a point | |
| 123 | + * where we have a stack but before any RET instruction. | |
| 124 | + */ | |
| 125 | +.macro UNTRAIN_RET | |
| 126 | +#ifdef CONFIG_RETPOLINE | |
| 127 | + ALTERNATIVE "", "call zen_untrain_ret", X86_FEATURE_UNRET | |
| 128 | +#endif | |
| 129 | +.endm | |
| 130 | + | |
| 115 | 131 | #else /* __ASSEMBLY__ */ |
| 116 | 132 | |
| 117 | 133 | #define ANNOTATE_RETPOLINE_SAFE \ |
| @@ -124,6 +140,7 @@ typedef u8 retpoline_thunk_t[RETPOLINE_THUNK_SIZE]; · 6 unmodified lines | ||
| 124 | 140 | extern retpoline_thunk_t __x86_indirect_thunk_array[]; |
| 125 | 141 | |
| 126 | 142 | extern void __x86_return_thunk(void); |
| 143 | +extern void zen_untrain_ret(void); | |
| 127 | 144 | |
| 128 | 145 | #ifdef CONFIG_RETPOLINE |
| 129 | 146 | |
| @@ -141,7 +141,7 @@ SECTIONS · 140 unmodified lines | ||
| 141 | 141 | |
| 142 | 142 | #ifdef CONFIG_RETPOLINE |
| 143 | 143 | __indirect_thunk_start = .; |
| 144 | − *(.text.__x86.indirect_thunk) | |
| 144 | + *(.text.__x86.*) | |
| 145 | 145 | __indirect_thunk_end = .; |
| 146 | 146 | #endif |
| 147 | 147 | } :text =0xcccc |
| @@ -110,6 +110,15 @@ SYM_FUNC_START(__svm_vcpu_run) · 109 unmodified lines | ||
| 110 | 110 | mov %r15, VCPU_R15(%_ASM_AX) |
| 111 | 111 | #endif |
| 112 | 112 | |
| 113 | + /* | |
| 114 | + * Mitigate RETBleed for AMD/Hygon Zen uarch. RET should be | |
| 115 | + * untrained as soon as we exit the VM and are back to the | |
| 116 | + * kernel. This should be done before re-enabling interrupts | |
| 117 | + * because interrupt handlers won't sanitize 'ret' if the return is | |
| 118 | + * from the kernel. | |
| 119 | + */ | |
| 120 | + UNTRAIN_RET | |
| 121 | + | |
| 113 | 122 | /* |
| 114 | 123 | * Clear all general purpose registers except RSP and RAX to prevent |
| 115 | 124 | * speculative use of the guest's values, even those that are reloaded |
| @@ -190,6 +199,15 @@ SYM_FUNC_START(__svm_sev_es_vcpu_run) · 74 unmodified lines | ||
| 190 | 199 | FILL_RETURN_BUFFER %_ASM_AX, RSB_CLEAR_LOOPS, X86_FEATURE_RETPOLINE |
| 191 | 200 | #endif |
| 192 | 201 | |
| 202 | + /* | |
| 203 | + * Mitigate RETBleed for AMD/Hygon Zen uarch. RET should be | |
| 204 | + * untrained as soon as we exit the VM and are back to the | |
| 205 | + * kernel. This should be done before re-enabling interrupts | |
| 206 | + * because interrupt handlers won't sanitize RET if the return is | |
| 207 | + * from the kernel. | |
| 208 | + */ | |
| 209 | + UNTRAIN_RET | |
| 210 | + | |
| 193 | 211 | pop %_ASM_BX |
| 194 | 212 | |
| 195 | 213 | #ifdef CONFIG_X86_64 |
| @@ -72,11 +72,67 @@ SYM_CODE_END(__x86_indirect_thunk_array) · 71 unmodified lines | ||
| 72 | 72 | * This function name is magical and is used by -mfunction-return=thunk-extern |
| 73 | 73 | * for the compiler to generate JMPs to it. |
| 74 | 74 | */ |
| 75 | −SYM_CODE_START(__x86_return_thunk) | |
| 76 | − UNWIND_HINT_EMPTY | |
| 77 | − ANNOTATE_NOENDBR | |
| 75 | + .section .text.__x86.return_thunk | |
| 76 | + | |
| 77 | +/* | |
| 78 | + * Safety details here pertain to the AMD Zen{1,2} microarchitecture: | |
| 79 | + * 1) The RET at __x86_return_thunk must be on a 64 byte boundary, for | |
| 80 | + * alignment within the BTB. | |
| 81 | + * 2) The instruction at zen_untrain_ret must contain, and not | |
| 82 | + * end with, the 0xc3 byte of the RET. | |
| 83 | + * 3) STIBP must be enabled, or SMT disabled, to prevent the sibling thread | |
| 84 | + * from re-poisioning the BTB prediction. | |
| 85 | + */ | |
| 86 | + .align 64 | |
| 87 | + .skip 63, 0xcc | |
| 88 | +SYM_FUNC_START_NOALIGN(zen_untrain_ret); | |
| 89 | + | |
| 90 | + /* | |
| 91 | + * As executed from zen_untrain_ret, this is: | |
| 92 | + * | |
| 93 | + * TEST $0xcc, %bl | |
| 94 | + * LFENCE | |
| 95 | + * JMP __x86_return_thunk | |
| 96 | + * | |
| 97 | + * Executing the TEST instruction has a side effect of evicting any BTB | |
| 98 | + * prediction (potentially attacker controlled) attached to the RET, as | |
| 99 | + * __x86_return_thunk + 1 isn't an instruction boundary at the moment. | |
| 100 | + */ | |
| 101 | + .byte 0xf6 | |
| 102 | + | |
| 103 | + /* | |
| 104 | + * As executed from __x86_return_thunk, this is a plain RET. | |
| 105 | + * | |
| 106 | + * As part of the TEST above, RET is the ModRM byte, and INT3 the imm8. | |
| 107 | + * | |
| 108 | + * We subsequently jump backwards and architecturally execute the RET. | |
| 109 | + * This creates a correct BTB prediction (type=ret), but in the | |
| 110 | + * meantime we suffer Straight Line Speculation (because the type was | |
| 111 | + * no branch) which is halted by the INT3. | |
| 112 | + * | |
| 113 | + * With SMT enabled and STIBP active, a sibling thread cannot poison | |
| 114 | + * RET's prediction to a type of its choice, but can evict the | |
| 115 | + * prediction due to competitive sharing. If the prediction is | |
| 116 | + * evicted, __x86_return_thunk will suffer Straight Line Speculation | |
| 117 | + * which will be contained safely by the INT3. | |
| 118 | + */ | |
| 119 | +SYM_INNER_LABEL(__x86_return_thunk, SYM_L_GLOBAL) | |
| 78 | 120 | ret |
| 79 | 121 | int3 |
| 80 | 122 | SYM_CODE_END(__x86_return_thunk) |
| 81 | 123 | |
| 82 | −__EXPORT_THUNK(__x86_return_thunk) | |
| 124 | + /* | |
| 125 | + * Ensure the TEST decoding / BTB invalidation is complete. | |
| 126 | + */ | |
| 127 | + lfence | |
| 128 | + | |
| 129 | + /* | |
| 130 | + * Jump back and execute the RET in the middle of the TEST instruction. | |
| 131 | + * INT3 is for SLS protection. | |
| 132 | + */ | |
| 133 | + jmp __x86_return_thunk | |
| 134 | + int3 | |
| 135 | +SYM_FUNC_END(zen_untrain_ret) | |
| 136 | +__EXPORT_THUNK(zen_untrain_ret) | |
| 137 | + | |
| 138 | +EXPORT_SYMBOL(__x86_return_thunk) | |
| @@ -1302,7 +1302,7 @@ static void add_retpoline_call(struct objtool_file *file, struct instruction *in · 1301 unmodified lines | ||
| 1302 | 1302 | annotate_call_site(file, insn, false); |
| 1303 | 1303 | } |
| 1304 | 1304 | |
| 1305 | −static void add_return_call(struct objtool_file *file, struct instruction *insn) | |
| 1305 | +static void add_return_call(struct objtool_file *file, struct instruction *insn, bool add) | |
| 1306 | 1306 | { |
| 1307 | 1307 | /* |
| 1308 | 1308 | * Return thunk tail calls are really just returns in disguise, |
| @@ -1311,7 +1311,8 @@ static void add_return_call(struct objtool_file *file, struct instruction *insn) · 2 unmodified lines | ||
| 1311 | 1311 | insn->type = INSN_RETURN; |
| 1312 | 1312 | insn->retpoline_safe = true; |
| 1313 | 1313 | |
| 1314 | − list_add_tail(&insn->call_node, &file->return_thunk_list); | |
| 1314 | + if (add) | |
| 1315 | + list_add_tail(&insn->call_node, &file->return_thunk_list); | |
| 1315 | 1316 | } |
| 1316 | 1317 | |
| 1317 | 1318 | static bool same_function(struct instruction *insn1, struct instruction *insn2) |
| @@ -1367,7 +1368,7 @@ static int add_jump_destinations(struct objtool_file *file) · 49 unmodified lines | ||
| 1367 | 1368 | add_retpoline_call(file, insn); |
| 1368 | 1369 | continue; |
| 1369 | 1370 | } else if (reloc->sym->return_thunk) { |
| 1370 | − add_return_call(file, insn); | |
| 1371 | + add_return_call(file, insn, true); | |
| 1371 | 1372 | continue; |
| 1372 | 1373 | } else if (insn->func) { |
| 1373 | 1374 | /* |
| @@ -1387,6 +1388,21 @@ static int add_jump_destinations(struct objtool_file *file) · 13 unmodified lines | ||
| 1387 | 1388 | |
| 1388 | 1389 | jump_dest = find_insn(file, dest_sec, dest_off); |
| 1389 | 1390 | if (!jump_dest) { |
| 1391 | + struct symbol *sym = find_symbol_by_offset(dest_sec, dest_off); | |
| 1392 | + | |
| 1393 | + /* | |
| 1394 | + * This is a special case for zen_untrain_ret(). | |
| 1395 | + * It jumps to __x86_return_thunk(), but objtool | |
| 1396 | + * can't find the thunk's starting RET | |
| 1397 | + * instruction, because the RET is also in the | |
| 1398 | + * middle of another instruction. Objtool only | |
| 1399 | + * knows about the outer instruction. | |
| 1400 | + */ | |
| 1401 | + if (sym && sym->return_thunk) { | |
| 1402 | + add_return_call(file, insn, false); | |
| 1403 | + continue; | |
| 1404 | + } | |
| 1405 | + | |
| 1390 | 1406 | WARN_FUNC("can't find jump dest instruction at %s+0x%lx", |
| 1391 | 1407 | insn->sec, insn->offset, dest_sec->name, |
| 1392 | 1408 | dest_off); |