remap_try_huge_pmd() called pmd_free_pte_page() to free an existing PTE page table when the PMD entry was present. However, pmd_free_pte_page() uses the kernel page-table free path, which is not appropriate for user mappings. When the PMD entry is non-empty, this results in the underlying page being freed through the kernel page-table free path (__pmd_free_pte_page -> __free_pages -> free_unref_page), corrupting page state and triggering: BUG: Bad page state in process drv_hlt_dsmi_te pfn:809a2b04 page: refcount:0 mapcount:0 ... page_type: 0xfffffdff(table) page dumped because: nonzero mapcount Call trace: bad_page+0xa0/0x1f8 free_unref_page+0xb58/0x1050 __pmd_free_pte_page.part.0+0x10c/0x1f8 pmd_free_pte_page+0x4c/0x78 remap_try_huge_pmd+0x134/0x468 remap_pfn_range_internal+0x334/0x690 remap_pfn_range_try_pmd+0xb4/0x138 When the PMD entry is non-empty, fall back to 4K granularity mapping instead of attempting to free the page table. Rather than simply switching to the user page table free path, the original logic is dropped entirely because it assumes the PTE table is empty without actually checking. If a driver maps the same range repeatedly, the PTE table may still contain valid entries, and freeing it would silently destroy existing mappings without any error. Falling back to 4K granularity avoids both the wrong free path and the risk of silently corrupting existing mappings. Fixes: 923e224a1246 ("mm: introduce remap_pfn_range_try_pmd() for PMD-level hugepage mapping") --- mm/memory.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mm/memory.c b/mm/memory.c index f98116b7b23e..7fc3eebcf1ea 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -2605,18 +2605,24 @@ static int remap_try_huge_pmd(struct mm_struct *mm, pmd_t *pmd, if (!IS_ALIGNED(pfn, HPAGE_PMD_NR)) return 0; - if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr)) + if (!pmd_none(*pmd)) return 0; pgtable = pte_alloc_one(mm); if (unlikely(!pgtable)) return 0; - mm_inc_nr_ptes(mm); ptl = pmd_lock(mm, pmd); + if (!pmd_none(*pmd)) { + spin_unlock(ptl); + pte_free(mm, pgtable); + return 0; + } + set_pmd_at(mm, addr, pmd, pmd_mkspecial(pmd_mkhuge(pfn_pmd(pfn, prot)))); pgtable_trans_huge_deposit(mm, pmd, pgtable); spin_unlock(ptl); + mm_inc_nr_ptes(mm); return 1; } -- 2.43.0