Re: [syzbot] [mm?] BUG: Bad page map (7)

From: Dave Hansen
Date: Mon Sep 11 2023 - 17:29:31 EST


On 9/11/23 06:26, Matthew Wilcox wrote:
> @@ -231,7 +235,10 @@ static inline void set_ptes(struct mm_struct *mm, unsigned long addr,
> if (--nr == 0)
> break;
> ptep++;
> - pte = __pte(pte_val(pte) + (1UL << PFN_PTE_SHIFT));
> + if (__pte_needs_invert(pte_val(pte)))
> + pte = __pte(pte_val(pte) - (1UL << PFN_PTE_SHIFT));
> + else
> + pte = __pte(pte_val(pte) + (1UL << PFN_PTE_SHIFT));
> }
> arch_leave_lazy_mmu_mode();
> }

This is much better than a whole x86 fork of set_ptes(). But it's still
a bit wonky because it exposes the PTE inversion logic to generic code.

Could we do something like this instead? It'll (probably) end up
repeating the PTE inversion logic each way though the loop, so it's less
efficient than what you have above. But unless I buggered something, it
"just works" without exposing any of the inversion logic to generic code.

The trick is that pte_pfn() undoes the inversion and then pfn_pte()
re-does it on each trip through the loop.

static inline void set_ptes(struct mm_struct *mm, unsigned long addr,
pte_t *ptep, pte_t pte, unsigned int nr)
{
pgprot_t prot = pte_pgprot(x);
unsigned long pfn = pte_pfn(pte);

page_table_check_ptes_set(mm, ptep, pte, nr);

arch_enter_lazy_mmu_mode();
for (;;) {
set_pte(ptep, pte);
if (--nr == 0)
break;
ptep++;
pfn++;
pte = pfn_pte(pfn, pgprot);
}
arch_leave_lazy_mmu_mode();
}

Obviously completely untested. :)