powerpc/mce: Don't reload pte val in addr_to_pfn
authorAneesh Kumar K.V <aneesh.kumar@linux.ibm.com>
Tue, 5 May 2020 07:17:13 +0000 (12:47 +0530)
committerMichael Ellerman <mpe@ellerman.id.au>
Tue, 5 May 2020 11:20:14 +0000 (21:20 +1000)
A lockless page table walk should be safe against parallel THP collapse, THP
split and madvise(MADV_DONTNEED)/parallel fault. This patch makes sure kernel
won't reload the pteval when checking for different conditions. The patch also added
a check for pte_present to make sure the kernel is indeed operating
on a PTE and not a pointer to level 0 table page.

The pfn value we find here can be different from the actual pfn on which
machine check happened. This can happen if we raced with a parallel update
of the page table. In such a scenario we end up isolating a wrong pfn. But that
doesn't have any other side effect.

Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20200505071729.54912-7-aneesh.kumar@linux.ibm.com
arch/powerpc/kernel/mce_power.c

index 067b094..1d18991 100644 (file)
@@ -27,7 +27,7 @@
  */
 unsigned long addr_to_pfn(struct pt_regs *regs, unsigned long addr)
 {
-       pte_t *ptep;
+       pte_t *ptep, pte;
        unsigned int shift;
        unsigned long pfn, flags;
        struct mm_struct *mm;
@@ -39,19 +39,23 @@ unsigned long addr_to_pfn(struct pt_regs *regs, unsigned long addr)
 
        local_irq_save(flags);
        ptep = __find_linux_pte(mm->pgd, addr, NULL, &shift);
+       if (!ptep) {
+               pfn = ULONG_MAX;
+               goto out;
+       }
+       pte = READ_ONCE(*ptep);
 
-       if (!ptep || pte_special(*ptep)) {
+       if (!pte_present(pte) || pte_special(pte)) {
                pfn = ULONG_MAX;
                goto out;
        }
 
        if (shift <= PAGE_SHIFT)
-               pfn = pte_pfn(*ptep);
+               pfn = pte_pfn(pte);
        else {
                unsigned long rpnmask = (1ul << shift) - PAGE_SIZE;
-               pfn = pte_pfn(__pte(pte_val(*ptep) | (addr & rpnmask)));
+               pfn = pte_pfn(__pte(pte_val(pte) | (addr & rpnmask)));
        }
-
 out:
        local_irq_restore(flags);
        return pfn;