* [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking
@ 2026-09-09 21:49 Ackerley Tng via B4 Relay
2026-09-09 21:49 ` [PATCH v2 1/4] mm: hugetlb: Track used_hpages when getting/putting pages from subpool Ackerley Tng via B4 Relay
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-09-09 21:49 UTC (permalink / raw)
To: Alex Shi, Andrew Morton, David Hildenbrand, Dongliang Mu,
Hongxiang Lou, Johannes Weiner, Jonathan Corbet, Joshua Hahn,
Liam R. Howlett, Lorenzo Stoakes, Miaohe Lin, Michal Hocko,
Mike Rapoport, Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu,
Randy Dunlap, Roman Gushchin, Shakeel Butt, Shuah Khan,
Suren Baghdasaryan, Usama Arif, Vlastimil Babka, Wupeng Ma,
Yanteng Si, fvdl, jthoughton, rientjes, vannapurve
Cc: linux-doc, linux-kernel, linux-mm, Ackerley Tng, stable
HugeTLB subpools currently only track used_hpages when the user configures
a size limit.
This is buggy since when there are existing allocations from the subpool
that would have satisfied the minimum reservations,
hugepage_subpool_put_pages() will still restore a reservation to the
subpool. See below for an example of a false reservation.
In addition, the subpool is considered free prematurely, is freed, and this
ends up causing a use-after-free.
The fix is to always track used_hpages within subpools, which is also
beneficial in general because with that information, reservation tracking
is also fully managed within hugepage_subpool_put_pages().
The invariant is that now (if min_hpages is requested),
used_hpages + rsv_hpages >= min_hpages
This allows hugepage_subpool_put_pages() to always be able to report how
many reservations it can absorb and hence return an accurate number of
reservations to be returned to the global pool.
Hugepage reservations and restorations can always happen in parallel, so
relying on local variables to compute whether to restore to the global pool
(on allocation failure) is not safe.
There is complexity and some bugs in hugetlb_reserve_pages() and
alloc_hugetlb_folio() failure handling paths.
+ In hugetlb_reserve_pages(): On hugetlb_acct_memory() failure,
out_put_pages manually calculates how many pages to return using local
variables, which is race-prone and can leak reservations or underflow
global counters.
+ In alloc_hugetlb_folio(): When allocation fails and gbl_chg == 1,
out_subpool_put skips hugepage_subpool_put_pages(), permanently leaking
used_hpages.
By tracking used_hpages in the subpool, these allocation/reservation paths
can handle failures by consistently returning pages to the subpool, and
relying on the return value to restore reservations to the global pool.
This series changes subpools to always track used_hpages, which itself
fixes the false reservation bug, and then uses used_hpages tracking in
subpools to fix other bugs.
This series is a subset of patches from [1] and replaces [1].
[1] https://lore.kernel.org/all/20260722-hugetlb-alloc-failure-fixes-v4-0-88e8b81970dc@google.com/
Tested:
+ Reproducers (see below) pass
+ tools/testing/selftests/mm/ksft_hugetlb.sh passes
+ libhugetlbfs tests pass
Changes in v2:
+ Add patch 4 to avoid a possible page allocation in the cleanup path of
hugetlb_reserve_pages(), addresses Sashiko's comment on v1.
v1: https://lore.kernel.org/r/20260902-hugetlb-subpool-always-track-used-v1-0-de1cd14bd713@google.com
I have reproducers, get them from
https://github.com/googleprodkernel/linux-cc/commits/hugetlb-subpool-always-track-used-with-reproducers-v2
Here's an example of a false restoration:
1. Mount time
+ spool->min_hpages = 1 (user requested min_size=2M)
+ spool->max_hpages = -1 (no maximum size specified)
+ spool->rsv_hpages = 1 (reserve min_hpages)
+ spool->used_hpages = 0 (not tracked when max_hpages == -1)
+ h->resv_huge_pages = 1 (reserved by hugetlb_acct_memory(h, 1))
2. Shared mapping of 4MB (2 pages) created (mmap with MAP_SHARED)
+ In hugetlb_reserve_pages(), region_chg() finds chg = 2 (pages 0 and 1
need reservations)
+ hugepage_subpool_get_pages(spool, 2)
+ spool->rsv_hpages = 0 (consumed the 1 subpool reservation)
+ Return 1, since this subpool only had 1 reservation
+ hugetlb_acct_memory(h, 1)
+ h->resv_huge_pages = 2 (incremented from 1 to 2 for the global
reservation)
+ region_add() records reservations for pages 0 and 1 in the inode
resv_map
3. Process touches and populates Page 0:
+ hugetlb_no_page() calls alloc_hugetlb_folio()
+ Page 0 reuses the existing reservation (vma_needs_reservation()
returns 0 => map_chg = MAP_CHG_REUSE = 0)
+ hugepage_subpool_get_pages() is not called (map_chg == 0)
+ dequeue_hugetlb_folio_nodemask() consumes 1 reservation:
h->resv_huge_pages--;
+ h->resv_huge_pages = 1 (decremented from 2 to 1)
4. Process closes the file and exits:
+ For MAP_SHARED mappings, reservations persist in the inode resv_map
+ Page 1 reservation remains active
+ h->resv_huge_pages = 1 (retained for Page 1)
5. File is truncated to 2MB (truncate -s 2M):
+ Truncation invokes remove_inode_hugepages() for page range [1,
LONG_MAX)
+ Page 1 was never faulted into page cache => freed = 0
+ Calls hugetlb_unreserve_pages(inode, 1, LONG_MAX, freed = 0)
+ region_del() removes Page 1 from resv_map => chg = 1
6. Inside hugetlb_unreserve_pages(): hugepage_subpool_put_pages(1)
+ delta = chg - freed = 1 - 0 = 1
+ Because spool->max_hpages == -1, spool->used_hpages always = 0
+ spool->used_hpages < spool->min_hpages => 0 < 1 => true
<<== subpool assumes 0 pages are in use, ignoring allocated Page 0
+ spool->rsv_hpages + delta <= spool->min_hpages => 0 + 1 <= 1 => true
+ spool->rsv_hpages += 1 => spool->rsv_hpages = 1
<<== false reservation restored to subpool!
+ Return 0 (subpool absorbed the reservation)
7. Back in hugetlb_unreserve_pages(): hugetlb_acct_memory()
+ hugetlb_acct_memory(h, -0) => does nothing
+ h->resv_huge_pages = 1 (remains 1, not decremented)
+ Both reservations have ended (Page 0 allocated, Page 1 truncated),
but h->resv_huge_pages remains stuck at 1
8. Later during unmounting:
+ subpool_is_free() checks spool->rsv_hpages == spool->min_hpages => 1
== 1 => true
+ Because spool->rsv_hpages was falsely restored to 1, the subpool is
erroneously considered completely free
+ hugetlb_acct_memory(spool->hstate, -spool->min_hpages) decrements
h->resv_huge_pages by 1 (1 - 1 = 0), masking the leak on unmount
9. If the folio outlives the inode, when the folio is freed (Page 0),
free_huge_folio() will read subpool from the folio and act on it =>
use-after-free and then double free
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
Ackerley Tng (4):
mm: hugetlb: Track used_hpages when getting/putting pages from subpool
mm: hugetlb: Fix out_put_pages subpool reserve calculation
mm: hugetlb: Fix subpool usage leak on allocation failure
mm: hugetlb: Avoid re-allocating global reservations on region add failure
Documentation/mm/hugetlbfs_reserv.rst | 17 +--
.../translations/zh_CN/mm/hugetlbfs_reserv.rst | 11 +-
fs/hugetlbfs/inode.c | 8 +-
include/linux/hugetlb.h | 4 +-
mm/hugetlb.c | 147 +++++++++++----------
5 files changed, 90 insertions(+), 97 deletions(-)
---
base-commit: df2908090cda368b01ff43709f51890076c56157
change-id: 20260902-hugetlb-subpool-always-track-used-2624840f4c08
Best regards,
--
Ackerley Tng <ackerleytng@google.com>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 1/4] mm: hugetlb: Track used_hpages when getting/putting pages from subpool
2026-09-09 21:49 [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Ackerley Tng via B4 Relay
@ 2026-09-09 21:49 ` Ackerley Tng via B4 Relay
2026-09-11 14:08 ` Joshua Hahn
2026-09-09 21:49 ` [PATCH v2 2/4] mm: hugetlb: Fix out_put_pages subpool reserve calculation Ackerley Tng via B4 Relay
` (3 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-09-09 21:49 UTC (permalink / raw)
To: Alex Shi, Andrew Morton, David Hildenbrand, Dongliang Mu,
Hongxiang Lou, Johannes Weiner, Jonathan Corbet, Joshua Hahn,
Liam R. Howlett, Lorenzo Stoakes, Miaohe Lin, Michal Hocko,
Mike Rapoport, Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu,
Randy Dunlap, Roman Gushchin, Shakeel Butt, Shuah Khan,
Suren Baghdasaryan, Usama Arif, Vlastimil Babka, Wupeng Ma,
Yanteng Si, fvdl, jthoughton, rientjes, vannapurve
Cc: linux-doc, linux-kernel, linux-mm, Ackerley Tng, stable
From: Ackerley Tng <ackerleytng@google.com>
HugeTLB subpools currently only track used_hpages when the user
configures a size limit.
This is buggy since when there are existing allocations from the
subpool that would have satisfied the minimum reservations,
hugepage_subpool_put_pages() will still restore a reservation to the
subpool. See below for an example of a false reservation.
In addition, the subpool is considered free prematurely, is freed, and
this ends up causing a use-after-free.
The fix is to always track used_hpages within subpools, which is also
beneficial in general because with that information, reservation
tracking is also fully managed within hugepage_subpool_put_pages().
The subpool always knows how many pages were allocated through it. Every
page allocated through the subpool increments used_hpages, regardless of
whether a reservation was taken from it.
Conceptually, now, every allocation involving a subpool uses a page from
the subpool, which must be returned to the subpool. Every page taken from
the subpool tries to use a subpool reservation. Restoring a page to the
subpool reservations only if the page was taken from subpool
reservations. (If used_hpages >= min_hpages, the page must have not have
been taken from the reservations.)
Always tracking used_hpages provides the subpool with information of both
used and reserved counts to make the correct decision for both max_size and
min_size correctly.
With used_hpages always tracked, subpool_is_free() can be simplified, such
that the subpool can be declared free if there are no more pages in use.
Also update the
+ Documentation for used_hpages in the subpool struct, since it no longer
matters whether the used pages count against the maximum.
+ Docstring for hugepage_subpool_{get,put}_pages
+ Documentation to use active voice, and remove some details in favor of
having details documented in the docstring
Also update statfs reporting. Previously, if max_hpages is negative,
used_hpages is static at 0, so returning max_hpages - used_hpages returns
-1 and is always correct. Now, if the subpool doesn't have a maximum
requested size, indicate no limit for free pages (-1). If it does have a
maximum size, report the difference between the requested size and the
number of used pages. This difference is always positive, because if the
mount does have a maximum size, hugepage_subpool_get_pages() ensures that
the subpool usage never exceeds the maximum.
Fixes: 1c5ecae3a93fa ("hugetlbfs: add minimum size accounting to subpools")
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Cc: stable@vger.kernel.org
---
Documentation/mm/hugetlbfs_reserv.rst | 17 +----
.../translations/zh_CN/mm/hugetlbfs_reserv.rst | 11 +---
fs/hugetlbfs/inode.c | 8 ++-
include/linux/hugetlb.h | 4 +-
mm/hugetlb.c | 73 +++++++++++++---------
5 files changed, 55 insertions(+), 58 deletions(-)
diff --git a/Documentation/mm/hugetlbfs_reserv.rst b/Documentation/mm/hugetlbfs_reserv.rst
index a49115db18c76..d244583fdcbc3 100644
--- a/Documentation/mm/hugetlbfs_reserv.rst
+++ b/Documentation/mm/hugetlbfs_reserv.rst
@@ -314,21 +314,8 @@ huge pages. If they can not be reserved, the mount fails.
The routines hugepage_subpool_get/put_pages() are called when pages are
obtained from or released back to a subpool. They perform all subpool
accounting, and track any reservations associated with the subpool.
-hugepage_subpool_get/put_pages are passed the number of huge pages by which
-to adjust the subpool 'used page' count (down for get, up for put). Normally,
-they return the same value that was passed or an error if not enough pages
-exist in the subpool.
-
-However, if reserves are associated with the subpool a return value less
-than the passed value may be returned. This return value indicates the
-number of additional global pool adjustments which must be made. For example,
-suppose a subpool contains 3 reserved huge pages and someone asks for 5.
-The 3 reserved pages associated with the subpool can be used to satisfy part
-of the request. But, 2 pages must be obtained from the global pools. To
-relay this information to the caller, the value 2 is returned. The caller
-is then responsible for attempting to obtain the additional two pages from
-the global pools.
-
+hugepage_subpool_get/put_pages() use the number of huge pages passed to adjust
+the subpool 'used page' count.
COW and Reservations
====================
diff --git a/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst b/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst
index 20947f8bd0654..ae1f1f31477fc 100644
--- a/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst
+++ b/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst
@@ -246,15 +246,8 @@ hugepage_subpool的min_hpages字段中被跟踪。在挂载时,hugetlb_acct_me
被调用以预留指定数量的巨页。如果它们不能被预留,挂载就会失败。
当从子池中获取或释放页面时,会调用hugepage_subpool_get/put_pages()函数。
-hugepage_subpool_get/put_pages被传递给巨页数量,以此来调整子池的 “已用页面” 计数
-(get为下降,put为上升)。通常情况下,如果子池中没有足够的页面,它们会返回与传递的相同的值或
-一个错误。
-
-然而,如果预留与子池相关联,可能会返回一个小于传递值的返回值。这个返回值表示必须进行的额外全局
-池调整的数量。例如,假设一个子池包含3个预留的巨页,有人要求5个。与子池相关的3个预留页可以用来
-满足部分请求。但是,必须从全局池中获得2个页面。为了向调用者转达这一信息,将返回值2。然后,调用
-者要负责从全局池中获取另外两个页面。
-
+它们负责所有子池的统计核算,并跟踪与子池相关联的预留。
+hugepage_subpool_get/put_pages()函数使用传入的巨页数量来调整子池的“已用页面”计数。
COW和预留
==========
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 7611a8470ea26..5113f743f6fc7 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -1109,8 +1109,12 @@ static int hugetlbfs_statfs(struct dentry *dentry, struct kstatfs *buf)
spin_lock_irq(&sbinfo->spool->lock);
buf->f_blocks = sbinfo->spool->max_hpages;
- free_pages = sbinfo->spool->max_hpages
- - sbinfo->spool->used_hpages;
+ if (sbinfo->spool->max_hpages == -1) {
+ free_pages = -1;
+ } else {
+ free_pages = sbinfo->spool->max_hpages -
+ sbinfo->spool->used_hpages;
+ }
buf->f_bavail = buf->f_bfree = free_pages;
spin_unlock_irq(&sbinfo->spool->lock);
buf->f_files = sbinfo->max_inodes;
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 16c4c4caa126c..4551ff3023640 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -39,8 +39,8 @@ struct hugepage_subpool {
spinlock_t lock;
long count;
long max_hpages; /* Maximum huge pages or -1 if no maximum. */
- long used_hpages; /* Used count against maximum, includes */
- /* both allocated and reserved pages. */
+ long used_hpages; /* Used page count, includes both */
+ /* allocated and reserved pages. */
struct hstate *hstate;
long min_hpages; /* Minimum huge pages or -1 if no minimum. */
long rsv_hpages; /* Pages reserved against global pool to */
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 4f6f58bf3db6c..e72e22f887478 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -130,12 +130,8 @@ static inline bool subpool_is_free(struct hugepage_subpool *spool)
{
if (spool->count)
return false;
- if (spool->max_hpages != -1)
- return spool->used_hpages == 0;
- if (spool->min_hpages != -1)
- return spool->rsv_hpages == spool->min_hpages;
- return true;
+ return spool->used_hpages == 0;
}
static inline void unlock_or_release_subpool(struct hugepage_subpool *spool,
@@ -193,13 +189,18 @@ void hugepage_put_subpool(struct hugepage_subpool *spool)
unlock_or_release_subpool(spool, flags);
}
-/*
- * Subpool accounting for allocating and reserving pages.
- * Return -ENOMEM if there are not enough resources to satisfy the
- * request. Otherwise, return the number of pages by which the
- * global pools must be adjusted (upward). The returned value may
- * only be different than the passed value (delta) in the case where
- * a subpool minimum size must be maintained.
+/**
+ * hugepage_subpool_get_pages - Get pages from a subpool
+ * @spool: pointer to subpool structure (may be NULL)
+ * @delta: number of pages to allocate or reserve
+ *
+ * Check and update subpool page usage counts when allocating or
+ * reserving @delta hugepages.
+ *
+ * Context: Takes spool->lock using spin_lock_irq().
+ * Return: Non-negative number of reservations that cannot be
+ * satisfied by the subpool, or -ENOMEM if the subpool maximum
+ * limit would be exceeded.
*/
static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
long delta)
@@ -211,15 +212,14 @@ static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
spin_lock_irq(&spool->lock);
- if (spool->max_hpages != -1) { /* maximum size accounting */
- if ((spool->used_hpages + delta) <= spool->max_hpages)
- spool->used_hpages += delta;
- else {
- ret = -ENOMEM;
- goto unlock_ret;
- }
+ if (spool->max_hpages != -1 &&
+ spool->used_hpages + delta > spool->max_hpages) {
+ ret = -ENOMEM;
+ goto unlock_ret;
}
+ spool->used_hpages += delta;
+
/* minimum size accounting */
if (spool->min_hpages != -1 && spool->rsv_hpages) {
if (delta > spool->rsv_hpages) {
@@ -240,11 +240,19 @@ static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
return ret;
}
-/*
- * Subpool accounting for freeing and unreserving pages.
- * Return the number of global page reservations that must be dropped.
- * The return value may only be different than the passed value (delta)
- * in the case where a subpool minimum size must be maintained.
+/**
+ * hugepage_subpool_put_pages - Release pages back to a subpool
+ * @spool: pointer to subpool structure (may be NULL)
+ * @delta: number of pages to free or unreserve
+ *
+ * Check and update subpool page usage counts when freeing or
+ * unreserving @delta hugepages.
+ *
+ * Context: Takes spool->lock using spin_lock_irqsave(). May release
+ * and free @spool if its usage count and references reach
+ * zero.
+ * Return: Non-negative number of reservations that the subpool cannot
+ * absorb.
*/
static long hugepage_subpool_put_pages(struct hugepage_subpool *spool,
long delta)
@@ -257,19 +265,24 @@ static long hugepage_subpool_put_pages(struct hugepage_subpool *spool,
spin_lock_irqsave(&spool->lock, flags);
- if (spool->max_hpages != -1) /* maximum size accounting */
- spool->used_hpages -= delta;
+ spool->used_hpages -= delta;
/* minimum size accounting */
if (spool->min_hpages != -1 && spool->used_hpages < spool->min_hpages) {
- if (spool->rsv_hpages + delta <= spool->min_hpages)
+ /*
+ * limit is the maximum number of reservations that
+ * can be restored to this subpool.
+ */
+ long limit = spool->min_hpages - spool->used_hpages;
+
+ if (spool->rsv_hpages + delta <= limit)
ret = 0;
else
- ret = spool->rsv_hpages + delta - spool->min_hpages;
+ ret = spool->rsv_hpages + delta - limit;
spool->rsv_hpages += delta;
- if (spool->rsv_hpages > spool->min_hpages)
- spool->rsv_hpages = spool->min_hpages;
+ if (spool->rsv_hpages > limit)
+ spool->rsv_hpages = limit;
}
/*
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 2/4] mm: hugetlb: Fix out_put_pages subpool reserve calculation
2026-09-09 21:49 [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Ackerley Tng via B4 Relay
2026-09-09 21:49 ` [PATCH v2 1/4] mm: hugetlb: Track used_hpages when getting/putting pages from subpool Ackerley Tng via B4 Relay
@ 2026-09-09 21:49 ` Ackerley Tng via B4 Relay
2026-09-11 14:37 ` Joshua Hahn
2026-09-09 21:49 ` [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure Ackerley Tng via B4 Relay
` (2 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-09-09 21:49 UTC (permalink / raw)
To: Alex Shi, Andrew Morton, David Hildenbrand, Dongliang Mu,
Hongxiang Lou, Johannes Weiner, Jonathan Corbet, Joshua Hahn,
Liam R. Howlett, Lorenzo Stoakes, Miaohe Lin, Michal Hocko,
Mike Rapoport, Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu,
Randy Dunlap, Roman Gushchin, Shakeel Butt, Shuah Khan,
Suren Baghdasaryan, Usama Arif, Vlastimil Babka, Wupeng Ma,
Yanteng Si, fvdl, jthoughton, rientjes, vannapurve
Cc: linux-doc, linux-kernel, linux-mm, Ackerley Tng, stable
From: Ackerley Tng <ackerleytng@google.com>
When reserving pages for a mapping fails during global accounting, the
error path rolls back the adjustments made to the subpool.
Currently, this rollback was performed in two separate steps:
1. Returning only the portion of reservations originally satisfied from the
subpool
2. Separately adjusting the subpool used pages counter for the portion that
was requested from the global pool.
In (1.), because the used pages counter had not yet been decremented for
the global portion, the subpool observed an inflated used pages count. If
the mount was configured with both a minimum size and a maximum size, this
inflated count prevented the subpool from recognizing that usage fell below
the minimum size guarantee.
As a result, the subpool failed to restore its reserved pages counter and
instead returned that a global reservation should be dropped. The
mount-time reservation is permanently destroyed, leaving global reservation
counts depleted and causing an underflow when the filesystem is eventually
unmounted.
Additionally, if concurrent threads modified subpool usage during the
reservation attempt, calculating the rollback amount using stale local
variables could cause global reservation counts to diverge.
Now that used pages are always tracked within the subpool, return the
entire requested page count to the subpool in a single call. Global
reservations are then adjusted using the difference between the
reservations originally requested and those returned, fixing the issues
described above.
Fixes: 1d3f9bb4c8af ("mm/hugetlb: restore failed global reservations to subpool")
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Cc: stable@vger.kernel.org
---
mm/hugetlb.c | 49 +++++++++++++++++++++++--------------------------
1 file changed, 23 insertions(+), 26 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index e72e22f887478..9eb9f3442574c 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -6676,12 +6676,14 @@ long hugetlb_reserve_pages(struct inode *inode,
struct vm_area_struct *vma,
vma_flags_t vma_flags)
{
- long chg = -1, add = -1, spool_resv, gbl_resv;
+ long chg = -1, add = -1;
struct hstate *h = hstate_inode(inode);
struct hugepage_subpool *spool = subpool_inode(inode);
struct resv_map *resv_map;
struct hugetlb_cgroup *h_cg = NULL;
- long gbl_reserve, regions_needed = 0;
+ long regions_needed = 0;
+ long gbl_resv_get;
+ long gbl_resv_put;
int err;
/* This should never happen */
@@ -6756,9 +6758,9 @@ long hugetlb_reserve_pages(struct inode *inode,
* the subpool has a minimum size, there may be some global
* reservations already in place (gbl_reserve).
*/
- gbl_reserve = hugepage_subpool_get_pages(spool, chg);
- if (gbl_reserve < 0) {
- err = gbl_reserve;
+ gbl_resv_get = hugepage_subpool_get_pages(spool, chg);
+ if (gbl_resv_get < 0) {
+ err = gbl_resv_get;
goto out_uncharge_cgroup;
}
@@ -6766,7 +6768,7 @@ long hugetlb_reserve_pages(struct inode *inode,
* Check enough hugepages are available for the reservation.
* Hand the pages back to the subpool if there are not
*/
- err = hugetlb_acct_memory(h, gbl_reserve);
+ err = hugetlb_acct_memory(h, gbl_resv_get);
if (err < 0)
goto out_put_pages;
@@ -6785,7 +6787,7 @@ long hugetlb_reserve_pages(struct inode *inode,
add = region_add(resv_map, from, to, regions_needed, h, h_cg);
if (unlikely(add < 0)) {
- hugetlb_acct_memory(h, -gbl_reserve);
+ hugetlb_acct_memory(h, -gbl_resv_get);
err = add;
goto out_put_pages;
} else if (unlikely(chg > add)) {
@@ -6821,26 +6823,21 @@ long hugetlb_reserve_pages(struct inode *inode,
}
return chg;
-out_put_pages:
- spool_resv = chg - gbl_reserve;
- if (spool_resv) {
- /* put sub pool's reservation back, chg - gbl_reserve */
- gbl_resv = hugepage_subpool_put_pages(spool, spool_resv);
- /*
- * subpool's reserved pages can not be put back due to race,
- * return to hstate.
- */
- hugetlb_acct_memory(h, -gbl_resv);
- }
- /* Restore used_hpages for pages that failed global reservation */
- if (gbl_reserve && spool) {
- unsigned long flags;
+ out_put_pages:
+ /*
+ * Return all that was requested from the subpool, let subpool
+ * tell us the new number of reservations that need to be
+ * returned to the global pool.
+ */
+ gbl_resv_put = hugepage_subpool_put_pages(spool, chg);
+ /*
+ * There may be a difference between the number of
+ * reservations to consume and the number to restore now if
+ * there are multiple threads interacting with the subpool -
+ * restore the difference.
+ */
+ hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
- spin_lock_irqsave(&spool->lock, flags);
- if (spool->max_hpages != -1)
- spool->used_hpages -= gbl_reserve;
- unlock_or_release_subpool(spool, flags);
- }
out_uncharge_cgroup:
hugetlb_cgroup_uncharge_cgroup_rsvd(hstate_index(h),
chg * pages_per_huge_page(h), h_cg);
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure
2026-09-09 21:49 [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Ackerley Tng via B4 Relay
2026-09-09 21:49 ` [PATCH v2 1/4] mm: hugetlb: Track used_hpages when getting/putting pages from subpool Ackerley Tng via B4 Relay
2026-09-09 21:49 ` [PATCH v2 2/4] mm: hugetlb: Fix out_put_pages subpool reserve calculation Ackerley Tng via B4 Relay
@ 2026-09-09 21:49 ` Ackerley Tng via B4 Relay
2026-09-09 22:54 ` Andrew Morton
2026-09-11 14:45 ` Joshua Hahn
2026-09-09 21:49 ` [PATCH v2 4/4] mm: hugetlb: Avoid re-allocating global reservations on region add failure Ackerley Tng via B4 Relay
2026-09-09 22:50 ` [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Andrew Morton
4 siblings, 2 replies; 12+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-09-09 21:49 UTC (permalink / raw)
To: Alex Shi, Andrew Morton, David Hildenbrand, Dongliang Mu,
Hongxiang Lou, Johannes Weiner, Jonathan Corbet, Joshua Hahn,
Liam R. Howlett, Lorenzo Stoakes, Miaohe Lin, Michal Hocko,
Mike Rapoport, Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu,
Randy Dunlap, Roman Gushchin, Shakeel Butt, Shuah Khan,
Suren Baghdasaryan, Usama Arif, Vlastimil Babka, Wupeng Ma,
Yanteng Si, fvdl, jthoughton, rientjes, vannapurve
Cc: linux-doc, linux-kernel, linux-mm, Ackerley Tng, stable
From: Ackerley Tng <ackerleytng@google.com>
When folio allocation fails early (e.g. buddy allocation failure or
cgroup charging failure) and a reservation was not used (meaning an
unreserved global page was needed), the subpool page acquired during the
allocation attempt must still be returned.
Currently, the subpool cleanup error path only returns the page to the
subpool if a reservation was used. If no reservation was used, it skips
releasing the page back to the subpool, permanently leaking the subpool's
used pages counter.
With subpools now always tracking used pages, always release the page
back to the subpool whenever a subpool page was acquired.
Opportunistically rename the local variables tracking global reservations
needed and global reservations returned. This clarifies the accounting:
a value of zero for needed global reservations indicates an existing
reservation satisfies the allocation, while a non-zero value indicates
new global pages are required.
Adjust global reservations using the difference between reservations
needed and reservations returned to properly handle races where concurrent
threads interact with the same subpool.
Fixes: a833a693a490 ("mm: hugetlb: fix incorrect fallback for subpool")
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Cc: stable@vger.kernel.org
---
mm/hugetlb.c | 23 ++++++++++-------------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 9eb9f3442574c..652cfb55c6e6e 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -2957,7 +2957,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
struct hugepage_subpool *spool = subpool_vma(vma);
struct hstate *h = hstate_vma(vma);
struct folio *folio;
- long retval, gbl_chg, gbl_reserve;
+ long retval, gbl_resv_get;
map_chg_state map_chg;
struct mempolicy_interpreted mpoli;
gfp_t gfp = htlb_alloc_mask(h);
@@ -2996,8 +2996,8 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
* Or if it can get one from the pool reservation directly.
*/
if (map_chg) {
- gbl_chg = hugepage_subpool_get_pages(spool, 1);
- if (gbl_chg < 0) {
+ gbl_resv_get = hugepage_subpool_get_pages(spool, 1);
+ if (gbl_resv_get < 0) {
ret = -ENOSPC;
goto out_end_reservation;
}
@@ -3006,7 +3006,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
* If we have the vma reservation ready, no need for extra
* global reservation.
*/
- gbl_chg = 0;
+ gbl_resv_get = 0;
}
/*
@@ -3017,10 +3017,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
alloc_flags |= HUGETLB_ALLOC_CHARG_CGROUP_RSVD;
/*
- * gbl_chg == 0 indicates a reservation exists for this
+ * gbl_resv_get == 0 indicates a reservation exists for this
* allocation, so try to use it.
*/
- if (gbl_chg == 0)
+ if (gbl_resv_get == 0)
alloc_flags |= HUGETLB_ALLOC_USE_GLOBAL_RESERVATIONS;
/* Takes reference on mpol. */
@@ -3074,13 +3074,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
return folio;
out_subpool_put:
- /*
- * put page to subpool iff the quota of subpool's rsv_hpages is used
- * during hugepage_subpool_get_pages.
- */
- if (map_chg && !gbl_chg) {
- gbl_reserve = hugepage_subpool_put_pages(spool, 1);
- hugetlb_acct_memory(h, -gbl_reserve);
+ if (map_chg) {
+ long gbl_resv_put = hugepage_subpool_put_pages(spool, 1);
+
+ hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
}
out_end_reservation:
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 4/4] mm: hugetlb: Avoid re-allocating global reservations on region add failure
2026-09-09 21:49 [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Ackerley Tng via B4 Relay
` (2 preceding siblings ...)
2026-09-09 21:49 ` [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure Ackerley Tng via B4 Relay
@ 2026-09-09 21:49 ` Ackerley Tng via B4 Relay
2026-09-11 14:49 ` Joshua Hahn
2026-09-09 22:50 ` [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Andrew Morton
4 siblings, 1 reply; 12+ messages in thread
From: Ackerley Tng via B4 Relay @ 2026-09-09 21:49 UTC (permalink / raw)
To: Alex Shi, Andrew Morton, David Hildenbrand, Dongliang Mu,
Hongxiang Lou, Johannes Weiner, Jonathan Corbet, Joshua Hahn,
Liam R. Howlett, Lorenzo Stoakes, Miaohe Lin, Michal Hocko,
Mike Rapoport, Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu,
Randy Dunlap, Roman Gushchin, Shakeel Butt, Shuah Khan,
Suren Baghdasaryan, Usama Arif, Vlastimil Babka, Wupeng Ma,
Yanteng Si, fvdl, jthoughton, rientjes, vannapurve
Cc: linux-doc, linux-kernel, linux-mm, Ackerley Tng
From: Ackerley Tng <ackerleytng@google.com>
When reserving huge pages for a shared mapping, reservations are first
requested from the subpool, and any remainder is accounted in global
reservations. When adding the file region entries fails later in the
process, the reservation attempt must be rolled back.
Previously, this error path explicitly dropped the global reservations
that were just acquired before jumping to the cleanup label. The cleanup
label then returned the pages to the subpool. If concurrent activity in
the subpool allowed the subpool to absorb more reservations upon return
than it supplied initially, the cleanup label calculated a positive
difference and attempted to allocate new global reservations from scratch.
This premature release was completely unnecessary because all requested
pages were already backed globally: partly by the mount guarantee and
partly by the global reservations just acquired. Prematurely dissolving
those reservations forced the cleanup path to attempt fresh buddy
allocations that could fail under memory pressure.
Instead, track the number of global reservations actually accounted so
far. In the cleanup label, subtract the already-accounted amount from the
difference between requested and returned reservations. This ensures
that when global reservations were already acquired, the adjustment is
purely non-positive, dropping excess reservations without ever attempting
fresh allocations.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
mm/hugetlb.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 652cfb55c6e6e..1151ad959ffd5 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -6678,6 +6678,7 @@ long hugetlb_reserve_pages(struct inode *inode,
struct hugepage_subpool *spool = subpool_inode(inode);
struct resv_map *resv_map;
struct hugetlb_cgroup *h_cg = NULL;
+ long gbl_resv_accted = 0;
long regions_needed = 0;
long gbl_resv_get;
long gbl_resv_put;
@@ -6768,6 +6769,7 @@ long hugetlb_reserve_pages(struct inode *inode,
err = hugetlb_acct_memory(h, gbl_resv_get);
if (err < 0)
goto out_put_pages;
+ gbl_resv_accted = gbl_resv_get;
/*
* Account for the reservations made. Shared mappings record regions
@@ -6784,7 +6786,6 @@ long hugetlb_reserve_pages(struct inode *inode,
add = region_add(resv_map, from, to, regions_needed, h, h_cg);
if (unlikely(add < 0)) {
- hugetlb_acct_memory(h, -gbl_resv_get);
err = add;
goto out_put_pages;
} else if (unlikely(chg > add)) {
@@ -6831,9 +6832,10 @@ long hugetlb_reserve_pages(struct inode *inode,
* There may be a difference between the number of
* reservations to consume and the number to restore now if
* there are multiple threads interacting with the subpool -
- * restore the difference.
+ * restore the difference, taking into account any global
+ * reservations already acquired.
*/
- hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
+ hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put - gbl_resv_accted);
out_uncharge_cgroup:
hugetlb_cgroup_uncharge_cgroup_rsvd(hstate_index(h),
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking
2026-09-09 21:49 [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Ackerley Tng via B4 Relay
` (3 preceding siblings ...)
2026-09-09 21:49 ` [PATCH v2 4/4] mm: hugetlb: Avoid re-allocating global reservations on region add failure Ackerley Tng via B4 Relay
@ 2026-09-09 22:50 ` Andrew Morton
4 siblings, 0 replies; 12+ messages in thread
From: Andrew Morton @ 2026-09-09 22:50 UTC (permalink / raw)
To: ackerleytng
Cc: Ackerley Tng via B4 Relay, Alex Shi, David Hildenbrand,
Dongliang Mu, Hongxiang Lou, Johannes Weiner, Jonathan Corbet,
Joshua Hahn, Liam R. Howlett, Lorenzo Stoakes, Miaohe Lin,
Michal Hocko, Mike Rapoport, Muchun Song, Nhat Pham,
Oscar Salvador, Peter Xu, Randy Dunlap, Roman Gushchin,
Shakeel Butt, Shuah Khan, Suren Baghdasaryan, Usama Arif,
Vlastimil Babka, Wupeng Ma, Yanteng Si, fvdl, jthoughton,
rientjes, vannapurve, linux-doc, linux-kernel, linux-mm, stable
On Wed, 09 Sep 2026 14:49:25 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org> wrote:
> HugeTLB subpools currently only track used_hpages when the user configures
> a size limit.
>
> This is buggy since when there are existing allocations from the subpool
> that would have satisfied the minimum reservations,
> hugepage_subpool_put_pages() will still restore a reservation to the
> subpool. See below for an example of a false reservation.
That sounds annoying, although isn't clear how this affects end-users.
> In addition, the subpool is considered free prematurely, is freed, and this
> ends up causing a use-after-free.
That sounds alarming.
Do you think it's best for us to submit [1-3] for -stable backporting?
Is it feasible to come up with a set of small little fixes to get
-stable out of trouble and then to prepare broader updates for our
ongoing mainline development?
If it's "shut up Andrew you're always saying that" then OK, I can take
that :)
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure
2026-09-09 21:49 ` [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure Ackerley Tng via B4 Relay
@ 2026-09-09 22:54 ` Andrew Morton
2026-09-10 19:57 ` Joshua Hahn
2026-09-11 14:45 ` Joshua Hahn
1 sibling, 1 reply; 12+ messages in thread
From: Andrew Morton @ 2026-09-09 22:54 UTC (permalink / raw)
To: ackerleytng
Cc: Ackerley Tng via B4 Relay, Alex Shi, David Hildenbrand,
Dongliang Mu, Hongxiang Lou, Johannes Weiner, Jonathan Corbet,
Joshua Hahn, Liam R. Howlett, Lorenzo Stoakes, Miaohe Lin,
Michal Hocko, Mike Rapoport, Muchun Song, Nhat Pham,
Oscar Salvador, Peter Xu, Randy Dunlap, Roman Gushchin,
Shakeel Butt, Shuah Khan, Suren Baghdasaryan, Usama Arif,
Vlastimil Babka, Wupeng Ma, Yanteng Si, fvdl, jthoughton,
rientjes, vannapurve, linux-doc, linux-kernel, linux-mm, stable,
Zhao Li
On Wed, 09 Sep 2026 14:49:28 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org> wrote:
> From: Ackerley Tng <ackerleytng@google.com>
>
> When folio allocation fails early (e.g. buddy allocation failure or
> cgroup charging failure) and a reservation was not used (meaning an
> unreserved global page was needed), the subpool page acquired during the
> allocation attempt must still be returned.
>
> Currently, the subpool cleanup error path only returns the page to the
> subpool if a reservation was used. If no reservation was used, it skips
> releasing the page back to the subpool, permanently leaking the subpool's
> used pages counter.
>
> With subpools now always tracking used pages, always release the page
> back to the subpool whenever a subpool page was acquired.
>
> Opportunistically rename the local variables tracking global reservations
> needed and global reservations returned. This clarifies the accounting:
> a value of zero for needed global reservations indicates an existing
> reservation satisfies the allocation, while a non-zero value indicates
> new global pages are required.
>
> Adjust global reservations using the difference between reservations
> needed and reservations returned to properly handle races where concurrent
> threads interact with the same subpool.
This one conflicts with "mm/hugetlb: fix max-only subpool accounting on
alloc_hugetlb_folio failure".
https://lore.kernel.org/20260428113037.88766-2-enderaoelyther@gmail.com.
> @@ -3074,13 +3074,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
> return folio;
>
> out_subpool_put:
> - /*
> - * put page to subpool iff the quota of subpool's rsv_hpages is used
> - * during hugepage_subpool_get_pages.
> - */
> - if (map_chg && !gbl_chg) {
> - gbl_reserve = hugepage_subpool_put_pages(spool, 1);
> - hugetlb_acct_memory(h, -gbl_reserve);
> + if (map_chg) {
> + long gbl_resv_put = hugepage_subpool_put_pages(spool, 1);
> +
> + hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
> }
>
> out_end_reservation:
Here.
I presently have Zhao Li's patch staged in mm.git's
mm-hotfixes-unstable branch, awaiting review.
What to do?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure
2026-09-09 22:54 ` Andrew Morton
@ 2026-09-10 19:57 ` Joshua Hahn
0 siblings, 0 replies; 12+ messages in thread
From: Joshua Hahn @ 2026-09-10 19:57 UTC (permalink / raw)
To: Andrew Morton
Cc: ackerleytng, Alex Shi, David Hildenbrand, Dongliang Mu,
Hongxiang Lou, Johannes Weiner, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Miaohe Lin, Michal Hocko, Mike Rapoport,
Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu, Randy Dunlap,
Roman Gushchin, Shakeel Butt, Shuah Khan, Suren Baghdasaryan,
Usama Arif, Vlastimil Babka, Wupeng Ma, Yanteng Si, fvdl,
jthoughton, rientjes, vannapurve, linux-doc, linux-kernel,
linux-mm, stable, Zhao Li
*/
On Wed, 9 Sep 2026 15:54:59 -0700 Andrew Morton <akpm@linux-foundation.org> wrote:
> On Wed, 09 Sep 2026 14:49:28 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org> wrote:
>
> > From: Ackerley Tng <ackerleytng@google.com>
> >
> > When folio allocation fails early (e.g. buddy allocation failure or
> > cgroup charging failure) and a reservation was not used (meaning an
> > unreserved global page was needed), the subpool page acquired during the
> > allocation attempt must still be returned.
> >
> > Currently, the subpool cleanup error path only returns the page to the
> > subpool if a reservation was used. If no reservation was used, it skips
> > releasing the page back to the subpool, permanently leaking the subpool's
> > used pages counter.
> >
> > With subpools now always tracking used pages, always release the page
> > back to the subpool whenever a subpool page was acquired.
> >
> > Opportunistically rename the local variables tracking global reservations
> > needed and global reservations returned. This clarifies the accounting:
> > a value of zero for needed global reservations indicates an existing
> > reservation satisfies the allocation, while a non-zero value indicates
> > new global pages are required.
> >
> > Adjust global reservations using the difference between reservations
> > needed and reservations returned to properly handle races where concurrent
> > threads interact with the same subpool.
>
> This one conflicts with "mm/hugetlb: fix max-only subpool accounting on
> alloc_hugetlb_folio failure".
> https://lore.kernel.org/20260428113037.88766-2-enderaoelyther@gmail.com.
>
>
> > @@ -3074,13 +3074,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
> > return folio;
> >
> > out_subpool_put:
> > - /*
> > - * put page to subpool iff the quota of subpool's rsv_hpages is used
> > - * during hugepage_subpool_get_pages.
> > - */
> > - if (map_chg && !gbl_chg) {
> > - gbl_reserve = hugepage_subpool_put_pages(spool, 1);
> > - hugetlb_acct_memory(h, -gbl_reserve);
> > + if (map_chg) {
> > + long gbl_resv_put = hugepage_subpool_put_pages(spool, 1);
> > +
> > + hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
> > }
> >
> > out_end_reservation:
>
> Here.
>
> I presently have Zhao Li's patch staged in mm.git's
> mm-hotfixes-unstable branch, awaiting review.
>
> What to do?
Hi Andrew, I hope you are having a good day!
Yeah, I also noticed this too [1]. There are actually 4 proposed
solutions for this in the mailing list which is a good indication that
something is wrong here : -) But unfortunately it seems like there was
no resolution the last time I pointed this out : -(
I CC-ed all the authors previously on that chain but wasn't able to
get a response, maybe we'll give it another shot here?
Ackerley, Zhao, David, Song, hopefully we can align here.
Personally I'm partial to Ackerley's approach here since I think the
get/put semantics that he proposes is the easiest to understand for me.
It would be great if we could sort this out since I think it's been
a while since we've had all of these proposals in flight.
Thank you everyone!
Joshua
[1] https://lore.kernel.org/all/20260713144557.3845941-1-joshua.hahnjy@gmail.com/
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 1/4] mm: hugetlb: Track used_hpages when getting/putting pages from subpool
2026-09-09 21:49 ` [PATCH v2 1/4] mm: hugetlb: Track used_hpages when getting/putting pages from subpool Ackerley Tng via B4 Relay
@ 2026-09-11 14:08 ` Joshua Hahn
0 siblings, 0 replies; 12+ messages in thread
From: Joshua Hahn @ 2026-09-11 14:08 UTC (permalink / raw)
To: Ackerley Tng via B4 Relay
Cc: Andrew Morton, David Hildenbrand, Dongliang Mu, Hongxiang Lou,
Johannes Weiner, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Miaohe Lin, Michal Hocko, Mike Rapoport,
Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu, Randy Dunlap,
Roman Gushchin, Shakeel Butt, Shuah Khan, Suren Baghdasaryan,
Usama Arif, Vlastimil Babka, Wupeng Ma, Yanteng Si, fvdl,
jthoughton, rientjes, vannapurve, linux-doc, linux-kernel,
linux-mm, Ackerley Tng, stable
On Wed, 09 Sep 2026 14:49:26 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org> wrote:
> From: Ackerley Tng <ackerleytng@google.com>
Hi Ackerley,
Thank you for working on this fix / simplification!
> HugeTLB subpools currently only track used_hpages when the user
> configures a size limit.
>
> This is buggy since when there are existing allocations from the
> subpool that would have satisfied the minimum reservations,
> hugepage_subpool_put_pages() will still restore a reservation to the
> subpool. See below for an example of a false reservation.
I'm not sure if I see the false reservation example, could I be
missing something here : -)
> In addition, the subpool is considered free prematurely, is freed, and
> this ends up causing a use-after-free.
That doesn't sound like a fun time!!
> The fix is to always track used_hpages within subpools, which is also
> beneficial in general because with that information, reservation
> tracking is also fully managed within hugepage_subpool_put_pages().
This is an increditly reasonable approach and I really like how we can
get rid of a lot of the if (spool->max_hpages) ... special casing.
Having different conditions for checking whether a subpool was / wasn't
free was also a bit strange to me as well...
> The subpool always knows how many pages were allocated through it. Every
> page allocated through the subpool increments used_hpages, regardless of
> whether a reservation was taken from it.
>
> Conceptually, now, every allocation involving a subpool uses a page from
> the subpool, which must be returned to the subpool. Every page taken from
> the subpool tries to use a subpool reservation. Restoring a page to the
> subpool reservations only if the page was taken from subpool
> reservations. (If used_hpages >= min_hpages, the page must have not have
> been taken from the reservations.)
>
> Always tracking used_hpages provides the subpool with information of both
> used and reserved counts to make the correct decision for both max_size and
> min_size correctly.
>
> With used_hpages always tracked, subpool_is_free() can be simplified, such
> that the subpool can be declared free if there are no more pages in use.
Awesome!
> Also update the
>
> + Documentation for used_hpages in the subpool struct, since it no longer
> matters whether the used pages count against the maximum.
> + Docstring for hugepage_subpool_{get,put}_pages
> + Documentation to use active voice, and remove some details in favor of
> having details documented in the docstring
>
> Also update statfs reporting. Previously, if max_hpages is negative,
> used_hpages is static at 0, so returning max_hpages - used_hpages returns
> -1 and is always correct. Now, if the subpool doesn't have a maximum
> requested size, indicate no limit for free pages (-1). If it does have a
> maximum size, report the difference between the requested size and the
> number of used pages. This difference is always positive, because if the
> mount does have a maximum size, hugepage_subpool_get_pages() ensures that
> the subpool usage never exceeds the maximum.
> Fixes: 1c5ecae3a93fa ("hugetlbfs: add minimum size accounting to subpools")
> Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> Cc: stable@vger.kernel.org
Feel free to add my:
Reviewed-by: Joshua Hahn <joshua.hahnjy@gmail.com>
> ---
> Documentation/mm/hugetlbfs_reserv.rst | 17 +----
> .../translations/zh_CN/mm/hugetlbfs_reserv.rst | 11 +---
> fs/hugetlbfs/inode.c | 8 ++-
> include/linux/hugetlb.h | 4 +-
> mm/hugetlb.c | 73 +++++++++++++---------
> 5 files changed, 55 insertions(+), 58 deletions(-)
>
> diff --git a/Documentation/mm/hugetlbfs_reserv.rst b/Documentation/mm/hugetlbfs_reserv.rst
> index a49115db18c76..d244583fdcbc3 100644
> --- a/Documentation/mm/hugetlbfs_reserv.rst
> +++ b/Documentation/mm/hugetlbfs_reserv.rst
> @@ -314,21 +314,8 @@ huge pages. If they can not be reserved, the mount fails.
> The routines hugepage_subpool_get/put_pages() are called when pages are
> obtained from or released back to a subpool. They perform all subpool
> accounting, and track any reservations associated with the subpool.
> -hugepage_subpool_get/put_pages are passed the number of huge pages by which
> -to adjust the subpool 'used page' count (down for get, up for put). Normally,
> -they return the same value that was passed or an error if not enough pages
> -exist in the subpool.
> -
> -However, if reserves are associated with the subpool a return value less
> -than the passed value may be returned. This return value indicates the
> -number of additional global pool adjustments which must be made. For example,
> -suppose a subpool contains 3 reserved huge pages and someone asks for 5.
> -The 3 reserved pages associated with the subpool can be used to satisfy part
> -of the request. But, 2 pages must be obtained from the global pools. To
> -relay this information to the caller, the value 2 is returned. The caller
> -is then responsible for attempting to obtain the additional two pages from
> -the global pools.
> -
> +hugepage_subpool_get/put_pages() use the number of huge pages passed to adjust
> +the subpool 'used page' count.
>
> COW and Reservations
> ====================
> diff --git a/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst b/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst
> index 20947f8bd0654..ae1f1f31477fc 100644
> --- a/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst
> +++ b/Documentation/translations/zh_CN/mm/hugetlbfs_reserv.rst
> @@ -246,15 +246,8 @@ hugepage_subpool的min_hpages字段中被跟踪。在挂载时,hugetlb_acct_me
> 被调用以预留指定数量的巨页。如果它们不能被预留,挂载就会失败。
>
> 当从子池中获取或释放页面时,会调用hugepage_subpool_get/put_pages()函数。
> -hugepage_subpool_get/put_pages被传递给巨页数量,以此来调整子池的 “已用页面” 计数
> -(get为下降,put为上升)。通常情况下,如果子池中没有足够的页面,它们会返回与传递的相同的值或
> -一个错误。
> -
> -然而,如果预留与子池相关联,可能会返回一个小于传递值的返回值。这个返回值表示必须进行的额外全局
> -池调整的数量。例如,假设一个子池包含3个预留的巨页,有人要求5个。与子池相关的3个预留页可以用来
> -满足部分请求。但是,必须从全局池中获得2个页面。为了向调用者转达这一信息,将返回值2。然后,调用
> -者要负责从全局池中获取另外两个页面。
> -
> +它们负责所有子池的统计核算,并跟踪与子池相关联的预留。
> +hugepage_subpool_get/put_pages()函数使用传入的巨页数量来调整子池的“已用页面”计数。
>
> COW和预留
> ==========
> diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
> index 7611a8470ea26..5113f743f6fc7 100644
> --- a/fs/hugetlbfs/inode.c
> +++ b/fs/hugetlbfs/inode.c
> @@ -1109,8 +1109,12 @@ static int hugetlbfs_statfs(struct dentry *dentry, struct kstatfs *buf)
>
> spin_lock_irq(&sbinfo->spool->lock);
> buf->f_blocks = sbinfo->spool->max_hpages;
> - free_pages = sbinfo->spool->max_hpages
> - - sbinfo->spool->used_hpages;
> + if (sbinfo->spool->max_hpages == -1) {
> + free_pages = -1;
> + } else {
> + free_pages = sbinfo->spool->max_hpages -
> + sbinfo->spool->used_hpages;
> + }
> buf->f_bavail = buf->f_bfree = free_pages;
> spin_unlock_irq(&sbinfo->spool->lock);
> buf->f_files = sbinfo->max_inodes;
> diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
> index 16c4c4caa126c..4551ff3023640 100644
> --- a/include/linux/hugetlb.h
> +++ b/include/linux/hugetlb.h
> @@ -39,8 +39,8 @@ struct hugepage_subpool {
> spinlock_t lock;
> long count;
> long max_hpages; /* Maximum huge pages or -1 if no maximum. */
> - long used_hpages; /* Used count against maximum, includes */
> - /* both allocated and reserved pages. */
> + long used_hpages; /* Used page count, includes both */
> + /* allocated and reserved pages. */
> struct hstate *hstate;
> long min_hpages; /* Minimum huge pages or -1 if no minimum. */
> long rsv_hpages; /* Pages reserved against global pool to */
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 4f6f58bf3db6c..e72e22f887478 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -130,12 +130,8 @@ static inline bool subpool_is_free(struct hugepage_subpool *spool)
> {
> if (spool->count)
> return false;
> - if (spool->max_hpages != -1)
> - return spool->used_hpages == 0;
> - if (spool->min_hpages != -1)
> - return spool->rsv_hpages == spool->min_hpages;
>
> - return true;
> + return spool->used_hpages == 0;
> }
>
> static inline void unlock_or_release_subpool(struct hugepage_subpool *spool,
> @@ -193,13 +189,18 @@ void hugepage_put_subpool(struct hugepage_subpool *spool)
> unlock_or_release_subpool(spool, flags);
> }
>
> -/*
> - * Subpool accounting for allocating and reserving pages.
> - * Return -ENOMEM if there are not enough resources to satisfy the
> - * request. Otherwise, return the number of pages by which the
> - * global pools must be adjusted (upward). The returned value may
> - * only be different than the passed value (delta) in the case where
> - * a subpool minimum size must be maintained.
> +/**
> + * hugepage_subpool_get_pages - Get pages from a subpool
> + * @spool: pointer to subpool structure (may be NULL)
> + * @delta: number of pages to allocate or reserve
> + *
> + * Check and update subpool page usage counts when allocating or
> + * reserving @delta hugepages.
> + *
> + * Context: Takes spool->lock using spin_lock_irq().
> + * Return: Non-negative number of reservations that cannot be
> + * satisfied by the subpool, or -ENOMEM if the subpool maximum
> + * limit would be exceeded.
> */
> static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
> long delta)
> @@ -211,15 +212,14 @@ static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
>
> spin_lock_irq(&spool->lock);
>
> - if (spool->max_hpages != -1) { /* maximum size accounting */
> - if ((spool->used_hpages + delta) <= spool->max_hpages)
> - spool->used_hpages += delta;
> - else {
> - ret = -ENOMEM;
> - goto unlock_ret;
> - }
> + if (spool->max_hpages != -1 &&
> + spool->used_hpages + delta > spool->max_hpages) {
> + ret = -ENOMEM;
> + goto unlock_ret;
> }
>
> + spool->used_hpages += delta;
> +
> /* minimum size accounting */
> if (spool->min_hpages != -1 && spool->rsv_hpages) {
> if (delta > spool->rsv_hpages) {
> @@ -240,11 +240,19 @@ static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
> return ret;
> }
>
> -/*
> - * Subpool accounting for freeing and unreserving pages.
> - * Return the number of global page reservations that must be dropped.
> - * The return value may only be different than the passed value (delta)
> - * in the case where a subpool minimum size must be maintained.
> +/**
> + * hugepage_subpool_put_pages - Release pages back to a subpool
> + * @spool: pointer to subpool structure (may be NULL)
> + * @delta: number of pages to free or unreserve
> + *
> + * Check and update subpool page usage counts when freeing or
> + * unreserving @delta hugepages.
> + *
> + * Context: Takes spool->lock using spin_lock_irqsave(). May release
> + * and free @spool if its usage count and references reach
> + * zero.
> + * Return: Non-negative number of reservations that the subpool cannot
> + * absorb.
> */
> static long hugepage_subpool_put_pages(struct hugepage_subpool *spool,
> long delta)
> @@ -257,19 +265,24 @@ static long hugepage_subpool_put_pages(struct hugepage_subpool *spool,
>
> spin_lock_irqsave(&spool->lock, flags);
>
> - if (spool->max_hpages != -1) /* maximum size accounting */
> - spool->used_hpages -= delta;
> + spool->used_hpages -= delta;
>
> /* minimum size accounting */
> if (spool->min_hpages != -1 && spool->used_hpages < spool->min_hpages) {
> - if (spool->rsv_hpages + delta <= spool->min_hpages)
>
> + /*
> + * limit is the maximum number of reservations that
> + * can be restored to this subpool.
> + */
> + long limit = spool->min_hpages - spool->used_hpages;
> +
> + if (spool->rsv_hpages + delta <= limit)
> ret = 0;
> else
> - ret = spool->rsv_hpages + delta - spool->min_hpages;
> + ret = spool->rsv_hpages + delta - limit;
>
> spool->rsv_hpages += delta;
> - if (spool->rsv_hpages > spool->min_hpages)
> - spool->rsv_hpages = spool->min_hpages;
> + if (spool->rsv_hpages > limit)
> + spool->rsv_hpages = limit;
> }
>
> /*
>
> --
> 2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/4] mm: hugetlb: Fix out_put_pages subpool reserve calculation
2026-09-09 21:49 ` [PATCH v2 2/4] mm: hugetlb: Fix out_put_pages subpool reserve calculation Ackerley Tng via B4 Relay
@ 2026-09-11 14:37 ` Joshua Hahn
0 siblings, 0 replies; 12+ messages in thread
From: Joshua Hahn @ 2026-09-11 14:37 UTC (permalink / raw)
To: Ackerley Tng via B4 Relay
Cc: Andrew Morton, David Hildenbrand, Dongliang Mu, Hongxiang Lou,
Johannes Weiner, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Miaohe Lin, Michal Hocko, Mike Rapoport,
Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu, Randy Dunlap,
Roman Gushchin, Shakeel Butt, Shuah Khan, Suren Baghdasaryan,
Usama Arif, Vlastimil Babka, Wupeng Ma, Yanteng Si, fvdl,
jthoughton, rientjes, vannapurve, linux-doc, linux-kernel,
linux-mm, Ackerley Tng, stable
On Wed, 09 Sep 2026 14:49:27 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org> wrote:
> From: Ackerley Tng <ackerleytng@google.com>
>
> When reserving pages for a mapping fails during global accounting, the
> error path rolls back the adjustments made to the subpool.
>
> Currently, this rollback was performed in two separate steps:
>
> 1. Returning only the portion of reservations originally satisfied from the
> subpool
> 2. Separately adjusting the subpool used pages counter for the portion that
> was requested from the global pool.
>
> In (1.), because the used pages counter had not yet been decremented for
> the global portion, the subpool observed an inflated used pages count. If
> the mount was configured with both a minimum size and a maximum size, this
> inflated count prevented the subpool from recognizing that usage fell below
> the minimum size guarantee.
>
> As a result, the subpool failed to restore its reserved pages counter and
> instead returned that a global reservation should be dropped. The
> mount-time reservation is permanently destroyed, leaving global reservation
> counts depleted and causing an underflow when the filesystem is eventually
> unmounted.
Yeah, this sounds pretty bad.
> Additionally, if concurrent threads modified subpool usage during the
> reservation attempt, calculating the rollback amount using stale local
> variables could cause global reservation counts to diverge.
>
> Now that used pages are always tracked within the subpool, return the
> entire requested page count to the subpool in a single call. Global
> reservations are then adjusted using the difference between the
> reservations originally requested and those returned, fixing the issues
> described above.
>
> Fixes: 1d3f9bb4c8af ("mm/hugetlb: restore failed global reservations to subpool")
> Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> Cc: stable@vger.kernel.org
Thanks! LGTM,
Reviewed-by: Joshua Hahn <joshua.hahnjy@gmail.com>
With one nit below:
> ---
> mm/hugetlb.c | 49 +++++++++++++++++++++++--------------------------
> 1 file changed, 23 insertions(+), 26 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index e72e22f887478..9eb9f3442574c 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -6676,12 +6676,14 @@ long hugetlb_reserve_pages(struct inode *inode,
> struct vm_area_struct *vma,
> vma_flags_t vma_flags)
> {
> - long chg = -1, add = -1, spool_resv, gbl_resv;
> + long chg = -1, add = -1;
> struct hstate *h = hstate_inode(inode);
> struct hugepage_subpool *spool = subpool_inode(inode);
> struct resv_map *resv_map;
> struct hugetlb_cgroup *h_cg = NULL;
> - long gbl_reserve, regions_needed = 0;
> + long regions_needed = 0;
> + long gbl_resv_get;
> + long gbl_resv_put;
> int err;
>
> /* This should never happen */
> @@ -6756,9 +6758,9 @@ long hugetlb_reserve_pages(struct inode *inode,
> * the subpool has a minimum size, there may be some global
> * reservations already in place (gbl_reserve).
> */
> - gbl_reserve = hugepage_subpool_get_pages(spool, chg);
> - if (gbl_reserve < 0) {
> - err = gbl_reserve;
> + gbl_resv_get = hugepage_subpool_get_pages(spool, chg);
> + if (gbl_resv_get < 0) {
> + err = gbl_resv_get;
> goto out_uncharge_cgroup;
> }
>
> @@ -6766,7 +6768,7 @@ long hugetlb_reserve_pages(struct inode *inode,
> * Check enough hugepages are available for the reservation.
> * Hand the pages back to the subpool if there are not
> */
> - err = hugetlb_acct_memory(h, gbl_reserve);
> + err = hugetlb_acct_memory(h, gbl_resv_get);
> if (err < 0)
> goto out_put_pages;
>
> @@ -6785,7 +6787,7 @@ long hugetlb_reserve_pages(struct inode *inode,
> add = region_add(resv_map, from, to, regions_needed, h, h_cg);
>
> if (unlikely(add < 0)) {
> - hugetlb_acct_memory(h, -gbl_reserve);
> + hugetlb_acct_memory(h, -gbl_resv_get);
> err = add;
> goto out_put_pages;
> } else if (unlikely(chg > add)) {
> @@ -6821,26 +6823,21 @@ long hugetlb_reserve_pages(struct inode *inode,
> }
> return chg;
>
> -out_put_pages:
> - spool_resv = chg - gbl_reserve;
> - if (spool_resv) {
> - /* put sub pool's reservation back, chg - gbl_reserve */
> - gbl_resv = hugepage_subpool_put_pages(spool, spool_resv);
> - /*
> - * subpool's reserved pages can not be put back due to race,
> - * return to hstate.
> - */
> - hugetlb_acct_memory(h, -gbl_resv);
> - }
> - /* Restore used_hpages for pages that failed global reservation */
> - if (gbl_reserve && spool) {
> - unsigned long flags;
> + out_put_pages:
I'm not sure if this section warrants these two comment blocks anymore.
The logic is quite straightforward now that we don't have to worry about
managing the global / subpool reservations separately and rather just
simply do the subtraction to account the memory.
> + /*
> + * Return all that was requested from the subpool, let subpool
> + * tell us the new number of reservations that need to be
> + * returned to the global pool.
> + */
> + gbl_resv_put = hugepage_subpool_put_pages(spool, chg);
> + /*
> + * There may be a difference between the number of
> + * reservations to consume and the number to restore now if
> + * there are multiple threads interacting with the subpool -
> + * restore the difference.
> + */
> + hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
>
> - spin_lock_irqsave(&spool->lock, flags);
> - if (spool->max_hpages != -1)
> - spool->used_hpages -= gbl_reserve;
> - unlock_or_release_subpool(spool, flags);
> - }
> out_uncharge_cgroup:
> hugetlb_cgroup_uncharge_cgroup_rsvd(hstate_index(h),
> chg * pages_per_huge_page(h), h_cg);
>
> --
> 2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure
2026-09-09 21:49 ` [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure Ackerley Tng via B4 Relay
2026-09-09 22:54 ` Andrew Morton
@ 2026-09-11 14:45 ` Joshua Hahn
1 sibling, 0 replies; 12+ messages in thread
From: Joshua Hahn @ 2026-09-11 14:45 UTC (permalink / raw)
To: Ackerley Tng via B4 Relay
Cc: Andrew Morton, David Hildenbrand, Dongliang Mu, Hongxiang Lou,
Johannes Weiner, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Miaohe Lin, Michal Hocko, Mike Rapoport,
Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu, Randy Dunlap,
Roman Gushchin, Shakeel Butt, Shuah Khan, Suren Baghdasaryan,
Usama Arif, Vlastimil Babka, Wupeng Ma, Yanteng Si, fvdl,
jthoughton, rientjes, vannapurve, linux-doc, linux-kernel,
linux-mm, Ackerley Tng, stable
On Wed, 09 Sep 2026 14:49:28 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org> wrote:
> From: Ackerley Tng <ackerleytng@google.com>
>
> When folio allocation fails early (e.g. buddy allocation failure or
> cgroup charging failure) and a reservation was not used (meaning an
> unreserved global page was needed), the subpool page acquired during the
> allocation attempt must still be returned.
>
> Currently, the subpool cleanup error path only returns the page to the
> subpool if a reservation was used. If no reservation was used, it skips
> releasing the page back to the subpool, permanently leaking the subpool's
> used pages counter.
>
> With subpools now always tracking used pages, always release the page
> back to the subpool whenever a subpool page was acquired.
>
> Opportunistically rename the local variables tracking global reservations
> needed and global reservations returned. This clarifies the accounting:
> a value of zero for needed global reservations indicates an existing
> reservation satisfies the allocation, while a non-zero value indicates
> new global pages are required.
>
> Adjust global reservations using the difference between reservations
> needed and reservations returned to properly handle races where concurrent
> threads interact with the same subpool.
>
> Fixes: a833a693a490 ("mm: hugetlb: fix incorrect fallback for subpool")
> Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> Cc: stable@vger.kernel.org
LGTM,
Reviewed-by: Joshua Hahn <joshua.hahnjy@gmail.com>
I think that we get parity in the accounting logic between
alloc_hugetlb_folio and hugetlb_reserve_pages is a nice plus as a result
of these two fixes.
> ---
> mm/hugetlb.c | 23 ++++++++++-------------
> 1 file changed, 10 insertions(+), 13 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 9eb9f3442574c..652cfb55c6e6e 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -2957,7 +2957,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
> struct hugepage_subpool *spool = subpool_vma(vma);
> struct hstate *h = hstate_vma(vma);
> struct folio *folio;
> - long retval, gbl_chg, gbl_reserve;
> + long retval, gbl_resv_get;
> map_chg_state map_chg;
> struct mempolicy_interpreted mpoli;
> gfp_t gfp = htlb_alloc_mask(h);
> @@ -2996,8 +2996,8 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
> * Or if it can get one from the pool reservation directly.
> */
> if (map_chg) {
> - gbl_chg = hugepage_subpool_get_pages(spool, 1);
> - if (gbl_chg < 0) {
> + gbl_resv_get = hugepage_subpool_get_pages(spool, 1);
> + if (gbl_resv_get < 0) {
> ret = -ENOSPC;
> goto out_end_reservation;
> }
> @@ -3006,7 +3006,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
> * If we have the vma reservation ready, no need for extra
> * global reservation.
> */
> - gbl_chg = 0;
> + gbl_resv_get = 0;
> }
>
> /*
> @@ -3017,10 +3017,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
> alloc_flags |= HUGETLB_ALLOC_CHARG_CGROUP_RSVD;
>
> /*
> - * gbl_chg == 0 indicates a reservation exists for this
> + * gbl_resv_get == 0 indicates a reservation exists for this
> * allocation, so try to use it.
> */
> - if (gbl_chg == 0)
> + if (gbl_resv_get == 0)
> alloc_flags |= HUGETLB_ALLOC_USE_GLOBAL_RESERVATIONS;
>
> /* Takes reference on mpol. */
> @@ -3074,13 +3074,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
> return folio;
>
> out_subpool_put:
> - /*
> - * put page to subpool iff the quota of subpool's rsv_hpages is used
> - * during hugepage_subpool_get_pages.
> - */
> - if (map_chg && !gbl_chg) {
> - gbl_reserve = hugepage_subpool_put_pages(spool, 1);
> - hugetlb_acct_memory(h, -gbl_reserve);
> + if (map_chg) {
> + long gbl_resv_put = hugepage_subpool_put_pages(spool, 1);
> +
> + hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
> }
>
> out_end_reservation:
>
> --
> 2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 4/4] mm: hugetlb: Avoid re-allocating global reservations on region add failure
2026-09-09 21:49 ` [PATCH v2 4/4] mm: hugetlb: Avoid re-allocating global reservations on region add failure Ackerley Tng via B4 Relay
@ 2026-09-11 14:49 ` Joshua Hahn
0 siblings, 0 replies; 12+ messages in thread
From: Joshua Hahn @ 2026-09-11 14:49 UTC (permalink / raw)
To: Ackerley Tng via B4 Relay
Cc: Andrew Morton, David Hildenbrand, Dongliang Mu, Hongxiang Lou,
Johannes Weiner, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Miaohe Lin, Michal Hocko, Mike Rapoport,
Muchun Song, Nhat Pham, Oscar Salvador, Peter Xu, Randy Dunlap,
Roman Gushchin, Shakeel Butt, Shuah Khan, Suren Baghdasaryan,
Usama Arif, Vlastimil Babka, Wupeng Ma, Yanteng Si, fvdl,
jthoughton, rientjes, vannapurve, linux-doc, linux-kernel,
linux-mm, Ackerley Tng
On Wed, 09 Sep 2026 14:49:29 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@kernel.org> wrote:
> From: Ackerley Tng <ackerleytng@google.com>
>
> When reserving huge pages for a shared mapping, reservations are first
> requested from the subpool, and any remainder is accounted in global
> reservations. When adding the file region entries fails later in the
> process, the reservation attempt must be rolled back.
>
> Previously, this error path explicitly dropped the global reservations
> that were just acquired before jumping to the cleanup label. The cleanup
> label then returned the pages to the subpool. If concurrent activity in
> the subpool allowed the subpool to absorb more reservations upon return
> than it supplied initially, the cleanup label calculated a positive
> difference and attempted to allocate new global reservations from scratch.
>
> This premature release was completely unnecessary because all requested
> pages were already backed globally: partly by the mount guarantee and
> partly by the global reservations just acquired. Prematurely dissolving
> those reservations forced the cleanup path to attempt fresh buddy
> allocations that could fail under memory pressure.
>
> Instead, track the number of global reservations actually accounted so
> far. In the cleanup label, subtract the already-accounted amount from the
> difference between requested and returned reservations. This ensures
> that when global reservations were already acquired, the adjustment is
> purely non-positive, dropping excess reservations without ever attempting
> fresh allocations.
>
> Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> ---
> mm/hugetlb.c | 8 +++++---
> 1 file changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 652cfb55c6e6e..1151ad959ffd5 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -6678,6 +6678,7 @@ long hugetlb_reserve_pages(struct inode *inode,
> struct hugepage_subpool *spool = subpool_inode(inode);
> struct resv_map *resv_map;
> struct hugetlb_cgroup *h_cg = NULL;
> + long gbl_resv_accted = 0;
Sorry, I think I have a hard time with this variable name ;p (I know,
even though the function is called huetlb_acct_memory) I get a little
bit confused because I can't tell if it's meant to be "accepted"
or "accounted".
I know it puts the line below at 81 columns :p but maybe we can just
split it across 2 lines?
> long regions_needed = 0;
> long gbl_resv_get;
> long gbl_resv_put;
> @@ -6768,6 +6769,7 @@ long hugetlb_reserve_pages(struct inode *inode,
> err = hugetlb_acct_memory(h, gbl_resv_get);
> if (err < 0)
> goto out_put_pages;
> + gbl_resv_accted = gbl_resv_get;
>
> /*
> * Account for the reservations made. Shared mappings record regions
> @@ -6784,7 +6786,6 @@ long hugetlb_reserve_pages(struct inode *inode,
> add = region_add(resv_map, from, to, regions_needed, h, h_cg);
>
> if (unlikely(add < 0)) {
> - hugetlb_acct_memory(h, -gbl_resv_get);
> err = add;
> goto out_put_pages;
> } else if (unlikely(chg > add)) {
> @@ -6831,9 +6832,10 @@ long hugetlb_reserve_pages(struct inode *inode,
> * There may be a difference between the number of
> * reservations to consume and the number to restore now if
> * there are multiple threads interacting with the subpool -
> - * restore the difference.
> + * restore the difference, taking into account any global
> + * reservations already acquired.
> */
> - hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
> + hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put - gbl_resv_accted);
>
> out_uncharge_cgroup:
> hugetlb_cgroup_uncharge_cgroup_rsvd(hstate_index(h),
>
> --
> 2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-09-11 14:50 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-09 21:49 [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Ackerley Tng via B4 Relay
2026-09-09 21:49 ` [PATCH v2 1/4] mm: hugetlb: Track used_hpages when getting/putting pages from subpool Ackerley Tng via B4 Relay
2026-09-11 14:08 ` Joshua Hahn
2026-09-09 21:49 ` [PATCH v2 2/4] mm: hugetlb: Fix out_put_pages subpool reserve calculation Ackerley Tng via B4 Relay
2026-09-11 14:37 ` Joshua Hahn
2026-09-09 21:49 ` [PATCH v2 3/4] mm: hugetlb: Fix subpool usage leak on allocation failure Ackerley Tng via B4 Relay
2026-09-09 22:54 ` Andrew Morton
2026-09-10 19:57 ` Joshua Hahn
2026-09-11 14:45 ` Joshua Hahn
2026-09-09 21:49 ` [PATCH v2 4/4] mm: hugetlb: Avoid re-allocating global reservations on region add failure Ackerley Tng via B4 Relay
2026-09-11 14:49 ` Joshua Hahn
2026-09-09 22:50 ` [PATCH v2 0/4] Fix HugeTLB subpool used_hpages tracking Andrew Morton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox