diff options
author | Josh Boyer <jwboyer@fedoraproject.org> | 2015-01-06 09:23:00 -0500 |
---|---|---|
committer | Josh Boyer <jwboyer@fedoraproject.org> | 2015-01-06 09:23:03 -0500 |
commit | e058acab450e054f8cab5b5730f4feefaaf8e6bf (patch) | |
tree | 0290b88a273289c2da520c126e1fa8e3f4bf9bd9 | |
parent | d994f31028496ec7022952c849624cd1ff304991 (diff) | |
download | kernel-e058acab450e054f8cab5b5730f4feefaaf8e6bf.tar.gz kernel-e058acab450e054f8cab5b5730f4feefaaf8e6bf.tar.xz kernel-e058acab450e054f8cab5b5730f4feefaaf8e6bf.zip |
CVE-2014-9419 partial ASLR bypass through TLS base addr leak (rhbz 1177260 1177263)
-rw-r--r-- | kernel.spec | 7 | ||||
-rw-r--r-- | x86_64-switch_to-Load-TLS-descriptors-before-switchi.patch | 309 |
2 files changed, 316 insertions, 0 deletions
diff --git a/kernel.spec b/kernel.spec index bcc9256c..ecf08241 100644 --- a/kernel.spec +++ b/kernel.spec @@ -679,6 +679,9 @@ Patch26121: Set-UID-in-sess_auth_rawntlmssp_authenticate-too.patch #CVE-2014-9428 rhbz 1178826,1178833 Patch26122: batman-adv-Calculate-extra-tail-size-based-on-queued.patch +#CVE-2014-9419 rhbz 1177260,1177263 +Patch26123: x86_64-switch_to-Load-TLS-descriptors-before-switchi.patch + # git clone ssh://git.fedorahosted.org/git/kernel-arm64.git, git diff master...devel Patch30000: kernel-arm64.patch @@ -1467,6 +1470,9 @@ ApplyPatch Set-UID-in-sess_auth_rawntlmssp_authenticate-too.patch #CVE-2014-9428 rhbz 1178826,1178833 ApplyPatch batman-adv-Calculate-extra-tail-size-based-on-queued.patch +#CVE-2014-9419 rhbz 1177260,1177263 +ApplyPatch x86_64-switch_to-Load-TLS-descriptors-before-switchi.patch + %if 0%{?aarch64patches} ApplyPatch kernel-arm64.patch %ifnarch aarch64 # this is stupid, but i want to notice before secondary koji does. @@ -2342,6 +2348,7 @@ fi # || || %changelog * Tue Jan 06 2015 Josh Boyer <jwboyer@fedoraproject.org> +- CVE-2014-9419 partial ASLR bypass through TLS base addr leak (rhbz 1177260 1177263) - CVE-2014-9428 remote DoS via batman-adv (rhbz 1178826 1178833) - Fix CIFS login issue (rhbz 1163927) diff --git a/x86_64-switch_to-Load-TLS-descriptors-before-switchi.patch b/x86_64-switch_to-Load-TLS-descriptors-before-switchi.patch new file mode 100644 index 00000000..29e57b7f --- /dev/null +++ b/x86_64-switch_to-Load-TLS-descriptors-before-switchi.patch @@ -0,0 +1,309 @@ +From: Andy Lutomirski <luto@amacapital.net> +Date: Mon, 8 Dec 2014 13:55:20 -0800 +Subject: [PATCH] x86_64, switch_to(): Load TLS descriptors before switching DS + and ES + +Otherwise, if buggy user code points DS or ES into the TLS +array, they would be corrupted after a context switch. + +This also significantly improves the comments and documents some +gotchas in the code. + +Before this patch, the both tests below failed. With this +patch, the es test passes, although the gsbase test still fails. + + ----- begin es test ----- + +/* + * Copyright (c) 2014 Andy Lutomirski + * GPL v2 + */ + +static unsigned short GDT3(int idx) +{ + return (idx << 3) | 3; +} + +static int create_tls(int idx, unsigned int base) +{ + struct user_desc desc = { + .entry_number = idx, + .base_addr = base, + .limit = 0xfffff, + .seg_32bit = 1, + .contents = 0, /* Data, grow-up */ + .read_exec_only = 0, + .limit_in_pages = 1, + .seg_not_present = 0, + .useable = 0, + }; + + if (syscall(SYS_set_thread_area, &desc) != 0) + err(1, "set_thread_area"); + + return desc.entry_number; +} + +int main() +{ + int idx = create_tls(-1, 0); + printf("Allocated GDT index %d\n", idx); + + unsigned short orig_es; + asm volatile ("mov %%es,%0" : "=rm" (orig_es)); + + int errors = 0; + int total = 1000; + for (int i = 0; i < total; i++) { + asm volatile ("mov %0,%%es" : : "rm" (GDT3(idx))); + usleep(100); + + unsigned short es; + asm volatile ("mov %%es,%0" : "=rm" (es)); + asm volatile ("mov %0,%%es" : : "rm" (orig_es)); + if (es != GDT3(idx)) { + if (errors == 0) + printf("[FAIL]\tES changed from 0x%hx to 0x%hx\n", + GDT3(idx), es); + errors++; + } + } + + if (errors) { + printf("[FAIL]\tES was corrupted %d/%d times\n", errors, total); + return 1; + } else { + printf("[OK]\tES was preserved\n"); + return 0; + } +} + + ----- end es test ----- + + ----- begin gsbase test ----- + +/* + * gsbase.c, a gsbase test + * Copyright (c) 2014 Andy Lutomirski + * GPL v2 + */ + +static unsigned char *testptr, *testptr2; + +static unsigned char read_gs_testvals(void) +{ + unsigned char ret; + asm volatile ("movb %%gs:%1, %0" : "=r" (ret) : "m" (*testptr)); + return ret; +} + +int main() +{ + int errors = 0; + + testptr = mmap((void *)0x200000000UL, 1, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + if (testptr == MAP_FAILED) + err(1, "mmap"); + + testptr2 = mmap((void *)0x300000000UL, 1, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + if (testptr2 == MAP_FAILED) + err(1, "mmap"); + + *testptr = 0; + *testptr2 = 1; + + if (syscall(SYS_arch_prctl, ARCH_SET_GS, + (unsigned long)testptr2 - (unsigned long)testptr) != 0) + err(1, "ARCH_SET_GS"); + + usleep(100); + + if (read_gs_testvals() == 1) { + printf("[OK]\tARCH_SET_GS worked\n"); + } else { + printf("[FAIL]\tARCH_SET_GS failed\n"); + errors++; + } + + asm volatile ("mov %0,%%gs" : : "r" (0)); + + if (read_gs_testvals() == 0) { + printf("[OK]\tWriting 0 to gs worked\n"); + } else { + printf("[FAIL]\tWriting 0 to gs failed\n"); + errors++; + } + + usleep(100); + + if (read_gs_testvals() == 0) { + printf("[OK]\tgsbase is still zero\n"); + } else { + printf("[FAIL]\tgsbase was corrupted\n"); + errors++; + } + + return errors == 0 ? 0 : 1; +} + + ----- end gsbase test ----- + +Signed-off-by: Andy Lutomirski <luto@amacapital.net> +Cc: <stable@vger.kernel.org> +Cc: Andi Kleen <andi@firstfloor.org> +Cc: Linus Torvalds <torvalds@linux-foundation.org> +Link: http://lkml.kernel.org/r/509d27c9fec78217691c3dad91cec87e1006b34a.1418075657.git.luto@amacapital.net +Signed-off-by: Ingo Molnar <mingo@kernel.org> +--- + arch/x86/kernel/process_64.c | 101 +++++++++++++++++++++++++++++++------------ + 1 file changed, 73 insertions(+), 28 deletions(-) + +diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c +index ca5b02d405c3..166119618afb 100644 +--- a/arch/x86/kernel/process_64.c ++++ b/arch/x86/kernel/process_64.c +@@ -286,24 +286,9 @@ __switch_to(struct task_struct *prev_p, struct task_struct *next_p) + + fpu = switch_fpu_prepare(prev_p, next_p, cpu); + +- /* +- * Reload esp0, LDT and the page table pointer: +- */ ++ /* Reload esp0 and ss1. */ + load_sp0(tss, next); + +- /* +- * Switch DS and ES. +- * This won't pick up thread selector changes, but I guess that is ok. +- */ +- savesegment(es, prev->es); +- if (unlikely(next->es | prev->es)) +- loadsegment(es, next->es); +- +- savesegment(ds, prev->ds); +- if (unlikely(next->ds | prev->ds)) +- loadsegment(ds, next->ds); +- +- + /* We must save %fs and %gs before load_TLS() because + * %fs and %gs may be cleared by load_TLS(). + * +@@ -312,41 +297,101 @@ __switch_to(struct task_struct *prev_p, struct task_struct *next_p) + savesegment(fs, fsindex); + savesegment(gs, gsindex); + ++ /* ++ * Load TLS before restoring any segments so that segment loads ++ * reference the correct GDT entries. ++ */ + load_TLS(next, cpu); + + /* +- * Leave lazy mode, flushing any hypercalls made here. +- * This must be done before restoring TLS segments so +- * the GDT and LDT are properly updated, and must be +- * done before math_state_restore, so the TS bit is up +- * to date. ++ * Leave lazy mode, flushing any hypercalls made here. This ++ * must be done after loading TLS entries in the GDT but before ++ * loading segments that might reference them, and and it must ++ * be done before math_state_restore, so the TS bit is up to ++ * date. + */ + arch_end_context_switch(next_p); + ++ /* Switch DS and ES. ++ * ++ * Reading them only returns the selectors, but writing them (if ++ * nonzero) loads the full descriptor from the GDT or LDT. The ++ * LDT for next is loaded in switch_mm, and the GDT is loaded ++ * above. ++ * ++ * We therefore need to write new values to the segment ++ * registers on every context switch unless both the new and old ++ * values are zero. ++ * ++ * Note that we don't need to do anything for CS and SS, as ++ * those are saved and restored as part of pt_regs. ++ */ ++ savesegment(es, prev->es); ++ if (unlikely(next->es | prev->es)) ++ loadsegment(es, next->es); ++ ++ savesegment(ds, prev->ds); ++ if (unlikely(next->ds | prev->ds)) ++ loadsegment(ds, next->ds); ++ + /* + * Switch FS and GS. + * +- * Segment register != 0 always requires a reload. Also +- * reload when it has changed. When prev process used 64bit +- * base always reload to avoid an information leak. ++ * These are even more complicated than FS and GS: they have ++ * 64-bit bases are that controlled by arch_prctl. Those bases ++ * only differ from the values in the GDT or LDT if the selector ++ * is 0. ++ * ++ * Loading the segment register resets the hidden base part of ++ * the register to 0 or the value from the GDT / LDT. If the ++ * next base address zero, writing 0 to the segment register is ++ * much faster than using wrmsr to explicitly zero the base. ++ * ++ * The thread_struct.fs and thread_struct.gs values are 0 ++ * if the fs and gs bases respectively are not overridden ++ * from the values implied by fsindex and gsindex. They ++ * are nonzero, and store the nonzero base addresses, if ++ * the bases are overridden. ++ * ++ * (fs != 0 && fsindex != 0) || (gs != 0 && gsindex != 0) should ++ * be impossible. ++ * ++ * Therefore we need to reload the segment registers if either ++ * the old or new selector is nonzero, and we need to override ++ * the base address if next thread expects it to be overridden. ++ * ++ * This code is unnecessarily slow in the case where the old and ++ * new indexes are zero and the new base is nonzero -- it will ++ * unnecessarily write 0 to the selector before writing the new ++ * base address. ++ * ++ * Note: This all depends on arch_prctl being the only way that ++ * user code can override the segment base. Once wrfsbase and ++ * wrgsbase are enabled, most of this code will need to change. + */ + if (unlikely(fsindex | next->fsindex | prev->fs)) { + loadsegment(fs, next->fsindex); ++ + /* +- * Check if the user used a selector != 0; if yes +- * clear 64bit base, since overloaded base is always +- * mapped to the Null selector ++ * If user code wrote a nonzero value to FS, then it also ++ * cleared the overridden base address. ++ * ++ * XXX: if user code wrote 0 to FS and cleared the base ++ * address itself, we won't notice and we'll incorrectly ++ * restore the prior base address next time we reschdule ++ * the process. + */ + if (fsindex) + prev->fs = 0; + } +- /* when next process has a 64bit base use it */ + if (next->fs) + wrmsrl(MSR_FS_BASE, next->fs); + prev->fsindex = fsindex; + + if (unlikely(gsindex | next->gsindex | prev->gs)) { + load_gs_index(next->gsindex); ++ ++ /* This works (and fails) the same way as fsindex above. */ + if (gsindex) + prev->gs = 0; + } +-- +2.1.0 + |