mm/hugetlb: restore reservation on error in hugetlb_mfill_atomic_pte() resubmission path
Subsystems (3)
PATCH 1/1 mm/hugetlb: restore reservation on error in hugetlb_mfill_atomic_pte() resubmission path
When the resubmission path in hugetlb_mfill_atomic_pte() allocates a new hugetlb folio via alloc_hugetlb_folio(), a VMA reservation is consumed. If copy_user_large_folio() subsequently fails, folio_put() restores the global hugetlb pool count through free_huge_folio(), but the per-VMA reservation map entry is left in an inconsistent state.
Add the missing restore_reserve_on_error() call before folio_put(), matching the first-attempt error path which already handles this correctly.
--- mm/hugetlb.c | 1 + 1 file changed, 1 insertion(+)
mm/hugetlb.c +1 -0
@@ -6295,6 +6295,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,folio_put (*foliop); *foliop =NULL ;if (ret) { +restore_reserve_on_error (h, dst_vma, dst_addr, folio);folio_put (folio); goto out; }
Signature · 1 line
2.53.0
Andrew Morton
maintainerQuoted 14 lines · On Sun, 22 Mar 2026 05:21:20 +0000 David Carlier <[email protected]> wrote:
When the resubmission path in
hugetlb_mfill_atomic_pte()allocates a new hugetlb folio viaalloc_hugetlb_folio(), a VMA reservation is consumed. Ifcopy_user_large_folio()subsequently fails,folio_put()restores the global hugetlb pool count throughfree_huge_folio(), but the per-VMA reservation map entry is left in an inconsistent state.Add the missing
restore_reserve_on_error()call beforefolio_put(), matching the first-attempt error path which already handles this correctly....
mm/hugetlb.c int hugetlb_mfill_atomic_pte(pte_t *dst_pte, · line 6301
Quoted 7 lines
mm/hugetlb.c +1
-0@@ -6295,6 +6295,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,folio_put (*foliop); *foliop =NULL ;if (ret) { +restore_reserve_on_error (h, dst_vma, dst_addr, folio);folio_put (folio); goto out; }
I guess we could goto out_release_nounlock here, although I'm not sure that improves anything - keeping track of the value of folio_in_pagecache is rather twisty.
Andrew Morton
maintainerQuoted 8 lines · On Sun, 22 Mar 2026 05:21:20 +0000 David Carlier <[email protected]> wrote:
When the resubmission path in
hugetlb_mfill_atomic_pte()allocates a new hugetlb folio viaalloc_hugetlb_folio(), a VMA reservation is consumed. Ifcopy_user_large_folio()subsequently fails,folio_put()restores the global hugetlb pool count throughfree_huge_folio(), but the per-VMA reservation map entry is left in an inconsistent state.Add the missing
restore_reserve_on_error()call beforefolio_put(), matching the first-attempt error path which already handles this correctly.
Could we please have some review here?
Quoted 2 lines
Fixes 8cc5fcbb5be8 ("mm, hugetlb: fix racy resv_huge_pages underflow on UFFDIO_COPY")
And agreement that this is desirable.
Thanks.
Quoted 15 lines
mm/hugetlb.c +1
-0@@ -6295,6 +6295,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,folio_put (*foliop); *foliop =NULL ;if (ret) { +restore_reserve_on_error (h, dst_vma, dst_addr, folio);folio_put (folio); goto out; }Signature · 1 line
2.53.0
Muchun Song
maintainerQuoted 10 lines · David Carlier
When the resubmission path in
hugetlb_mfill_atomic_pte()allocates a new hugetlb folio viaalloc_hugetlb_folio(), a VMA reservation is consumed. Ifcopy_user_large_folio()subsequently fails,folio_put()restores the global hugetlb pool count throughfree_huge_folio(), but the per-VMA reservation map entry is left in an inconsistent state.
Quoted 4 lines
Add the missing
restore_reserve_on_error()call beforefolio_put(), matching the first-attempt error path which already handles this correctly.Fixes 8cc5fcbb5be8 ("mm, hugetlb: fix racy resv_huge_pages underflow on UFFDIO_COPY")
Hi David,
Thanks for this fix. The patch looks good to me and clearly solves the reservation leak in the resubmission path of hugetlb_mfill_atomic_pte().
However, I'm a bit curious about the Fixes tag. While commit 8cc5fcbb5be8 did introduce this code structure and the retry path, it seems the bug wasn't actually introduced there. At that time, copy_huge_page() returned void, so the failure path simply did not exist.
Instead, looking at the git history, the failure branch if (ret) was added later by commit 1cb9dc4b475c ("mm: hwpoison: support recovery from HugePage copy-on-write faults"). It modified copy_user_large_folio() to return an int and introduced error handling paths that unfortunately missed restoring the reservations. Should the Fixes tag perhaps point to 1cb9dc4b475c instead?
Furthermore, if commit 1cb9dc4b475c is indeed the root cause, I noticed it also introduced similar error handling paths in other places. For example, in copy_hugetlb_page_range():
ret = copy_user_large_folio(new_folio, pte_folio, addr, dst_vma);
folio_put(pte_folio);
if (ret) {
folio_put(new_folio);
break;
}Here, new_folio was allocated with alloc_hugetlb_folio(), which consumes reservations. But if the copy fails, new_folio is freed via folio_put() without calling restore_reserve_on_error() first.
Does this imply we might have similar reservation leaks in other error paths touched by 1cb9dc4b475c? I'd love to hear your thoughts on this.
Thanks,
Muchun
Quoted 21 lines
Signed-off-by David Carlier <[email protected]>---
mm/hugetlb.c| 1 +1 file changed, 1 insertion(+)mm/hugetlb.c +1
-0@@ -6295,6 +6295,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,folio_put (*foliop); *foliop =NULL ;if (ret) { +restore_reserve_on_error (h, dst_vma, dst_addr, folio);folio_put (folio); goto out; }Signature · 1 line
2.53.0
Andrew Morton
maintainerQuoted 43 lines · On Sat, 4 Apr 2026 20:59:11 +0800 Muchun Song <[email protected]> wrote:
Quoted 10 lines · David Carlier
When the resubmission path in
hugetlb_mfill_atomic_pte()allocates a new hugetlb folio viaalloc_hugetlb_folio(), a VMA reservation is consumed. Ifcopy_user_large_folio()subsequently fails,folio_put()restores the global hugetlb pool count throughfree_huge_folio(), but the per-VMA reservation map entry is left in an inconsistent state.Quoted 4 lines
Add the missing
restore_reserve_on_error()call beforefolio_put(), matching the first-attempt error path which already handles this correctly.Fixes 8cc5fcbb5be8 ("mm, hugetlb: fix racy resv_huge_pages underflow on UFFDIO_COPY")Hi David,
Thanks for this fix. The patch looks good to me and clearly solves the reservation leak in the resubmission path of
hugetlb_mfill_atomic_pte().However, I'm a bit curious about the Fixes tag. While commit
8cc5fcbb5be8did introduce this code structure and the retry path, it seems the bug wasn't actually introduced there. At that time,copy_huge_page()returned void, so the failure path simply did not exist.Instead, looking at the git history, the failure branch
if (ret)was added later by commit1cb9dc4b475c("mm: hwpoison: support recovery from HugePage copy-on-write faults"). It modifiedcopy_user_large_folio()to return an int and introduced error handling paths that unfortunately missed restoring the reservations. Should the Fixes tag perhaps point to1cb9dc4b475cinstead?Furthermore, if commit
1cb9dc4b475cis indeed the root cause, I noticed it also introduced similar error handling paths in other places. For example, incopy_hugetlb_page_range():ret = copy_user_large_folio(new_folio, pte_folio, addr, dst_vma); folio_put(pte_folio); if (ret) { folio_put(new_folio); break; }
Quoted 6 lines
Here,
new_foliowas allocated withalloc_hugetlb_folio(), which consumes reservations. But if the copy fails,new_foliois freed viafolio_put()without callingrestore_reserve_on_error()first.Does this imply we might have similar reservation leaks in other error paths touched by
1cb9dc4b475c? I'd love to hear your thoughts on this.
It's been a while. Can people please refocus on this?
David, your patch had cc:stable but I'm not seeing a description of the userspace-visible effects of the bug. Can you please describe?
From: David Carlier <[email protected]>
Subject: mm/hugetlb: restore reservation on error in hugetlb_mfill_atomic_pte() resubmission path
Date: Sun, 22 Mar 2026 05:21:20 +0000
When the resubmission path in hugetlb_mfill_atomic_pte() allocates a new hugetlb folio via alloc_hugetlb_folio(), a VMA reservation is consumed. If copy_user_large_folio() subsequently fails, folio_put() restores the global hugetlb pool count through free_huge_folio(), but the per-VMA reservation map entry is left in an inconsistent state.
Add the missing restore_reserve_on_error() call before folio_put(), matching the first-attempt error path which already handles this correctly.
---
mm/hugetlb.c | 1 + 1 file changed, 1 insertion(+)
mm/hugetlb.c renamed from mm/hugetlb.c~mm-hugetlb-restore-reservation-on-error-in-hugetlb_mfill_atomic_pte-resubmission-path +1 -0
@@ -6290,6 +6290,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_folio_put (*foliop); *foliop =NULL ;if (ret) { +restore_reserve_on_error (h, dst_vma, dst_addr, folio);folio_put (folio); goto out; }
_
Quoted 2 lines · On Tue, 19 May 2026 14:52:15 -0700 Andrew Morton <[email protected]>…
David, your patch had cc:stable but I'm not seeing a description of the userspace-visible effects of the bug. Can you please describe?
When UFFDIO_COPY's resubmission copy_user_large_folio() fails (e.g. -EHWPOISON on a hwpoisoned source page), the per-VMA reservation map entry for dst_addr stays marked consumed while free_huge_folio() puts h->resv_huge_pages back. A later fault at the same address then takes the no-reservation path and, under pool pressure, SIGBUSes the task at an address it had already reserved. Each hit leaks one map entry for the lifetime of the mapping.
Quoted 1 line · On Sat, 4 Apr 2026 20:59:11 +0800 Muchun Song <[email protected]> wrote:
Should the Fixes tag perhaps point to
1cb9dc4b475cinstead?
Yes. copy_user_large_folio() was void at 8cc5fcbb5be8, so the failing arm didn't exist yet; 1cb9dc4b475c is what made it int-returning and added the if (ret) { folio_put(folio); goto out; } without restore_reserve_on_error(). Will fix in v2.
Quoted 2 lines
Does this imply we might have similar reservation leaks in other error paths touched by
1cb9dc4b475c?
copy_hugetlb_page_range()'s hugetlb_try_dup_anon_rmap() fallback has the same shape and new_folio comes from alloc_hugetlb_folio(dst_vma, addr, false), so it leaks the same way. hugetlb_wp() is fine -- it goes through out_release_all which already calls restore_reserve_on_error(). I'll send the copy_hugetlb_page_range() fix as a separate patch.
Cheers.