mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-09-22 09:34:56 +02:00
x86/bugs: Make Safe-RET robust against interrupt injection
commit 7e7f81cf6f upstream.
An attacker injecting interrupts while the Safe-RET mitigation executes
on machines affected by SRSO can neutralize the safe return sequence,
potentially leading to data leakage through speculative execution.
Fixup register state as if the Safe-RET sequence executed successfully
by "emulating" it, in a manner of speaking, and avoid executing a RET
instruction after returning from the interrupt.
Co-developed-by: David Kaplan <David.Kaplan@amd.com>
Signed-off-by: David Kaplan <David.Kaplan@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
4c34a21ff8
commit
6703dba1d1
@@ -976,6 +976,8 @@ SYM_CODE_START(paranoid_entry)
|
||||
IBRS_ENTER save_reg=%r15
|
||||
UNTRAIN_RET_FROM_CALL
|
||||
|
||||
HANDLE_INTR_SAFERET 8(%rsp)
|
||||
|
||||
RET
|
||||
SYM_CODE_END(paranoid_entry)
|
||||
|
||||
@@ -1078,6 +1080,11 @@ SYM_CODE_START(error_entry)
|
||||
movl %ecx, %eax /* zero extend */
|
||||
cmpq %rax, RIP+8(%rsp)
|
||||
je .Lbstep_iret
|
||||
|
||||
VALIDATE_UNRET_END
|
||||
|
||||
HANDLE_INTR_SAFERET 8(%rsp)
|
||||
|
||||
cmpq $.Lgs_change, RIP+8(%rsp)
|
||||
jne .Lerror_entry_done_lfence
|
||||
|
||||
@@ -1096,7 +1103,6 @@ SYM_CODE_START(error_entry)
|
||||
FENCE_SWAPGS_KERNEL_ENTRY
|
||||
CALL_DEPTH_ACCOUNT
|
||||
leaq 8(%rsp), %rax /* return pt_regs pointer */
|
||||
VALIDATE_UNRET_END
|
||||
RET
|
||||
|
||||
.Lbstep_iret:
|
||||
|
||||
@@ -186,6 +186,53 @@
|
||||
add $(BITS_PER_LONG/8), %_ASM_SP; \
|
||||
lfence;
|
||||
|
||||
/*
|
||||
* Helper for detecting if an interrupt occurred at an unsafe location within
|
||||
* Safe-RET. If Safe-RET is interrupted after the CALL or LEA the RSB may get
|
||||
* poisoned by the interrupt handler.
|
||||
*
|
||||
* The Safe-RET sequence is:
|
||||
*
|
||||
* CALL
|
||||
* LEA 8(%RSP), %RSP
|
||||
* RET
|
||||
*
|
||||
* The two CMPs below check whether RIP points to after the CALL or after the
|
||||
* LEA.
|
||||
*
|
||||
* The LFENCE below is to address this particular speculation case:
|
||||
*
|
||||
* 1. Userspace runs and poisons the BTB around the safe-RET routine
|
||||
*
|
||||
* 2. Userspace triggers some kind of exception
|
||||
*
|
||||
* 3. Kernel executes error_entry() and mis-speculates the branch into thinking
|
||||
* it actually came from kernel space
|
||||
*
|
||||
* 4. The kernel then further mis-speculates that the exception occurred due
|
||||
* to an interrupted safe-RET
|
||||
*
|
||||
* 5. The handle_interrupted_saferet() routine speculatively executes and
|
||||
* speculatively does a safe-RET. But this is unsafe since it was never
|
||||
* untrained.
|
||||
*
|
||||
* The LFENCE fixes this by ensuring step 5 is never reached speculatively.
|
||||
* Note that this LFENCE only occurs if safe-RET was actually interrupted (so
|
||||
* it's outside of the normal path).
|
||||
*
|
||||
* (The 128 below is RIP offset, used as a naked number here for ease of
|
||||
* backporting).
|
||||
*/
|
||||
#define __HANDLE_INTR_SAFERET(name, pt_regs) \
|
||||
cmpq $(name), 128+pt_regs; \
|
||||
jb 1f; \
|
||||
cmpq $(name)+5, 128+pt_regs; \
|
||||
ja 1f; \
|
||||
lfence; \
|
||||
leaq pt_regs, %rdi; \
|
||||
call handle_interrupted_saferet; \
|
||||
1:
|
||||
|
||||
#ifdef __ASSEMBLY__
|
||||
|
||||
/*
|
||||
@@ -315,6 +362,14 @@
|
||||
#define UNTRAIN_RET_FROM_CALL \
|
||||
__UNTRAIN_RET X86_FEATURE_ENTRY_IBPB, __stringify(RESET_CALL_DEPTH_FROM_CALL)
|
||||
|
||||
.macro HANDLE_INTR_SAFERET pt_regs
|
||||
#ifdef CONFIG_CPU_SRSO
|
||||
ALTERNATIVE_2 "", \
|
||||
__stringify(__HANDLE_INTR_SAFERET(srso_safe_ret, \pt_regs)), X86_FEATURE_SRSO, \
|
||||
__stringify(__HANDLE_INTR_SAFERET(srso_alias_safe_ret, \pt_regs)), X86_FEATURE_SRSO_ALIAS
|
||||
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.macro CALL_DEPTH_ACCOUNT
|
||||
#ifdef CONFIG_CALL_DEPTH_TRACKING
|
||||
@@ -649,6 +704,10 @@ static __always_inline void x86_idle_clear_cpu_buffers(void)
|
||||
x86_clear_cpu_buffers();
|
||||
}
|
||||
|
||||
void srso_safe_ret(void);
|
||||
void srso_alias_safe_ret(void);
|
||||
void handle_interrupted_saferet(struct pt_regs *regs);
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#endif /* _ASM_X86_NOSPEC_BRANCH_H_ */
|
||||
|
||||
@@ -3489,3 +3489,42 @@ ssize_t cpu_show_vmscape(struct device *dev, struct device_attribute *attr, char
|
||||
return cpu_show_common(dev, attr, buf, X86_BUG_VMSCAPE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CPU_SRSO
|
||||
/*
|
||||
* Called during exception/interrupt entry if interrupted during the
|
||||
* safe-RET sequence. The safe-RET sequence consists of 3 instructions:
|
||||
*
|
||||
* CALL
|
||||
* LEA 8(%RSP), %RSP
|
||||
* RET
|
||||
*
|
||||
* An interrupt after the CALL or after the LEA could potentially lead
|
||||
* to branch predictor poisoning and results in the sequence not being
|
||||
* able to be safely resumed.
|
||||
*
|
||||
* Therefore, modify the regs state as if the remaining part of the
|
||||
* safe-RET sequence executed so the interrupt returns back to the
|
||||
* desired return target, instead of the to the safe-RET sequence.
|
||||
*/
|
||||
void noinstr handle_interrupted_saferet(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long rip = regs->ip;
|
||||
|
||||
if (rip == (unsigned long) srso_safe_ret ||
|
||||
rip == (unsigned long) srso_alias_safe_ret) {
|
||||
/* Modify stack pointer as if LEA executed: */
|
||||
regs->sp += 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjust registers as if RET executed:
|
||||
*
|
||||
* 1. Read the return address off the stack and into rIP:
|
||||
*/
|
||||
regs->ip = *(unsigned long *)(regs->sp);
|
||||
|
||||
/* 2. Pop rIP off the stack: */
|
||||
regs->sp += 8;
|
||||
}
|
||||
#endif /* CONFIG_CPU_SRSO */
|
||||
|
||||
@@ -161,10 +161,24 @@ __EXPORT_THUNK(srso_alias_untrain_ret)
|
||||
|
||||
.pushsection .text..__x86.rethunk_safe
|
||||
SYM_START(srso_alias_safe_ret, SYM_L_GLOBAL, SYM_A_NONE)
|
||||
|
||||
/*
|
||||
* Tell objtool that those are not function pointers referenced by
|
||||
* __HANDLE_INTR_SAFERET(). Below too.
|
||||
*/
|
||||
ANNOTATE_NOENDBR
|
||||
|
||||
/*
|
||||
* Safe-RET sequence. If you need to change it, adjust
|
||||
* handle_interrupted_saferet() too.
|
||||
*/
|
||||
lea 8(%_ASM_SP), %_ASM_SP
|
||||
UNWIND_HINT_FUNC
|
||||
|
||||
ANNOTATE_NOENDBR
|
||||
ANNOTATE_UNRET_SAFE
|
||||
ret
|
||||
/* End of Safe-RET sequence */
|
||||
int3
|
||||
SYM_FUNC_END(srso_alias_safe_ret)
|
||||
|
||||
@@ -199,8 +213,14 @@ SYM_START(srso_untrain_ret, SYM_L_LOCAL, SYM_A_NONE)
|
||||
* the stack.
|
||||
*/
|
||||
SYM_INNER_LABEL(srso_safe_ret, SYM_L_GLOBAL)
|
||||
/*
|
||||
* Safe-RET sequence. If you need to change it, adjust
|
||||
* handle_interrupted_saferet() too.
|
||||
*/
|
||||
lea 8(%_ASM_SP), %_ASM_SP
|
||||
ret
|
||||
/* End of Safe-RET sequence */
|
||||
|
||||
int3
|
||||
int3
|
||||
/* end of movabs */
|
||||
|
||||
Reference in New Issue
Block a user