Skip to content

Commit d5c8028

Browse files
ebiggersIngo Molnar
authored andcommitted
x86/fpu: Reinitialize FPU registers if restoring FPU state fails
Userspace can change the FPU state of a task using the ptrace() or rt_sigreturn() system calls. Because reserved bits in the FPU state can cause the XRSTOR instruction to fail, the kernel has to carefully validate that no reserved bits or other invalid values are being set. Unfortunately, there have been bugs in this validation code. For example, we were not checking that the 'xcomp_bv' field in the xstate_header was 0. As-is, such bugs are exploitable to read the FPU registers of other processes on the system. To do so, an attacker can create a task, assign to it an invalid FPU state, then spin in a loop and monitor the values of the FPU registers. Because the task's FPU registers are not being restored, sometimes the FPU registers will have the values from another process. This is likely to continue to be a problem in the future because the validation done by the CPU instructions like XRSTOR is not immediately visible to kernel developers. Nor will invalid FPU states ever be encountered during ordinary use --- they will only be seen during fuzzing or exploits. There can even be reserved bits outside the xstate_header which are easy to forget about. For example, the MXCSR register contains reserved bits, which were not validated by the KVM_SET_XSAVE ioctl until commit a575813 ("KVM: x86: Fix load damaged SSEx MXCSR register"). Therefore, mitigate this class of vulnerability by restoring the FPU registers from init_fpstate if restoring from the task's state fails. We actually used to do this, but it was (perhaps unwisely) removed by commit 9ccc27a ("x86/fpu: Remove error return values from copy_kernel_to_*regs() functions"). This new patch is also a bit different. First, it only clears the registers, not also the bad in-memory state; this is simpler and makes it easier to make the mitigation cover all callers of __copy_kernel_to_fpregs(). Second, it does the register clearing in an exception handler so that no extra instructions are added to context switches. In fact, we *remove* instructions, since previously we were always zeroing the register containing 'err' even if CONFIG_X86_DEBUG_FPU was disabled. Signed-off-by: Eric Biggers <[email protected]> Reviewed-by: Rik van Riel <[email protected]> Cc: Andrew Morton <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Borislav Petkov <[email protected]> Cc: Dave Hansen <[email protected]> Cc: Dmitry Vyukov <[email protected]> Cc: Eric Biggers <[email protected]> Cc: Fenghua Yu <[email protected]> Cc: Kevin Hao <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Michael Halcrow <[email protected]> Cc: Oleg Nesterov <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: Wanpeng Li <[email protected]> Cc: Yu-cheng Yu <[email protected]> Cc: [email protected] Link: http://lkml.kernel.org/r/[email protected] Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Ingo Molnar <[email protected]>
1 parent 814fb7b commit d5c8028

File tree

2 files changed

+39
-36
lines changed

2 files changed

+39
-36
lines changed

arch/x86/include/asm/fpu/internal.h

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -120,20 +120,11 @@ extern void fpstate_sanitize_xstate(struct fpu *fpu);
120120
err; \
121121
})
122122

123-
#define check_insn(insn, output, input...) \
124-
({ \
125-
int err; \
123+
#define kernel_insn(insn, output, input...) \
126124
asm volatile("1:" #insn "\n\t" \
127125
"2:\n" \
128-
".section .fixup,\"ax\"\n" \
129-
"3: movl $-1,%[err]\n" \
130-
" jmp 2b\n" \
131-
".previous\n" \
132-
_ASM_EXTABLE(1b, 3b) \
133-
: [err] "=r" (err), output \
134-
: "0"(0), input); \
135-
err; \
136-
})
126+
_ASM_EXTABLE_HANDLE(1b, 2b, ex_handler_fprestore) \
127+
: output : input)
137128

138129
static inline int copy_fregs_to_user(struct fregs_state __user *fx)
139130
{
@@ -153,20 +144,16 @@ static inline int copy_fxregs_to_user(struct fxregs_state __user *fx)
153144

154145
static inline void copy_kernel_to_fxregs(struct fxregs_state *fx)
155146
{
156-
int err;
157-
158147
if (IS_ENABLED(CONFIG_X86_32)) {
159-
err = check_insn(fxrstor %[fx], "=m" (*fx), [fx] "m" (*fx));
148+
kernel_insn(fxrstor %[fx], "=m" (*fx), [fx] "m" (*fx));
160149
} else {
161150
if (IS_ENABLED(CONFIG_AS_FXSAVEQ)) {
162-
err = check_insn(fxrstorq %[fx], "=m" (*fx), [fx] "m" (*fx));
151+
kernel_insn(fxrstorq %[fx], "=m" (*fx), [fx] "m" (*fx));
163152
} else {
164153
/* See comment in copy_fxregs_to_kernel() below. */
165-
err = check_insn(rex64/fxrstor (%[fx]), "=m" (*fx), [fx] "R" (fx), "m" (*fx));
154+
kernel_insn(rex64/fxrstor (%[fx]), "=m" (*fx), [fx] "R" (fx), "m" (*fx));
166155
}
167156
}
168-
/* Copying from a kernel buffer to FPU registers should never fail: */
169-
WARN_ON_FPU(err);
170157
}
171158

172159
static inline int copy_user_to_fxregs(struct fxregs_state __user *fx)
@@ -183,9 +170,7 @@ static inline int copy_user_to_fxregs(struct fxregs_state __user *fx)
183170

184171
static inline void copy_kernel_to_fregs(struct fregs_state *fx)
185172
{
186-
int err = check_insn(frstor %[fx], "=m" (*fx), [fx] "m" (*fx));
187-
188-
WARN_ON_FPU(err);
173+
kernel_insn(frstor %[fx], "=m" (*fx), [fx] "m" (*fx));
189174
}
190175

191176
static inline int copy_user_to_fregs(struct fregs_state __user *fx)
@@ -281,18 +266,13 @@ static inline void copy_fxregs_to_kernel(struct fpu *fpu)
281266
* Use XRSTORS to restore context if it is enabled. XRSTORS supports compact
282267
* XSAVE area format.
283268
*/
284-
#define XSTATE_XRESTORE(st, lmask, hmask, err) \
269+
#define XSTATE_XRESTORE(st, lmask, hmask) \
285270
asm volatile(ALTERNATIVE(XRSTOR, \
286271
XRSTORS, X86_FEATURE_XSAVES) \
287272
"\n" \
288-
"xor %[err], %[err]\n" \
289273
"3:\n" \
290-
".pushsection .fixup,\"ax\"\n" \
291-
"4: movl $-2, %[err]\n" \
292-
"jmp 3b\n" \
293-
".popsection\n" \
294-
_ASM_EXTABLE(661b, 4b) \
295-
: [err] "=r" (err) \
274+
_ASM_EXTABLE_HANDLE(661b, 3b, ex_handler_fprestore)\
275+
: \
296276
: "D" (st), "m" (*st), "a" (lmask), "d" (hmask) \
297277
: "memory")
298278

@@ -336,7 +316,10 @@ static inline void copy_kernel_to_xregs_booting(struct xregs_state *xstate)
336316
else
337317
XSTATE_OP(XRSTOR, xstate, lmask, hmask, err);
338318

339-
/* We should never fault when copying from a kernel buffer: */
319+
/*
320+
* We should never fault when copying from a kernel buffer, and the FPU
321+
* state we set at boot time should be valid.
322+
*/
340323
WARN_ON_FPU(err);
341324
}
342325

@@ -365,12 +348,8 @@ static inline void copy_kernel_to_xregs(struct xregs_state *xstate, u64 mask)
365348
{
366349
u32 lmask = mask;
367350
u32 hmask = mask >> 32;
368-
int err;
369-
370-
XSTATE_XRESTORE(xstate, lmask, hmask, err);
371351

372-
/* We should never fault when copying from a kernel buffer: */
373-
WARN_ON_FPU(err);
352+
XSTATE_XRESTORE(xstate, lmask, hmask);
374353
}
375354

376355
/*

arch/x86/mm/extable.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <linux/uaccess.h>
33
#include <linux/sched/debug.h>
44

5+
#include <asm/fpu/internal.h>
56
#include <asm/traps.h>
67
#include <asm/kdebug.h>
78

@@ -78,6 +79,29 @@ bool ex_handler_refcount(const struct exception_table_entry *fixup,
7879
}
7980
EXPORT_SYMBOL_GPL(ex_handler_refcount);
8081

82+
/*
83+
* Handler for when we fail to restore a task's FPU state. We should never get
84+
* here because the FPU state of a task using the FPU (task->thread.fpu.state)
85+
* should always be valid. However, past bugs have allowed userspace to set
86+
* reserved bits in the XSAVE area using PTRACE_SETREGSET or sys_rt_sigreturn().
87+
* These caused XRSTOR to fail when switching to the task, leaking the FPU
88+
* registers of the task previously executing on the CPU. Mitigate this class
89+
* of vulnerability by restoring from the initial state (essentially, zeroing
90+
* out all the FPU registers) if we can't restore from the task's FPU state.
91+
*/
92+
bool ex_handler_fprestore(const struct exception_table_entry *fixup,
93+
struct pt_regs *regs, int trapnr)
94+
{
95+
regs->ip = ex_fixup_addr(fixup);
96+
97+
WARN_ONCE(1, "Bad FPU state detected at %pB, reinitializing FPU registers.",
98+
(void *)instruction_pointer(regs));
99+
100+
__copy_kernel_to_fpregs(&init_fpstate, -1);
101+
return true;
102+
}
103+
EXPORT_SYMBOL_GPL(ex_handler_fprestore);
104+
81105
bool ex_handler_ext(const struct exception_table_entry *fixup,
82106
struct pt_regs *regs, int trapnr)
83107
{

0 commit comments

Comments
 (0)