Skip to content

Commit dc81e55

Browse files
xinli-intelIngo Molnar
authored andcommitted
x86/fred: Clear WFE in missing-ENDBRANCH #CPs
An indirect branch instruction sets the CPU indirect branch tracker (IBT) into WAIT_FOR_ENDBRANCH (WFE) state and WFE stays asserted across the instruction boundary. When the decoder finds an inappropriate instruction while WFE is set ENDBR, the CPU raises a #CP fault. For the "kernel IBT no ENDBR" selftest where #CPs are deliberately triggered, the WFE state of the interrupted context needs to be cleared to let execution continue. Otherwise when the CPU resumes from the instruction that just caused the previous #CP, another missing-ENDBRANCH #CP is raised and the CPU enters a dead loop. This is not a problem with IDT because it doesn't preserve WFE and IRET doesn't set WFE. But FRED provides space on the entry stack (in an expanded CS area) to save and restore the WFE state, thus the WFE state is no longer clobbered, so software must clear it. Clear WFE to avoid dead looping in ibt_clear_fred_wfe() and the !ibt_fatal code path when execution is allowed to continue. Clobbering WFE in any other circumstance is a security-relevant bug. [ dhansen: changelog rewording ] Fixes: a5f6c2a ("x86/shstk: Add user control-protection fault handler") Signed-off-by: Xin Li (Intel) <xin@zytor.com> Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Acked-by: Dave Hansen <dave.hansen@linux.intel.com> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20241113175934.3897541-1-xin%40zytor.com
1 parent 4bbf902 commit dc81e55

File tree

1 file changed

+30
-0
lines changed

1 file changed

+30
-0
lines changed

arch/x86/kernel/cet.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,34 @@ static void do_user_cp_fault(struct pt_regs *regs, unsigned long error_code)
8181

8282
static __ro_after_init bool ibt_fatal = true;
8383

84+
/*
85+
* By definition, all missing-ENDBRANCH #CPs are a result of WFE && !ENDBR.
86+
*
87+
* For the kernel IBT no ENDBR selftest where #CPs are deliberately triggered,
88+
* the WFE state of the interrupted context needs to be cleared to let execution
89+
* continue. Otherwise when the CPU resumes from the instruction that just
90+
* caused the previous #CP, another missing-ENDBRANCH #CP is raised and the CPU
91+
* enters a dead loop.
92+
*
93+
* This is not a problem with IDT because it doesn't preserve WFE and IRET doesn't
94+
* set WFE. But FRED provides space on the entry stack (in an expanded CS area)
95+
* to save and restore the WFE state, thus the WFE state is no longer clobbered,
96+
* so software must clear it.
97+
*/
98+
static void ibt_clear_fred_wfe(struct pt_regs *regs)
99+
{
100+
/*
101+
* No need to do any FRED checks.
102+
*
103+
* For IDT event delivery, the high-order 48 bits of CS are pushed
104+
* as 0s into the stack, and later IRET ignores these bits.
105+
*
106+
* For FRED, a test to check if fred_cs.wfe is set would be dropped
107+
* by compilers.
108+
*/
109+
regs->fred_cs.wfe = 0;
110+
}
111+
84112
static void do_kernel_cp_fault(struct pt_regs *regs, unsigned long error_code)
85113
{
86114
if ((error_code & CP_EC) != CP_ENDBR) {
@@ -90,13 +118,15 @@ static void do_kernel_cp_fault(struct pt_regs *regs, unsigned long error_code)
90118

91119
if (unlikely(regs->ip == (unsigned long)&ibt_selftest_noendbr)) {
92120
regs->ax = 0;
121+
ibt_clear_fred_wfe(regs);
93122
return;
94123
}
95124

96125
pr_err("Missing ENDBR: %pS\n", (void *)instruction_pointer(regs));
97126
if (!ibt_fatal) {
98127
printk(KERN_DEFAULT CUT_HERE);
99128
__warn(__FILE__, __LINE__, (void *)regs->ip, TAINT_WARN, regs, NULL);
129+
ibt_clear_fred_wfe(regs);
100130
return;
101131
}
102132
BUG();

0 commit comments

Comments
 (0)