Skip to content

Commit 42e4089

Browse files
Andi KleenKAGA-KOKO
authored andcommitted
x86/speculation/l1tf: Disallow non privileged high MMIO PROT_NONE mappings
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>
1 parent 17dbca1 commit 42e4089

File tree

5 files changed

+117
-10
lines changed

5 files changed

+117
-10
lines changed

arch/x86/include/asm/pgtable.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,14 @@ static inline bool pud_access_permitted(pud_t pud, bool write)
13381338
return __pte_access_permitted(pud_val(pud), write);
13391339
}
13401340

1341+
#define __HAVE_ARCH_PFN_MODIFY_ALLOWED 1
1342+
extern bool pfn_modify_allowed(unsigned long pfn, pgprot_t prot);
1343+
1344+
static inline bool arch_has_pfn_modify_check(void)
1345+
{
1346+
return boot_cpu_has_bug(X86_BUG_L1TF);
1347+
}
1348+
13411349
#include <asm-generic/pgtable.h>
13421350
#endif /* __ASSEMBLY__ */
13431351

arch/x86/mm/mmap.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,24 @@ int valid_mmap_phys_addr_range(unsigned long pfn, size_t count)
240240

241241
return phys_addr_valid(addr + count - 1);
242242
}
243+
244+
/*
245+
* Only allow root to set high MMIO mappings to PROT_NONE.
246+
* This prevents an unpriv. user to set them to PROT_NONE and invert
247+
* them, then pointing to valid memory for L1TF speculation.
248+
*
249+
* Note: for locked down kernels may want to disable the root override.
250+
*/
251+
bool pfn_modify_allowed(unsigned long pfn, pgprot_t prot)
252+
{
253+
if (!boot_cpu_has_bug(X86_BUG_L1TF))
254+
return true;
255+
if (!__pte_needs_invert(pgprot_val(prot)))
256+
return true;
257+
/* If it's real memory always allow */
258+
if (pfn_valid(pfn))
259+
return true;
260+
if (pfn > l1tf_pfn_limit() && !capable(CAP_SYS_ADMIN))
261+
return false;
262+
return true;
263+
}

include/asm-generic/pgtable.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,4 +1097,16 @@ static inline void init_espfix_bsp(void) { }
10971097
#endif
10981098
#endif
10991099

1100+
#ifndef __HAVE_ARCH_PFN_MODIFY_ALLOWED
1101+
static inline bool pfn_modify_allowed(unsigned long pfn, pgprot_t prot)
1102+
{
1103+
return true;
1104+
}
1105+
1106+
static inline bool arch_has_pfn_modify_check(void)
1107+
{
1108+
return false;
1109+
}
1110+
#endif
1111+
11001112
#endif /* _ASM_GENERIC_PGTABLE_H */

mm/memory.c

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,9 @@ int vm_insert_pfn_prot(struct vm_area_struct *vma, unsigned long addr,
18861886
if (addr < vma->vm_start || addr >= vma->vm_end)
18871887
return -EFAULT;
18881888

1889+
if (!pfn_modify_allowed(pfn, pgprot))
1890+
return -EACCES;
1891+
18891892
track_pfn_insert(vma, &pgprot, __pfn_to_pfn_t(pfn, PFN_DEV));
18901893

18911894
ret = insert_pfn(vma, addr, __pfn_to_pfn_t(pfn, PFN_DEV), pgprot,
@@ -1921,6 +1924,9 @@ static int __vm_insert_mixed(struct vm_area_struct *vma, unsigned long addr,
19211924

19221925
track_pfn_insert(vma, &pgprot, pfn);
19231926

1927+
if (!pfn_modify_allowed(pfn_t_to_pfn(pfn), pgprot))
1928+
return -EACCES;
1929+
19241930
/*
19251931
* If we don't have pte special, then we have to use the pfn_valid()
19261932
* based VM_MIXEDMAP scheme (see vm_normal_page), and thus we *must*
@@ -1982,19 +1988,24 @@ static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd,
19821988
{
19831989
pte_t *pte;
19841990
spinlock_t *ptl;
1991+
int err = 0;
19851992

19861993
pte = pte_alloc_map_lock(mm, pmd, addr, &ptl);
19871994
if (!pte)
19881995
return -ENOMEM;
19891996
arch_enter_lazy_mmu_mode();
19901997
do {
19911998
BUG_ON(!pte_none(*pte));
1999+
if (!pfn_modify_allowed(pfn, prot)) {
2000+
err = -EACCES;
2001+
break;
2002+
}
19922003
set_pte_at(mm, addr, pte, pte_mkspecial(pfn_pte(pfn, prot)));
19932004
pfn++;
19942005
} while (pte++, addr += PAGE_SIZE, addr != end);
19952006
arch_leave_lazy_mmu_mode();
19962007
pte_unmap_unlock(pte - 1, ptl);
1997-
return 0;
2008+
return err;
19982009
}
19992010

20002011
static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
@@ -2003,6 +2014,7 @@ static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
20032014
{
20042015
pmd_t *pmd;
20052016
unsigned long next;
2017+
int err;
20062018

20072019
pfn -= addr >> PAGE_SHIFT;
20082020
pmd = pmd_alloc(mm, pud, addr);
@@ -2011,9 +2023,10 @@ static inline int remap_pmd_range(struct mm_struct *mm, pud_t *pud,
20112023
VM_BUG_ON(pmd_trans_huge(*pmd));
20122024
do {
20132025
next = pmd_addr_end(addr, end);
2014-
if (remap_pte_range(mm, pmd, addr, next,
2015-
pfn + (addr >> PAGE_SHIFT), prot))
2016-
return -ENOMEM;
2026+
err = remap_pte_range(mm, pmd, addr, next,
2027+
pfn + (addr >> PAGE_SHIFT), prot);
2028+
if (err)
2029+
return err;
20172030
} while (pmd++, addr = next, addr != end);
20182031
return 0;
20192032
}
@@ -2024,16 +2037,18 @@ static inline int remap_pud_range(struct mm_struct *mm, p4d_t *p4d,
20242037
{
20252038
pud_t *pud;
20262039
unsigned long next;
2040+
int err;
20272041

20282042
pfn -= addr >> PAGE_SHIFT;
20292043
pud = pud_alloc(mm, p4d, addr);
20302044
if (!pud)
20312045
return -ENOMEM;
20322046
do {
20332047
next = pud_addr_end(addr, end);
2034-
if (remap_pmd_range(mm, pud, addr, next,
2035-
pfn + (addr >> PAGE_SHIFT), prot))
2036-
return -ENOMEM;
2048+
err = remap_pmd_range(mm, pud, addr, next,
2049+
pfn + (addr >> PAGE_SHIFT), prot);
2050+
if (err)
2051+
return err;
20372052
} while (pud++, addr = next, addr != end);
20382053
return 0;
20392054
}
@@ -2044,16 +2059,18 @@ static inline int remap_p4d_range(struct mm_struct *mm, pgd_t *pgd,
20442059
{
20452060
p4d_t *p4d;
20462061
unsigned long next;
2062+
int err;
20472063

20482064
pfn -= addr >> PAGE_SHIFT;
20492065
p4d = p4d_alloc(mm, pgd, addr);
20502066
if (!p4d)
20512067
return -ENOMEM;
20522068
do {
20532069
next = p4d_addr_end(addr, end);
2054-
if (remap_pud_range(mm, p4d, addr, next,
2055-
pfn + (addr >> PAGE_SHIFT), prot))
2056-
return -ENOMEM;
2070+
err = remap_pud_range(mm, p4d, addr, next,
2071+
pfn + (addr >> PAGE_SHIFT), prot);
2072+
if (err)
2073+
return err;
20572074
} while (p4d++, addr = next, addr != end);
20582075
return 0;
20592076
}

mm/mprotect.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,42 @@ unsigned long change_protection(struct vm_area_struct *vma, unsigned long start,
306306
return pages;
307307
}
308308

309+
static int prot_none_pte_entry(pte_t *pte, unsigned long addr,
310+
unsigned long next, struct mm_walk *walk)
311+
{
312+
return pfn_modify_allowed(pte_pfn(*pte), *(pgprot_t *)(walk->private)) ?
313+
0 : -EACCES;
314+
}
315+
316+
static int prot_none_hugetlb_entry(pte_t *pte, unsigned long hmask,
317+
unsigned long addr, unsigned long next,
318+
struct mm_walk *walk)
319+
{
320+
return pfn_modify_allowed(pte_pfn(*pte), *(pgprot_t *)(walk->private)) ?
321+
0 : -EACCES;
322+
}
323+
324+
static int prot_none_test(unsigned long addr, unsigned long next,
325+
struct mm_walk *walk)
326+
{
327+
return 0;
328+
}
329+
330+
static int prot_none_walk(struct vm_area_struct *vma, unsigned long start,
331+
unsigned long end, unsigned long newflags)
332+
{
333+
pgprot_t new_pgprot = vm_get_page_prot(newflags);
334+
struct mm_walk prot_none_walk = {
335+
.pte_entry = prot_none_pte_entry,
336+
.hugetlb_entry = prot_none_hugetlb_entry,
337+
.test_walk = prot_none_test,
338+
.mm = current->mm,
339+
.private = &new_pgprot,
340+
};
341+
342+
return walk_page_range(start, end, &prot_none_walk);
343+
}
344+
309345
int
310346
mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
311347
unsigned long start, unsigned long end, unsigned long newflags)
@@ -323,6 +359,19 @@ mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
323359
return 0;
324360
}
325361

362+
/*
363+
* Do PROT_NONE PFN permission checks here when we can still
364+
* bail out without undoing a lot of state. This is a rather
365+
* uncommon case, so doesn't need to be very optimized.
366+
*/
367+
if (arch_has_pfn_modify_check() &&
368+
(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) &&
369+
(newflags & (VM_READ|VM_WRITE|VM_EXEC)) == 0) {
370+
error = prot_none_walk(vma, start, end, newflags);
371+
if (error)
372+
return error;
373+
}
374+
326375
/*
327376
* If we make a private mapping writable we increase our commit;
328377
* but (without finer accounting) cannot reduce our commit if we

0 commit comments

Comments
 (0)