diff options
author | Andrea Arcangeli <aarcange@redhat.com> | 2013-02-22 18:11:51 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2013-02-24 07:01:45 -0500 |
commit | a8aed3e0752b4beb2e37cbed6df69faae88268da (patch) | |
tree | 52ebb4885c99772d317dc51e9abdca83a1dd150f /arch/x86 | |
parent | 954f857187033ee3d3704a8206715cf354c38898 (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>
Diffstat (limited to 'arch/x86')
-rw-r--r-- | arch/x86/mm/pageattr.c | 50 |
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 |