[PATCH v4 08/16] fs: hugetlbfs: Fix global reservation leak in hugetlbfs_fill_super()

From: Ackerley Tng via B4 Relay

Date: Wed Jul 22 2026 - 19:46:41 EST


From: Ackerley Tng <ackerleytng@xxxxxxxxxx>

In hugetlbfs_fill_super(), if hugepage_new_subpool() succeeds in
allocating a subpool (which reserves global huge pages when
min_hpages != -1), but a subsequent initialization step like
d_make_root() fails, the error path directly invoked
kfree(sbinfo->spool).

Directly freeing the subpool structure with kfree() bypasses
hugepage_put_subpool() and its underlying unlock_or_release_subpool()
destructor. Consequently, hugetlb_acct_memory() is never called to
release the global page reservations allocated for min_hpages,
permanently leaking global huge page reservations.

Fix this leak by calling hugepage_put_subpool(sbinfo->spool) on the
error cleanup path instead of kfree(sbinfo->spool).

sbinfo->spool must be checked before dereferencing the subpool in
hugepage_put_subpool(). Add a NULL guard to hugepage_put_subpool() instead
of checking it in the caller to align it with other subpool helpers like
hugepage_subpool_get_pages() and hugepage_subpool_put_pages() that
gracefully handle NULL subpools. This allows callers to safely invoke
hugepage_put_subpool() without requiring explicit NULL checks. This is also
aligned with how kfree() can be called on NULL.

With the NULL guard in hugepage_put_subpool(), the NULL check in the only
other caller can also be removed.

Fixes: 7ca02d0ae586f ("hugetlbfs: accept subpool min_size mount option and setup accordingly")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
---
fs/hugetlbfs/inode.c | 6 ++----
mm/hugetlb.c | 3 +++
2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 26c0187340636..e5d86f31eba5b 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -1133,9 +1133,7 @@ static void hugetlbfs_put_super(struct super_block *sb)
if (sbi) {
sb->s_fs_info = NULL;

- if (sbi->spool)
- hugepage_put_subpool(sbi->spool);
-
+ hugepage_put_subpool(sbi->spool);
kfree(sbi);
}
}
@@ -1423,7 +1421,7 @@ hugetlbfs_fill_super(struct super_block *sb, struct fs_context *fc)
goto out_free;
return 0;
out_free:
- kfree(sbinfo->spool);
+ hugepage_put_subpool(sbinfo->spool);
kfree(sbinfo);
return -ENOMEM;
}
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index b759748468734..90ec015a11181 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -177,6 +177,9 @@ void hugepage_put_subpool(struct hugepage_subpool *spool)
{
unsigned long flags;

+ if (!spool)
+ return;
+
spin_lock_irqsave(&spool->lock, flags);
BUG_ON(!spool->count);
spool->count--;

--
2.55.0.229.g6434b31f56-goog