| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/21 18:08 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"HUGETLBFS",
"MEMCG",
"CGROUP_HUGETLB"
],
"FocusSymbols": [
"alloc_hugetlb_folio",
"hugetlb_reserve_pages",
"mem_cgroup_hugetlb_try_charge",
"mem_cgroup_hugetlb_commit_charge",
"mem_cgroup_hugetlb_cancel_charge",
"hugepage_subpool_get_pages",
"hugepage_subpool_put_pages",
"hugepage_new_subpool",
"hugepage_put_subpool"
],
"Reasoning": "The patch series refactors the hugetlb subpool logic into a separate file and modifies the hugetlb allocation path to use a new try/commit/cancel charging model for memory cgroups. It also modifies the hugetlb_reserve_pages error path. These are functional changes to core memory management and cgroup logic that are reachable from userspace via hugetlbfs and cgroupfs, making them highly relevant for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/21 18:08 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 700852cd9a023a5105e2341d247b864002ccc6fc\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Jul 21 18:08:15 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/cgroup_v2_allocation_failure.c b/cgroup_v2_allocation_failure.c\nnew file mode 100644\nindex 00000000000000..938cbf02ae6f7a\n--- /dev/null\n+++ b/cgroup_v2_allocation_failure.c\n@@ -0,0 +1,160 @@\n+#define _GNU_SOURCE\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/types.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003cerrno.h\u003e\n+\n+#define CGROUP_PATH \"/sys/fs/cgroup\"\n+#define TEST_CGROUP \"test_reproducer\"\n+#define TEST_CGROUP_PATH CGROUP_PATH \"/\" TEST_CGROUP\n+\n+void write_file(const char *path, const char *val) {\n+ int fd = open(path, O_WRONLY);\n+ if (fd \u003c 0) {\n+ fprintf(stderr, \"Failed to open %s: %s\\n\", path, strerror(errno));\n+ exit(1);\n+ }\n+ if (write(fd, val, strlen(val)) \u003c 0) {\n+ fprintf(stderr, \"Failed to write %s to %s: %s\\n\", val, path, strerror(errno));\n+ close(fd);\n+ exit(1);\n+ }\n+ close(fd);\n+}\n+\n+int is_hugetlb_accounting_enabled() {\n+ FILE *fp = fopen(\"/proc/mounts\", \"r\");\n+ if (!fp) {\n+ perror(\"fopen /proc/mounts\");\n+ return -1;\n+ }\n+\n+ char line[1024];\n+ int enabled = 0;\n+ while (fgets(line, sizeof(line), fp)) {\n+ char spec[256], file[256], type[256], opts[512];\n+ if (sscanf(line, \"%255s %255s %255s %511s\", spec, file, type, opts) == 4) {\n+ if (strcmp(file, CGROUP_PATH) == 0 \u0026\u0026 strcmp(type, \"cgroup2\") == 0) {\n+ if (strstr(opts, \"memory_hugetlb_accounting\") != NULL) {\n+ enabled = 1;\n+ }\n+ break;\n+ }\n+ }\n+ }\n+ fclose(fp);\n+ return enabled;\n+}\n+\n+int enable_hugetlb_accounting() {\n+ printf(\"Attempting to remount cgroup2 with memory_hugetlb_accounting...\\n\");\n+ int ret = system(\"mount -o remount,memory_hugetlb_accounting \" CGROUP_PATH);\n+ if (ret != 0) {\n+ fprintf(stderr, \"Failed to remount: system() returned %d\\n\", ret);\n+ return -1;\n+ }\n+ return 0;\n+}\n+\n+int main() {\n+ struct stat st;\n+ if (stat(CGROUP_PATH, \u0026st) != 0 || !S_ISDIR(st.st_mode)) {\n+ fprintf(stderr, \"cgroup v2 not mounted at %s\\n\", CGROUP_PATH);\n+ return 1;\n+ }\n+\n+ int enabled = is_hugetlb_accounting_enabled();\n+ if (enabled \u003c 0) {\n+ return 1;\n+ }\n+ if (!enabled) {\n+ if (enable_hugetlb_accounting() != 0) {\n+ fprintf(stderr, \"Could not enable memory_hugetlb_accounting\\n\");\n+ return 1;\n+ }\n+ // Re-check\n+ enabled = is_hugetlb_accounting_enabled();\n+ if (enabled \u003c= 0) {\n+ fprintf(stderr, \"Failed to enable memory_hugetlb_accounting (re-check failed)\\n\");\n+ return 1;\n+ }\n+ printf(\"Successfully enabled memory_hugetlb_accounting\\n\");\n+ } else {\n+ printf(\"memory_hugetlb_accounting is already enabled\\n\");\n+ }\n+\n+ // Enable memory controller in subtree\n+ int fd = open(CGROUP_PATH \"/cgroup.subtree_control\", O_WRONLY);\n+ if (fd \u003e= 0) {\n+ if (write(fd, \"+memory\", 7) \u003c 0) {\n+ // Might fail if already enabled or not supported, ignore for now\n+ }\n+ close(fd);\n+ }\n+\n+ if (mkdir(TEST_CGROUP_PATH, 0755) != 0) {\n+ if (errno != EEXIST) {\n+ perror(\"mkdir test_reproducer\");\n+ return 1;\n+ }\n+ }\n+\n+ // Set memory limit to 1MB (less than 2MB hugepage)\n+ write_file(TEST_CGROUP_PATH \"/memory.max\", \"1M\");\n+\n+ pid_t pid = fork();\n+ if (pid \u003c 0) {\n+ perror(\"fork\");\n+ return 1;\n+ }\n+\n+ if (pid == 0) {\n+ // Child\n+ // Move to cgroup\n+ write_file(TEST_CGROUP_PATH \"/cgroup.procs\", \"0\");\n+\n+ printf(\"Child: Attempting to allocate and touch 2MB hugepage...\\n\");\n+ // Allocate 2MB hugepage\n+ size_t size = 2 * 1024 * 1024;\n+ void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,\n+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);\n+ if (addr == MAP_FAILED) {\n+ perror(\"Child: mmap MAP_HUGETLB\");\n+ exit(1);\n+ }\n+\n+ printf(\"Child: mmap succeeded at %p, touching it now (should trigger fault)...\\n\", addr);\n+ // This should trigger the fault and call alloc_hugetlb_folio -\u003e mem_cgroup_charge_hugetlb\n+ // which should fail and trigger the bug.\n+ *(volatile char *)addr = 1;\n+\n+ printf(\"Child: Successfully touched page (bug not triggered?).\\n\");\n+ munmap(addr, size);\n+ exit(0);\n+ }\n+\n+ // Parent\n+ int status;\n+ waitpid(pid, \u0026status, 0);\n+\n+ printf(\"Parent: Child exited. Cleaning up.\\n\");\n+ rmdir(TEST_CGROUP_PATH);\n+\n+ if (WIFSIGNALED(status)) {\n+ printf(\"Parent: Child killed by signal %d (%s)\\n\",\n+ WTERMSIG(status), strsignal(WTERMSIG(status)));\n+ if (WTERMSIG(status) == SIGBUS) {\n+ printf(\"Parent: Child got SIGBUS as expected (if kernel didn't crash).\\n\");\n+ }\n+ } else if (WIFEXITED(status)) {\n+ printf(\"Parent: Child exited with status %d\\n\", WEXITSTATUS(status));\n+ }\n+\n+ return 0;\n+}\ndiff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c\nindex 216e1a0dd0b237..8abe0574b1c7ea 100644\n--- a/fs/hugetlbfs/inode.c\n+++ b/fs/hugetlbfs/inode.c\n@@ -25,6 +25,7 @@\n #include \u003clinux/ctype.h\u003e\n #include \u003clinux/backing-dev.h\u003e\n #include \u003clinux/hugetlb.h\u003e\n+#include \"../../mm/hugetlb_subpool.h\"\n #include \u003clinux/folio_batch.h\u003e\n #include \u003clinux/fs_parser.h\u003e\n #include \u003clinux/mman.h\u003e\n@@ -1059,7 +1060,6 @@ static int hugetlbfs_show_options(struct seq_file *m, struct dentry *root)\n \tstruct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(root-\u003ed_sb);\n \tstruct hugepage_subpool *spool = sbinfo-\u003espool;\n \tunsigned long hpage_size = huge_page_size(sbinfo-\u003ehstate);\n-\tunsigned hpage_shift = huge_page_shift(sbinfo-\u003ehstate);\n \tchar mod;\n \n \tif (!uid_eq(sbinfo-\u003euid, GLOBAL_ROOT_UID))\n@@ -1081,12 +1081,13 @@ static int hugetlbfs_show_options(struct seq_file *m, struct dentry *root)\n \t}\n \tseq_printf(m, \",pagesize=%lu%c\", hpage_size, mod);\n \tif (spool) {\n-\t\tif (spool-\u003emax_hpages != -1)\n-\t\t\tseq_printf(m, \",size=%llu\",\n-\t\t\t\t (unsigned long long)spool-\u003emax_hpages \u003c\u003c hpage_shift);\n-\t\tif (spool-\u003emin_hpages != -1)\n-\t\t\tseq_printf(m, \",min_size=%llu\",\n-\t\t\t\t (unsigned long long)spool-\u003emin_hpages \u003c\u003c hpage_shift);\n+\t\tunsigned long long max_size = hugepage_subpool_max_size(spool);\n+\t\tunsigned long long min_size = hugepage_subpool_min_size(spool);\n+\n+\t\tif (max_size != -1ULL)\n+\t\t\tseq_printf(m, \",size=%llu\", max_size);\n+\t\tif (min_size != -1ULL)\n+\t\t\tseq_printf(m, \",min_size=%llu\", min_size);\n \t}\n \treturn 0;\n }\n@@ -1105,14 +1106,8 @@ static int hugetlbfs_statfs(struct dentry *dentry, struct kstatfs *buf)\n \t\t/* If no limits set, just report 0 or -1 for max/free/used\n \t\t * blocks, like simple_statfs() */\n \t\tif (sbinfo-\u003espool) {\n-\t\t\tlong free_pages;\n-\n-\t\t\tspin_lock_irq(\u0026sbinfo-\u003espool-\u003elock);\n-\t\t\tbuf-\u003ef_blocks = sbinfo-\u003espool-\u003emax_hpages;\n-\t\t\tfree_pages = sbinfo-\u003espool-\u003emax_hpages\n-\t\t\t\t- sbinfo-\u003espool-\u003eused_hpages;\n-\t\t\tbuf-\u003ef_bavail = buf-\u003ef_bfree = free_pages;\n-\t\t\tspin_unlock_irq(\u0026sbinfo-\u003espool-\u003elock);\n+\t\t\tbuf-\u003ef_blocks = hugepage_subpool_max_hpages(sbinfo-\u003espool);\n+\t\t\tbuf-\u003ef_bavail = buf-\u003ef_bfree = hugepage_subpool_free_hpages(sbinfo-\u003espool);\n \t\t\tbuf-\u003ef_files = sbinfo-\u003emax_inodes;\n \t\t\tbuf-\u003ef_ffree = sbinfo-\u003efree_inodes;\n \t\t}\ndiff --git a/hugetlb_reserve_pages_out_put_pages.c b/hugetlb_reserve_pages_out_put_pages.c\nnew file mode 100644\nindex 00000000000000..9e63fc8997d57d\n--- /dev/null\n+++ b/hugetlb_reserve_pages_out_put_pages.c\n@@ -0,0 +1,49 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#define _GNU_SOURCE\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cerr.h\u003e\n+#include \u003cerrno.h\u003e\n+\n+int main(int argc, char **argv)\n+{\n+\tconst char *file_path;\n+\tsize_t size;\n+\tint fd;\n+\tvoid *addr;\n+\n+\tif (argc \u003c 3) {\n+\t\tfprintf(stderr, \"Usage: %s \u003chugetlbfs_file\u003e \u003csize_in_bytes\u003e\\n\",\n+\t\t\targv[0]);\n+\t\treturn 1;\n+\t}\n+\n+\tfile_path = argv[1];\n+\tsize = strtoull(argv[2], NULL, 0);\n+\n+\tfd = open(file_path, O_CREAT | O_RDWR, 0666);\n+\tif (fd \u003c 0)\n+\t\terr(1, \"open\");\n+\n+\tprintf(\"Attempting to mmap %zu bytes shared on %s...\\n\", size,\n+\t file_path);\n+\taddr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);\n+\tif (addr == MAP_FAILED) {\n+\t\tif (errno == ENOMEM) {\n+\t\t\tprintf(\"mmap failed with ENOMEM as expected.\\n\");\n+\t\t\tclose(fd);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tperror(\"mmap failed with unexpected error\");\n+\t\tclose(fd);\n+\t\treturn 1;\n+\t}\n+\n+\tprintf(\"ERROR: mmap SUCCEEDED unexpectedly at %p\\n\", addr);\n+\tmunmap(addr, size);\n+\tclose(fd);\n+\treturn 1;\n+}\ndiff --git a/hugetlb_reserve_pages_out_put_pages.sh b/hugetlb_reserve_pages_out_put_pages.sh\nnew file mode 100755\nindex 00000000000000..030e1915539b4e\n--- /dev/null\n+++ b/hugetlb_reserve_pages_out_put_pages.sh\n@@ -0,0 +1,153 @@\n+#!/bin/bash\n+# SPDX-License-Identifier: GPL-2.0\n+set -e\n+\n+if [ \"$EUID\" -ne 0 ]; then\n+ echo \"Please run as root\"\n+ exit 1\n+fi\n+\n+SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" \u0026\u0026 pwd)\"\n+cd \"$SCRIPT_DIR\"\n+\n+# Detect default hugepage size to support both 2MB and 1GB pages robustly\n+hpz=$(grep -i hugepagesize /proc/meminfo | awk '{print $2}')\n+kb=$hpz\n+mb=$((kb / 1024))\n+hpage_size_bytes=$((kb * 1024))\n+\n+hpage_dir=\"hugepages-${kb}kB\"\n+SYSFS_PATH=\"/sys/kernel/mm/hugepages/$hpage_dir\"\n+\n+MNT_PATH=\"/tmp/mnt_hugetlb_repro\"\n+FILE_PATH=\"$MNT_PATH/test_file\"\n+\n+# Save original values for safe restoration\n+orig_nr=$(cat \"$SYSFS_PATH/nr_hugepages\")\n+orig_overcommit=$(cat \"$SYSFS_PATH/nr_overcommit_hugepages\")\n+\n+cleanup() {\n+ echo \"Cleaning up...\"\n+ rm -f \"$FILE_PATH\"\n+ umount \"$MNT_PATH\" 2\u003e/dev/null\n+ rmdir \"$MNT_PATH\" 2\u003e/dev/null\n+ echo \"$orig_nr\" \u003e \"$SYSFS_PATH/nr_hugepages\"\n+ echo \"$orig_overcommit\" \u003e \"$SYSFS_PATH/nr_overcommit_hugepages\"\n+ echo \"Cleanup done.\"\n+}\n+trap cleanup EXIT\n+\n+# Verify reproducer binary exists\n+if [ ! -x ./hugetlb_reserve_pages_out_put_pages ]; then\n+ echo \"reproducer binary './hugetlb_reserve_pages_out_put_pages' not found or not executable.\"\n+ echo \"Please compile it first: gcc -static -o hugetlb_reserve_pages_out_put_pages hugetlb_reserve_pages_out_put_pages.c\"\n+ exit 1\n+fi\n+\n+# 1. Set global pool such that only the mount-time reservation can succeed\n+echo 1 \u003e \"$SYSFS_PATH/nr_hugepages\"\n+echo 0 \u003e \"$SYSFS_PATH/nr_overcommit_hugepages\"\n+\n+initial_resv=$(cat \"$SYSFS_PATH/resv_hugepages\")\n+echo \"Initial resv_hugepages (before mount): $initial_resv\"\n+\n+# 2. Mount with min_size = 1 page, max size = 2 pages\n+min_size_str=\"${mb}M\"\n+max_size_str=\"$((mb * 2))M\"\n+mmap_size_bytes=$((hpage_size_bytes * 2))\n+\n+mkdir -p \"$MNT_PATH\"\n+echo \"Mounting hugetlbfs with pagesize=${mb}M, min_size=$min_size_str, size=$max_size_str...\"\n+if ! mount -t hugetlbfs -o \"pagesize=${mb}M,min_size=$min_size_str,size=$max_size_str\" none \"$MNT_PATH\"; then\n+ echo \"Failed to mount hugetlbfs\"\n+ exit 1\n+fi\n+\n+resv_after_mount=$(cat \"$SYSFS_PATH/resv_hugepages\")\n+echo \"resv_hugepages after mount: $resv_after_mount\"\n+expected_after_mount=$((initial_resv + 1))\n+if [ \"$resv_after_mount\" != \"$expected_after_mount\" ]; then\n+ echo \"ERROR: resv_hugepages is not $expected_after_mount after mount (actual: $resv_after_mount)!\"\n+ exit 1\n+fi\n+\n+# Check mount stats after mount\n+expected_bsize=$hpage_size_bytes\n+bsize_S=$(stat -f -c \"%S\" \"$MNT_PATH\")\n+bsize_s=$(stat -f -c \"%s\" \"$MNT_PATH\")\n+echo \"Mount block size after mount: $bsize_S / $bsize_s (expected: $expected_bsize)\"\n+if [ \"$bsize_S\" != \"$expected_bsize\" ] \u0026\u0026 [ \"$bsize_s\" != \"$expected_bsize\" ]; then\n+ echo \"ERROR: Unexpected mount block size after mount (actual S:$bsize_S s:$bsize_s, expected: $expected_bsize)\"\n+ exit 1\n+fi\n+\n+actual_stats_mount=$(stat -f -c \"%b %f %a\" \"$MNT_PATH\")\n+expected_stats_mount=\"2 2 2\"\n+echo \"Mount stats after mount (total free avail): $actual_stats_mount (expected: $expected_stats_mount)\"\n+if [ \"$actual_stats_mount\" != \"$expected_stats_mount\" ]; then\n+ echo \"ERROR: Unexpected mount stats after mount: $actual_stats_mount (expected: $expected_stats_mount)\"\n+ exit 1\n+fi\n+\n+# 3. Run the reproducer to trigger the out_put_pages failure path\n+echo \"Running reproducer (expecting mmap failure with ENOMEM)...\"\n+if ./hugetlb_reserve_pages_out_put_pages \"$FILE_PATH\" \"$mmap_size_bytes\"; then\n+ echo \"Reproducer finished successfully.\"\n+ resv_after_mmap=$(cat \"$SYSFS_PATH/resv_hugepages\")\n+ echo \"resv_hugepages after failed mmap: $resv_after_mmap\"\n+ expected_after_mmap=$expected_after_mount\n+ if [ \"$resv_after_mmap\" = \"$expected_after_mmap\" ]; then\n+ echo \"RESULT: out_put_pages EXERCISED (resv_hugepages preserved at $expected_after_mmap as expected)\"\n+\n+ # Check mount stats\n+ expected_bsize=$hpage_size_bytes\n+ bsize_S=$(stat -f -c \"%S\" \"$MNT_PATH\")\n+ bsize_s=$(stat -f -c \"%s\" \"$MNT_PATH\")\n+ echo \"Mount block size: $bsize_S / $bsize_s (expected: $expected_bsize)\"\n+ if [ \"$bsize_S\" != \"$expected_bsize\" ] \u0026\u0026 [ \"$bsize_s\" != \"$expected_bsize\" ]; then\n+ echo \"ERROR: Unexpected mount block size (actual S:$bsize_S s:$bsize_s, expected: $expected_bsize)\"\n+ exit 1\n+ fi\n+\n+ actual_stats=$(stat -f -c \"%b %f %a\" \"$MNT_PATH\")\n+ expected_stats=\"2 2 2\"\n+ echo \"Mount stats (total free avail): $actual_stats (expected: $expected_stats)\"\n+ if [ \"$actual_stats\" != \"$expected_stats\" ]; then\n+ echo \"RESULT: Unexpected mount stats after failed mmap (FAIL)\"\n+ exit 1\n+ else\n+ echo \"RESULT: Mount stats restored to $expected_stats as expected (PASS)\"\n+ fi\n+ else\n+ echo \"RESULT: Unexpected resv_hugepages value: $resv_after_mmap (expected: $expected_after_mmap)\"\n+ exit 1\n+ fi\n+else\n+ echo \"FAIL: Reproducer returned non-zero (mmap didn't fail with ENOMEM)\"\n+ exit 1\n+fi\n+\n+# 4. Disable trap and do manual cleanup to check for final unmount underflow\n+trap - EXIT\n+\n+echo \"Unmounting...\"\n+umount \"$MNT_PATH\"\n+rmdir \"$MNT_PATH\"\n+\n+final_resv=$(cat \"$SYSFS_PATH/resv_hugepages\")\n+echo \"Final resv_hugepages (after unmount): $final_resv\"\n+\n+# Restore original values\n+echo \"Restoring original hugepage settings...\"\n+echo \"$orig_nr\" \u003e \"$SYSFS_PATH/nr_hugepages\"\n+echo \"$orig_overcommit\" \u003e \"$SYSFS_PATH/nr_overcommit_hugepages\"\n+\n+if [ \"$final_resv\" = \"$initial_resv\" ]; then\n+ echo \"RESULT: State restored to $initial_resv (or cleaned up if fixed)\"\n+ echo \"ALL DONE.\"\n+ exit 0\n+else\n+ echo \"RESULT: Underflow/Leak/Incorrect state detected! (final_resv = $final_resv, expected = $initial_resv)\"\n+ echo \"ALL DONE.\"\n+ exit 1\n+fi\ndiff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h\nindex 2abaf99321e90d..074a45903973eb 100644\n--- a/include/linux/hugetlb.h\n+++ b/include/linux/hugetlb.h\n@@ -18,6 +18,7 @@\n \n struct mmu_gather;\n struct node;\n+struct hstate;\n \n void free_huge_folio(struct folio *folio);\n \n@@ -34,17 +35,7 @@ void free_huge_folio(struct folio *folio);\n */\n #define __NR_USED_SUBPAGE 3\n \n-struct hugepage_subpool {\n-\tspinlock_t lock;\n-\tlong count;\n-\tlong max_hpages;\t/* Maximum huge pages or -1 if no maximum. */\n-\tlong used_hpages;\t/* Used count against maximum, includes */\n-\t\t\t\t/* both allocated and reserved pages. */\n-\tstruct hstate *hstate;\n-\tlong min_hpages;\t/* Minimum huge pages or -1 if no minimum. */\n-\tlong rsv_hpages;\t/* Pages reserved against global pool to */\n-\t\t\t\t/* satisfy minimum size. */\n-};\n+struct hugepage_subpool;\n \n struct resv_map {\n \tstruct kref refs;\n@@ -114,9 +105,7 @@ extern int hugetlb_max_hstate __read_mostly;\n #define for_each_hstate(h) \\\n \tfor ((h) = hstates; (h) \u003c \u0026hstates[hugetlb_max_hstate]; (h)++)\n \n-struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,\n-\t\t\t\t\t\tlong min_hpages);\n-void hugepage_put_subpool(struct hugepage_subpool *spool);\n+int hugetlb_acct_memory(struct hstate *h, long delta);\n \n void hugetlb_dup_vma_private(struct vm_area_struct *vma);\n void clear_vma_resv_huge_pages(struct vm_area_struct *vma);\ndiff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h\nindex e1f46a0016fcfd..23fbbbc8e11f20 100644\n--- a/include/linux/memcontrol.h\n+++ b/include/linux/memcontrol.h\n@@ -641,7 +641,15 @@ static inline int mem_cgroup_charge(struct folio *folio, struct mm_struct *mm,\n \treturn __mem_cgroup_charge(folio, mm, gfp);\n }\n \n-int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp);\n+int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp,\n+\t\t\t\t struct mem_cgroup **memcg_p,\n+\t\t\t\t struct obj_cgroup **objcg_p);\n+void mem_cgroup_hugetlb_commit_charge(struct folio *folio,\n+\t\t\t\t struct mem_cgroup *memcg,\n+\t\t\t\t struct obj_cgroup *objcg);\n+void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages,\n+\t\t\t\t struct mem_cgroup *memcg,\n+\t\t\t\t struct obj_cgroup *objcg);\n \n int mem_cgroup_swapin_charge_folio(struct folio *folio, unsigned short id,\n \t\t\t\t struct mm_struct *mm, gfp_t gfp);\n@@ -1128,9 +1136,25 @@ static inline int mem_cgroup_charge(struct folio *folio,\n \treturn 0;\n }\n \n-static inline int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp)\n+static inline int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp,\n+\t\t\t\t\t\tstruct mem_cgroup **memcg_p,\n+\t\t\t\t\t\tstruct obj_cgroup **objcg_p)\n+{\n+\t*memcg_p = NULL;\n+\t*objcg_p = NULL;\n+\treturn 0;\n+}\n+\n+static inline void mem_cgroup_hugetlb_commit_charge(struct folio *folio,\n+\t\t\t\t\t\t struct mem_cgroup *memcg,\n+\t\t\t\t\t\t struct obj_cgroup *objcg)\n+{\n+}\n+\n+static inline void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages,\n+\t\t\t\t\t\t struct mem_cgroup *memcg,\n+\t\t\t\t\t\t struct obj_cgroup *objcg)\n {\n- return 0;\n }\n \n static inline int mem_cgroup_swapin_charge_folio(struct folio *folio,\ndiff --git a/mm/Makefile b/mm/Makefile\nindex eff9f9e7e061c1..3965c959e50992 100644\n--- a/mm/Makefile\n+++ b/mm/Makefile\n@@ -78,7 +78,7 @@ endif\n obj-$(CONFIG_SWAP)\t+= page_io.o swap_state.o swapfile.o\n obj-$(CONFIG_ZSWAP)\t+= zswap.o\n obj-$(CONFIG_HAS_DMA)\t+= dmapool.o\n-obj-$(CONFIG_HUGETLBFS)\t+= hugetlb.o hugetlb_sysfs.o hugetlb_sysctl.o\n+obj-$(CONFIG_HUGETLBFS)\t+= hugetlb.o hugetlb_subpool.o hugetlb_sysfs.o hugetlb_sysctl.o\n ifdef CONFIG_CMA\n obj-$(CONFIG_HUGETLBFS)\t+= hugetlb_cma.o\n endif\ndiff --git a/mm/hugetlb.c b/mm/hugetlb.c\nindex 571212b80835ec..e87f26a3e1f3e4 100644\n--- a/mm/hugetlb.c\n+++ b/mm/hugetlb.c\n@@ -38,6 +38,8 @@\n #include \u003clinux/mm_inline.h\u003e\n #include \u003clinux/padata.h\u003e\n #include \u003clinux/pgalloc.h\u003e\n+#include \u003clinux/memcontrol.h\u003e\n+#include \"hugetlb_subpool.h\"\n \n #include \u003casm/page.h\u003e\n #include \u003casm/tlb.h\u003e\n@@ -113,8 +115,7 @@ __cacheline_aligned_in_smp DEFINE_SPINLOCK(hugetlb_lock);\n static int num_fault_mutexes __ro_after_init;\n struct mutex *hugetlb_fault_mutex_table __ro_after_init;\n \n-/* Forward declaration */\n-static int hugetlb_acct_memory(struct hstate *h, long delta);\n+/* Forward declarations */\n static void hugetlb_vma_lock_free(struct vm_area_struct *vma);\n static void hugetlb_vma_lock_alloc(struct vm_area_struct *vma);\n static void __hugetlb_vma_unlock_write_free(struct vm_area_struct *vma);\n@@ -125,156 +126,6 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma,\n \t\tunsigned long start, unsigned long end, bool take_locks);\n static struct resv_map *vma_resv_map(struct vm_area_struct *vma);\n \n-static inline bool subpool_is_free(struct hugepage_subpool *spool)\n-{\n-\tif (spool-\u003ecount)\n-\t\treturn false;\n-\tif (spool-\u003emax_hpages != -1)\n-\t\treturn spool-\u003eused_hpages == 0;\n-\tif (spool-\u003emin_hpages != -1)\n-\t\treturn spool-\u003ersv_hpages == spool-\u003emin_hpages;\n-\n-\treturn true;\n-}\n-\n-static inline void unlock_or_release_subpool(struct hugepage_subpool *spool,\n-\t\t\t\t\t\tunsigned long irq_flags)\n-{\n-\tspin_unlock_irqrestore(\u0026spool-\u003elock, irq_flags);\n-\n-\t/* If no pages are used, and no other handles to the subpool\n-\t * remain, give up any reservations based on minimum size and\n-\t * free the subpool */\n-\tif (subpool_is_free(spool)) {\n-\t\tif (spool-\u003emin_hpages != -1)\n-\t\t\thugetlb_acct_memory(spool-\u003ehstate,\n-\t\t\t\t\t\t-spool-\u003emin_hpages);\n-\t\tkfree(spool);\n-\t}\n-}\n-\n-struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,\n-\t\t\t\t\t\tlong min_hpages)\n-{\n-\tstruct hugepage_subpool *spool;\n-\n-\tspool = kzalloc_obj(*spool);\n-\tif (!spool)\n-\t\treturn NULL;\n-\n-\tspin_lock_init(\u0026spool-\u003elock);\n-\tspool-\u003ecount = 1;\n-\tspool-\u003emax_hpages = max_hpages;\n-\tspool-\u003ehstate = h;\n-\tspool-\u003emin_hpages = min_hpages;\n-\n-\tif (min_hpages != -1 \u0026\u0026 hugetlb_acct_memory(h, min_hpages)) {\n-\t\tkfree(spool);\n-\t\treturn NULL;\n-\t}\n-\tspool-\u003ersv_hpages = min_hpages;\n-\n-\treturn spool;\n-}\n-\n-void hugepage_put_subpool(struct hugepage_subpool *spool)\n-{\n-\tunsigned long flags;\n-\n-\tspin_lock_irqsave(\u0026spool-\u003elock, flags);\n-\tBUG_ON(!spool-\u003ecount);\n-\tspool-\u003ecount--;\n-\tunlock_or_release_subpool(spool, flags);\n-}\n-\n-/*\n- * Subpool accounting for allocating and reserving pages.\n- * Return -ENOMEM if there are not enough resources to satisfy the\n- * request. Otherwise, return the number of pages by which the\n- * global pools must be adjusted (upward). The returned value may\n- * only be different than the passed value (delta) in the case where\n- * a subpool minimum size must be maintained.\n- */\n-static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,\n-\t\t\t\t long delta)\n-{\n-\tlong ret = delta;\n-\n-\tif (!spool)\n-\t\treturn ret;\n-\n-\tspin_lock_irq(\u0026spool-\u003elock);\n-\n-\tif (spool-\u003emax_hpages != -1) {\t\t/* maximum size accounting */\n-\t\tif ((spool-\u003eused_hpages + delta) \u003c= spool-\u003emax_hpages)\n-\t\t\tspool-\u003eused_hpages += delta;\n-\t\telse {\n-\t\t\tret = -ENOMEM;\n-\t\t\tgoto unlock_ret;\n-\t\t}\n-\t}\n-\n-\t/* minimum size accounting */\n-\tif (spool-\u003emin_hpages != -1 \u0026\u0026 spool-\u003ersv_hpages) {\n-\t\tif (delta \u003e spool-\u003ersv_hpages) {\n-\t\t\t/*\n-\t\t\t * Asking for more reserves than those already taken on\n-\t\t\t * behalf of subpool. Return difference.\n-\t\t\t */\n-\t\t\tret = delta - spool-\u003ersv_hpages;\n-\t\t\tspool-\u003ersv_hpages = 0;\n-\t\t} else {\n-\t\t\tret = 0;\t/* reserves already accounted for */\n-\t\t\tspool-\u003ersv_hpages -= delta;\n-\t\t}\n-\t}\n-\n-unlock_ret:\n-\tspin_unlock_irq(\u0026spool-\u003elock);\n-\treturn ret;\n-}\n-\n-/*\n- * Subpool accounting for freeing and unreserving pages.\n- * Return the number of global page reservations that must be dropped.\n- * The return value may only be different than the passed value (delta)\n- * in the case where a subpool minimum size must be maintained.\n- */\n-static long hugepage_subpool_put_pages(struct hugepage_subpool *spool,\n-\t\t\t\t long delta)\n-{\n-\tlong ret = delta;\n-\tunsigned long flags;\n-\n-\tif (!spool)\n-\t\treturn delta;\n-\n-\tspin_lock_irqsave(\u0026spool-\u003elock, flags);\n-\n-\tif (spool-\u003emax_hpages != -1)\t\t/* maximum size accounting */\n-\t\tspool-\u003eused_hpages -= delta;\n-\n-\t /* minimum size accounting */\n-\tif (spool-\u003emin_hpages != -1 \u0026\u0026 spool-\u003eused_hpages \u003c spool-\u003emin_hpages) {\n-\t\tif (spool-\u003ersv_hpages + delta \u003c= spool-\u003emin_hpages)\n-\t\t\tret = 0;\n-\t\telse\n-\t\t\tret = spool-\u003ersv_hpages + delta - spool-\u003emin_hpages;\n-\n-\t\tspool-\u003ersv_hpages += delta;\n-\t\tif (spool-\u003ersv_hpages \u003e spool-\u003emin_hpages)\n-\t\t\tspool-\u003ersv_hpages = spool-\u003emin_hpages;\n-\t}\n-\n-\t/*\n-\t * If hugetlbfs_put_super couldn't free spool due to an outstanding\n-\t * quota reference, free it now.\n-\t */\n-\tunlock_or_release_subpool(spool, flags);\n-\n-\treturn ret;\n-}\n-\n static inline struct hugepage_subpool *subpool_vma(struct vm_area_struct *vma)\n {\n \treturn subpool_inode(file_inode(vma-\u003evm_file));\n@@ -2858,11 +2709,13 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n \tstruct hugepage_subpool *spool = subpool_vma(vma);\n \tstruct hstate *h = hstate_vma(vma);\n \tstruct folio *folio;\n-\tlong retval, gbl_chg, gbl_reserve;\n+\tlong retval, gbl_chg;\n \tmap_chg_state map_chg;\n \tint ret, idx;\n \tstruct hugetlb_cgroup *h_cg = NULL;\n \tstruct hugetlb_cgroup *h_cg_rsvd = NULL;\n+\tstruct mem_cgroup *mem_cg = NULL;\n+\tstruct obj_cgroup *obj_cg = NULL;\n \tgfp_t gfp = htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL;\n \n \tidx = hstate_index(h);\n@@ -2922,6 +2775,11 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n \tif (ret)\n \t\tgoto out_uncharge_cgroup_reservation;\n \n+\tret = mem_cgroup_hugetlb_try_charge(pages_per_huge_page(h), gfp,\n+\t\t\t\t\t \u0026mem_cg, \u0026obj_cg);\n+\tif (ret)\n+\t\tgoto out_uncharge_cgroup;\n+\n \tspin_lock_irq(\u0026hugetlb_lock);\n \t/*\n \t * glb_chg is passed to indicate whether or not a page must be taken\n@@ -2933,7 +2791,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n \t\tspin_unlock_irq(\u0026hugetlb_lock);\n \t\tfolio = alloc_buddy_hugetlb_folio_with_mpol(h, vma, addr);\n \t\tif (!folio)\n-\t\t\tgoto out_uncharge_cgroup;\n+\t\t\tgoto out_uncharge_cgroup_memcg;\n \t\tspin_lock_irq(\u0026hugetlb_lock);\n \t\tlist_add(\u0026folio-\u003elru, \u0026h-\u003ehugepage_activelist);\n \t\tfolio_ref_unfreeze(folio, 1);\n@@ -2960,6 +2818,9 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n \n \tspin_unlock_irq(\u0026hugetlb_lock);\n \n+\tmem_cgroup_hugetlb_commit_charge(folio, mem_cg, obj_cg);\n+\tlruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h));\n+\n \thugetlb_set_folio_subpool(folio, spool);\n \n \tif (map_chg != MAP_CHG_ENFORCED) {\n@@ -2987,21 +2848,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n \t\t}\n \t}\n \n-\tret = mem_cgroup_charge_hugetlb(folio, gfp);\n-\t/*\n-\t * Unconditionally increment NR_HUGETLB here. If it turns out that\n-\t * mem_cgroup_charge_hugetlb failed, then immediately free the page and\n-\t * decrement NR_HUGETLB.\n-\t */\n-\tlruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h));\n-\n-\tif (ret == -ENOMEM) {\n-\t\tfree_huge_folio(folio);\n-\t\treturn ERR_PTR(-ENOMEM);\n-\t}\n-\n \treturn folio;\n \n+out_uncharge_cgroup_memcg:\n+\tmem_cgroup_hugetlb_cancel_charge(pages_per_huge_page(h), mem_cg, obj_cg);\n out_uncharge_cgroup:\n \thugetlb_cgroup_uncharge_cgroup(idx, pages_per_huge_page(h), h_cg);\n out_uncharge_cgroup_reservation:\n@@ -3009,19 +2859,28 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n \t\thugetlb_cgroup_uncharge_cgroup_rsvd(idx, pages_per_huge_page(h),\n \t\t\t\t\t\t h_cg_rsvd);\n out_subpool_put:\n-\t/*\n-\t * put page to subpool iff the quota of subpool's rsv_hpages is used\n-\t * during hugepage_subpool_get_pages.\n-\t */\n-\tif (map_chg \u0026\u0026 !gbl_chg) {\n-\t\tgbl_reserve = hugepage_subpool_put_pages(spool, 1);\n-\t\thugetlb_acct_memory(h, -gbl_reserve);\n+\tif (map_chg) {\n+\t\tlong gbl_reserve = hugepage_subpool_put_pages(spool, 1);\n+\n+\t\tif (!gbl_chg)\n+\t\t\thugetlb_acct_memory(h, -gbl_reserve);\n \t}\n \n \n out_end_reservation:\n \tif (map_chg != MAP_CHG_ENFORCED)\n \t\tvma_end_reservation(h, vma, addr);\n+\n+\t/*\n+\t * Return -ENOSPC when this function fails to allocate or\n+\t * charge a huge page. If a standard (PAGE_SIZE) page\n+\t * allocation fails, the OOM killer is given a chance to run,\n+\t * which may resolve the failure on retry. However, for\n+\t * HugeTLB allocations, the OOM killer is not triggered.\n+\t * Returning -ENOMEM (or anything resulting in VM_FAULT_OOM)\n+\t * would leak to the #PF handler, causing it to loop\n+\t * indefinitely retrying the fault.\n+\t */\n \treturn ERR_PTR(-ENOSPC);\n }\n \n@@ -4593,7 +4452,7 @@ unsigned long hugetlb_total_pages(void)\n \treturn nr_total_pages;\n }\n \n-static int hugetlb_acct_memory(struct hstate *h, long delta)\n+int hugetlb_acct_memory(struct hstate *h, long delta)\n {\n \tint ret = -ENOMEM;\n \n@@ -6542,7 +6401,7 @@ long hugetlb_reserve_pages(struct inode *inode,\n \t\tstruct vm_area_struct *vma,\n \t\tvma_flags_t vma_flags)\n {\n-\tlong chg = -1, add = -1, spool_resv, gbl_resv;\n+\tlong chg = -1, add = -1, gbl_resv;\n \tstruct hstate *h = hstate_inode(inode);\n \tstruct hugepage_subpool *spool = subpool_inode(inode);\n \tstruct resv_map *resv_map;\n@@ -6687,26 +6546,21 @@ long hugetlb_reserve_pages(struct inode *inode,\n \t}\n \treturn chg;\n \n-out_put_pages:\n-\tspool_resv = chg - gbl_reserve;\n-\tif (spool_resv) {\n-\t\t/* put sub pool's reservation back, chg - gbl_reserve */\n-\t\tgbl_resv = hugepage_subpool_put_pages(spool, spool_resv);\n-\t\t/*\n-\t\t * subpool's reserved pages can not be put back due to race,\n-\t\t * return to hstate.\n-\t\t */\n-\t\thugetlb_acct_memory(h, -gbl_resv);\n-\t}\n-\t/* Restore used_hpages for pages that failed global reservation */\n-\tif (gbl_reserve \u0026\u0026 spool) {\n-\t\tunsigned long flags;\n+ out_put_pages:\n+\t/*\n+\t * Return all that was requested from the subpool, let subpool\n+\t * tell us the new number of reservations that need to be\n+\t * returned to the global pool.\n+\t */\n+\tgbl_resv = hugepage_subpool_put_pages(spool, chg);\n+\t/*\n+\t * There may be a difference between the number of\n+\t * reservations to consume and the number to restore now if\n+\t * there are multiple threads interacting with the subpool -\n+\t * restore the difference.\n+\t */\n+\thugetlb_acct_memory(h, -(gbl_resv - gbl_reserve));\n \n-\t\tspin_lock_irqsave(\u0026spool-\u003elock, flags);\n-\t\tif (spool-\u003emax_hpages != -1)\n-\t\t\tspool-\u003eused_hpages -= gbl_reserve;\n-\t\tunlock_or_release_subpool(spool, flags);\n-\t}\n out_uncharge_cgroup:\n \thugetlb_cgroup_uncharge_cgroup_rsvd(hstate_index(h),\n \t\t\t\t\t chg * pages_per_huge_page(h), h_cg);\ndiff --git a/mm/hugetlb_subpool.c b/mm/hugetlb_subpool.c\nnew file mode 100644\nindex 00000000000000..eb5472bdf21924\n--- /dev/null\n+++ b/mm/hugetlb_subpool.c\n@@ -0,0 +1,223 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Subpool and reserve accounting for HugeTLB folios.\n+ * Extracted from mm/hugetlb.c\n+ */\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#ifdef __KERNEL__\n+#include \u003clinux/hugetlb.h\u003e\n+#endif\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/bug.h\u003e\n+#include \"hugetlb_subpool.h\"\n+\n+struct hugepage_subpool {\n+\tspinlock_t lock;\n+\tlong count;\n+\tlong max_hpages;\t/* Maximum huge pages or -1 if no maximum. */\n+\tlong used_hpages;\t/* Used page count, includes both */\n+\t\t\t\t/* allocated and reserved pages. */\n+\tstruct hstate *hstate;\n+\tlong min_hpages;\t/* Minimum huge pages or -1 if no minimum. */\n+\tlong rsv_hpages;\t/* Pages reserved against global pool to */\n+\t\t\t\t/* satisfy minimum size. */\n+};\n+\n+static inline bool subpool_is_free(struct hugepage_subpool *spool)\n+{\n+\tif (spool-\u003ecount)\n+\t\treturn false;\n+\n+\treturn spool-\u003eused_hpages == 0;\n+}\n+\n+static inline void unlock_or_release_subpool(struct hugepage_subpool *spool,\n+\t\t\t\t\t\tunsigned long irq_flags)\n+{\n+\tspin_unlock_irqrestore(\u0026spool-\u003elock, irq_flags);\n+\n+\t/*\n+\t * If no pages are used, and no other handles to the subpool\n+\t * remain, give up any reservations based on minimum size and\n+\t * free the subpool.\n+\t */\n+\tif (subpool_is_free(spool)) {\n+\t\tif (spool-\u003emin_hpages != -1)\n+\t\t\thugetlb_acct_memory(spool-\u003ehstate,\n+\t\t\t\t\t\t-spool-\u003emin_hpages);\n+\t\tkfree(spool);\n+\t}\n+}\n+\n+long hugepage_subpool_free_hpages(struct hugepage_subpool *spool)\n+{\n+\tlong free_pages;\n+\n+\tspin_lock_irq(\u0026spool-\u003elock);\n+\tif (spool-\u003emax_hpages == -1)\n+\t\tfree_pages = -1;\n+\telse\n+\t\tfree_pages = spool-\u003emax_hpages - spool-\u003eused_hpages;\n+\tspin_unlock_irq(\u0026spool-\u003elock);\n+\n+\treturn free_pages;\n+}\n+\n+static unsigned int hugepage_subpool_hpage_shift(struct hugepage_subpool *spool)\n+{\n+\treturn huge_page_shift(spool-\u003ehstate);\n+}\n+\n+unsigned long long hugepage_subpool_max_size(struct hugepage_subpool *spool)\n+{\n+\tif (spool-\u003emax_hpages == -1)\n+\t\treturn -1ULL;\n+\treturn (unsigned long long)spool-\u003emax_hpages \u003c\u003c hugepage_subpool_hpage_shift(spool);\n+}\n+\n+unsigned long long hugepage_subpool_min_size(struct hugepage_subpool *spool)\n+{\n+\tif (spool-\u003emin_hpages == -1)\n+\t\treturn -1ULL;\n+\treturn (unsigned long long)spool-\u003emin_hpages \u003c\u003c hugepage_subpool_hpage_shift(spool);\n+}\n+\n+long hugepage_subpool_max_hpages(struct hugepage_subpool *spool)\n+{\n+\tlong max_hpages;\n+\n+\tspin_lock_irq(\u0026spool-\u003elock);\n+\tmax_hpages = spool-\u003emax_hpages;\n+\tspin_unlock_irq(\u0026spool-\u003elock);\n+\n+\treturn max_hpages;\n+}\n+\n+struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,\n+\t\t\t\t\t\tlong min_hpages)\n+{\n+\tstruct hugepage_subpool *spool;\n+\n+\tspool = kzalloc_obj(*spool);\n+\tif (!spool)\n+\t\treturn NULL;\n+\n+\tspin_lock_init(\u0026spool-\u003elock);\n+\tspool-\u003ecount = 1;\n+\tspool-\u003emax_hpages = max_hpages;\n+\tspool-\u003ehstate = h;\n+\tspool-\u003emin_hpages = min_hpages;\n+\n+\tif (min_hpages != -1 \u0026\u0026 hugetlb_acct_memory(h, min_hpages)) {\n+\t\tkfree(spool);\n+\t\treturn NULL;\n+\t}\n+\tspool-\u003ersv_hpages = min_hpages;\n+\n+\treturn spool;\n+}\n+\n+void hugepage_put_subpool(struct hugepage_subpool *spool)\n+{\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026spool-\u003elock, flags);\n+\tBUG_ON(!spool-\u003ecount);\n+\tspool-\u003ecount--;\n+\tunlock_or_release_subpool(spool, flags);\n+}\n+\n+/*\n+ * Subpool accounting for allocating and reserving pages.\n+ * Return -ENOMEM if there are not enough resources to satisfy the\n+ * request. Otherwise, return the number of pages by which the\n+ * global pools must be adjusted (upward). The returned value may\n+ * only be different than the passed value (delta) in the case where\n+ * a subpool minimum size must be maintained.\n+ */\n+long hugepage_subpool_get_pages(struct hugepage_subpool *spool,\n+\t\t\t\t long delta)\n+{\n+\tlong ret = delta;\n+\n+\tif (!spool)\n+\t\treturn ret;\n+\n+\tspin_lock_irq(\u0026spool-\u003elock);\n+\n+\tif (spool-\u003emax_hpages != -1 \u0026\u0026\n+\t spool-\u003eused_hpages + delta \u003e spool-\u003emax_hpages) {\n+\t\tret = -ENOMEM;\n+\t\tgoto unlock_ret;\n+\t}\n+\n+\tspool-\u003eused_hpages += delta;\n+\n+\t/* minimum size accounting */\n+\tif (spool-\u003emin_hpages != -1 \u0026\u0026 spool-\u003ersv_hpages) {\n+\t\tif (delta \u003e spool-\u003ersv_hpages) {\n+\t\t\t/*\n+\t\t\t * Asking for more reserves than those already taken on\n+\t\t\t * behalf of subpool. Return difference.\n+\t\t\t */\n+\t\t\tret = delta - spool-\u003ersv_hpages;\n+\t\t\tspool-\u003ersv_hpages = 0;\n+\t\t} else {\n+\t\t\tret = 0;\t/* reserves already accounted for */\n+\t\t\tspool-\u003ersv_hpages -= delta;\n+\t\t}\n+\t}\n+\n+unlock_ret:\n+\tspin_unlock_irq(\u0026spool-\u003elock);\n+\treturn ret;\n+}\n+\n+/*\n+ * Subpool accounting for freeing and unreserving pages.\n+ * Return the number of global page reservations that must be dropped.\n+ * The return value may only be different than the passed value (delta)\n+ * in the case where a subpool minimum size must be maintained.\n+ */\n+long hugepage_subpool_put_pages(struct hugepage_subpool *spool,\n+\t\t\t\t long delta)\n+{\n+\tlong ret = delta;\n+\tunsigned long flags;\n+\n+\tif (!spool)\n+\t\treturn delta;\n+\n+\tspin_lock_irqsave(\u0026spool-\u003elock, flags);\n+\n+\tspool-\u003eused_hpages -= delta;\n+\n+\t /* minimum size accounting */\n+\tif (spool-\u003emin_hpages != -1 \u0026\u0026 spool-\u003eused_hpages \u003c spool-\u003emin_hpages) {\n+\t\t/*\n+\t\t * limit is the maximum number of reservations that\n+\t\t * can be restored to this subpool.\n+\t\t */\n+\t\tlong limit = spool-\u003emin_hpages - spool-\u003eused_hpages;\n+\n+\t\tif (spool-\u003ersv_hpages + delta \u003c= limit)\n+\t\t\tret = 0;\n+\t\telse\n+\t\t\tret = spool-\u003ersv_hpages + delta - limit;\n+\n+\t\tspool-\u003ersv_hpages += delta;\n+\t\tif (spool-\u003ersv_hpages \u003e limit)\n+\t\t\tspool-\u003ersv_hpages = limit;\n+\t}\n+\n+\t/*\n+\t * If hugetlbfs_put_super couldn't free spool due to an outstanding\n+\t * quota reference, free it now.\n+\t */\n+\tunlock_or_release_subpool(spool, flags);\n+\n+\treturn ret;\n+}\ndiff --git a/mm/hugetlb_subpool.h b/mm/hugetlb_subpool.h\nnew file mode 100644\nindex 00000000000000..41d22239f2c3ed\n--- /dev/null\n+++ b/mm/hugetlb_subpool.h\n@@ -0,0 +1,21 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef _MM_HUGETLB_SUBPOOL_H\n+#define _MM_HUGETLB_SUBPOOL_H\n+\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+struct hstate;\n+struct hugepage_subpool;\n+\n+struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,\n+\t\t\t\t\t\tlong min_hpages);\n+void hugepage_put_subpool(struct hugepage_subpool *spool);\n+long hugepage_subpool_get_pages(struct hugepage_subpool *spool, long delta);\n+long hugepage_subpool_put_pages(struct hugepage_subpool *spool, long delta);\n+long hugepage_subpool_free_hpages(struct hugepage_subpool *spool);\n+long hugepage_subpool_max_hpages(struct hugepage_subpool *spool);\n+unsigned long long hugepage_subpool_max_size(struct hugepage_subpool *spool);\n+unsigned long long hugepage_subpool_min_size(struct hugepage_subpool *spool);\n+\n+#endif /* _MM_HUGETLB_SUBPOOL_H */\ndiff --git a/mm/memcontrol.c b/mm/memcontrol.c\nindex 6dc4888a90f3fd..6764ff041c1965 100644\n--- a/mm/memcontrol.c\n+++ b/mm/memcontrol.c\n@@ -5147,39 +5147,119 @@ int __mem_cgroup_charge(struct folio *folio, struct mm_struct *mm, gfp_t gfp)\n }\n \n /**\n- * mem_cgroup_charge_hugetlb - charge the memcg for a hugetlb folio\n- * @folio: folio being charged\n+ * mem_cgroup_hugetlb_try_charge - Try to charge the memcg for a hugetlb folio\n+ * @nr_pages: number of base pages to charge\n * @gfp: reclaim mode\n+ * @memcg_p: Output pointer to the charged mem_cgroup (if successful and enabled)\n+ * @objcg_p: Output pointer to the charged obj_cgroup (if successful and enabled)\n *\n- * This function is called when allocating a huge page folio, after the page has\n- * already been obtained and charged to the appropriate hugetlb cgroup\n- * controller (if it is enabled).\n+ * Prepares and tries to reserve the memory counter for the folio from the current\n+ * task's memcg. If successful, both *memcg_p and *objcg_p are populated and their\n+ * references are pinned until a subsequent call to mem_cgroup_hugetlb_commit_charge\n+ * or mem_cgroup_hugetlb_cancel_charge.\n *\n * Returns ENOMEM if the memcg is already full.\n- * Returns 0 if either the charge was successful, or if we skip the charging.\n+ * Returns 0 if either the charge was successful, or if we skip charging.\n */\n-int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp)\n+int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp,\n+\t\t\t\t struct mem_cgroup **memcg_p,\n+\t\t\t\t struct obj_cgroup **objcg_p)\n {\n-\tstruct mem_cgroup *memcg = get_mem_cgroup_from_current();\n+\tstruct mem_cgroup *memcg;\n+\tstruct obj_cgroup *objcg;\n \tint ret = 0;\n \n-\t/*\n-\t * Even memcg does not account for hugetlb, we still want to update\n-\t * system-level stats via lruvec_stat_mod_folio. Return 0, and skip\n-\t * charging the memcg.\n-\t */\n+\t*memcg_p = NULL;\n+\t*objcg_p = NULL;\n+\n \tif (mem_cgroup_disabled() || !memcg_accounts_hugetlb() ||\n-\t\t!memcg || !cgroup_subsys_on_dfl(memory_cgrp_subsys))\n-\t\tgoto out;\n+\t !cgroup_subsys_on_dfl(memory_cgrp_subsys))\n+\t\treturn 0;\n \n-\tif (charge_memcg(folio, memcg, gfp))\n-\t\tret = -ENOMEM;\n+\tmemcg = get_mem_cgroup_from_current();\n+\tif (!memcg)\n+\t\treturn 0;\n \n-out:\n+\tobjcg = get_obj_cgroup_from_memcg(memcg);\n+\tif (!objcg)\n+\t\tgoto put_memcg;\n+\n+\tif (!obj_cgroup_is_root(objcg)) {\n+\t\tret = try_charge_memcg(memcg, gfp, nr_pages);\n+\t\tif (ret)\n+\t\t\tgoto put_objcg;\n+\t}\n+\n+\t*memcg_p = memcg;\n+\t*objcg_p = objcg;\n+\treturn 0;\n+\n+put_objcg:\n+\tobj_cgroup_put(objcg);\n+put_memcg:\n \tmem_cgroup_put(memcg);\n \treturn ret;\n }\n \n+/**\n+ * mem_cgroup_hugetlb_commit_charge - Commit the memcg charge for a hugetlb folio\n+ * @folio: folio being charged\n+ * @memcg: Target mem_cgroup obtained from mem_cgroup_hugetlb_try_charge\n+ * @objcg: Target obj_cgroup obtained from mem_cgroup_hugetlb_try_charge\n+ *\n+ * Finalizes the memory and statistics charging for the folio in the specified memcg.\n+ * Transfers the pinned objcg reference to the folio structure (for automatic\n+ * uncharging upon freeing via mem_cgroup_uncharge). Releases the try-commit reference\n+ * on memcg.\n+ */\n+void mem_cgroup_hugetlb_commit_charge(struct folio *folio,\n+\t\t\t\t struct mem_cgroup *memcg,\n+\t\t\t\t struct obj_cgroup *objcg)\n+{\n+\tif (!memcg || !objcg)\n+\t\treturn;\n+\n+\tcommit_charge(folio, objcg);\n+\tmemcg1_commit_charge(folio, memcg);\n+\n+\t/*\n+\t * Drop our try-commit-cancel protocol reference on memcg.\n+\t * The objcg reference is TRANSFERRED to the folio by commit_charge,\n+\t * so it will be put automatically by __mem_cgroup_uncharge() when\n+\t * the folio is freed.\n+\t */\n+\tmem_cgroup_put(memcg);\n+}\n+\n+/**\n+ * mem_cgroup_hugetlb_cancel_charge - Cancel and undo a hugetlb folio memcg charge\n+ * @nr_pages: number of base pages to uncharge\n+ * @memcg: Target mem_cgroup obtained from mem_cgroup_hugetlb_try_charge\n+ * @objcg: Target obj_cgroup obtained from mem_cgroup_hugetlb_try_charge\n+ *\n+ * Cancels and safely rolls back the prepared memory charge for the folio in the\n+ * specified memcg. Releases the try-commit pinned references on both memcg and objcg.\n+ */\n+void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages,\n+\t\t\t\t struct mem_cgroup *memcg,\n+\t\t\t\t struct obj_cgroup *objcg)\n+{\n+\tif (!memcg || !objcg)\n+\t\treturn;\n+\n+\tif (!obj_cgroup_is_root(objcg))\n+\t\trefill_stock(memcg, nr_pages);\n+\n+\t/*\n+\t * Drop our try-commit-cancel protocol references on both objcg\n+\t * and memcg, since this mapping attempt was aborted and the folio\n+\t * was never committed.\n+\t */\n+\tobj_cgroup_put(objcg);\n+\tmem_cgroup_put(memcg);\n+}\n+\n+\n /**\n * mem_cgroup_swapin_charge_folio - Charge a newly allocated folio for swapin.\n * @folio: the folio to charge\ndiff --git a/subpool_leak_max_size.sh b/subpool_leak_max_size.sh\nnew file mode 100755\nindex 00000000000000..bfafa1ba074eac\n--- /dev/null\n+++ b/subpool_leak_max_size.sh\n@@ -0,0 +1,71 @@\n+#!/bin/bash\n+\n+if [ \"$EUID\" -ne 0 ]; then\n+ echo \"Please run as root\"\n+ exit 1\n+fi\n+\n+MNT_PATH=\"/tmp/mnt_hugetlb\"\n+FILE_PATH=\"$MNT_PATH/test_file\"\n+\n+# Save original values\n+orig_nr=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)\n+orig_overcommit=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages)\n+\n+cleanup() {\n+ echo \"Cleaning up...\"\n+ rm -f \"$FILE_PATH\"\n+ umount \"$MNT_PATH\" 2\u003e/dev/null\n+ rmdir \"$MNT_PATH\" 2\u003e/dev/null\n+ echo \"$orig_nr\" \u003e /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages\n+ echo \"$orig_overcommit\" \u003e /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages\n+ echo \"Cleanup done.\"\n+}\n+trap cleanup EXIT\n+\n+# 1. Mount hugetlbfs with size=2M (1 page)\n+mkdir -p \"$MNT_PATH\"\n+if ! mount -t hugetlbfs -o size=2M none \"$MNT_PATH\"; then\n+ echo \"Failed to mount hugetlbfs\"\n+ exit 1\n+fi\n+\n+# 2. Set nr_hugepages to 0, overcommit to 0\n+echo 0 \u003e /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages\n+echo 0 \u003e /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages\n+\n+# Check subpool usage before running\n+read total free \u003c \u003c(stat -f -c \"%b %f\" \"$MNT_PATH\")\n+used_before=$((total - free))\n+echo \"Before test - Subpool total blocks: $total\"\n+echo \"Before test - Subpool free blocks: $free\"\n+echo \"Before test - Subpool used blocks: $used_before\"\n+if [ \"$used_before\" -ne 0 ]; then\n+ echo \"ERROR: Subpool is not clean before test starts!\"\n+ exit 1\n+fi\n+\n+# Run fallocate (expecting failure)\n+echo \"Running fallocate (expecting failure)...\"\n+if fallocate -l 2M \"$FILE_PATH\" 2\u003e/dev/null; then\n+ echo \"ERROR: fallocate succeeded but should have failed (nr_hugepages is 0)\"\n+ exit 1\n+fi\n+\n+# Check subpool usage via statfs\n+# %b: Total blocks\n+# %f: Free blocks\n+read total free \u003c \u003c(stat -f -c \"%b %f\" \"$MNT_PATH\")\n+used=$((total - free))\n+\n+echo \"Subpool total blocks: $total\"\n+echo \"Subpool free blocks: $free\"\n+echo \"Subpool used blocks (leaked if \u003e 0): $used\"\n+\n+if [ \"$used\" -gt 0 ]; then\n+ echo \"RESULT: LEAK DETECTED (FAIL)\"\n+ exit 1\n+else\n+ echo \"RESULT: NO LEAK (PASS)\"\n+ exit 0\n+fi\ndiff --git a/subpool_shared_leak.c b/subpool_shared_leak.c\nnew file mode 100644\nindex 00000000000000..5811e18d7f8be7\n--- /dev/null\n+++ b/subpool_shared_leak.c\n@@ -0,0 +1,29 @@\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003cfcntl.h\u003e\n+\n+#define HPAGE_SIZE (2 * 1024 * 1024)\n+\n+int main(int argc, char **argv) {\n+ if (argc \u003c 2) {\n+ fprintf(stderr, \"Usage: %s \u003cfile_path\u003e\\n\", argv[0]);\n+ return 1;\n+ }\n+ const char *file_path = argv[1];\n+\n+ int fd = open(file_path, O_CREAT | O_RDWR, 0666);\n+ if (fd \u003c 0) { perror(\"open\"); return 1; }\n+\n+ void *addr = mmap(NULL, 2 * HPAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);\n+ if (addr == MAP_FAILED) { perror(\"mmap\"); close(fd); return 1; }\n+\n+ *(volatile char *)addr = 1; // Allocate 1st page only. 2nd page remains unallocated (but reserved).\n+\n+ munmap(addr, 2 * HPAGE_SIZE);\n+ close(fd);\n+\n+ return 0;\n+}\ndiff --git a/subpool_shared_leak.sh b/subpool_shared_leak.sh\nnew file mode 100755\nindex 00000000000000..46c622b18559a0\n--- /dev/null\n+++ b/subpool_shared_leak.sh\n@@ -0,0 +1,86 @@\n+#!/bin/bash\n+\n+if [ \"$EUID\" -ne 0 ]; then\n+ echo \"Please run as root\"\n+ exit 1\n+fi\n+\n+MNT_PATH=\"/tmp/mnt_hugetlb_shared_leak\"\n+FILE_PATH=\"$MNT_PATH/test_file\"\n+\n+# Save original values\n+orig_nr=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)\n+\n+cleanup() {\n+ echo \"Cleaning up...\"\n+ rm -f \"$FILE_PATH\"\n+ umount \"$MNT_PATH\" 2\u003e/dev/null\n+ rmdir \"$MNT_PATH\" 2\u003e/dev/null\n+ echo \"$orig_nr\" \u003e /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages\n+ echo \"Cleanup done.\"\n+}\n+trap cleanup EXIT\n+\n+# 1. Set nr_hugepages to 2\n+echo 2 \u003e /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages\n+\n+# 2. Mount hugetlbfs with min_size=2M (1 page)\n+mkdir -p \"$MNT_PATH\"\n+if ! mount -t hugetlbfs -o min_size=2M none \"$MNT_PATH\"; then\n+ echo \"Failed to mount hugetlbfs\"\n+ exit 1\n+fi\n+\n+# Check resv_hugepages after mount (should be 1)\n+initial_resv=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages)\n+echo \"Initial resv_hugepages (after mount): $initial_resv\"\n+if [ \"$initial_resv\" -ne 1 ]; then\n+ echo \"ERROR: Initial resv_hugepages is not 1!\"\n+ exit 1\n+fi\n+\n+# Verify reproducer binary exists\n+if [ ! -x ./subpool_shared_leak ]; then\n+ echo \"reproducer binary './subpool_shared_leak' not found or not executable.\"\n+ echo \"Please compile it first: gcc -static -o subpool_shared_leak subpool_shared_leak.c\"\n+ exit 1\n+fi\n+\n+# 3. Run helper to map 4MB, allocate 2MB, and close.\n+# This creates 2 reservations, consumes 1 (by allocating Page 0).\n+# The unallocated Page 1 reservation remains active in the inode's resv_map.\n+echo \"Running helper...\"\n+./subpool_shared_leak \"$FILE_PATH\"\n+\n+resv_after_helper=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages)\n+echo \"resv_hugepages after helper (should be 1): $resv_after_helper\"\n+# Page 0 is allocated (no longer reserved). Page 1 is reserved.\n+# So resv_hugepages should be 1.\n+if [ \"$resv_after_helper\" -ne 1 ]; then\n+ echo \"ERROR: resv_hugepages is not 1 after helper run!\"\n+ exit 1\n+fi\n+\n+# 4. Truncate file to 2MB (releases Page 1 reservation)\n+echo \"Truncating file to 2MB (releasing 1 page reservation)...\"\n+truncate -s 2M \"$FILE_PATH\"\n+\n+# Check resv_hugepages after truncate.\n+# Since Page 0 is still allocated (and in page cache), and satisfies the\n+# min_size=2M guarantee, we should have 0 reservations remaining.\n+# If the bug is present, the truncate path will incorrectly restore the\n+# reservation to the subpool and skip releasing it globally, leaving\n+# resv_hugepages at 1.\n+final_resv=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages)\n+echo \"Final resv_hugepages (after 2MB truncate): $final_resv\"\n+\n+if [ \"$final_resv\" -eq 1 ]; then\n+ echo \"RESULT: LEAK DETECTED (FAIL)\"\n+ exit 1\n+elif [ \"$final_resv\" -eq 0 ]; then\n+ echo \"RESULT: NO LEAK (PASS)\"\n+ exit 0\n+else\n+ echo \"RESULT: UNEXPECTED STATE ($final_resv)\"\n+ exit 2\n+fi\ndiff --git a/tools/testing/hugetlb_subpool/.gitignore b/tools/testing/hugetlb_subpool/.gitignore\nnew file mode 100644\nindex 00000000000000..7348c2c72f1e11\n--- /dev/null\n+++ b/tools/testing/hugetlb_subpool/.gitignore\n@@ -0,0 +1 @@\n+test_subpool\ndiff --git a/tools/testing/hugetlb_subpool/Makefile b/tools/testing/hugetlb_subpool/Makefile\nnew file mode 100644\nindex 00000000000000..1bdb7e26356141\n--- /dev/null\n+++ b/tools/testing/hugetlb_subpool/Makefile\n@@ -0,0 +1,18 @@\n+# SPDX-License-Identifier: GPL-2.0\n+.PHONY: all clean test\n+\n+CC = gcc\n+CFLAGS = -Wall -O2 -I../shared -I. -I../../include -I../../arch/x86/include -pthread\n+KERNEL_SUBPOOL_H = ../../../mm/hugetlb_subpool.h\n+KERNEL_SUBPOOL_C = ../../../mm/hugetlb_subpool.c\n+\n+all: test\n+\n+test_subpool: test_subpool.c $(KERNEL_SUBPOOL_C) $(KERNEL_SUBPOOL_H)\n+\t$(CC) $(CFLAGS) test_subpool.c -o test_subpool\n+\n+test: test_subpool\n+\t./test_subpool\n+\n+clean:\n+\trm -f test_subpool\ndiff --git a/tools/testing/hugetlb_subpool/test_subpool.c b/tools/testing/hugetlb_subpool/test_subpool.c\nnew file mode 100644\nindex 00000000000000..78900274cc2647\n--- /dev/null\n+++ b/tools/testing/hugetlb_subpool/test_subpool.c\n@@ -0,0 +1,400 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003cassert.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/bug.h\u003e\n+\n+/* Mocked Userspace implementation for Kernel Subpool allocation dependencies */\n+struct hstate {\n+\tint dummy;\n+};\n+\n+#undef kzalloc_obj\n+#undef kzalloc_objs\n+#define kzalloc_obj(P, ...) malloc(sizeof(P))\n+#define kzalloc_objs(P, COUNT, ...) malloc(sizeof(P) * (COUNT))\n+\n+#define kfree free\n+#define kmalloc malloc\n+\n+#define huge_page_shift(h) (21 + (0 * ((unsigned long)(h) \u0026 0)))\n+#define huge_page_size(h) (1UL \u003c\u003c huge_page_shift(h))\n+\n+static bool hugetlb_acct_memory_called;\n+static struct hstate *hugetlb_acct_memory_h;\n+static long hugetlb_acct_memory_delta;\n+\n+static int hugetlb_acct_memory(struct hstate *h, long delta)\n+{\n+\thugetlb_acct_memory_called = true;\n+\thugetlb_acct_memory_h = h;\n+\thugetlb_acct_memory_delta = delta;\n+\treturn 0;\n+}\n+\n+static void reset_hugetlb_acct_memory_mock(void)\n+{\n+\thugetlb_acct_memory_called = false;\n+\thugetlb_acct_memory_h = NULL;\n+\thugetlb_acct_memory_delta = 0;\n+}\n+\n+static void assert_hugetlb_acct_memory_called(struct hstate *h, long delta)\n+{\n+\tassert(hugetlb_acct_memory_called);\n+\tassert(hugetlb_acct_memory_h == h);\n+\tassert(hugetlb_acct_memory_delta == delta);\n+\n+\treset_hugetlb_acct_memory_mock();\n+}\n+\n+static void assert_hugetlb_acct_memory_not_called(void)\n+{\n+\tassert(!hugetlb_acct_memory_called);\n+}\n+\n+#include \"../../../mm/hugetlb_subpool.h\"\n+#include \"../../../mm/hugetlb_subpool.c\"\n+\n+static void test_subpool_new_put_no_min_limit(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\n+\tspool = hugepage_new_subpool(\u0026h, 10, -1);\n+\tassert(spool != NULL);\n+\tassert(spool-\u003emax_hpages == 10);\n+\tassert(spool-\u003emin_hpages == -1);\n+\tassert(spool-\u003ersv_hpages == -1);\n+\tassert(spool-\u003ecount == 1);\n+\tassert_hugetlb_acct_memory_not_called();\n+\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_not_called();\n+}\n+\n+static void test_subpool_new_put_with_min_limit(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\n+\tspool = hugepage_new_subpool(\u0026h, 20, 5);\n+\tassert(spool != NULL);\n+\tassert(spool-\u003emax_hpages == 20);\n+\tassert(spool-\u003emin_hpages == 5);\n+\tassert(spool-\u003ersv_hpages == 5);\n+\tassert(spool-\u003ecount == 1);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+}\n+\n+static void test_subpool_get_pages_below_min(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\tlong ret;\n+\n+\t/* Let's initialize: min_hpages = 10, used_hpages = 9, rsv_hpages = 1 */\n+\tspool = hugepage_new_subpool(\u0026h, -1, 10);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 10);\n+\n+\tret = hugepage_subpool_get_pages(spool, 9);\n+\tassert(ret == 0);\n+\tassert(spool-\u003eused_hpages == 9);\n+\tassert(spool-\u003ersv_hpages == 1);\n+\n+\t/* Invoke Get (Consumes the remaining 1 subpool reserve!) */\n+\tret = hugepage_subpool_get_pages(spool, 1);\n+\tassert(ret == 0); /* Covered by subpool reserve! */\n+\tassert(spool-\u003eused_hpages == 10);\n+\tassert(spool-\u003ersv_hpages == 0);\n+\n+\t/* Invoke Put (Replenishes the subpool reserve!) */\n+\tret = hugepage_subpool_put_pages(spool, 1);\n+\tassert(ret == 0); /* Kept by subpool reserve! */\n+\tassert(spool-\u003eused_hpages == 9);\n+\tassert(spool-\u003ersv_hpages == 1);\n+\n+\t/* Cleanup: Return used_hpages to 0 so the subpool frees symmetrically! */\n+\thugepage_subpool_put_pages(spool, 9);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -10);\n+}\n+\n+static void test_subpool_get_pages_crossing_min(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\tlong ret;\n+\n+\t/* Let's initialize: min_hpages = 10, used_hpages = 10, rsv_hpages = 0 */\n+\tspool = hugepage_new_subpool(\u0026h, -1, 10);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 10);\n+\n+\thugepage_subpool_get_pages(spool, 10);\n+\tassert(spool-\u003eused_hpages == 10);\n+\tassert(spool-\u003ersv_hpages == 0);\n+\n+\t/* Invoke Get (Triggers a request for a Global Buddy/Surplus page!) */\n+\tret = hugepage_subpool_get_pages(spool, 1);\n+\tassert(ret == 1); /* Requires global page! */\n+\tassert(spool-\u003eused_hpages == 11);\n+\tassert(spool-\u003ersv_hpages == 0);\n+\n+\t/* Invoke Put (Above minimum, so it releases the page to the Global Pool!) */\n+\tret = hugepage_subpool_put_pages(spool, 1);\n+\tassert(ret == 1); /* Dropped to global pool! */\n+\tassert(spool-\u003eused_hpages == 10);\n+\tassert(spool-\u003ersv_hpages == 0);\n+\n+\t/* Cleanup */\n+\thugepage_subpool_put_pages(spool, 10);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -10);\n+}\n+\n+static void test_subpool_get_pages_crossing_min_multi(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\tlong ret;\n+\n+\t/* Scenario 1: Crossing entirely into surplus territory by a delta \u003e 1 */\n+\t/* Let's initialize: min_hpages = 10, used_hpages = 8, rsv_hpages = 2 */\n+\tspool = hugepage_new_subpool(\u0026h, -1, 10);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 10);\n+\n+\tret = hugepage_subpool_get_pages(spool, 8);\n+\tassert(ret == 0);\n+\tassert(spool-\u003eused_hpages == 8);\n+\tassert(spool-\u003ersv_hpages == 2);\n+\n+\t/* Invoke Get with delta = 5 (Crosses min limit of 10 up to 13) */\n+\tret = hugepage_subpool_get_pages(spool, 5);\n+\tassert(ret == 3); /* (8 + 5) - 10 = 3 global pages required! */\n+\tassert(spool-\u003eused_hpages == 13);\n+\tassert(spool-\u003ersv_hpages == 0);\n+\n+\t/* Invoke Put with delta = 5 (Drops from 13 down to 8) */\n+\tret = hugepage_subpool_put_pages(spool, 5);\n+\tassert(ret == 3); /* 3 surplus pages released to the global pool! */\n+\tassert(spool-\u003eused_hpages == 8);\n+\tassert(spool-\u003ersv_hpages == 2); /* 2 subpool reserves perfectly restored! */\n+\n+\t/* Scenario 2: Landing exactly on the min_hpages boundary with delta \u003e 1 */\n+\tret = hugepage_subpool_get_pages(spool, 2);\n+\tassert(ret == 0); /* Perfectly covered by remaining 2 subpool reserves! */\n+\tassert(spool-\u003eused_hpages == 10);\n+\tassert(spool-\u003ersv_hpages == 0);\n+\n+\tret = hugepage_subpool_put_pages(spool, 2);\n+\tassert(ret == 0); /* Swallowed perfectly to replenish the 2 subpool reserves! */\n+\tassert(spool-\u003eused_hpages == 8);\n+\tassert(spool-\u003ersv_hpages == 2);\n+\n+\t/* Cleanup */\n+\thugepage_subpool_put_pages(spool, 8);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -10);\n+}\n+\n+static void test_subpool_get_pages_max_limit(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\tlong ret;\n+\n+\tspool = hugepage_new_subpool(\u0026h, 5, -1);\n+\tassert_hugetlb_acct_memory_not_called();\n+\n+\tret = hugepage_subpool_get_pages(spool, 5);\n+\tassert(ret == 5);\n+\tassert(spool-\u003eused_hpages == 5);\n+\tassert(spool-\u003ersv_hpages == -1);\n+\n+\t/* Invoke Get (Should trigger -ENOMEM due to max cap limit exceeded!) */\n+\tret = hugepage_subpool_get_pages(spool, 1);\n+\tassert(ret == -ENOMEM);\n+\tassert(spool-\u003eused_hpages == 5); /* Unchanged */\n+\n+\t/* Cleanup */\n+\thugepage_subpool_put_pages(spool, 5);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_not_called();\n+}\n+\n+static void test_subpool_get_pages_no_limits(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\tlong ret;\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, -1);\n+\tassert_hugetlb_acct_memory_not_called();\n+\n+\thugepage_subpool_get_pages(spool, 5);\n+\tassert(spool-\u003eused_hpages == 5);\n+\n+\t/* Invoke Get (Surplus Global Territory) */\n+\tret = hugepage_subpool_get_pages(spool, 2);\n+\tassert(ret == 2);\n+\tassert(spool-\u003eused_hpages == 7);\n+\n+\t/* Invoke Put */\n+\tret = hugepage_subpool_put_pages(spool, 2);\n+\tassert(ret == 2);\n+\tassert(spool-\u003eused_hpages == 5);\n+\n+\t/* Cleanup */\n+\thugepage_subpool_put_pages(spool, 5);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_not_called();\n+}\n+\n+static void test_subpool_free_hpages(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\n+\t/* Test that free_hpages with NO min_size works perfectly */\n+\tspool = hugepage_new_subpool(\u0026h, 15, -1);\n+\thugepage_subpool_get_pages(spool, 3);\n+\tassert(hugepage_subpool_free_hpages(spool) == 12);\n+\thugepage_subpool_put_pages(spool, 3);\n+\thugepage_put_subpool(spool);\n+\n+\t/* Test that free_hpages with a min_size configured is COMPLETELY UNAFFECTED by it */\n+\tspool = hugepage_new_subpool(\u0026h, 15, 5);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\thugepage_subpool_get_pages(spool, 3);\n+\tassert(hugepage_subpool_free_hpages(spool) == 12); /* Should still be 15 - 3 = 12! */\n+\thugepage_subpool_put_pages(spool, 3);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, -1);\n+\thugepage_subpool_get_pages(spool, 3);\n+\tassert(hugepage_subpool_free_hpages(spool) == -1);\n+\thugepage_subpool_put_pages(spool, 3);\n+\thugepage_put_subpool(spool);\n+\n+\t/* Test that free_hpages with a min_size configured and NO max size returns -1 */\n+\tspool = hugepage_new_subpool(\u0026h, -1, 5);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\thugepage_subpool_get_pages(spool, 3);\n+\tassert(hugepage_subpool_free_hpages(spool) == -1);\n+\thugepage_subpool_put_pages(spool, 3);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+\n+\tspool = hugepage_new_subpool(\u0026h, 3, -1);\n+\thugepage_subpool_get_pages(spool, 3);\n+\tassert(hugepage_subpool_free_hpages(spool) == 0);\n+\thugepage_subpool_put_pages(spool, 3);\n+\thugepage_put_subpool(spool);\n+}\n+\n+static void test_subpool_max_hpages(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\n+\tspool = hugepage_new_subpool(\u0026h, 123, -1);\n+\tassert(hugepage_subpool_max_hpages(spool) == 123);\n+\thugepage_put_subpool(spool);\n+\n+\t/* Test that max_hpages with a min_size configured is COMPLETELY UNAFFECTED by it */\n+\tspool = hugepage_new_subpool(\u0026h, 123, 5);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\tassert(hugepage_subpool_max_hpages(spool) == 123);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, -1);\n+\tassert(hugepage_subpool_max_hpages(spool) == -1);\n+\thugepage_put_subpool(spool);\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, 5);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\tassert(hugepage_subpool_max_hpages(spool) == -1);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+\n+\tspool = hugepage_new_subpool(\u0026h, 0, -1);\n+\tassert(hugepage_subpool_max_hpages(spool) == 0);\n+\thugepage_put_subpool(spool);\n+}\n+\n+static void test_subpool_max_size(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\n+\tspool = hugepage_new_subpool(\u0026h, 10, -1);\n+\tassert(hugepage_subpool_max_size(spool) == (10ULL \u003c\u003c 21));\n+\thugepage_put_subpool(spool);\n+\n+\t/* Test that max_size with a min_size configured is COMPLETELY UNAFFECTED by it */\n+\tspool = hugepage_new_subpool(\u0026h, 10, 5);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\tassert(hugepage_subpool_max_size(spool) == (10ULL \u003c\u003c 21));\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, -1);\n+\tassert(hugepage_subpool_max_size(spool) == -1ULL);\n+\thugepage_put_subpool(spool);\n+\n+\tspool = hugepage_new_subpool(\u0026h, 0, -1);\n+\tassert(hugepage_subpool_max_size(spool) == 0ULL);\n+\thugepage_put_subpool(spool);\n+}\n+\n+static void test_subpool_min_size(void)\n+{\n+\tstruct hstate h;\n+\tstruct hugepage_subpool *spool;\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, 5);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\tassert(hugepage_subpool_min_size(spool) == (5ULL \u003c\u003c 21));\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+\n+\t/* Test that min_size with a max_size configured is COMPLETELY UNAFFECTED by it */\n+\tspool = hugepage_new_subpool(\u0026h, 20, 5);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 5);\n+\tassert(hugepage_subpool_min_size(spool) == (5ULL \u003c\u003c 21));\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, -5);\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, -1);\n+\tassert(hugepage_subpool_min_size(spool) == -1ULL);\n+\thugepage_put_subpool(spool);\n+\n+\tspool = hugepage_new_subpool(\u0026h, -1, 0);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 0);\n+\tassert(hugepage_subpool_min_size(spool) == 0ULL);\n+\thugepage_put_subpool(spool);\n+\tassert_hugetlb_acct_memory_called(\u0026h, 0);\n+}\n+\n+int main(void)\n+{\n+\ttest_subpool_new_put_no_min_limit();\n+\ttest_subpool_new_put_with_min_limit();\n+\ttest_subpool_get_pages_below_min();\n+\ttest_subpool_get_pages_crossing_min();\n+\ttest_subpool_get_pages_crossing_min_multi();\n+\ttest_subpool_get_pages_max_limit();\n+\ttest_subpool_get_pages_no_limits();\n+\ttest_subpool_free_hpages();\n+\ttest_subpool_max_hpages();\n+\ttest_subpool_max_size();\n+\ttest_subpool_min_size();\n+\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/07/21 18:08 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"HUGETLBFS",
"MEMCG",
"CGROUP_HUGETLB"
],
"FocusSymbols": [
"alloc_hugetlb_folio",
"hugetlb_reserve_pages",
"mem_cgroup_hugetlb_try_charge",
"mem_cgroup_hugetlb_commit_charge",
"mem_cgroup_hugetlb_cancel_charge",
"hugepage_subpool_get_pages",
"hugepage_subpool_put_pages",
"hugepage_new_subpool",
"hugepage_put_subpool"
],
"Reasoning": "The patch series refactors the hugetlb subpool logic into a separate file and modifies the hugetlb allocation path to use a new try/commit/cancel charging model for memory cgroups. It also modifies the hugetlb_reserve_pages error path. These are functional changes to core memory management and cgroup logic that are reachable from userspace via hugetlbfs and cgroupfs, making them highly relevant for fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 700852cd9a023a5105e2341d247b864002ccc6fc
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Jul 21 18:08:15 2026 +0000
syz-cluster: applied patch under review
diff --git a/cgroup_v2_allocation_failure.c b/cgroup_v2_allocation_failure.c
new file mode 100644
index 00000000000000..938cbf02ae6f7a
--- /dev/null
+++ b/cgroup_v2_allocation_failure.c
@@ -0,0 +1,160 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <errno.h>
+
+#define CGROUP_PATH "/sys/fs/cgroup"
+#define TEST_CGROUP "test_reproducer"
+#define TEST_CGROUP_PATH CGROUP_PATH "/" TEST_CGROUP
+
+void write_file(const char *path, const char *val) {
+ int fd = open(path, O_WRONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
+ exit(1);
+ }
+ if (write(fd, val, strlen(val)) < 0) {
+ fprintf(stderr, "Failed to write %s to %s: %s\n", val, path, strerror(errno));
+ close(fd);
+ exit(1);
+ }
+ close(fd);
+}
+
+int is_hugetlb_accounting_enabled() {
+ FILE *fp = fopen("/proc/mounts", "r");
+ if (!fp) {
+ perror("fopen /proc/mounts");
+ return -1;
+ }
+
+ char line[1024];
+ int enabled = 0;
+ while (fgets(line, sizeof(line), fp)) {
+ char spec[256], file[256], type[256], opts[512];
+ if (sscanf(line, "%255s %255s %255s %511s", spec, file, type, opts) == 4) {
+ if (strcmp(file, CGROUP_PATH) == 0 && strcmp(type, "cgroup2") == 0) {
+ if (strstr(opts, "memory_hugetlb_accounting") != NULL) {
+ enabled = 1;
+ }
+ break;
+ }
+ }
+ }
+ fclose(fp);
+ return enabled;
+}
+
+int enable_hugetlb_accounting() {
+ printf("Attempting to remount cgroup2 with memory_hugetlb_accounting...\n");
+ int ret = system("mount -o remount,memory_hugetlb_accounting " CGROUP_PATH);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to remount: system() returned %d\n", ret);
+ return -1;
+ }
+ return 0;
+}
+
+int main() {
+ struct stat st;
+ if (stat(CGROUP_PATH, &st) != 0 || !S_ISDIR(st.st_mode)) {
+ fprintf(stderr, "cgroup v2 not mounted at %s\n", CGROUP_PATH);
+ return 1;
+ }
+
+ int enabled = is_hugetlb_accounting_enabled();
+ if (enabled < 0) {
+ return 1;
+ }
+ if (!enabled) {
+ if (enable_hugetlb_accounting() != 0) {
+ fprintf(stderr, "Could not enable memory_hugetlb_accounting\n");
+ return 1;
+ }
+ // Re-check
+ enabled = is_hugetlb_accounting_enabled();
+ if (enabled <= 0) {
+ fprintf(stderr, "Failed to enable memory_hugetlb_accounting (re-check failed)\n");
+ return 1;
+ }
+ printf("Successfully enabled memory_hugetlb_accounting\n");
+ } else {
+ printf("memory_hugetlb_accounting is already enabled\n");
+ }
+
+ // Enable memory controller in subtree
+ int fd = open(CGROUP_PATH "/cgroup.subtree_control", O_WRONLY);
+ if (fd >= 0) {
+ if (write(fd, "+memory", 7) < 0) {
+ // Might fail if already enabled or not supported, ignore for now
+ }
+ close(fd);
+ }
+
+ if (mkdir(TEST_CGROUP_PATH, 0755) != 0) {
+ if (errno != EEXIST) {
+ perror("mkdir test_reproducer");
+ return 1;
+ }
+ }
+
+ // Set memory limit to 1MB (less than 2MB hugepage)
+ write_file(TEST_CGROUP_PATH "/memory.max", "1M");
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ return 1;
+ }
+
+ if (pid == 0) {
+ // Child
+ // Move to cgroup
+ write_file(TEST_CGROUP_PATH "/cgroup.procs", "0");
+
+ printf("Child: Attempting to allocate and touch 2MB hugepage...\n");
+ // Allocate 2MB hugepage
+ size_t size = 2 * 1024 * 1024;
+ void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
+ if (addr == MAP_FAILED) {
+ perror("Child: mmap MAP_HUGETLB");
+ exit(1);
+ }
+
+ printf("Child: mmap succeeded at %p, touching it now (should trigger fault)...\n", addr);
+ // This should trigger the fault and call alloc_hugetlb_folio -> mem_cgroup_charge_hugetlb
+ // which should fail and trigger the bug.
+ *(volatile char *)addr = 1;
+
+ printf("Child: Successfully touched page (bug not triggered?).\n");
+ munmap(addr, size);
+ exit(0);
+ }
+
+ // Parent
+ int status;
+ waitpid(pid, &status, 0);
+
+ printf("Parent: Child exited. Cleaning up.\n");
+ rmdir(TEST_CGROUP_PATH);
+
+ if (WIFSIGNALED(status)) {
+ printf("Parent: Child killed by signal %d (%s)\n",
+ WTERMSIG(status), strsignal(WTERMSIG(status)));
+ if (WTERMSIG(status) == SIGBUS) {
+ printf("Parent: Child got SIGBUS as expected (if kernel didn't crash).\n");
+ }
+ } else if (WIFEXITED(status)) {
+ printf("Parent: Child exited with status %d\n", WEXITSTATUS(status));
+ }
+
+ return 0;
+}
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 216e1a0dd0b237..8abe0574b1c7ea 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -25,6 +25,7 @@
#include <linux/ctype.h>
#include <linux/backing-dev.h>
#include <linux/hugetlb.h>
+#include "../../mm/hugetlb_subpool.h"
#include <linux/folio_batch.h>
#include <linux/fs_parser.h>
#include <linux/mman.h>
@@ -1059,7 +1060,6 @@ static int hugetlbfs_show_options(struct seq_file *m, struct dentry *root)
struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(root->d_sb);
struct hugepage_subpool *spool = sbinfo->spool;
unsigned long hpage_size = huge_page_size(sbinfo->hstate);
- unsigned hpage_shift = huge_page_shift(sbinfo->hstate);
char mod;
if (!uid_eq(sbinfo->uid, GLOBAL_ROOT_UID))
@@ -1081,12 +1081,13 @@ static int hugetlbfs_show_options(struct seq_file *m, struct dentry *root)
}
seq_printf(m, ",pagesize=%lu%c", hpage_size, mod);
if (spool) {
- if (spool->max_hpages != -1)
- seq_printf(m, ",size=%llu",
- (unsigned long long)spool->max_hpages << hpage_shift);
- if (spool->min_hpages != -1)
- seq_printf(m, ",min_size=%llu",
- (unsigned long long)spool->min_hpages << hpage_shift);
+ unsigned long long max_size = hugepage_subpool_max_size(spool);
+ unsigned long long min_size = hugepage_subpool_min_size(spool);
+
+ if (max_size != -1ULL)
+ seq_printf(m, ",size=%llu", max_size);
+ if (min_size != -1ULL)
+ seq_printf(m, ",min_size=%llu", min_size);
}
return 0;
}
@@ -1105,14 +1106,8 @@ static int hugetlbfs_statfs(struct dentry *dentry, struct kstatfs *buf)
/* If no limits set, just report 0 or -1 for max/free/used
* blocks, like simple_statfs() */
if (sbinfo->spool) {
- long free_pages;
-
- spin_lock_irq(&sbinfo->spool->lock);
- buf->f_blocks = sbinfo->spool->max_hpages;
- 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_blocks = hugepage_subpool_max_hpages(sbinfo->spool);
+ buf->f_bavail = buf->f_bfree = hugepage_subpool_free_hpages(sbinfo->spool);
buf->f_files = sbinfo->max_inodes;
buf->f_ffree = sbinfo->free_inodes;
}
diff --git a/hugetlb_reserve_pages_out_put_pages.c b/hugetlb_reserve_pages_out_put_pages.c
new file mode 100644
index 00000000000000..9e63fc8997d57d
--- /dev/null
+++ b/hugetlb_reserve_pages_out_put_pages.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+
+int main(int argc, char **argv)
+{
+ const char *file_path;
+ size_t size;
+ int fd;
+ void *addr;
+
+ if (argc < 3) {
+ fprintf(stderr, "Usage: %s <hugetlbfs_file> <size_in_bytes>\n",
+ argv[0]);
+ return 1;
+ }
+
+ file_path = argv[1];
+ size = strtoull(argv[2], NULL, 0);
+
+ fd = open(file_path, O_CREAT | O_RDWR, 0666);
+ if (fd < 0)
+ err(1, "open");
+
+ printf("Attempting to mmap %zu bytes shared on %s...\n", size,
+ file_path);
+ addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED) {
+ if (errno == ENOMEM) {
+ printf("mmap failed with ENOMEM as expected.\n");
+ close(fd);
+ return 0;
+ }
+ perror("mmap failed with unexpected error");
+ close(fd);
+ return 1;
+ }
+
+ printf("ERROR: mmap SUCCEEDED unexpectedly at %p\n", addr);
+ munmap(addr, size);
+ close(fd);
+ return 1;
+}
diff --git a/hugetlb_reserve_pages_out_put_pages.sh b/hugetlb_reserve_pages_out_put_pages.sh
new file mode 100755
index 00000000000000..030e1915539b4e
--- /dev/null
+++ b/hugetlb_reserve_pages_out_put_pages.sh
@@ -0,0 +1,153 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+set -e
+
+if [ "$EUID" -ne 0 ]; then
+ echo "Please run as root"
+ exit 1
+fi
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR"
+
+# Detect default hugepage size to support both 2MB and 1GB pages robustly
+hpz=$(grep -i hugepagesize /proc/meminfo | awk '{print $2}')
+kb=$hpz
+mb=$((kb / 1024))
+hpage_size_bytes=$((kb * 1024))
+
+hpage_dir="hugepages-${kb}kB"
+SYSFS_PATH="/sys/kernel/mm/hugepages/$hpage_dir"
+
+MNT_PATH="/tmp/mnt_hugetlb_repro"
+FILE_PATH="$MNT_PATH/test_file"
+
+# Save original values for safe restoration
+orig_nr=$(cat "$SYSFS_PATH/nr_hugepages")
+orig_overcommit=$(cat "$SYSFS_PATH/nr_overcommit_hugepages")
+
+cleanup() {
+ echo "Cleaning up..."
+ rm -f "$FILE_PATH"
+ umount "$MNT_PATH" 2>/dev/null
+ rmdir "$MNT_PATH" 2>/dev/null
+ echo "$orig_nr" > "$SYSFS_PATH/nr_hugepages"
+ echo "$orig_overcommit" > "$SYSFS_PATH/nr_overcommit_hugepages"
+ echo "Cleanup done."
+}
+trap cleanup EXIT
+
+# Verify reproducer binary exists
+if [ ! -x ./hugetlb_reserve_pages_out_put_pages ]; then
+ echo "reproducer binary './hugetlb_reserve_pages_out_put_pages' not found or not executable."
+ echo "Please compile it first: gcc -static -o hugetlb_reserve_pages_out_put_pages hugetlb_reserve_pages_out_put_pages.c"
+ exit 1
+fi
+
+# 1. Set global pool such that only the mount-time reservation can succeed
+echo 1 > "$SYSFS_PATH/nr_hugepages"
+echo 0 > "$SYSFS_PATH/nr_overcommit_hugepages"
+
+initial_resv=$(cat "$SYSFS_PATH/resv_hugepages")
+echo "Initial resv_hugepages (before mount): $initial_resv"
+
+# 2. Mount with min_size = 1 page, max size = 2 pages
+min_size_str="${mb}M"
+max_size_str="$((mb * 2))M"
+mmap_size_bytes=$((hpage_size_bytes * 2))
+
+mkdir -p "$MNT_PATH"
+echo "Mounting hugetlbfs with pagesize=${mb}M, min_size=$min_size_str, size=$max_size_str..."
+if ! mount -t hugetlbfs -o "pagesize=${mb}M,min_size=$min_size_str,size=$max_size_str" none "$MNT_PATH"; then
+ echo "Failed to mount hugetlbfs"
+ exit 1
+fi
+
+resv_after_mount=$(cat "$SYSFS_PATH/resv_hugepages")
+echo "resv_hugepages after mount: $resv_after_mount"
+expected_after_mount=$((initial_resv + 1))
+if [ "$resv_after_mount" != "$expected_after_mount" ]; then
+ echo "ERROR: resv_hugepages is not $expected_after_mount after mount (actual: $resv_after_mount)!"
+ exit 1
+fi
+
+# Check mount stats after mount
+expected_bsize=$hpage_size_bytes
+bsize_S=$(stat -f -c "%S" "$MNT_PATH")
+bsize_s=$(stat -f -c "%s" "$MNT_PATH")
+echo "Mount block size after mount: $bsize_S / $bsize_s (expected: $expected_bsize)"
+if [ "$bsize_S" != "$expected_bsize" ] && [ "$bsize_s" != "$expected_bsize" ]; then
+ echo "ERROR: Unexpected mount block size after mount (actual S:$bsize_S s:$bsize_s, expected: $expected_bsize)"
+ exit 1
+fi
+
+actual_stats_mount=$(stat -f -c "%b %f %a" "$MNT_PATH")
+expected_stats_mount="2 2 2"
+echo "Mount stats after mount (total free avail): $actual_stats_mount (expected: $expected_stats_mount)"
+if [ "$actual_stats_mount" != "$expected_stats_mount" ]; then
+ echo "ERROR: Unexpected mount stats after mount: $actual_stats_mount (expected: $expected_stats_mount)"
+ exit 1
+fi
+
+# 3. Run the reproducer to trigger the out_put_pages failure path
+echo "Running reproducer (expecting mmap failure with ENOMEM)..."
+if ./hugetlb_reserve_pages_out_put_pages "$FILE_PATH" "$mmap_size_bytes"; then
+ echo "Reproducer finished successfully."
+ resv_after_mmap=$(cat "$SYSFS_PATH/resv_hugepages")
+ echo "resv_hugepages after failed mmap: $resv_after_mmap"
+ expected_after_mmap=$expected_after_mount
+ if [ "$resv_after_mmap" = "$expected_after_mmap" ]; then
+ echo "RESULT: out_put_pages EXERCISED (resv_hugepages preserved at $expected_after_mmap as expected)"
+
+ # Check mount stats
+ expected_bsize=$hpage_size_bytes
+ bsize_S=$(stat -f -c "%S" "$MNT_PATH")
+ bsize_s=$(stat -f -c "%s" "$MNT_PATH")
+ echo "Mount block size: $bsize_S / $bsize_s (expected: $expected_bsize)"
+ if [ "$bsize_S" != "$expected_bsize" ] && [ "$bsize_s" != "$expected_bsize" ]; then
+ echo "ERROR: Unexpected mount block size (actual S:$bsize_S s:$bsize_s, expected: $expected_bsize)"
+ exit 1
+ fi
+
+ actual_stats=$(stat -f -c "%b %f %a" "$MNT_PATH")
+ expected_stats="2 2 2"
+ echo "Mount stats (total free avail): $actual_stats (expected: $expected_stats)"
+ if [ "$actual_stats" != "$expected_stats" ]; then
+ echo "RESULT: Unexpected mount stats after failed mmap (FAIL)"
+ exit 1
+ else
+ echo "RESULT: Mount stats restored to $expected_stats as expected (PASS)"
+ fi
+ else
+ echo "RESULT: Unexpected resv_hugepages value: $resv_after_mmap (expected: $expected_after_mmap)"
+ exit 1
+ fi
+else
+ echo "FAIL: Reproducer returned non-zero (mmap didn't fail with ENOMEM)"
+ exit 1
+fi
+
+# 4. Disable trap and do manual cleanup to check for final unmount underflow
+trap - EXIT
+
+echo "Unmounting..."
+umount "$MNT_PATH"
+rmdir "$MNT_PATH"
+
+final_resv=$(cat "$SYSFS_PATH/resv_hugepages")
+echo "Final resv_hugepages (after unmount): $final_resv"
+
+# Restore original values
+echo "Restoring original hugepage settings..."
+echo "$orig_nr" > "$SYSFS_PATH/nr_hugepages"
+echo "$orig_overcommit" > "$SYSFS_PATH/nr_overcommit_hugepages"
+
+if [ "$final_resv" = "$initial_resv" ]; then
+ echo "RESULT: State restored to $initial_resv (or cleaned up if fixed)"
+ echo "ALL DONE."
+ exit 0
+else
+ echo "RESULT: Underflow/Leak/Incorrect state detected! (final_resv = $final_resv, expected = $initial_resv)"
+ echo "ALL DONE."
+ exit 1
+fi
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 2abaf99321e90d..074a45903973eb 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -18,6 +18,7 @@
struct mmu_gather;
struct node;
+struct hstate;
void free_huge_folio(struct folio *folio);
@@ -34,17 +35,7 @@ void free_huge_folio(struct folio *folio);
*/
#define __NR_USED_SUBPAGE 3
-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. */
- struct hstate *hstate;
- long min_hpages; /* Minimum huge pages or -1 if no minimum. */
- long rsv_hpages; /* Pages reserved against global pool to */
- /* satisfy minimum size. */
-};
+struct hugepage_subpool;
struct resv_map {
struct kref refs;
@@ -114,9 +105,7 @@ extern int hugetlb_max_hstate __read_mostly;
#define for_each_hstate(h) \
for ((h) = hstates; (h) < &hstates[hugetlb_max_hstate]; (h)++)
-struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,
- long min_hpages);
-void hugepage_put_subpool(struct hugepage_subpool *spool);
+int hugetlb_acct_memory(struct hstate *h, long delta);
void hugetlb_dup_vma_private(struct vm_area_struct *vma);
void clear_vma_resv_huge_pages(struct vm_area_struct *vma);
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index e1f46a0016fcfd..23fbbbc8e11f20 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -641,7 +641,15 @@ static inline int mem_cgroup_charge(struct folio *folio, struct mm_struct *mm,
return __mem_cgroup_charge(folio, mm, gfp);
}
-int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp);
+int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp,
+ struct mem_cgroup **memcg_p,
+ struct obj_cgroup **objcg_p);
+void mem_cgroup_hugetlb_commit_charge(struct folio *folio,
+ struct mem_cgroup *memcg,
+ struct obj_cgroup *objcg);
+void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages,
+ struct mem_cgroup *memcg,
+ struct obj_cgroup *objcg);
int mem_cgroup_swapin_charge_folio(struct folio *folio, unsigned short id,
struct mm_struct *mm, gfp_t gfp);
@@ -1128,9 +1136,25 @@ static inline int mem_cgroup_charge(struct folio *folio,
return 0;
}
-static inline int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp)
+static inline int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp,
+ struct mem_cgroup **memcg_p,
+ struct obj_cgroup **objcg_p)
+{
+ *memcg_p = NULL;
+ *objcg_p = NULL;
+ return 0;
+}
+
+static inline void mem_cgroup_hugetlb_commit_charge(struct folio *folio,
+ struct mem_cgroup *memcg,
+ struct obj_cgroup *objcg)
+{
+}
+
+static inline void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages,
+ struct mem_cgroup *memcg,
+ struct obj_cgroup *objcg)
{
- return 0;
}
static inline int mem_cgroup_swapin_charge_folio(struct folio *folio,
diff --git a/mm/Makefile b/mm/Makefile
index eff9f9e7e061c1..3965c959e50992 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -78,7 +78,7 @@ endif
obj-$(CONFIG_SWAP) += page_io.o swap_state.o swapfile.o
obj-$(CONFIG_ZSWAP) += zswap.o
obj-$(CONFIG_HAS_DMA) += dmapool.o
-obj-$(CONFIG_HUGETLBFS) += hugetlb.o hugetlb_sysfs.o hugetlb_sysctl.o
+obj-$(CONFIG_HUGETLBFS) += hugetlb.o hugetlb_subpool.o hugetlb_sysfs.o hugetlb_sysctl.o
ifdef CONFIG_CMA
obj-$(CONFIG_HUGETLBFS) += hugetlb_cma.o
endif
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 571212b80835ec..e87f26a3e1f3e4 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -38,6 +38,8 @@
#include <linux/mm_inline.h>
#include <linux/padata.h>
#include <linux/pgalloc.h>
+#include <linux/memcontrol.h>
+#include "hugetlb_subpool.h"
#include <asm/page.h>
#include <asm/tlb.h>
@@ -113,8 +115,7 @@ __cacheline_aligned_in_smp DEFINE_SPINLOCK(hugetlb_lock);
static int num_fault_mutexes __ro_after_init;
struct mutex *hugetlb_fault_mutex_table __ro_after_init;
-/* Forward declaration */
-static int hugetlb_acct_memory(struct hstate *h, long delta);
+/* Forward declarations */
static void hugetlb_vma_lock_free(struct vm_area_struct *vma);
static void hugetlb_vma_lock_alloc(struct vm_area_struct *vma);
static void __hugetlb_vma_unlock_write_free(struct vm_area_struct *vma);
@@ -125,156 +126,6 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma,
unsigned long start, unsigned long end, bool take_locks);
static struct resv_map *vma_resv_map(struct vm_area_struct *vma);
-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;
-}
-
-static inline void unlock_or_release_subpool(struct hugepage_subpool *spool,
- unsigned long irq_flags)
-{
- spin_unlock_irqrestore(&spool->lock, irq_flags);
-
- /* If no pages are used, and no other handles to the subpool
- * remain, give up any reservations based on minimum size and
- * free the subpool */
- if (subpool_is_free(spool)) {
- if (spool->min_hpages != -1)
- hugetlb_acct_memory(spool->hstate,
- -spool->min_hpages);
- kfree(spool);
- }
-}
-
-struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,
- long min_hpages)
-{
- struct hugepage_subpool *spool;
-
- spool = kzalloc_obj(*spool);
- if (!spool)
- return NULL;
-
- spin_lock_init(&spool->lock);
- spool->count = 1;
- spool->max_hpages = max_hpages;
- spool->hstate = h;
- spool->min_hpages = min_hpages;
-
- if (min_hpages != -1 && hugetlb_acct_memory(h, min_hpages)) {
- kfree(spool);
- return NULL;
- }
- spool->rsv_hpages = min_hpages;
-
- return spool;
-}
-
-void hugepage_put_subpool(struct hugepage_subpool *spool)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&spool->lock, flags);
- BUG_ON(!spool->count);
- spool->count--;
- 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.
- */
-static long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
- long delta)
-{
- long ret = delta;
-
- if (!spool)
- return ret;
-
- 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;
- }
- }
-
- /* minimum size accounting */
- if (spool->min_hpages != -1 && spool->rsv_hpages) {
- if (delta > spool->rsv_hpages) {
- /*
- * Asking for more reserves than those already taken on
- * behalf of subpool. Return difference.
- */
- ret = delta - spool->rsv_hpages;
- spool->rsv_hpages = 0;
- } else {
- ret = 0; /* reserves already accounted for */
- spool->rsv_hpages -= delta;
- }
- }
-
-unlock_ret:
- spin_unlock_irq(&spool->lock);
- 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.
- */
-static long hugepage_subpool_put_pages(struct hugepage_subpool *spool,
- long delta)
-{
- long ret = delta;
- unsigned long flags;
-
- if (!spool)
- return delta;
-
- spin_lock_irqsave(&spool->lock, flags);
-
- if (spool->max_hpages != -1) /* maximum size accounting */
- 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)
- ret = 0;
- else
- ret = spool->rsv_hpages + delta - spool->min_hpages;
-
- spool->rsv_hpages += delta;
- if (spool->rsv_hpages > spool->min_hpages)
- spool->rsv_hpages = spool->min_hpages;
- }
-
- /*
- * If hugetlbfs_put_super couldn't free spool due to an outstanding
- * quota reference, free it now.
- */
- unlock_or_release_subpool(spool, flags);
-
- return ret;
-}
-
static inline struct hugepage_subpool *subpool_vma(struct vm_area_struct *vma)
{
return subpool_inode(file_inode(vma->vm_file));
@@ -2858,11 +2709,13 @@ 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_chg;
map_chg_state map_chg;
int ret, idx;
struct hugetlb_cgroup *h_cg = NULL;
struct hugetlb_cgroup *h_cg_rsvd = NULL;
+ struct mem_cgroup *mem_cg = NULL;
+ struct obj_cgroup *obj_cg = NULL;
gfp_t gfp = htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL;
idx = hstate_index(h);
@@ -2922,6 +2775,11 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
if (ret)
goto out_uncharge_cgroup_reservation;
+ ret = mem_cgroup_hugetlb_try_charge(pages_per_huge_page(h), gfp,
+ &mem_cg, &obj_cg);
+ if (ret)
+ goto out_uncharge_cgroup;
+
spin_lock_irq(&hugetlb_lock);
/*
* glb_chg is passed to indicate whether or not a page must be taken
@@ -2933,7 +2791,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
spin_unlock_irq(&hugetlb_lock);
folio = alloc_buddy_hugetlb_folio_with_mpol(h, vma, addr);
if (!folio)
- goto out_uncharge_cgroup;
+ goto out_uncharge_cgroup_memcg;
spin_lock_irq(&hugetlb_lock);
list_add(&folio->lru, &h->hugepage_activelist);
folio_ref_unfreeze(folio, 1);
@@ -2960,6 +2818,9 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
spin_unlock_irq(&hugetlb_lock);
+ mem_cgroup_hugetlb_commit_charge(folio, mem_cg, obj_cg);
+ lruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h));
+
hugetlb_set_folio_subpool(folio, spool);
if (map_chg != MAP_CHG_ENFORCED) {
@@ -2987,21 +2848,10 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
}
}
- ret = mem_cgroup_charge_hugetlb(folio, gfp);
- /*
- * Unconditionally increment NR_HUGETLB here. If it turns out that
- * mem_cgroup_charge_hugetlb failed, then immediately free the page and
- * decrement NR_HUGETLB.
- */
- lruvec_stat_mod_folio(folio, NR_HUGETLB, pages_per_huge_page(h));
-
- if (ret == -ENOMEM) {
- free_huge_folio(folio);
- return ERR_PTR(-ENOMEM);
- }
-
return folio;
+out_uncharge_cgroup_memcg:
+ mem_cgroup_hugetlb_cancel_charge(pages_per_huge_page(h), mem_cg, obj_cg);
out_uncharge_cgroup:
hugetlb_cgroup_uncharge_cgroup(idx, pages_per_huge_page(h), h_cg);
out_uncharge_cgroup_reservation:
@@ -3009,19 +2859,28 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
hugetlb_cgroup_uncharge_cgroup_rsvd(idx, pages_per_huge_page(h),
h_cg_rsvd);
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_reserve = hugepage_subpool_put_pages(spool, 1);
+
+ if (!gbl_chg)
+ hugetlb_acct_memory(h, -gbl_reserve);
}
out_end_reservation:
if (map_chg != MAP_CHG_ENFORCED)
vma_end_reservation(h, vma, addr);
+
+ /*
+ * Return -ENOSPC when this function fails to allocate or
+ * charge a huge page. If a standard (PAGE_SIZE) page
+ * allocation fails, the OOM killer is given a chance to run,
+ * which may resolve the failure on retry. However, for
+ * HugeTLB allocations, the OOM killer is not triggered.
+ * Returning -ENOMEM (or anything resulting in VM_FAULT_OOM)
+ * would leak to the #PF handler, causing it to loop
+ * indefinitely retrying the fault.
+ */
return ERR_PTR(-ENOSPC);
}
@@ -4593,7 +4452,7 @@ unsigned long hugetlb_total_pages(void)
return nr_total_pages;
}
-static int hugetlb_acct_memory(struct hstate *h, long delta)
+int hugetlb_acct_memory(struct hstate *h, long delta)
{
int ret = -ENOMEM;
@@ -6542,7 +6401,7 @@ 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, gbl_resv;
struct hstate *h = hstate_inode(inode);
struct hugepage_subpool *spool = subpool_inode(inode);
struct resv_map *resv_map;
@@ -6687,26 +6546,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 = 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 - gbl_reserve));
- 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);
diff --git a/mm/hugetlb_subpool.c b/mm/hugetlb_subpool.c
new file mode 100644
index 00000000000000..eb5472bdf21924
--- /dev/null
+++ b/mm/hugetlb_subpool.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Subpool and reserve accounting for HugeTLB folios.
+ * Extracted from mm/hugetlb.c
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#ifdef __KERNEL__
+#include <linux/hugetlb.h>
+#endif
+#include <linux/spinlock.h>
+#include <linux/bug.h>
+#include "hugetlb_subpool.h"
+
+struct hugepage_subpool {
+ spinlock_t lock;
+ long count;
+ long max_hpages; /* Maximum huge pages or -1 if no maximum. */
+ 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 */
+ /* satisfy minimum size. */
+};
+
+static inline bool subpool_is_free(struct hugepage_subpool *spool)
+{
+ if (spool->count)
+ return false;
+
+ return spool->used_hpages == 0;
+}
+
+static inline void unlock_or_release_subpool(struct hugepage_subpool *spool,
+ unsigned long irq_flags)
+{
+ spin_unlock_irqrestore(&spool->lock, irq_flags);
+
+ /*
+ * If no pages are used, and no other handles to the subpool
+ * remain, give up any reservations based on minimum size and
+ * free the subpool.
+ */
+ if (subpool_is_free(spool)) {
+ if (spool->min_hpages != -1)
+ hugetlb_acct_memory(spool->hstate,
+ -spool->min_hpages);
+ kfree(spool);
+ }
+}
+
+long hugepage_subpool_free_hpages(struct hugepage_subpool *spool)
+{
+ long free_pages;
+
+ spin_lock_irq(&spool->lock);
+ if (spool->max_hpages == -1)
+ free_pages = -1;
+ else
+ free_pages = spool->max_hpages - spool->used_hpages;
+ spin_unlock_irq(&spool->lock);
+
+ return free_pages;
+}
+
+static unsigned int hugepage_subpool_hpage_shift(struct hugepage_subpool *spool)
+{
+ return huge_page_shift(spool->hstate);
+}
+
+unsigned long long hugepage_subpool_max_size(struct hugepage_subpool *spool)
+{
+ if (spool->max_hpages == -1)
+ return -1ULL;
+ return (unsigned long long)spool->max_hpages << hugepage_subpool_hpage_shift(spool);
+}
+
+unsigned long long hugepage_subpool_min_size(struct hugepage_subpool *spool)
+{
+ if (spool->min_hpages == -1)
+ return -1ULL;
+ return (unsigned long long)spool->min_hpages << hugepage_subpool_hpage_shift(spool);
+}
+
+long hugepage_subpool_max_hpages(struct hugepage_subpool *spool)
+{
+ long max_hpages;
+
+ spin_lock_irq(&spool->lock);
+ max_hpages = spool->max_hpages;
+ spin_unlock_irq(&spool->lock);
+
+ return max_hpages;
+}
+
+struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,
+ long min_hpages)
+{
+ struct hugepage_subpool *spool;
+
+ spool = kzalloc_obj(*spool);
+ if (!spool)
+ return NULL;
+
+ spin_lock_init(&spool->lock);
+ spool->count = 1;
+ spool->max_hpages = max_hpages;
+ spool->hstate = h;
+ spool->min_hpages = min_hpages;
+
+ if (min_hpages != -1 && hugetlb_acct_memory(h, min_hpages)) {
+ kfree(spool);
+ return NULL;
+ }
+ spool->rsv_hpages = min_hpages;
+
+ return spool;
+}
+
+void hugepage_put_subpool(struct hugepage_subpool *spool)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&spool->lock, flags);
+ BUG_ON(!spool->count);
+ spool->count--;
+ 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.
+ */
+long hugepage_subpool_get_pages(struct hugepage_subpool *spool,
+ long delta)
+{
+ long ret = delta;
+
+ if (!spool)
+ return ret;
+
+ spin_lock_irq(&spool->lock);
+
+ 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) {
+ /*
+ * Asking for more reserves than those already taken on
+ * behalf of subpool. Return difference.
+ */
+ ret = delta - spool->rsv_hpages;
+ spool->rsv_hpages = 0;
+ } else {
+ ret = 0; /* reserves already accounted for */
+ spool->rsv_hpages -= delta;
+ }
+ }
+
+unlock_ret:
+ spin_unlock_irq(&spool->lock);
+ 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.
+ */
+long hugepage_subpool_put_pages(struct hugepage_subpool *spool,
+ long delta)
+{
+ long ret = delta;
+ unsigned long flags;
+
+ if (!spool)
+ return delta;
+
+ spin_lock_irqsave(&spool->lock, flags);
+
+ spool->used_hpages -= delta;
+
+ /* minimum size accounting */
+ if (spool->min_hpages != -1 && spool->used_hpages < 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 - limit;
+
+ spool->rsv_hpages += delta;
+ if (spool->rsv_hpages > limit)
+ spool->rsv_hpages = limit;
+ }
+
+ /*
+ * If hugetlbfs_put_super couldn't free spool due to an outstanding
+ * quota reference, free it now.
+ */
+ unlock_or_release_subpool(spool, flags);
+
+ return ret;
+}
diff --git a/mm/hugetlb_subpool.h b/mm/hugetlb_subpool.h
new file mode 100644
index 00000000000000..41d22239f2c3ed
--- /dev/null
+++ b/mm/hugetlb_subpool.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _MM_HUGETLB_SUBPOOL_H
+#define _MM_HUGETLB_SUBPOOL_H
+
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+struct hstate;
+struct hugepage_subpool;
+
+struct hugepage_subpool *hugepage_new_subpool(struct hstate *h, long max_hpages,
+ long min_hpages);
+void hugepage_put_subpool(struct hugepage_subpool *spool);
+long hugepage_subpool_get_pages(struct hugepage_subpool *spool, long delta);
+long hugepage_subpool_put_pages(struct hugepage_subpool *spool, long delta);
+long hugepage_subpool_free_hpages(struct hugepage_subpool *spool);
+long hugepage_subpool_max_hpages(struct hugepage_subpool *spool);
+unsigned long long hugepage_subpool_max_size(struct hugepage_subpool *spool);
+unsigned long long hugepage_subpool_min_size(struct hugepage_subpool *spool);
+
+#endif /* _MM_HUGETLB_SUBPOOL_H */
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 6dc4888a90f3fd..6764ff041c1965 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -5147,39 +5147,119 @@ int __mem_cgroup_charge(struct folio *folio, struct mm_struct *mm, gfp_t gfp)
}
/**
- * mem_cgroup_charge_hugetlb - charge the memcg for a hugetlb folio
- * @folio: folio being charged
+ * mem_cgroup_hugetlb_try_charge - Try to charge the memcg for a hugetlb folio
+ * @nr_pages: number of base pages to charge
* @gfp: reclaim mode
+ * @memcg_p: Output pointer to the charged mem_cgroup (if successful and enabled)
+ * @objcg_p: Output pointer to the charged obj_cgroup (if successful and enabled)
*
- * This function is called when allocating a huge page folio, after the page has
- * already been obtained and charged to the appropriate hugetlb cgroup
- * controller (if it is enabled).
+ * Prepares and tries to reserve the memory counter for the folio from the current
+ * task's memcg. If successful, both *memcg_p and *objcg_p are populated and their
+ * references are pinned until a subsequent call to mem_cgroup_hugetlb_commit_charge
+ * or mem_cgroup_hugetlb_cancel_charge.
*
* Returns ENOMEM if the memcg is already full.
- * Returns 0 if either the charge was successful, or if we skip the charging.
+ * Returns 0 if either the charge was successful, or if we skip charging.
*/
-int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp)
+int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp,
+ struct mem_cgroup **memcg_p,
+ struct obj_cgroup **objcg_p)
{
- struct mem_cgroup *memcg = get_mem_cgroup_from_current();
+ struct mem_cgroup *memcg;
+ struct obj_cgroup *objcg;
int ret = 0;
- /*
- * Even memcg does not account for hugetlb, we still want to update
- * system-level stats via lruvec_stat_mod_folio. Return 0, and skip
- * charging the memcg.
- */
+ *memcg_p = NULL;
+ *objcg_p = NULL;
+
if (mem_cgroup_disabled() || !memcg_accounts_hugetlb() ||
- !memcg || !cgroup_subsys_on_dfl(memory_cgrp_subsys))
- goto out;
+ !cgroup_subsys_on_dfl(memory_cgrp_subsys))
+ return 0;
- if (charge_memcg(folio, memcg, gfp))
- ret = -ENOMEM;
+ memcg = get_mem_cgroup_from_current();
+ if (!memcg)
+ return 0;
-out:
+ objcg = get_obj_cgroup_from_memcg(memcg);
+ if (!objcg)
+ goto put_memcg;
+
+ if (!obj_cgroup_is_root(objcg)) {
+ ret = try_charge_memcg(memcg, gfp, nr_pages);
+ if (ret)
+ goto put_objcg;
+ }
+
+ *memcg_p = memcg;
+ *objcg_p = objcg;
+ return 0;
+
+put_objcg:
+ obj_cgroup_put(objcg);
+put_memcg:
mem_cgroup_put(memcg);
return ret;
}
+/**
+ * mem_cgroup_hugetlb_commit_charge - Commit the memcg charge for a hugetlb folio
+ * @folio: folio being charged
+ * @memcg: Target mem_cgroup obtained from mem_cgroup_hugetlb_try_charge
+ * @objcg: Target obj_cgroup obtained from mem_cgroup_hugetlb_try_charge
+ *
+ * Finalizes the memory and statistics charging for the folio in the specified memcg.
+ * Transfers the pinned objcg reference to the folio structure (for automatic
+ * uncharging upon freeing via mem_cgroup_uncharge). Releases the try-commit reference
+ * on memcg.
+ */
+void mem_cgroup_hugetlb_commit_charge(struct folio *folio,
+ struct mem_cgroup *memcg,
+ struct obj_cgroup *objcg)
+{
+ if (!memcg || !objcg)
+ return;
+
+ commit_charge(folio, objcg);
+ memcg1_commit_charge(folio, memcg);
+
+ /*
+ * Drop our try-commit-cancel protocol reference on memcg.
+ * The objcg reference is TRANSFERRED to the folio by commit_charge,
+ * so it will be put automatically by __mem_cgroup_uncharge() when
+ * the folio is freed.
+ */
+ mem_cgroup_put(memcg);
+}
+
+/**
+ * mem_cgroup_hugetlb_cancel_charge - Cancel and undo a hugetlb folio memcg charge
+ * @nr_pages: number of base pages to uncharge
+ * @memcg: Target mem_cgroup obtained from mem_cgroup_hugetlb_try_charge
+ * @objcg: Target obj_cgroup obtained from mem_cgroup_hugetlb_try_charge
+ *
+ * Cancels and safely rolls back the prepared memory charge for the folio in the
+ * specified memcg. Releases the try-commit pinned references on both memcg and objcg.
+ */
+void mem_cgroup_hugetlb_cancel_charge(unsigned int nr_pages,
+ struct mem_cgroup *memcg,
+ struct obj_cgroup *objcg)
+{
+ if (!memcg || !objcg)
+ return;
+
+ if (!obj_cgroup_is_root(objcg))
+ refill_stock(memcg, nr_pages);
+
+ /*
+ * Drop our try-commit-cancel protocol references on both objcg
+ * and memcg, since this mapping attempt was aborted and the folio
+ * was never committed.
+ */
+ obj_cgroup_put(objcg);
+ mem_cgroup_put(memcg);
+}
+
+
/**
* mem_cgroup_swapin_charge_folio - Charge a newly allocated folio for swapin.
* @folio: the folio to charge
diff --git a/subpool_leak_max_size.sh b/subpool_leak_max_size.sh
new file mode 100755
index 00000000000000..bfafa1ba074eac
--- /dev/null
+++ b/subpool_leak_max_size.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+
+if [ "$EUID" -ne 0 ]; then
+ echo "Please run as root"
+ exit 1
+fi
+
+MNT_PATH="/tmp/mnt_hugetlb"
+FILE_PATH="$MNT_PATH/test_file"
+
+# Save original values
+orig_nr=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)
+orig_overcommit=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages)
+
+cleanup() {
+ echo "Cleaning up..."
+ rm -f "$FILE_PATH"
+ umount "$MNT_PATH" 2>/dev/null
+ rmdir "$MNT_PATH" 2>/dev/null
+ echo "$orig_nr" > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
+ echo "$orig_overcommit" > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages
+ echo "Cleanup done."
+}
+trap cleanup EXIT
+
+# 1. Mount hugetlbfs with size=2M (1 page)
+mkdir -p "$MNT_PATH"
+if ! mount -t hugetlbfs -o size=2M none "$MNT_PATH"; then
+ echo "Failed to mount hugetlbfs"
+ exit 1
+fi
+
+# 2. Set nr_hugepages to 0, overcommit to 0
+echo 0 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
+echo 0 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_overcommit_hugepages
+
+# Check subpool usage before running
+read total free < <(stat -f -c "%b %f" "$MNT_PATH")
+used_before=$((total - free))
+echo "Before test - Subpool total blocks: $total"
+echo "Before test - Subpool free blocks: $free"
+echo "Before test - Subpool used blocks: $used_before"
+if [ "$used_before" -ne 0 ]; then
+ echo "ERROR: Subpool is not clean before test starts!"
+ exit 1
+fi
+
+# Run fallocate (expecting failure)
+echo "Running fallocate (expecting failure)..."
+if fallocate -l 2M "$FILE_PATH" 2>/dev/null; then
+ echo "ERROR: fallocate succeeded but should have failed (nr_hugepages is 0)"
+ exit 1
+fi
+
+# Check subpool usage via statfs
+# %b: Total blocks
+# %f: Free blocks
+read total free < <(stat -f -c "%b %f" "$MNT_PATH")
+used=$((total - free))
+
+echo "Subpool total blocks: $total"
+echo "Subpool free blocks: $free"
+echo "Subpool used blocks (leaked if > 0): $used"
+
+if [ "$used" -gt 0 ]; then
+ echo "RESULT: LEAK DETECTED (FAIL)"
+ exit 1
+else
+ echo "RESULT: NO LEAK (PASS)"
+ exit 0
+fi
diff --git a/subpool_shared_leak.c b/subpool_shared_leak.c
new file mode 100644
index 00000000000000..5811e18d7f8be7
--- /dev/null
+++ b/subpool_shared_leak.c
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define HPAGE_SIZE (2 * 1024 * 1024)
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s <file_path>\n", argv[0]);
+ return 1;
+ }
+ const char *file_path = argv[1];
+
+ int fd = open(file_path, O_CREAT | O_RDWR, 0666);
+ if (fd < 0) { perror("open"); return 1; }
+
+ void *addr = mmap(NULL, 2 * HPAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED) { perror("mmap"); close(fd); return 1; }
+
+ *(volatile char *)addr = 1; // Allocate 1st page only. 2nd page remains unallocated (but reserved).
+
+ munmap(addr, 2 * HPAGE_SIZE);
+ close(fd);
+
+ return 0;
+}
diff --git a/subpool_shared_leak.sh b/subpool_shared_leak.sh
new file mode 100755
index 00000000000000..46c622b18559a0
--- /dev/null
+++ b/subpool_shared_leak.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+if [ "$EUID" -ne 0 ]; then
+ echo "Please run as root"
+ exit 1
+fi
+
+MNT_PATH="/tmp/mnt_hugetlb_shared_leak"
+FILE_PATH="$MNT_PATH/test_file"
+
+# Save original values
+orig_nr=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)
+
+cleanup() {
+ echo "Cleaning up..."
+ rm -f "$FILE_PATH"
+ umount "$MNT_PATH" 2>/dev/null
+ rmdir "$MNT_PATH" 2>/dev/null
+ echo "$orig_nr" > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
+ echo "Cleanup done."
+}
+trap cleanup EXIT
+
+# 1. Set nr_hugepages to 2
+echo 2 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
+
+# 2. Mount hugetlbfs with min_size=2M (1 page)
+mkdir -p "$MNT_PATH"
+if ! mount -t hugetlbfs -o min_size=2M none "$MNT_PATH"; then
+ echo "Failed to mount hugetlbfs"
+ exit 1
+fi
+
+# Check resv_hugepages after mount (should be 1)
+initial_resv=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages)
+echo "Initial resv_hugepages (after mount): $initial_resv"
+if [ "$initial_resv" -ne 1 ]; then
+ echo "ERROR: Initial resv_hugepages is not 1!"
+ exit 1
+fi
+
+# Verify reproducer binary exists
+if [ ! -x ./subpool_shared_leak ]; then
+ echo "reproducer binary './subpool_shared_leak' not found or not executable."
+ echo "Please compile it first: gcc -static -o subpool_shared_leak subpool_shared_leak.c"
+ exit 1
+fi
+
+# 3. Run helper to map 4MB, allocate 2MB, and close.
+# This creates 2 reservations, consumes 1 (by allocating Page 0).
+# The unallocated Page 1 reservation remains active in the inode's resv_map.
+echo "Running helper..."
+./subpool_shared_leak "$FILE_PATH"
+
+resv_after_helper=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages)
+echo "resv_hugepages after helper (should be 1): $resv_after_helper"
+# Page 0 is allocated (no longer reserved). Page 1 is reserved.
+# So resv_hugepages should be 1.
+if [ "$resv_after_helper" -ne 1 ]; then
+ echo "ERROR: resv_hugepages is not 1 after helper run!"
+ exit 1
+fi
+
+# 4. Truncate file to 2MB (releases Page 1 reservation)
+echo "Truncating file to 2MB (releasing 1 page reservation)..."
+truncate -s 2M "$FILE_PATH"
+
+# Check resv_hugepages after truncate.
+# Since Page 0 is still allocated (and in page cache), and satisfies the
+# min_size=2M guarantee, we should have 0 reservations remaining.
+# If the bug is present, the truncate path will incorrectly restore the
+# reservation to the subpool and skip releasing it globally, leaving
+# resv_hugepages at 1.
+final_resv=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/resv_hugepages)
+echo "Final resv_hugepages (after 2MB truncate): $final_resv"
+
+if [ "$final_resv" -eq 1 ]; then
+ echo "RESULT: LEAK DETECTED (FAIL)"
+ exit 1
+elif [ "$final_resv" -eq 0 ]; then
+ echo "RESULT: NO LEAK (PASS)"
+ exit 0
+else
+ echo "RESULT: UNEXPECTED STATE ($final_resv)"
+ exit 2
+fi
diff --git a/tools/testing/hugetlb_subpool/.gitignore b/tools/testing/hugetlb_subpool/.gitignore
new file mode 100644
index 00000000000000..7348c2c72f1e11
--- /dev/null
+++ b/tools/testing/hugetlb_subpool/.gitignore
@@ -0,0 +1 @@
+test_subpool
diff --git a/tools/testing/hugetlb_subpool/Makefile b/tools/testing/hugetlb_subpool/Makefile
new file mode 100644
index 00000000000000..1bdb7e26356141
--- /dev/null
+++ b/tools/testing/hugetlb_subpool/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+.PHONY: all clean test
+
+CC = gcc
+CFLAGS = -Wall -O2 -I../shared -I. -I../../include -I../../arch/x86/include -pthread
+KERNEL_SUBPOOL_H = ../../../mm/hugetlb_subpool.h
+KERNEL_SUBPOOL_C = ../../../mm/hugetlb_subpool.c
+
+all: test
+
+test_subpool: test_subpool.c $(KERNEL_SUBPOOL_C) $(KERNEL_SUBPOOL_H)
+ $(CC) $(CFLAGS) test_subpool.c -o test_subpool
+
+test: test_subpool
+ ./test_subpool
+
+clean:
+ rm -f test_subpool
diff --git a/tools/testing/hugetlb_subpool/test_subpool.c b/tools/testing/hugetlb_subpool/test_subpool.c
new file mode 100644
index 00000000000000..78900274cc2647
--- /dev/null
+++ b/tools/testing/hugetlb_subpool/test_subpool.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <assert.h>
+#include <stdlib.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/bug.h>
+
+/* Mocked Userspace implementation for Kernel Subpool allocation dependencies */
+struct hstate {
+ int dummy;
+};
+
+#undef kzalloc_obj
+#undef kzalloc_objs
+#define kzalloc_obj(P, ...) malloc(sizeof(P))
+#define kzalloc_objs(P, COUNT, ...) malloc(sizeof(P) * (COUNT))
+
+#define kfree free
+#define kmalloc malloc
+
+#define huge_page_shift(h) (21 + (0 * ((unsigned long)(h) & 0)))
+#define huge_page_size(h) (1UL << huge_page_shift(h))
+
+static bool hugetlb_acct_memory_called;
+static struct hstate *hugetlb_acct_memory_h;
+static long hugetlb_acct_memory_delta;
+
+static int hugetlb_acct_memory(struct hstate *h, long delta)
+{
+ hugetlb_acct_memory_called = true;
+ hugetlb_acct_memory_h = h;
+ hugetlb_acct_memory_delta = delta;
+ return 0;
+}
+
+static void reset_hugetlb_acct_memory_mock(void)
+{
+ hugetlb_acct_memory_called = false;
+ hugetlb_acct_memory_h = NULL;
+ hugetlb_acct_memory_delta = 0;
+}
+
+static void assert_hugetlb_acct_memory_called(struct hstate *h, long delta)
+{
+ assert(hugetlb_acct_memory_called);
+ assert(hugetlb_acct_memory_h == h);
+ assert(hugetlb_acct_memory_delta == delta);
+
+ reset_hugetlb_acct_memory_mock();
+}
+
+static void assert_hugetlb_acct_memory_not_called(void)
+{
+ assert(!hugetlb_acct_memory_called);
+}
+
+#include "../../../mm/hugetlb_subpool.h"
+#include "../../../mm/hugetlb_subpool.c"
+
+static void test_subpool_new_put_no_min_limit(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+
+ spool = hugepage_new_subpool(&h, 10, -1);
+ assert(spool != NULL);
+ assert(spool->max_hpages == 10);
+ assert(spool->min_hpages == -1);
+ assert(spool->rsv_hpages == -1);
+ assert(spool->count == 1);
+ assert_hugetlb_acct_memory_not_called();
+
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_not_called();
+}
+
+static void test_subpool_new_put_with_min_limit(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+
+ spool = hugepage_new_subpool(&h, 20, 5);
+ assert(spool != NULL);
+ assert(spool->max_hpages == 20);
+ assert(spool->min_hpages == 5);
+ assert(spool->rsv_hpages == 5);
+ assert(spool->count == 1);
+ assert_hugetlb_acct_memory_called(&h, 5);
+
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+}
+
+static void test_subpool_get_pages_below_min(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+ long ret;
+
+ /* Let's initialize: min_hpages = 10, used_hpages = 9, rsv_hpages = 1 */
+ spool = hugepage_new_subpool(&h, -1, 10);
+ assert_hugetlb_acct_memory_called(&h, 10);
+
+ ret = hugepage_subpool_get_pages(spool, 9);
+ assert(ret == 0);
+ assert(spool->used_hpages == 9);
+ assert(spool->rsv_hpages == 1);
+
+ /* Invoke Get (Consumes the remaining 1 subpool reserve!) */
+ ret = hugepage_subpool_get_pages(spool, 1);
+ assert(ret == 0); /* Covered by subpool reserve! */
+ assert(spool->used_hpages == 10);
+ assert(spool->rsv_hpages == 0);
+
+ /* Invoke Put (Replenishes the subpool reserve!) */
+ ret = hugepage_subpool_put_pages(spool, 1);
+ assert(ret == 0); /* Kept by subpool reserve! */
+ assert(spool->used_hpages == 9);
+ assert(spool->rsv_hpages == 1);
+
+ /* Cleanup: Return used_hpages to 0 so the subpool frees symmetrically! */
+ hugepage_subpool_put_pages(spool, 9);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -10);
+}
+
+static void test_subpool_get_pages_crossing_min(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+ long ret;
+
+ /* Let's initialize: min_hpages = 10, used_hpages = 10, rsv_hpages = 0 */
+ spool = hugepage_new_subpool(&h, -1, 10);
+ assert_hugetlb_acct_memory_called(&h, 10);
+
+ hugepage_subpool_get_pages(spool, 10);
+ assert(spool->used_hpages == 10);
+ assert(spool->rsv_hpages == 0);
+
+ /* Invoke Get (Triggers a request for a Global Buddy/Surplus page!) */
+ ret = hugepage_subpool_get_pages(spool, 1);
+ assert(ret == 1); /* Requires global page! */
+ assert(spool->used_hpages == 11);
+ assert(spool->rsv_hpages == 0);
+
+ /* Invoke Put (Above minimum, so it releases the page to the Global Pool!) */
+ ret = hugepage_subpool_put_pages(spool, 1);
+ assert(ret == 1); /* Dropped to global pool! */
+ assert(spool->used_hpages == 10);
+ assert(spool->rsv_hpages == 0);
+
+ /* Cleanup */
+ hugepage_subpool_put_pages(spool, 10);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -10);
+}
+
+static void test_subpool_get_pages_crossing_min_multi(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+ long ret;
+
+ /* Scenario 1: Crossing entirely into surplus territory by a delta > 1 */
+ /* Let's initialize: min_hpages = 10, used_hpages = 8, rsv_hpages = 2 */
+ spool = hugepage_new_subpool(&h, -1, 10);
+ assert_hugetlb_acct_memory_called(&h, 10);
+
+ ret = hugepage_subpool_get_pages(spool, 8);
+ assert(ret == 0);
+ assert(spool->used_hpages == 8);
+ assert(spool->rsv_hpages == 2);
+
+ /* Invoke Get with delta = 5 (Crosses min limit of 10 up to 13) */
+ ret = hugepage_subpool_get_pages(spool, 5);
+ assert(ret == 3); /* (8 + 5) - 10 = 3 global pages required! */
+ assert(spool->used_hpages == 13);
+ assert(spool->rsv_hpages == 0);
+
+ /* Invoke Put with delta = 5 (Drops from 13 down to 8) */
+ ret = hugepage_subpool_put_pages(spool, 5);
+ assert(ret == 3); /* 3 surplus pages released to the global pool! */
+ assert(spool->used_hpages == 8);
+ assert(spool->rsv_hpages == 2); /* 2 subpool reserves perfectly restored! */
+
+ /* Scenario 2: Landing exactly on the min_hpages boundary with delta > 1 */
+ ret = hugepage_subpool_get_pages(spool, 2);
+ assert(ret == 0); /* Perfectly covered by remaining 2 subpool reserves! */
+ assert(spool->used_hpages == 10);
+ assert(spool->rsv_hpages == 0);
+
+ ret = hugepage_subpool_put_pages(spool, 2);
+ assert(ret == 0); /* Swallowed perfectly to replenish the 2 subpool reserves! */
+ assert(spool->used_hpages == 8);
+ assert(spool->rsv_hpages == 2);
+
+ /* Cleanup */
+ hugepage_subpool_put_pages(spool, 8);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -10);
+}
+
+static void test_subpool_get_pages_max_limit(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+ long ret;
+
+ spool = hugepage_new_subpool(&h, 5, -1);
+ assert_hugetlb_acct_memory_not_called();
+
+ ret = hugepage_subpool_get_pages(spool, 5);
+ assert(ret == 5);
+ assert(spool->used_hpages == 5);
+ assert(spool->rsv_hpages == -1);
+
+ /* Invoke Get (Should trigger -ENOMEM due to max cap limit exceeded!) */
+ ret = hugepage_subpool_get_pages(spool, 1);
+ assert(ret == -ENOMEM);
+ assert(spool->used_hpages == 5); /* Unchanged */
+
+ /* Cleanup */
+ hugepage_subpool_put_pages(spool, 5);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_not_called();
+}
+
+static void test_subpool_get_pages_no_limits(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+ long ret;
+
+ spool = hugepage_new_subpool(&h, -1, -1);
+ assert_hugetlb_acct_memory_not_called();
+
+ hugepage_subpool_get_pages(spool, 5);
+ assert(spool->used_hpages == 5);
+
+ /* Invoke Get (Surplus Global Territory) */
+ ret = hugepage_subpool_get_pages(spool, 2);
+ assert(ret == 2);
+ assert(spool->used_hpages == 7);
+
+ /* Invoke Put */
+ ret = hugepage_subpool_put_pages(spool, 2);
+ assert(ret == 2);
+ assert(spool->used_hpages == 5);
+
+ /* Cleanup */
+ hugepage_subpool_put_pages(spool, 5);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_not_called();
+}
+
+static void test_subpool_free_hpages(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+
+ /* Test that free_hpages with NO min_size works perfectly */
+ spool = hugepage_new_subpool(&h, 15, -1);
+ hugepage_subpool_get_pages(spool, 3);
+ assert(hugepage_subpool_free_hpages(spool) == 12);
+ hugepage_subpool_put_pages(spool, 3);
+ hugepage_put_subpool(spool);
+
+ /* Test that free_hpages with a min_size configured is COMPLETELY UNAFFECTED by it */
+ spool = hugepage_new_subpool(&h, 15, 5);
+ assert_hugetlb_acct_memory_called(&h, 5);
+ hugepage_subpool_get_pages(spool, 3);
+ assert(hugepage_subpool_free_hpages(spool) == 12); /* Should still be 15 - 3 = 12! */
+ hugepage_subpool_put_pages(spool, 3);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+
+ spool = hugepage_new_subpool(&h, -1, -1);
+ hugepage_subpool_get_pages(spool, 3);
+ assert(hugepage_subpool_free_hpages(spool) == -1);
+ hugepage_subpool_put_pages(spool, 3);
+ hugepage_put_subpool(spool);
+
+ /* Test that free_hpages with a min_size configured and NO max size returns -1 */
+ spool = hugepage_new_subpool(&h, -1, 5);
+ assert_hugetlb_acct_memory_called(&h, 5);
+ hugepage_subpool_get_pages(spool, 3);
+ assert(hugepage_subpool_free_hpages(spool) == -1);
+ hugepage_subpool_put_pages(spool, 3);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+
+ spool = hugepage_new_subpool(&h, 3, -1);
+ hugepage_subpool_get_pages(spool, 3);
+ assert(hugepage_subpool_free_hpages(spool) == 0);
+ hugepage_subpool_put_pages(spool, 3);
+ hugepage_put_subpool(spool);
+}
+
+static void test_subpool_max_hpages(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+
+ spool = hugepage_new_subpool(&h, 123, -1);
+ assert(hugepage_subpool_max_hpages(spool) == 123);
+ hugepage_put_subpool(spool);
+
+ /* Test that max_hpages with a min_size configured is COMPLETELY UNAFFECTED by it */
+ spool = hugepage_new_subpool(&h, 123, 5);
+ assert_hugetlb_acct_memory_called(&h, 5);
+ assert(hugepage_subpool_max_hpages(spool) == 123);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+
+ spool = hugepage_new_subpool(&h, -1, -1);
+ assert(hugepage_subpool_max_hpages(spool) == -1);
+ hugepage_put_subpool(spool);
+
+ spool = hugepage_new_subpool(&h, -1, 5);
+ assert_hugetlb_acct_memory_called(&h, 5);
+ assert(hugepage_subpool_max_hpages(spool) == -1);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+
+ spool = hugepage_new_subpool(&h, 0, -1);
+ assert(hugepage_subpool_max_hpages(spool) == 0);
+ hugepage_put_subpool(spool);
+}
+
+static void test_subpool_max_size(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+
+ spool = hugepage_new_subpool(&h, 10, -1);
+ assert(hugepage_subpool_max_size(spool) == (10ULL << 21));
+ hugepage_put_subpool(spool);
+
+ /* Test that max_size with a min_size configured is COMPLETELY UNAFFECTED by it */
+ spool = hugepage_new_subpool(&h, 10, 5);
+ assert_hugetlb_acct_memory_called(&h, 5);
+ assert(hugepage_subpool_max_size(spool) == (10ULL << 21));
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+
+ spool = hugepage_new_subpool(&h, -1, -1);
+ assert(hugepage_subpool_max_size(spool) == -1ULL);
+ hugepage_put_subpool(spool);
+
+ spool = hugepage_new_subpool(&h, 0, -1);
+ assert(hugepage_subpool_max_size(spool) == 0ULL);
+ hugepage_put_subpool(spool);
+}
+
+static void test_subpool_min_size(void)
+{
+ struct hstate h;
+ struct hugepage_subpool *spool;
+
+ spool = hugepage_new_subpool(&h, -1, 5);
+ assert_hugetlb_acct_memory_called(&h, 5);
+ assert(hugepage_subpool_min_size(spool) == (5ULL << 21));
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+
+ /* Test that min_size with a max_size configured is COMPLETELY UNAFFECTED by it */
+ spool = hugepage_new_subpool(&h, 20, 5);
+ assert_hugetlb_acct_memory_called(&h, 5);
+ assert(hugepage_subpool_min_size(spool) == (5ULL << 21));
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, -5);
+
+ spool = hugepage_new_subpool(&h, -1, -1);
+ assert(hugepage_subpool_min_size(spool) == -1ULL);
+ hugepage_put_subpool(spool);
+
+ spool = hugepage_new_subpool(&h, -1, 0);
+ assert_hugetlb_acct_memory_called(&h, 0);
+ assert(hugepage_subpool_min_size(spool) == 0ULL);
+ hugepage_put_subpool(spool);
+ assert_hugetlb_acct_memory_called(&h, 0);
+}
+
+int main(void)
+{
+ test_subpool_new_put_no_min_limit();
+ test_subpool_new_put_with_min_limit();
+ test_subpool_get_pages_below_min();
+ test_subpool_get_pages_crossing_min();
+ test_subpool_get_pages_crossing_min_multi();
+ test_subpool_get_pages_max_limit();
+ test_subpool_get_pages_no_limits();
+ test_subpool_free_hpages();
+ test_subpool_max_hpages();
+ test_subpool_max_size();
+ test_subpool_min_size();
+
+ return 0;
+}
|
| 3/2 | 2026/07/21 18:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25986 output: 22 thoughts: 516 Thoughts: **Analyzing the Hugetlbfs and Memcg Patch Series** Okay, so I'm looking at this patch series, and it's making some pretty significant changes to the core memory management, specifically in the realms of hugetlbfs and memcg. My initial impression is that it's refactoring some existing logic and introducing new memcg integration for huge pages. Specifically, it looks like `hugepage_subpool` logic is being extracted into its own file, `mm/hugetlb_subpool.c`. This suggests an effort to modularize the code, which is always a good sign for maintainability. The core of the functional change seems to revolve around `alloc_hugetlb_folio`. This function is now using `mem_cgroup_hugetlb_try_charge`, `mem_cgroup_hugetlb_commit_charge`, and `mem_cgroup_hugetlb_cancel_charge` to manage memcg accounting during folio allocation. Smart. This is the integration I was expecting. Diving deeper, I see that the `hugetlb_reserve_pages` function in `mm/hugetlb.c` also has been modified, particularly in the error path `out_put_pages`. It's worth digging into the details of *that* change to see how it's playing with the new memcg accounting. Now, let's look at the `mm/memcontrol.c` file. The functions like `mem_cgroup_hugetlb_try_charge`, `mem_cgroup_hugetlb_commit_charge`, and `mem_cgroup_hugetlb_cancel_charge` are all new, as expected. This confirms the functional addition of memcg-aware hugetlb accounting. This seems like a solid, functional change to the core kernel logic. It definitely warrants fuzzing. My list of focus symbols: * `alloc_hugetlb_folio` * `hugetlb_reserve_pages` * `mem_cgroup_hugetlb_try_charge` * `mem_cgroup_hugetlb_commit_charge` * `mem_cgroup_hugetlb_cancel_charge` * `hugepage_subpool_get_pages` * `hugepage_subpool_put_pages` And of course, I need to make sure the relevant kernel config options are enabled for testing this. Let's see... I'll need `HUGETLBFS`, obviously. Also, `MEMCG`. I think `CGROUP_HUGETLB` is relevant. Let me double-check that last one, I don't quite remember if that is the correct config. Let's look for `memcg_accounts_hugetlb` or `CGROUP_HUGETLB` in `mm/memcontrol.c` or `init/Kconfig` to be sure. |
| 4/2 | 2026/07/21 18:08 | tool | grepper |
0mArgs:{
"Expression": "memcg_accounts_hugetlb"
}Results: {
"Output": "mm/memcontrol.c=1674=unsigned long memcg_page_state_local_output(struct mem_cgroup *memcg, int item)\n--\nmm/memcontrol.c-1681-#ifdef CONFIG_HUGETLB_PAGE\nmm/memcontrol.c:1682:static bool memcg_accounts_hugetlb(void)\nmm/memcontrol.c-1683-{\n--\nmm/memcontrol.c-1686-#else /* CONFIG_HUGETLB_PAGE */\nmm/memcontrol.c:1687:static bool memcg_accounts_hugetlb(void)\nmm/memcontrol.c-1688-{\n--\nmm/memcontrol.c=1693=static void memcg_stat_format(struct mem_cgroup *memcg, struct seq_buf *s)\n--\nmm/memcontrol.c-1713-\t\tif (unlikely(memory_stats[i].idx == NR_HUGETLB) \u0026\u0026\nmm/memcontrol.c:1714:\t\t\t!memcg_accounts_hugetlb())\nmm/memcontrol.c-1715-\t\t\tcontinue;\n--\nmm/memcontrol.c=5164=int mem_cgroup_hugetlb_try_charge(unsigned int nr_pages, gfp_t gfp,\n--\nmm/memcontrol.c-5174-\nmm/memcontrol.c:5175:\tif (mem_cgroup_disabled() || !memcg_accounts_hugetlb() ||\nmm/memcontrol.c-5176-\t !cgroup_subsys_on_dfl(memory_cgrp_subsys))\n"
} |
| 5/2 | 2026/07/21 18:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27577 output: 21 thoughts: 62 Thoughts: **Investigating HugeTLB Memory Accounting in the Kernel** Okay, so `memcg_accounts_hugetlb` is only defined when `CONFIG_HUGETLB_PAGE` is enabled. That makes sense, but it raises a question: how does that interact with `CONFIG_CGROUP_HUGETLB`? Is `CONFIG_CGROUP_HUGETLB` also checked anywhere? I'd better confirm whether or not `CONFIG_CGROUP_HUGETLB` is even defined. Gotta be precise about this. |
| 6/2 | 2026/07/21 18:08 | tool | grepper |
0mArgs:{
"Expression": "CGROUP_HUGETLB"
}Results: {
"Output": "arch/arm64/configs/defconfig=21=CONFIG_CGROUP_FREEZER=y\narch/arm64/configs/defconfig:22:CONFIG_CGROUP_HUGETLB=y\narch/arm64/configs/defconfig-23-CONFIG_CPUSETS=y\n--\narch/loongarch/configs/loongson64_defconfig=30=CONFIG_CGROUP_FREEZER=y\narch/loongarch/configs/loongson64_defconfig:31:CONFIG_CGROUP_HUGETLB=y\narch/loongarch/configs/loongson64_defconfig-32-CONFIG_CPUSETS=y\n--\narch/powerpc/configs/ppc64_defconfig=27=CONFIG_CGROUP_FREEZER=y\narch/powerpc/configs/ppc64_defconfig:28:CONFIG_CGROUP_HUGETLB=y\narch/powerpc/configs/ppc64_defconfig-29-CONFIG_CPUSETS=y\n--\narch/riscv/configs/defconfig=14=CONFIG_CGROUP_FREEZER=y\narch/riscv/configs/defconfig:15:CONFIG_CGROUP_HUGETLB=y\narch/riscv/configs/defconfig-16-CONFIG_CPUSETS=y\n--\narch/s390/configs/debug_defconfig=30=CONFIG_CGROUP_FREEZER=y\narch/s390/configs/debug_defconfig:31:CONFIG_CGROUP_HUGETLB=y\narch/s390/configs/debug_defconfig-32-CONFIG_CPUSETS=y\n--\narch/s390/configs/defconfig=28=CONFIG_CGROUP_FREEZER=y\narch/s390/configs/defconfig:29:CONFIG_CGROUP_HUGETLB=y\narch/s390/configs/defconfig-30-CONFIG_CPUSETS=y\n--\narch/x86/configs/i386_defconfig=19=CONFIG_CGROUP_FREEZER=y\narch/x86/configs/i386_defconfig:20:CONFIG_CGROUP_HUGETLB=y\narch/x86/configs/i386_defconfig-21-CONFIG_CPUSETS=y\n--\narch/x86/configs/x86_64_defconfig=19=CONFIG_CGROUP_FREEZER=y\narch/x86/configs/x86_64_defconfig:20:CONFIG_CGROUP_HUGETLB=y\narch/x86/configs/x86_64_defconfig-21-CONFIG_CPUSETS=y\n--\ninclude/linux/cgroup_subsys.h=49=SUBSYS(net_prio)\n--\ninclude/linux/cgroup_subsys.h-51-\ninclude/linux/cgroup_subsys.h:52:#if IS_ENABLED(CONFIG_CGROUP_HUGETLB)\ninclude/linux/cgroup_subsys.h-53-SUBSYS(hugetlb)\n--\ninclude/linux/hugetlb.h=40=struct resv_map {\n--\ninclude/linux/hugetlb.h-47-\tstruct rw_semaphore rw_sema;\ninclude/linux/hugetlb.h:48:#ifdef CONFIG_CGROUP_HUGETLB\ninclude/linux/hugetlb.h-49-\t/*\n--\ninclude/linux/hugetlb.h=79=struct file_region {\n--\ninclude/linux/hugetlb.h-82-\tlong to;\ninclude/linux/hugetlb.h:83:#ifdef CONFIG_CGROUP_HUGETLB\ninclude/linux/hugetlb.h-84-\t/*\n--\ninclude/linux/hugetlb_cgroup.h=22=struct file_region;\ninclude/linux/hugetlb_cgroup.h-23-\ninclude/linux/hugetlb_cgroup.h:24:#ifdef CONFIG_CGROUP_HUGETLB\ninclude/linux/hugetlb_cgroup.h-25-enum hugetlb_memory_event {\n--\ninit/Kconfig=1270=config CGROUP_FREEZER\n--\ninit/Kconfig-1280-\ninit/Kconfig:1281:config CGROUP_HUGETLB\ninit/Kconfig-1282-\tbool \"HugeTLB controller\"\n--\nmm/Makefile=108=endif\nmm/Makefile:109:obj-$(CONFIG_CGROUP_HUGETLB) += hugetlb_cgroup.o\nmm/Makefile-110-obj-$(CONFIG_GUP_TEST) += gup_test.o\n--\nmm/hugetlb.c=325=static void copy_hugetlb_cgroup_uncharge_info(struct file_region *nrg,\n--\nmm/hugetlb.c-327-{\nmm/hugetlb.c:328:#ifdef CONFIG_CGROUP_HUGETLB\nmm/hugetlb.c-329-\tnrg-\u003ereservation_counter = rg-\u003ereservation_counter;\n--\nmm/hugetlb.c=337=static void record_hugetlb_cgroup_uncharge_info(struct hugetlb_cgroup *h_cg,\n--\nmm/hugetlb.c-341-{\nmm/hugetlb.c:342:#ifdef CONFIG_CGROUP_HUGETLB\nmm/hugetlb.c-343-\tif (h_cg) {\n--\nmm/hugetlb.c=371=static void put_uncharge_info(struct file_region *rg)\nmm/hugetlb.c-372-{\nmm/hugetlb.c:373:#ifdef CONFIG_CGROUP_HUGETLB\nmm/hugetlb.c-374-\tif (rg-\u003ecss)\n--\nmm/hugetlb.c=379=static bool has_same_uncharge_info(struct file_region *rg,\n--\nmm/hugetlb.c-381-{\nmm/hugetlb.c:382:#ifdef CONFIG_CGROUP_HUGETLB\nmm/hugetlb.c-383-\treturn rg-\u003ereservation_counter == org-\u003ereservation_counter \u0026\u0026\n--\nmm/hugetlb.c=909=resv_map_set_hugetlb_cgroup_uncharge_info(struct resv_map *resv_map,\n--\nmm/hugetlb.c-912-{\nmm/hugetlb.c:913:#ifdef CONFIG_CGROUP_HUGETLB\nmm/hugetlb.c-914-\tif (!h_cg || !h) {\n--\ntools/testing/selftests/bpf/config.aarch64=21=CONFIG_CGROUP_FREEZER=y\ntools/testing/selftests/bpf/config.aarch64:22:CONFIG_CGROUP_HUGETLB=y\ntools/testing/selftests/bpf/config.aarch64-23-CONFIG_CGROUP_NET_CLASSID=y\n--\ntools/testing/selftests/bpf/config.ppc64el=12=CONFIG_CGROUP_FREEZER=y\ntools/testing/selftests/bpf/config.ppc64el:13:CONFIG_CGROUP_HUGETLB=y\ntools/testing/selftests/bpf/config.ppc64el-14-CONFIG_CGROUP_NET_CLASSID=y\n--\ntools/testing/selftests/bpf/config.riscv64=12=CONFIG_CGROUP_FREEZER=y\ntools/testing/selftests/bpf/config.riscv64:13:CONFIG_CGROUP_HUGETLB=y\ntools/testing/selftests/bpf/config.riscv64-14-CONFIG_CGROUP_NET_CLASSID=y\n--\ntools/testing/selftests/bpf/config.s390x=14=CONFIG_CGROUP_FREEZER=y\ntools/testing/selftests/bpf/config.s390x:15:CONFIG_CGROUP_HUGETLB=y\ntools/testing/selftests/bpf/config.s390x-16-CONFIG_CGROUP_NET_CLASSID=y\n--\ntools/testing/selftests/bpf/config.x86_64=27=CONFIG_CGROUP_FREEZER=y\ntools/testing/selftests/bpf/config.x86_64:28:CONFIG_CGROUP_HUGETLB=y\ntools/testing/selftests/bpf/config.x86_64-29-CONFIG_CGROUP_PERF=y\n--\ntools/testing/selftests/hid/config.common=22=CONFIG_CGROUP_FREEZER=y\ntools/testing/selftests/hid/config.common:23:CONFIG_CGROUP_HUGETLB=y\ntools/testing/selftests/hid/config.common-24-CONFIG_CGROUP_NET_CLASSID=y\n"
} |
| 7/2 | 2026/07/21 18:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30062 output: 17 thoughts: 37 Thoughts: Okay, here's my thought process on this: **Investigating Memory Control Groups** Alright, so `CGROUP_HUGETLB` is definitely a thing. Now, I need to figure out the relationship with `MEMCG`. My first instinct is to delve into the `init/Kconfig` file. That's the go-to place for understanding kernel configuration options, so I'll start there to see how `MEMCG` is defined and if there's any immediate connection or dependency laid out within the configuration framework. This is the logical first step for me to understand how these modules relate to each other in the kernel. |
| 8/2 | 2026/07/21 18:08 | tool | grepper |
0mArgs:{
"Expression": "config MEMCG"
}Results: {
"Output": "init/Kconfig=1063=config CGROUP_FAVOR_DYNMODS\n--\ninit/Kconfig-1072-\ninit/Kconfig:1073:config MEMCG\ninit/Kconfig-1074-\tbool \"Memory controller\"\n--\ninit/Kconfig-1081-\ninit/Kconfig:1082:config MEMCG_NMI_UNSAFE\ninit/Kconfig-1083-\tbool\n--\ninit/Kconfig-1088-\ninit/Kconfig:1089:config MEMCG_NMI_SAFETY_REQUIRES_ATOMIC\ninit/Kconfig-1090-\tbool\n--\ninit/Kconfig-1095-\ninit/Kconfig:1096:config MEMCG_V1\ninit/Kconfig-1097-\tbool \"Legacy cgroup v1 memory controller\"\n"
} |
| 9/2 | 2026/07/21 18:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30479 output: 20 thoughts: 80 Thoughts: **Kernel Configuration Quandary** Okay, so I'm looking at these kernel configurations, and immediately my brain jumps to `HUGETLBFS`, `MEMCG`, and `CGROUP_HUGETLB`. Those are the usual suspects. But, hold on a second... isn't `HUGETLB_PAGE` the *primary* config for enabling huge pages in the first place? It feels like that should be the foundation. Right, `HUGETLBFS` is related to the filesystem implementation, making that distinction in my mind now. Let me just double-check where `HUGETLB_PAGE` is actually defined. I should look in either `fs/Kconfig` or `mm/Kconfig`, it'll likely be there. Gotta refresh my memory and pinpoint that configuration dependency. |
| 10/2 | 2026/07/21 18:08 | tool | grepper |
0mArgs:{
"Expression": "config HUGETLB_PAGE"
}Results: {
"Output": "arch/sh/mm/Kconfig=158=choice\n--\narch/sh/mm/Kconfig-163-\narch/sh/mm/Kconfig:164:config HUGETLB_PAGE_SIZE_64K\narch/sh/mm/Kconfig-165-\tbool \"64kB\"\n--\narch/sh/mm/Kconfig-167-\narch/sh/mm/Kconfig:168:config HUGETLB_PAGE_SIZE_256K\narch/sh/mm/Kconfig-169-\tbool \"256kB\"\n--\narch/sh/mm/Kconfig-171-\narch/sh/mm/Kconfig:172:config HUGETLB_PAGE_SIZE_1MB\narch/sh/mm/Kconfig-173-\tbool \"1MB\"\narch/sh/mm/Kconfig-174-\narch/sh/mm/Kconfig:175:config HUGETLB_PAGE_SIZE_4MB\narch/sh/mm/Kconfig-176-\tbool \"4MB\"\n--\narch/sh/mm/Kconfig-178-\narch/sh/mm/Kconfig:179:config HUGETLB_PAGE_SIZE_64MB\narch/sh/mm/Kconfig-180-\tbool \"64MB\"\n--\nfs/Kconfig=262=if HUGETLBFS\nfs/Kconfig:263:config HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON\nfs/Kconfig-264-\tbool \"HugeTLB Vmemmap Optimization (HVO) defaults to on\"\n--\nfs/Kconfig=271=endif # HUGETLBFS\nfs/Kconfig-272-\nfs/Kconfig:273:config HUGETLB_PAGE\nfs/Kconfig-274-\tdef_bool HUGETLBFS\n--\nfs/Kconfig-276-\nfs/Kconfig:277:config HUGETLB_PAGE_OPTIMIZE_VMEMMAP\nfs/Kconfig-278-\tdef_bool HUGETLB_PAGE\n--\nmm/Kconfig=706=config ARCH_ENABLE_THP_MIGRATION\n--\nmm/Kconfig-708-\nmm/Kconfig:709:config HUGETLB_PAGE_SIZE_VARIABLE\nmm/Kconfig-710-\tdef_bool n\n"
} |
| 11/2 | 2026/07/21 18:08 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31344 output: 250 thoughts: 604 Thoughts: **My Analysis of the Hugetlb Subpool Refactoring Patch** Okay, so I'm looking at this patch series, and it's all about refactoring the hugetlb subpool logic. Right off the bat, I see that `HUGETLB_PAGE` is tied to `HUGETLBFS`, so that's the key thing to enable, and I need to make sure `MEMCG` is also set. I need to make sure that these are all working in harmony with any existing configurations. I should probably double-check the `memcg_accounts_hugetlb` config. It's defined if `CONFIG_HUGETLB_PAGE` is set, and it's used in `mem_cgroup_hugetlb_try_charge`. Got it. So we're looking at `HUGETLBFS` and `MEMCG` as the essential configs here, as well as `CGROUP_HUGETLB`. Now, let me zero in on the key functions being modified. I should give these extra focus: * `alloc_hugetlb_folio` * `hugetlb_reserve_pages` * `mem_cgroup_hugetlb_try_charge` * `mem_cgroup_hugetlb_commit_charge` * `mem_cgroup_hugetlb_cancel_charge` * `hugepage_subpool_get_pages` * `hugepage_subpool_put_pages` * `hugepage_new_subpool` * `hugepage_put_subpool` * `hugepage_subpool_free_hpages` * `hugepage_subpool_max_hpages` * `hugepage_subpool_max_size` * `hugepage_subpool_min_size` Alright, let's make sure I'm using the exact function names from the diff when it comes to focus. The core of this patch series is refactoring the hugetlb subpool logic into its own file, `mm/hugetlb_subpool.c`. More importantly, it seems to be updating the hugetlb allocation path (`alloc_hugetlb_folio`) to employ a try/commit/cancel charging scheme for memory cgroups using functions like `mem_cgroup_hugetlb_try_charge`, `mem_cgroup_hugetlb_commit_charge`, and `mem_cgroup_hugetlb_cancel_charge`. Plus, there's a fix for an error path in `hugetlb_reserve_pages`. These are significant changes to core memory management and cgroup functionality, and since they can be triggered from userspace via hugetlbfs and cgroupfs, they're prime candidates for fuzzing. Time to set-results. |
| 12/2 | 2026/07/21 18:08 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"HUGETLBFS",
"MEMCG",
"CGROUP_HUGETLB"
],
"FocusSymbols": [
"alloc_hugetlb_folio",
"hugetlb_reserve_pages",
"mem_cgroup_hugetlb_try_charge",
"mem_cgroup_hugetlb_commit_charge",
"mem_cgroup_hugetlb_cancel_charge",
"hugepage_subpool_get_pages",
"hugepage_subpool_put_pages",
"hugepage_new_subpool",
"hugepage_put_subpool"
],
"Reasoning": "The patch series refactors the hugetlb subpool logic into a separate file and modifies the hugetlb allocation path to use a new try/commit/cancel charging model for memory cgroups. It also modifies the hugetlb_reserve_pages error path. These are functional changes to core memory management and cgroup logic that are reachable from userspace via hugetlbfs and cgroupfs, making them highly relevant for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"HUGETLBFS",
"MEMCG",
"CGROUP_HUGETLB"
],
"FocusSymbols": [
"alloc_hugetlb_folio",
"hugetlb_reserve_pages",
"mem_cgroup_hugetlb_try_charge",
"mem_cgroup_hugetlb_commit_charge",
"mem_cgroup_hugetlb_cancel_charge",
"hugepage_subpool_get_pages",
"hugepage_subpool_put_pages",
"hugepage_new_subpool",
"hugepage_put_subpool"
],
"Reasoning": "The patch series refactors the hugetlb subpool logic into a separate file and modifies the hugetlb allocation path to use a new try/commit/cancel charging model for memory cgroups. It also modifies the hugetlb_reserve_pages error path. These are functional changes to core memory management and cgroup logic that are reachable from userspace via hugetlbfs and cgroupfs, making them highly relevant for fuzzing.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|