]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
x86/speculation/l1tf: Disallow non privileged high MMIO PROT_NONE mappings
authorAndi Klein <ak@linux.intel.com>
Wed, 13 Jun 2018 22:48:27 +0000 (15:48 -0700)
committerKonrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Fri, 10 Aug 2018 22:56:32 +0000 (18:56 -0400)
For L1TF PROT_NONE mappings are protected by inverting the PFN in the page
table entry. This sets the high bits in the CPU's address space, thus
making sure to point to not point an unmapped entry to valid cached memory.

Some server system BIOSes put the MMIO mappings high up in the physical
address space. If such an high mapping was mapped to unprivileged users
they could attack low memory by setting such a mapping to PROT_NONE. This
could happen through a special device driver which is not access
protected. Normal /dev/mem is of course access protected.

To avoid this forbid PROT_NONE mappings or mprotect for high MMIO mappings.

Valid page mappings are allowed because the system is then unsafe anyways.

It's not expected that users commonly use PROT_NONE on MMIO. But to
minimize any impact this is only enforced if the mapping actually refers to
a high MMIO address (defined as the MAX_PA-1 bit being set), and also skip
the check for root.

For mmaps this is straight forward and can be handled in vm_insert_pfn and
in remap_pfn_range().

For mprotect it's a bit trickier. At the point where the actual PTEs are
accessed a lot of state has been changed and it would be difficult to undo
on an error. Since this is a uncommon case use a separate early page talk
walk pass for MMIO PROT_NONE mappings that checks for this condition
early. For non MMIO and non PROT_NONE there are no changes.

Signed-off-by: Andi Kleen <ak@linux.intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Josh Poimboeuf <jpoimboe@redhat.com>
Acked-by: Dave Hansen <dave.hansen@intel.com>
Orabug: 28220674
CVE: CVE-2018-3620

(cherry picked from commit 42e4089c7890725fcd329999252dc489b72f2921)

Signed-off-by: Mihai Carabas <mihai.carabas@oracle.com>
Reviewed-by: Darren Kenny <darren.kenny@oracle.com>
Reviewed-by: Boris Ostrovsky <boris.ostrovsky@oracle.com>
Conflicts:
include/asm-generic/pgtable.h
mm/memory.c
pgtable.h: contextual, not the same functions
memory.c: different content of the modified functions

arch/x86/include/asm/pgtable.h
arch/x86/mm/mmap.c
include/asm-generic/pgtable.h
mm/memory.c
mm/mprotect.c

index f14cf1d69cd0c933b3d007a5e80901e277a3b40b..f08973965079aa4ec58e99169f0c07c6d3b396f9 100644 (file)
@@ -915,6 +915,14 @@ static inline pte_t pte_swp_clear_soft_dirty(pte_t pte)
 }
 #endif
 
+#define __HAVE_ARCH_PFN_MODIFY_ALLOWED 1
+extern bool pfn_modify_allowed(unsigned long pfn, pgprot_t prot);
+
+static inline bool arch_has_pfn_modify_check(void)
+{
+       return boot_cpu_has_bug(X86_BUG_L1TF);
+}
+
 #include <asm-generic/pgtable.h>
 #endif /* __ASSEMBLY__ */
 
index cb87336f44e1dbd2bbcef7cd9acd99f847eb8482..5b726ecb83ddf1bf62069f6303dec08f34772353 100644 (file)
@@ -183,3 +183,24 @@ int valid_mmap_phys_addr_range(unsigned long pfn, size_t count)
 
        return phys_addr_valid(addr + count - 1);
 }
+
+/*
+ * Only allow root to set high MMIO mappings to PROT_NONE.
+ * This prevents an unpriv. user to set them to PROT_NONE and invert
+ * them, then pointing to valid memory for L1TF speculation.
+ *
+ * Note: for locked down kernels may want to disable the root override.
+ */
+bool pfn_modify_allowed(unsigned long pfn, pgprot_t prot)
+{
+       if (!boot_cpu_has_bug(X86_BUG_L1TF))
+               return true;
+       if (!__pte_needs_invert(pgprot_val(prot)))
+               return true;
+       /* If it's real memory always allow */
+       if (pfn_valid(pfn))
+               return true;
+       if (pfn > l1tf_pfn_limit() && !capable(CAP_SYS_ADMIN))
+               return false;
+       return true;
+}
index d041167a9b2909542ef39f51c001fd155661945a..42344e002c30d43b7096f5cf83303ed65d2c4f3a 100644 (file)
@@ -801,4 +801,16 @@ static inline int pmd_clear_huge(pmd_t *pmd)
 #define io_remap_pfn_range remap_pfn_range
 #endif
 
+#ifndef __HAVE_ARCH_PFN_MODIFY_ALLOWED
+static inline bool pfn_modify_allowed(unsigned long pfn, pgprot_t prot)
+{
+       return true;
+}
+
+static inline bool arch_has_pfn_modify_check(void)
+{
+       return false;
+}
+#endif
+
 #endif /* _ASM_GENERIC_PGTABLE_H */
index 60549daa68dfc197d1676a4bdf08d00aa164b313..825f39347ab141fd7584eee0e6e7a0b8d3d5831d 100644 (file)
@@ -1660,6 +1660,8 @@ int vm_insert_pfn(struct vm_area_struct *vma, unsigned long addr,
 
        if (addr < vma->vm_start || addr >= vma->vm_end)
                return -EFAULT;
+       if (!pfn_modify_allowed(pfn, pgprot))
+               return -EACCES;
        if (track_pfn_insert(vma, &pgprot, pfn))
                return -EINVAL;
 
@@ -1674,9 +1676,14 @@ int vm_insert_mixed(struct vm_area_struct *vma, unsigned long addr,
 {
        BUG_ON(!(vma->vm_flags & VM_MIXEDMAP));
 
+       pgprot_t pgprot = vma->vm_page_prot;
+
        if (addr < vma->vm_start || addr >= vma->vm_end)
                return -EFAULT;
 
+       if (!pfn_modify_allowed(pfn, pgprot))
+               return -EACCES;
+
        /*
         * If we don't have pte special, then we have to use the pfn_valid()
         * based VM_MIXEDMAP scheme (see vm_normal_page), and thus we *must*
@@ -1705,6 +1712,7 @@ static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd,
 {
        pte_t *pte;
        spinlock_t *ptl;
+       int err = 0;
 
        pte = pte_alloc_map_lock(mm, pmd, addr, &ptl);
        if (!pte)
@@ -1712,12 +1720,16 @@ static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd,
        arch_enter_lazy_mmu_mode();
        do {
                BUG_ON(!pte_none(*pte));
+               if (!pfn_modify_allowed(pfn, prot)) {
+                       err = -EACCES;
+                       break;
+               }
                set_pte_at(mm, addr, pte, pte_mkspecial(pfn_pte(pfn, prot)));
                pfn++;
        } while (pte++, addr += PAGE_SIZE, addr != end);
        arch_leave_lazy_mmu_mode();
        pte_unmap_unlock(pte - 1, ptl);
-       return 0;
+       return err;
 }
 
 static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
@@ -1726,6 +1738,7 @@ static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
 {
        pmd_t *pmd;
        unsigned long next;
+       int err;
 
        pfn -= addr >> PAGE_SHIFT;
        pmd = pmd_alloc(mm, pud, addr);
@@ -1734,9 +1747,10 @@ static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
        VM_BUG_ON(pmd_trans_huge(*pmd));
        do {
                next = pmd_addr_end(addr, end);
-               if (remap_pte_range(mm, pmd, addr, next,
-                               pfn + (addr >> PAGE_SHIFT), prot))
-                       return -ENOMEM;
+               err = remap_pte_range(mm, pmd, addr, next,
+                               pfn + (addr >> PAGE_SHIFT), prot);
+               if (err)
+                       return err;
        } while (pmd++, addr = next, addr != end);
        return 0;
 }
@@ -1747,6 +1761,7 @@ static inline int remap_pud_range(struct mm_struct *mm, p4d_t *p4d,
 {
        pud_t *pud;
        unsigned long next;
+       int err;
 
        pfn -= addr >> PAGE_SHIFT;
        pud = pud_alloc(mm, p4d, addr);
@@ -1754,9 +1769,10 @@ static inline int remap_pud_range(struct mm_struct *mm, p4d_t *p4d,
                return -ENOMEM;
        do {
                next = pud_addr_end(addr, end);
-               if (remap_pmd_range(mm, pud, addr, next,
-                               pfn + (addr >> PAGE_SHIFT), prot))
-                       return -ENOMEM;
+               err = remap_pmd_range(mm, pud, addr, next,
+                               pfn + (addr >> PAGE_SHIFT), prot);
+               if (err)
+                       return err;
        } while (pud++, addr = next, addr != end);
        return 0;
 }
@@ -1767,6 +1783,7 @@ static inline int remap_p4d_range(struct mm_struct *mm, pgd_t *pgd,
 {
        p4d_t *p4d;
        unsigned long next;
+       int err;
 
        pfn -= addr >> PAGE_SHIFT;
        p4d = p4d_alloc(mm, pgd, addr);
@@ -1774,9 +1791,10 @@ static inline int remap_p4d_range(struct mm_struct *mm, pgd_t *pgd,
                return -ENOMEM;
        do {
                next = p4d_addr_end(addr, end);
-               if (remap_pud_range(mm, p4d, addr, next,
-                               pfn + (addr >> PAGE_SHIFT), prot))
-                       return -ENOMEM;
+               err = remap_pud_range(mm, p4d, addr, next,
+                               pfn + (addr >> PAGE_SHIFT), prot);
+               if (err)
+                       return err;
        } while (p4d++, addr = next, addr != end);
        return 0;
 }
index fbdeb814f270f1d39b7f6d1d661a4220531a63b0..85644e33ab548b69d3fada930c31efebf893ee5b 100644 (file)
@@ -272,6 +272,42 @@ unsigned long change_protection(struct vm_area_struct *vma, unsigned long start,
        return pages;
 }
 
+static int prot_none_pte_entry(pte_t *pte, unsigned long addr,
+                              unsigned long next, struct mm_walk *walk)
+{
+       return pfn_modify_allowed(pte_pfn(*pte), *(pgprot_t *)(walk->private)) ?
+               0 : -EACCES;
+}
+
+static int prot_none_hugetlb_entry(pte_t *pte, unsigned long hmask,
+                                  unsigned long addr, unsigned long next,
+                                  struct mm_walk *walk)
+{
+       return pfn_modify_allowed(pte_pfn(*pte), *(pgprot_t *)(walk->private)) ?
+               0 : -EACCES;
+}
+
+static int prot_none_test(unsigned long addr, unsigned long next,
+                         struct mm_walk *walk)
+{
+       return 0;
+}
+
+static int prot_none_walk(struct vm_area_struct *vma, unsigned long start,
+                          unsigned long end, unsigned long newflags)
+{
+       pgprot_t new_pgprot = vm_get_page_prot(newflags);
+       struct mm_walk prot_none_walk = {
+               .pte_entry = prot_none_pte_entry,
+               .hugetlb_entry = prot_none_hugetlb_entry,
+               .test_walk = prot_none_test,
+               .mm = current->mm,
+               .private = &new_pgprot,
+       };
+
+       return walk_page_range(start, end, &prot_none_walk);
+}
+
 int
 mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
        unsigned long start, unsigned long end, unsigned long newflags)
@@ -289,6 +325,19 @@ mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
                return 0;
        }
 
+       /*
+        * Do PROT_NONE PFN permission checks here when we can still
+        * bail out without undoing a lot of state. This is a rather
+        * uncommon case, so doesn't need to be very optimized.
+        */
+       if (arch_has_pfn_modify_check() &&
+           (vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) &&
+           (newflags & (VM_READ|VM_WRITE|VM_EXEC)) == 0) {
+               error = prot_none_walk(vma, start, end, newflags);
+               if (error)
+                       return error;
+       }
+
        /*
         * If we make a private mapping writable we increase our commit;
         * but (without finer accounting) cannot reduce our commit if we