| From ak@suse.de Tue Apr 18 22:18:03 2006 |
| From: Andi Kleen <ak@suse.de> |
| To: Greg KH <greg@kroah.com> |
| Subject: i386/x86-64: Fix x87 information leak between processes (CVE-2006-1056) |
| Date: Wed, 19 Apr 2006 07:17:31 +0200 |
| Cc: vendor-sec@lst.de, security@kernel.org, richard.brunner@amd.com, jbeulich@novell.com |
| Content-Disposition: inline |
| Message-Id: <200604190717.31631.ak@suse.de> |
| |
| |
| AMD K7/K8 CPUs only save/restore the FOP/FIP/FDP x87 registers in FXSAVE |
| when an exception is pending. This means the value leak through context |
| switches and allow processes to observe some x87 instruction state of |
| other processes. |
| |
| This was actually documented by AMD, but nobody recognized it as being |
| different from Intel before. |
| |
| The fix first adds an optimization: instead of unconditionally calling |
| FNCLEX after each FXSAVE test if ES is pending and skip it when not |
| needed. Then do a x87 load from a kernel variable to clear FOP/FIP/FDP. |
| |
| This means other processes always will only see a constant value defined |
| by the kernel in their FP state. |
| |
| I took some pain to make sure to chose a variable that's already in L1 |
| during context switch to make the overhead of this low. |
| |
| Also alternative() is used to patch away the new code on CPUs who don't |
| need it. |
| |
| Patch for both i386/x86-64. |
| |
| The problem was discovered originally by Jan Beulich. Richard Brunner |
| provided the basic code for the workarounds, with contribution from Jan. |
| |
| This is CVE-2006-1056 |
| |
| Cc: richard.brunner@amd.com |
| Cc: jbeulich@novell.com |
| Signed-off-by: Andi Kleen <ak@suse.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| arch/i386/kernel/cpu/amd.c | 2 ++ |
| arch/x86_64/kernel/process.c | 8 ++++++-- |
| arch/x86_64/kernel/setup.c | 4 ++++ |
| include/asm-i386/cpufeature.h | 1 + |
| include/asm-i386/i387.h | 30 ++++++++++++++++++++++++++---- |
| include/asm-x86_64/cpufeature.h | 1 + |
| include/asm-x86_64/i387.h | 20 +++++++++++++++++++- |
| 7 files changed, 59 insertions(+), 7 deletions(-) |
| |
| --- linux-2.6.16.8.orig/arch/i386/kernel/cpu/amd.c |
| +++ linux-2.6.16.8/arch/i386/kernel/cpu/amd.c |
| @@ -207,6 +207,8 @@ static void __init init_amd(struct cpuin |
| set_bit(X86_FEATURE_K7, c->x86_capability); |
| break; |
| } |
| + if (c->x86 >= 6) |
| + set_bit(X86_FEATURE_FXSAVE_LEAK, c->x86_capability); |
| |
| display_cacheinfo(c); |
| |
| --- linux-2.6.16.8.orig/arch/x86_64/kernel/process.c |
| +++ linux-2.6.16.8/arch/x86_64/kernel/process.c |
| @@ -527,8 +527,6 @@ __switch_to(struct task_struct *prev_p, |
| int cpu = smp_processor_id(); |
| struct tss_struct *tss = &per_cpu(init_tss, cpu); |
| |
| - unlazy_fpu(prev_p); |
| - |
| /* |
| * Reload esp0, LDT and the page table pointer: |
| */ |
| @@ -591,6 +589,12 @@ __switch_to(struct task_struct *prev_p, |
| prev->userrsp = read_pda(oldrsp); |
| write_pda(oldrsp, next->userrsp); |
| write_pda(pcurrent, next_p); |
| + |
| + /* This must be here to ensure both math_state_restore() and |
| + kernel_fpu_begin() work consistently. |
| + And the AMD workaround requires it to be after DS reload. */ |
| + unlazy_fpu(prev_p); |
| + |
| write_pda(kernelstack, |
| task_stack_page(next_p) + THREAD_SIZE - PDA_STACKOFFSET); |
| |
| --- linux-2.6.16.8.orig/arch/x86_64/kernel/setup.c |
| +++ linux-2.6.16.8/arch/x86_64/kernel/setup.c |
| @@ -909,6 +909,10 @@ static int __init init_amd(struct cpuinf |
| if (c->x86 == 15 && ((level >= 0x0f48 && level < 0x0f50) || level >= 0x0f58)) |
| set_bit(X86_FEATURE_REP_GOOD, &c->x86_capability); |
| |
| + /* Enable workaround for FXSAVE leak */ |
| + if (c->x86 >= 6) |
| + set_bit(X86_FEATURE_FXSAVE_LEAK, &c->x86_capability); |
| + |
| r = get_model_name(c); |
| if (!r) { |
| switch (c->x86) { |
| --- linux-2.6.16.8.orig/include/asm-i386/cpufeature.h |
| +++ linux-2.6.16.8/include/asm-i386/cpufeature.h |
| @@ -70,6 +70,7 @@ |
| #define X86_FEATURE_P3 (3*32+ 6) /* P3 */ |
| #define X86_FEATURE_P4 (3*32+ 7) /* P4 */ |
| #define X86_FEATURE_CONSTANT_TSC (3*32+ 8) /* TSC ticks at a constant rate */ |
| +#define X86_FEATURE_FXSAVE_LEAK (3*32+10) /* FXSAVE leaks FOP/FIP/FOP */ |
| |
| /* Intel-defined CPU features, CPUID level 0x00000001 (ecx), word 4 */ |
| #define X86_FEATURE_XMM3 (4*32+ 0) /* Streaming SIMD Extensions-3 */ |
| --- linux-2.6.16.8.orig/include/asm-i386/i387.h |
| +++ linux-2.6.16.8/include/asm-i386/i387.h |
| @@ -13,6 +13,7 @@ |
| |
| #include <linux/sched.h> |
| #include <linux/init.h> |
| +#include <linux/kernel_stat.h> |
| #include <asm/processor.h> |
| #include <asm/sigcontext.h> |
| #include <asm/user.h> |
| @@ -38,17 +39,38 @@ extern void init_fpu(struct task_struct |
| extern void kernel_fpu_begin(void); |
| #define kernel_fpu_end() do { stts(); preempt_enable(); } while(0) |
| |
| +/* We need a safe address that is cheap to find and that is already |
| + in L1 during context switch. The best choices are unfortunately |
| + different for UP and SMP */ |
| +#ifdef CONFIG_SMP |
| +#define safe_address (__per_cpu_offset[0]) |
| +#else |
| +#define safe_address (kstat_cpu(0).cpustat.user) |
| +#endif |
| + |
| /* |
| * These must be called with preempt disabled |
| */ |
| static inline void __save_init_fpu( struct task_struct *tsk ) |
| { |
| + /* Use more nops than strictly needed in case the compiler |
| + varies code */ |
| alternative_input( |
| - "fnsave %1 ; fwait ;" GENERIC_NOP2, |
| - "fxsave %1 ; fnclex", |
| + "fnsave %[fx] ;fwait;" GENERIC_NOP8 GENERIC_NOP4, |
| + "fxsave %[fx]\n" |
| + "bt $7,%[fsw] ; jc 1f ; fnclex\n1:", |
| X86_FEATURE_FXSR, |
| - "m" (tsk->thread.i387.fxsave) |
| - :"memory"); |
| + [fx] "m" (tsk->thread.i387.fxsave), |
| + [fsw] "m" (tsk->thread.i387.fxsave.swd) : "memory"); |
| + /* AMD K7/K8 CPUs don't save/restore FDP/FIP/FOP unless an exception |
| + is pending. Clear the x87 state here by setting it to fixed |
| + values. __per_cpu_offset[0] is a random variable that should be in L1 */ |
| + alternative_input( |
| + GENERIC_NOP8 GENERIC_NOP2, |
| + "emms\n\t" /* clear stack tags */ |
| + "fildl %[addr]", /* set F?P to defined value */ |
| + X86_FEATURE_FXSAVE_LEAK, |
| + [addr] "m" (safe_address)); |
| task_thread_info(tsk)->status &= ~TS_USEDFPU; |
| } |
| |
| --- linux-2.6.16.8.orig/include/asm-x86_64/cpufeature.h |
| +++ linux-2.6.16.8/include/asm-x86_64/cpufeature.h |
| @@ -64,6 +64,7 @@ |
| #define X86_FEATURE_REP_GOOD (3*32+ 4) /* rep microcode works well on this CPU */ |
| #define X86_FEATURE_CONSTANT_TSC (3*32+5) /* TSC runs at constant rate */ |
| #define X86_FEATURE_SYNC_RDTSC (3*32+6) /* RDTSC syncs CPU core */ |
| +#define X86_FEATURE_FXSAVE_LEAK (3*32+7) /* FIP/FOP/FDP leaks through FXSAVE */ |
| |
| /* Intel-defined CPU features, CPUID level 0x00000001 (ecx), word 4 */ |
| #define X86_FEATURE_XMM3 (4*32+ 0) /* Streaming SIMD Extensions-3 */ |
| --- linux-2.6.16.8.orig/include/asm-x86_64/i387.h |
| +++ linux-2.6.16.8/include/asm-x86_64/i387.h |
| @@ -72,6 +72,23 @@ extern int set_fpregs(struct task_struct |
| #define set_fpu_swd(t,val) ((t)->thread.i387.fxsave.swd = (val)) |
| #define set_fpu_fxsr_twd(t,val) ((t)->thread.i387.fxsave.twd = (val)) |
| |
| +#define X87_FSW_ES (1 << 7) /* Exception Summary */ |
| + |
| +/* AMD CPUs don't save/restore FDP/FIP/FOP unless an exception |
| + is pending. Clear the x87 state here by setting it to fixed |
| + values. The kernel data segment can be sometimes 0 and sometimes |
| + new user value. Both should be ok. |
| + Use the PDA as safe address because it should be already in L1. */ |
| +static inline void clear_fpu_state(struct i387_fxsave_struct *fx) |
| +{ |
| + if (unlikely(fx->swd & X87_FSW_ES)) |
| + asm volatile("fnclex"); |
| + alternative_input(ASM_NOP8 ASM_NOP2, |
| + " emms\n" /* clear stack tags */ |
| + " fildl %%gs:0", /* load to clear state */ |
| + X86_FEATURE_FXSAVE_LEAK); |
| +} |
| + |
| static inline int restore_fpu_checking(struct i387_fxsave_struct *fx) |
| { |
| int err; |
| @@ -119,6 +136,7 @@ static inline int save_i387_checking(str |
| #endif |
| if (unlikely(err)) |
| __clear_user(fx, sizeof(struct i387_fxsave_struct)); |
| + /* No need to clear here because the caller clears USED_MATH */ |
| return err; |
| } |
| |
| @@ -149,7 +167,7 @@ static inline void __fxsave_clear(struct |
| "i" (offsetof(__typeof__(*tsk), |
| thread.i387.fxsave))); |
| #endif |
| - __asm__ __volatile__("fnclex"); |
| + clear_fpu_state(&tsk->thread.i387.fxsave); |
| } |
| |
| static inline void kernel_fpu_begin(void) |