pmdp_invalidate() replaces a huge PMD with an invalidated one
(_PAGE_PRESENT cleared and _PAGE_INVALID set) and calls
page_table_check_pmd_clear() on the old entry. This decreases the page
table check map count. When the invalidated entry is later replaced with
set_pmd_at(), as MADV_COLD, page_mkclean_one() and change_huge_pmd() do,
page_table_check_pmds_set() in set_pmd_at() clears it a second time and
decreases the map count because pmd_user_accessible_page() still counts it
as a live user mapping. The same happens when it is cleared with
pmdp_huge_get_and_clear(). Then the map count underflows and hits BUG_ON()
in page_table_check_clear().
Found by code inspection and confirmed in QEMU (pseries, POWER9, 64K
pages, CONFIG_PAGE_TABLE_CHECK_ENFORCED=y): MADV_COLD or mprotect() on
an anonymous THP hits the BUG_ON() with both the radix and the hash MMU.
Fix it by only counting entries that are hardware valid, so an
invalidated entry is skipped, as the patch "riscv: mm: exclude invalid
THP PMDs from page table check" did for riscv.
Fixes: 641d47d4c963 ("powerpc/mm: support page table check")
Cc: [email protected]
Assisted-by: LLM
Signed-off-by: Zi Yan <[email protected]>
To: Madhavan Srinivasan <[email protected]>
To: Andrew Donnellan <[email protected]>
To: Rohan McLure <[email protected]>
To: Pasha Tatashin <[email protected]>
To: Andrew Morton <[email protected]>
Cc: Michael Ellerman <[email protected]>
Cc: Nicholas Piggin <[email protected]>
Cc: "Christophe Leroy (CS GROUP)" <[email protected]>
Cc: "Ritesh Harjani (IBM)" <[email protected]>
Cc: Shrikanth Hegde <[email protected]>
Cc: [email protected]
Cc: [email protected]
---
arch/powerpc/include/asm/book3s/64/pgtable.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/include/asm/book3s/64/pgtable.h
b/arch/powerpc/include/asm/book3s/64/pgtable.h
index dff8790a047db..392474972777c 100644
--- a/arch/powerpc/include/asm/book3s/64/pgtable.h
+++ b/arch/powerpc/include/asm/book3s/64/pgtable.h
@@ -1091,7 +1091,8 @@ static inline bool pmd_access_permitted(pmd_t pmd, bool
write)
#define pmd_user_accessible_page pmd_user_accessible_page
static inline bool pmd_user_accessible_page(struct mm_struct *mm, unsigned
long addr, pmd_t pmd)
{
- return pmd_leaf(pmd) && pte_user_accessible_page(mm, addr,
pmd_pte(pmd));
+ return pmd_leaf(pmd) && pte_hw_valid(pmd_pte(pmd)) &&
+ pte_user_accessible_page(mm, addr, pmd_pte(pmd));
}
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
--
2.53.0