[v3 05/24] mm: thp: handle split failure in zap_pmd_range()

Usama Arif
2026-03-27T02:14:44Z

zap_pmd_range() splits a huge PMD when the zap range doesn't cover the
full PMD (partial unmap). If the split fails, the PMD stays huge.
Falling through to zap_pte_range() would dereference the huge PMD entry
as a PTE page table pointer.

Skip the range covered by the PMD on split failure instead.

The skip is safe across all call paths into zap_pmd_range():

- exit_mmap() and OOM reaper: the zap range covers entire VMAs, so
every PMD is fully covered (next - addr == HPAGE_PMD_SIZE). The
zap_huge_pmd() branch handles these without splitting. The split
failure path is unreachable.

- munmap / mmap overlay: vma_adjust_trans_huge() (called from
__split_vma) splits any PMD straddling the VMA boundary before the
VMA is split. If that PMD split fails, __split_vma() returns
-ENOMEM and the munmap is aborted before reaching zap_pmd_range().
The split failure path is unreachable.

- MADV_DONTNEED: advisory hint, the kernel is allowed to ignore it.
The pages remain valid and accessible. A subsequent access returns
existing data without faulting.

Signed-off-by: Usama Arif <usama.arif@linux.dev>
---
mm/memory.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/mm/memory.c b/mm/memory.c
index e44469f9cf659..caf97c48cb166 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -1985,9 +1985,18 @@ static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
do {
next = pmd_addr_end(addr, end);
if (pmd_is_huge(*pmd)) {
- if (next - addr != HPAGE_PMD_SIZE)
- __split_huge_pmd(vma, pmd, addr, false);
- else if (zap_huge_pmd(tlb, vma, pmd, addr)) {
+ if (next - addr != HPAGE_PMD_SIZE) {
+ /*
+ * If split fails, the PMD stays huge.
+ * Skip the range to avoid falling through
+ * to zap_pte_range, which would treat the
+ * huge PMD entry as a page table pointer.
+ */
+ if (__split_huge_pmd(vma, pmd, addr, false)) {
+ addr = next;
+ continue;
+ }
+ } else if (zap_huge_pmd(tlb, vma, pmd, addr)) {
addr = next;
continue;
}
--
2.52.0