copy_huge_pmd() splits the source PMD when a folio is pinned and can't be COW-shared at PMD granularity. It then returns -EAGAIN so copy_pmd_range() falls through to copy_pte_range().
If the split fails, the PMD is still huge. Returning -EAGAIN would cause copy_pmd_range() to call copy_pte_range(), which would dereference the huge PMD entry as if it were a pointer to a PTE page table. Return -ENOMEM on split failure instead (which is already done in copy_huge_pmd() if pte_alloc_one() fails), which causes copy_page_range() to abort the fork with -ENOMEM, similar to how copy_pmd_range() would be aborted if pmd_alloc() and copy_pte_range() fail. Signed-off-by: Usama Arif <[email protected]> --- mm/huge_memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index a979aa5bd2995..d9fb5875fa59e 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -1929,7 +1929,13 @@ int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm, pte_free(dst_mm, pgtable); spin_unlock(src_ptl); spin_unlock(dst_ptl); - __split_huge_pmd(src_vma, src_pmd, addr, false); + /* + * If split fails, the PMD is still huge so copy_pte_range + * (via -EAGAIN) would misinterpret it as a page table + * pointer. Return -ENOMEM directly to copy_pmd_range. + */ + if (__split_huge_pmd(src_vma, src_pmd, addr, false)) + return -ENOMEM; return -EAGAIN; } add_mm_counter(dst_mm, MM_ANONPAGES, HPAGE_PMD_NR); -- 2.47.3
