aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrea Arcangeli <aarcange@redhat.com>2013-02-22 18:11:51 -0500
committerIngo Molnar <mingo@kernel.org>2013-02-24 07:01:45 -0500
commita8aed3e0752b4beb2e37cbed6df69faae88268da (patch)
tree52ebb4885c99772d317dc51e9abdca83a1dd150f
parent954f857187033ee3d3704a8206715cf354c38898 (diff)
x86/mm/pageattr: Prevent PSE and GLOABL leftovers to confuse pmd/pte_present and pmd_huge
Without this patch any kernel code that reads kernel memory in non present kernel pte/pmds (as set by pageattr.c) will crash. With this kernel code: static struct page *crash_page; static unsigned long *crash_address; [..] crash_page = alloc_pages(GFP_KERNEL, 9); crash_address = page_address(crash_page); if (set_memory_np((unsigned long)crash_address, 1)) printk("set_memory_np failure\n"); [..] The kernel will crash if inside the "crash tool" one would try to read the memory at the not present address. crash> p crash_address crash_address = $8 = (long unsigned int *) 0xffff88023c000000 crash> rd 0xffff88023c000000 [ *lockup* ] The lockup happens because _PAGE_GLOBAL and _PAGE_PROTNONE shares the same bit, and pageattr leaves _PAGE_GLOBAL set on a kernel pte which is then mistaken as _PAGE_PROTNONE (so pte_present returns true by mistake and the kernel fault then gets confused and loops). With THP the same can happen after we taught pmd_present to check _PAGE_PROTNONE and _PAGE_PSE in commit 027ef6c87853b0a9df5317 ("mm: thp: fix pmd_present for split_huge_page and PROT_NONE with THP"). THP has the same problem with _PAGE_GLOBAL as the 4k pages, but it also has a problem with _PAGE_PSE, which must be cleared too. After the patch is applied copy_user correctly returns -EFAULT and doesn't lockup anymore. crash> p crash_address crash_address = $9 = (long unsigned int *) 0xffff88023c000000 crash> rd 0xffff88023c000000 rd: read error: kernel virtual address: ffff88023c000000 type: "64-bit KVADDR" Signed-off-by: Andrea Arcangeli <aarcange@redhat.com> Cc: Andi Kleen <andi@firstfloor.org> Cc: Shaohua Li <shaohua.li@intel.com> Cc: "H. Peter Anvin" <hpa@linux.intel.com> Cc: Mel Gorman <mgorman@suse.de> Cc: Hugh Dickins <hughd@google.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Ingo Molnar <mingo@kernel.org>
-rw-r--r--arch/x86/mm/pageattr.c50
1 files changed, 47 insertions, 3 deletions
diff --git a/arch/x86/mm/pageattr.c b/arch/x86/mm/pageattr.c
index a718e0d23503..2713be4bca41 100644
--- a/arch/x86/mm/pageattr.c
+++ b/arch/x86/mm/pageattr.c
@@ -445,6 +445,19 @@ try_preserve_large_page(pte_t *kpte, unsigned long address,
445 pgprot_val(req_prot) |= pgprot_val(cpa->mask_set); 445 pgprot_val(req_prot) |= pgprot_val(cpa->mask_set);
446 446
447 /* 447 /*
448 * Set the PSE and GLOBAL flags only if the PRESENT flag is
449 * set otherwise pmd_present/pmd_huge will return true even on
450 * a non present pmd. The canon_pgprot will clear _PAGE_GLOBAL
451 * for the ancient hardware that doesn't support it.
452 */
453 if (pgprot_val(new_prot) & _PAGE_PRESENT)
454 pgprot_val(new_prot) |= _PAGE_PSE | _PAGE_GLOBAL;
455 else
456 pgprot_val(new_prot) &= ~(_PAGE_PSE | _PAGE_GLOBAL);
457
458 new_prot = canon_pgprot(new_prot);
459
460 /*
448 * old_pte points to the large page base address. So we need 461 * old_pte points to the large page base address. So we need
449 * to add the offset of the virtual address: 462 * to add the offset of the virtual address:
450 */ 463 */
@@ -489,7 +502,7 @@ try_preserve_large_page(pte_t *kpte, unsigned long address,
489 * The address is aligned and the number of pages 502 * The address is aligned and the number of pages
490 * covers the full page. 503 * covers the full page.
491 */ 504 */
492 new_pte = pfn_pte(pte_pfn(old_pte), canon_pgprot(new_prot)); 505 new_pte = pfn_pte(pte_pfn(old_pte), new_prot);
493 __set_pmd_pte(kpte, address, new_pte); 506 __set_pmd_pte(kpte, address, new_pte);
494 cpa->flags |= CPA_FLUSHTLB; 507 cpa->flags |= CPA_FLUSHTLB;
495 do_split = 0; 508 do_split = 0;
@@ -540,16 +553,35 @@ static int split_large_page(pte_t *kpte, unsigned long address)
540#ifdef CONFIG_X86_64 553#ifdef CONFIG_X86_64
541 if (level == PG_LEVEL_1G) { 554 if (level == PG_LEVEL_1G) {
542 pfninc = PMD_PAGE_SIZE >> PAGE_SHIFT; 555 pfninc = PMD_PAGE_SIZE >> PAGE_SHIFT;
543 pgprot_val(ref_prot) |= _PAGE_PSE; 556 /*
557 * Set the PSE flags only if the PRESENT flag is set
558 * otherwise pmd_present/pmd_huge will return true
559 * even on a non present pmd.
560 */
561 if (pgprot_val(ref_prot) & _PAGE_PRESENT)
562 pgprot_val(ref_prot) |= _PAGE_PSE;
563 else
564 pgprot_val(ref_prot) &= ~_PAGE_PSE;
544 } 565 }
545#endif 566#endif
546 567
547 /* 568 /*
569 * Set the GLOBAL flags only if the PRESENT flag is set
570 * otherwise pmd/pte_present will return true even on a non
571 * present pmd/pte. The canon_pgprot will clear _PAGE_GLOBAL
572 * for the ancient hardware that doesn't support it.
573 */
574 if (pgprot_val(ref_prot) & _PAGE_PRESENT)
575 pgprot_val(ref_prot) |= _PAGE_GLOBAL;
576 else
577 pgprot_val(ref_prot) &= ~_PAGE_GLOBAL;
578
579 /*
548 * Get the target pfn from the original entry: 580 * Get the target pfn from the original entry:
549 */ 581 */
550 pfn = pte_pfn(*kpte); 582 pfn = pte_pfn(*kpte);
551 for (i = 0; i < PTRS_PER_PTE; i++, pfn += pfninc) 583 for (i = 0; i < PTRS_PER_PTE; i++, pfn += pfninc)
552 set_pte(&pbase[i], pfn_pte(pfn, ref_prot)); 584 set_pte(&pbase[i], pfn_pte(pfn, canon_pgprot(ref_prot)));
553 585
554 if (address >= (unsigned long)__va(0) && 586 if (address >= (unsigned long)__va(0) &&
555 address < (unsigned long)__va(max_low_pfn_mapped << PAGE_SHIFT)) 587 address < (unsigned long)__va(max_low_pfn_mapped << PAGE_SHIFT))
@@ -660,6 +692,18 @@ repeat:
660 new_prot = static_protections(new_prot, address, pfn); 692 new_prot = static_protections(new_prot, address, pfn);
661 693
662 /* 694 /*
695 * Set the GLOBAL flags only if the PRESENT flag is
696 * set otherwise pte_present will return true even on
697 * a non present pte. The canon_pgprot will clear
698 * _PAGE_GLOBAL for the ancient hardware that doesn't
699 * support it.
700 */
701 if (pgprot_val(new_prot) & _PAGE_PRESENT)
702 pgprot_val(new_prot) |= _PAGE_GLOBAL;
703 else
704 pgprot_val(new_prot) &= ~_PAGE_GLOBAL;
705
706 /*
663 * We need to keep the pfn from the existing PTE, 707 * We need to keep the pfn from the existing PTE,
664 * after all we're only going to change it's attributes 708 * after all we're only going to change it's attributes
665 * not the memory it points to 709 * not the memory it points to