| From 9f5cb6b32d9e0a3a7453222baaf15664d92adbf2 Mon Sep 17 00:00:00 2001 |
| From: Thomas Gleixner <tglx@linutronix.de> |
| Date: Fri, 15 Dec 2017 20:35:11 +0100 |
| Subject: x86/ldt: Make the LDT mapping RO |
| |
| From: Thomas Gleixner <tglx@linutronix.de> |
| |
| commit 9f5cb6b32d9e0a3a7453222baaf15664d92adbf2 upstream. |
| |
| Now that the LDT mapping is in a known area when PAGE_TABLE_ISOLATION is |
| enabled its a primary target for attacks, if a user space interface fails |
| to validate a write address correctly. That can never happen, right? |
| |
| The SDM states: |
| |
| If the segment descriptors in the GDT or an LDT are placed in ROM, the |
| processor can enter an indefinite loop if software or the processor |
| attempts to update (write to) the ROM-based segment descriptors. To |
| prevent this problem, set the accessed bits for all segment descriptors |
| placed in a ROM. Also, remove operating-system or executive code that |
| attempts to modify segment descriptors located in ROM. |
| |
| So its a valid approach to set the ACCESS bit when setting up the LDT entry |
| and to map the table RO. Fixup the selftest so it can handle that new mode. |
| |
| Remove the manual ACCESS bit setter in set_tls_desc() as this is now |
| pointless. Folded the patch from Peter Ziljstra. |
| |
| Signed-off-by: Thomas Gleixner <tglx@linutronix.de> |
| Cc: Andy Lutomirski <luto@kernel.org> |
| Cc: Borislav Petkov <bp@alien8.de> |
| Cc: Dave Hansen <dave.hansen@linux.intel.com> |
| Cc: H. Peter Anvin <hpa@zytor.com> |
| Cc: Josh Poimboeuf <jpoimboe@redhat.com> |
| Cc: Juergen Gross <jgross@suse.com> |
| Cc: Linus Torvalds <torvalds@linux-foundation.org> |
| Cc: Peter Zijlstra <peterz@infradead.org> |
| Signed-off-by: Ingo Molnar <mingo@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/x86/include/asm/desc.h | 2 ++ |
| arch/x86/kernel/ldt.c | 7 ++++++- |
| arch/x86/kernel/tls.c | 11 ++--------- |
| tools/testing/selftests/x86/ldt_gdt.c | 3 +-- |
| 4 files changed, 11 insertions(+), 12 deletions(-) |
| |
| --- a/arch/x86/include/asm/desc.h |
| +++ b/arch/x86/include/asm/desc.h |
| @@ -21,6 +21,8 @@ static inline void fill_ldt(struct desc_ |
| |
| desc->type = (info->read_exec_only ^ 1) << 1; |
| desc->type |= info->contents << 2; |
| + /* Set the ACCESS bit so it can be mapped RO */ |
| + desc->type |= 1; |
| |
| desc->s = 1; |
| desc->dpl = 0x3; |
| --- a/arch/x86/kernel/ldt.c |
| +++ b/arch/x86/kernel/ldt.c |
| @@ -158,7 +158,12 @@ map_ldt_struct(struct mm_struct *mm, str |
| ptep = get_locked_pte(mm, va, &ptl); |
| if (!ptep) |
| return -ENOMEM; |
| - pte = pfn_pte(pfn, __pgprot(__PAGE_KERNEL & ~_PAGE_GLOBAL)); |
| + /* |
| + * Map it RO so the easy to find address is not a primary |
| + * target via some kernel interface which misses a |
| + * permission check. |
| + */ |
| + pte = pfn_pte(pfn, __pgprot(__PAGE_KERNEL_RO & ~_PAGE_GLOBAL)); |
| set_pte_at(mm, va, ptep, pte); |
| pte_unmap_unlock(ptep, ptl); |
| } |
| --- a/arch/x86/kernel/tls.c |
| +++ b/arch/x86/kernel/tls.c |
| @@ -93,17 +93,10 @@ static void set_tls_desc(struct task_str |
| cpu = get_cpu(); |
| |
| while (n-- > 0) { |
| - if (LDT_empty(info) || LDT_zero(info)) { |
| + if (LDT_empty(info) || LDT_zero(info)) |
| memset(desc, 0, sizeof(*desc)); |
| - } else { |
| + else |
| fill_ldt(desc, info); |
| - |
| - /* |
| - * Always set the accessed bit so that the CPU |
| - * doesn't try to write to the (read-only) GDT. |
| - */ |
| - desc->type |= 1; |
| - } |
| ++info; |
| ++desc; |
| } |
| --- a/tools/testing/selftests/x86/ldt_gdt.c |
| +++ b/tools/testing/selftests/x86/ldt_gdt.c |
| @@ -122,8 +122,7 @@ static void check_valid_segment(uint16_t |
| * NB: Different Linux versions do different things with the |
| * accessed bit in set_thread_area(). |
| */ |
| - if (ar != expected_ar && |
| - (ldt || ar != (expected_ar | AR_ACCESSED))) { |
| + if (ar != expected_ar && ar != (expected_ar | AR_ACCESSED)) { |
| printf("[FAIL]\t%s entry %hu has AR 0x%08X but expected 0x%08X\n", |
| (ldt ? "LDT" : "GDT"), index, ar, expected_ar); |
| nerrs++; |