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 <[email protected]> --- mm/memory.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/mm/memory.c b/mm/memory.c index 9385842c35034..7ba1221c63792 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1983,9 +1983,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.47.3
