Skip to content

Commit deed19b

Browse files
jpoimboePeter Zijlstra
authored andcommitted
x86/fred: Play nice with invoking asm_fred_entry_from_kvm() on non-FRED hardware
Modify asm_fred_entry_from_kvm() to allow it to be invoked by KVM even when FRED isn't fully enabled, e.g. when running with CONFIG_X86_FRED=y on non-FRED hardware. This will allow forcing KVM to always use the FRED entry points for 64-bit kernels, which in turn will eliminate a rather gross non-CFI indirect call that KVM uses to trampoline IRQs by doing IDT lookups. The point of asm_fred_entry_from_kvm() is to bridge between C (vmx:handle_external_interrupt_irqoff()) and more C (__fred_entry_from_kvm()) while changing the calling context to appear like an interrupt (pt_regs). Making the whole thing bound by C ABI. All that remains for non-FRED hardware is to restore RSP (to undo the redzone and alignment). However the trivial change would result in code like: push %rbp mov %rsp, %rbp sub $REDZONE, %rsp and $MASK, %rsp PUSH_AND_CLEAR_REGS push %rbp POP_REGS pop %rbp <-- *objtool fail* mov %rbp, %rsp pop %rbp ret And this will confuse objtool something wicked -- it gets confused by the extra pop %rbp, not realizing the push and pop preserve the value. Rather than trying to each objtool about this, recognise that since the code is bound by C ABI on both ends and interrupts are not allowed to change pt_regs (only exceptions are) it is sufficient to PUSH_REGS in order to create pt_regs, but there is no reason to POP_REGS -- provided the callee-saved registers are preserved. So avoid clearing callee-saved regs and skip POP_REGS. [Original patch by Sean; much of this version by Josh; Changelog, comments and final form by Peterz] Originally-by: Sean Christopherson <[email protected]> Signed-off-by: Josh Poimboeuf <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Acked-by: Sean Christopherson <[email protected]> Link: https://lkml.kernel.org/r/[email protected]
1 parent 2d1435b commit deed19b

File tree

3 files changed

+32
-13
lines changed

3 files changed

+32
-13
lines changed

arch/x86/entry/calling.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ For 32-bit we have the following conventions - kernel is built with
9999
.endif
100100
.endm
101101

102-
.macro CLEAR_REGS clear_bp=1
102+
.macro CLEAR_REGS clear_callee=1
103103
/*
104104
* Sanitize registers of values that a speculation attack might
105105
* otherwise want to exploit. The lower registers are likely clobbered
@@ -113,20 +113,19 @@ For 32-bit we have the following conventions - kernel is built with
113113
xorl %r9d, %r9d /* nospec r9 */
114114
xorl %r10d, %r10d /* nospec r10 */
115115
xorl %r11d, %r11d /* nospec r11 */
116+
.if \clear_callee
116117
xorl %ebx, %ebx /* nospec rbx */
117-
.if \clear_bp
118118
xorl %ebp, %ebp /* nospec rbp */
119-
.endif
120119
xorl %r12d, %r12d /* nospec r12 */
121120
xorl %r13d, %r13d /* nospec r13 */
122121
xorl %r14d, %r14d /* nospec r14 */
123122
xorl %r15d, %r15d /* nospec r15 */
124-
123+
.endif
125124
.endm
126125

127-
.macro PUSH_AND_CLEAR_REGS rdx=%rdx rcx=%rcx rax=%rax save_ret=0 clear_bp=1 unwind_hint=1
126+
.macro PUSH_AND_CLEAR_REGS rdx=%rdx rcx=%rcx rax=%rax save_ret=0 clear_callee=1 unwind_hint=1
128127
PUSH_REGS rdx=\rdx, rcx=\rcx, rax=\rax, save_ret=\save_ret unwind_hint=\unwind_hint
129-
CLEAR_REGS clear_bp=\clear_bp
128+
CLEAR_REGS clear_callee=\clear_callee
130129
.endm
131130

132131
.macro POP_REGS pop_rdi=1

arch/x86/entry/entry_64_fred.S

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,37 @@ SYM_FUNC_START(asm_fred_entry_from_kvm)
112112
push %rax /* Return RIP */
113113
push $0 /* Error code, 0 for IRQ/NMI */
114114

115-
PUSH_AND_CLEAR_REGS clear_bp=0 unwind_hint=0
115+
PUSH_AND_CLEAR_REGS clear_callee=0 unwind_hint=0
116+
116117
movq %rsp, %rdi /* %rdi -> pt_regs */
118+
/*
119+
* At this point: {rdi, rsi, rdx, rcx, r8, r9}, {r10, r11}, {rax, rdx}
120+
* are clobbered, which corresponds to: arguments, extra caller-saved
121+
* and return. All registers a C function is allowed to clobber.
122+
*
123+
* Notably, the callee-saved registers: {rbx, r12, r13, r14, r15}
124+
* are untouched, with the exception of rbp, which carries the stack
125+
* frame and will be restored before exit.
126+
*
127+
* Further calling another C function will not alter this state.
128+
*/
117129
call __fred_entry_from_kvm /* Call the C entry point */
118-
POP_REGS
119-
ERETS
120-
1:
130+
121131
/*
122-
* Objtool doesn't understand what ERETS does, this hint tells it that
123-
* yes, we'll reach here and with what stack state. A save/restore pair
124-
* isn't strictly needed, but it's the simplest form.
132+
* When FRED, use ERETS to potentially clear NMIs, otherwise simply
133+
* restore the stack pointer.
134+
*/
135+
ALTERNATIVE "nop; nop; mov %rbp, %rsp", \
136+
__stringify(add $C_PTREGS_SIZE, %rsp; ERETS), \
137+
X86_FEATURE_FRED
138+
139+
1: /*
140+
* Objtool doesn't understand ERETS, and the cfi register state is
141+
* different from initial_func_cfi due to PUSH_REGS. Tell it the state
142+
* is similar to where UNWIND_HINT_SAVE is.
125143
*/
126144
UNWIND_HINT_RESTORE
145+
127146
pop %rbp
128147
RET
129148

arch/x86/kernel/asm-offsets.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ static void __used common(void)
102102

103103
BLANK();
104104
DEFINE(PTREGS_SIZE, sizeof(struct pt_regs));
105+
OFFSET(C_PTREGS_SIZE, pt_regs, orig_ax);
105106

106107
/* TLB state for the entry code */
107108
OFFSET(TLB_STATE_user_pcid_flush_mask, tlb_state, user_pcid_flush_mask);

0 commit comments

Comments
 (0)