From: Andrew Morton <akpm@linux-foundation.org>
To: mm-commits@vger.kernel.org,shuah@kernel.org,richard.weiyang@linux.alibaba.com,osalvador@suse.de,muchun.song@linux.dev,david@kernel.org,baoquan.he@linux.dev,caixiangfeng@bytedance.com,akpm@linux-foundation.org
Subject: [withdrawn] selftests-mm-add-hugetlb_region_cache_race-regression-test.patch removed from -mm tree
Date: Tue, 14 Jul 2026 21:04:46 -0700 [thread overview]
Message-ID: <20260715040447.76AA71F000E9@smtp.kernel.org> (raw)
The quilt patch titled
Subject: selftests/mm: add hugetlb_region_cache_race regression test
has been removed from the -mm tree. Its filename was
selftests-mm-add-hugetlb_region_cache_race-regression-test.patch
This patch was dropped because it was withdrawn
------------------------------------------------------
From: "Xiangfeng Cai" <caixiangfeng@bytedance.com>
Subject: selftests/mm: add hugetlb_region_cache_race regression test
Date: Tue, 14 Jul 2026 01:14:56 +0800
Add a regression test for the list corruption in
allocate_file_region_entries() fixed by the previous patch ("mm/hugetlb:
fix list corruption in allocate_file_region_entries()").
Triggering the bug requires a concurrent reservation operation to drain
resv->region_cache while allocate_file_region_entries() has dropped
resv->lock for its GFP_KERNEL allocation, forcing its retry loop to run
again. As the mmap() and fallocate(PUNCH_HOLE) paths serialise on
inode_lock, the cache has to be drained by faults from a separate address
space. The test forks several processes sharing one hugetlb inode via
memfd_create(MFD_HUGETLB); each mmap()s and faults ranges and punches
holes to keep the shared resv_map fragmented.
Two modes are provided:
- default: a safe single-process functional check that exercises the buggy
line without forcing a second loop iteration; safe on any kernel.
- --trigger: the concurrent reproducer, which panics a vulnerable
CONFIG_DEBUG_LIST=y kernel and is therefore opt-in. It faults pages in,
so it needs as many free huge pages as the file is large.
Assisted-by: Claude:claude-opus-4-8
Link: https://lore.kernel.org/20260713171456.300518-3-caixiangfeng@bytedance.com
Signed-off-by: Xiangfeng Cai <caixiangfeng@bytedance.com>
Cc: Baoquan He <baoquan.he@linux.dev>
Cc: David Hildenbrand <david@kernel.org>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Oscar Salvador <osalvador@suse.de>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Wei Yang <richard.weiyang@linux.alibaba.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
tools/testing/selftests/mm/.gitignore | 1
tools/testing/selftests/mm/Makefile | 1
tools/testing/selftests/mm/hugetlb_region_cache_race.c | 315 ++++++++++
tools/testing/selftests/mm/run_vmtests.sh | 1
4 files changed, 318 insertions(+)
--- a/tools/testing/selftests/mm/.gitignore~selftests-mm-add-hugetlb_region_cache_race-regression-test
+++ a/tools/testing/selftests/mm/.gitignore
@@ -59,6 +59,7 @@ hugetlb_madv_vs_map
mseal_test
droppable
hugetlb_dio
+hugetlb_region_cache_race
pkey_sighandler_tests_32
pkey_sighandler_tests_64
guard-regions
diff --git a/tools/testing/selftests/mm/hugetlb_region_cache_race.c a/tools/testing/selftests/mm/hugetlb_region_cache_race.c
new file mode 100644
--- /dev/null
+++ a/tools/testing/selftests/mm/hugetlb_region_cache_race.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Regression test for list corruption in
+ * mm/hugetlb.c:allocate_file_region_entries().
+ *
+ * Fork processes sharing a hugetlb inode via `memfd_create(MFD_HUGETLB)`
+ * and have them fault ranges and punch holes to fragment the shared
+ * `resv_map` while draining `resv->region_cache` from a separate address
+ * space.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include <linux/falloc.h>
+#include <linux/memfd.h>
+
+#include "../kselftest.h"
+#include "hugepage_settings.h"
+
+/* Tunables (overridable via argv). */
+static unsigned long file_pages = 64; /* huge pages backing the shared file */
+static unsigned int nr_procs; /* 0 => auto (2 * nr online cpus) */
+static unsigned int duration_sec = 5; /* --trigger run time */
+
+static unsigned long huge_page_size; /* bytes */
+static int memfd = -1;
+static volatile int *stop; /* shared across forked children */
+
+/*
+ * Per-process SIGBUS escape: a fault can fail (pool exhausted) and raise
+ * SIGBUS; jump back and abandon the rest of the current range instead of
+ * dying. Each child is single-threaded, so a file-scope buffer is fine.
+ */
+static sigjmp_buf sigbus_env;
+
+static void sigbus_handler(int sig)
+{
+ siglongjmp(sigbus_env, 1);
+}
+
+static void install_sigbus_handler(void)
+{
+ struct sigaction sa = {
+ .sa_handler = sigbus_handler,
+ };
+
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGBUS, &sa, NULL);
+}
+
+static long sys_memfd_create(const char *name, unsigned int flags)
+{
+ return syscall(__NR_memfd_create, name, flags);
+}
+
+/*
+ * Map [start, start+len) of the shared file and fault every page in.
+ *
+ * The mmap() drives mmap-time hugetlb_reserve_pages -> region_chg (the
+ * wide-window victim when the range is fragmented) + region_add. The
+ * subsequent stores fault pages in *without* inode_lock, driving the
+ * lock-free region_add/region_chg that drains resv->region_cache -- the
+ * concurrency the buggy loop needs from *other* processes.
+ */
+static void map_and_fault_range(unsigned long start, unsigned long len)
+{
+ unsigned long i;
+ char *addr;
+
+ if (!len)
+ return;
+ addr = mmap(NULL, len * huge_page_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, memfd, start * huge_page_size);
+ if (addr == MAP_FAILED)
+ return; /* ENOMEM under pool pressure is fine */
+
+ for (i = 0; i < len; i++) {
+ if (sigsetjmp(sigbus_env, 1) == 0)
+ *(volatile char *)(addr + i * huge_page_size) = (char)i;
+ else
+ break; /* SIGBUS: pool exhausted, stop faulting */
+ }
+ munmap(addr, len * huge_page_size);
+}
+
+/*
+ * Remove [start, start+len) huge pages from the shared file, driving
+ * hugetlbfs_punch_hole -> remove_inode_hugepages -> region_del for the pages
+ * that were faulted in above. This frees pages back to the pool (bounding
+ * residency) and re-fragments the resv_map so later region_chg calls keep
+ * needing multiple entries.
+ */
+static void punch_range(unsigned long start, unsigned long len)
+{
+ if (!len)
+ return;
+ fallocate(memfd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ start * huge_page_size, len * huge_page_size);
+}
+
+/*
+ * One forked worker. Each worker is simultaneously a victim (its wide
+ * mmap-time region_chg) and, for the other workers, a lock-free cache
+ * drainer (its faults) -- exactly the shared-inode map+fault churn seen in
+ * the original crash (QEMU prealloc + SPDK/Bytelight on one guest-RAM file).
+ */
+static void child_worker(unsigned int id)
+{
+ unsigned int seed = id * 2654435761u ^ (unsigned int)time(NULL) ^
+ ((unsigned int)getpid() << 1);
+
+ install_sigbus_handler();
+
+ while (!*stop) {
+ unsigned long start = rand_r(&seed) % file_pages;
+ unsigned long len = 1 + rand_r(&seed) % (file_pages - start);
+
+ /*
+ * Bias toward map+fault (victims and drainers); punch a
+ * quarter of the time to keep the map fragmented and to cap
+ * how many pages stay resident.
+ */
+ if (rand_r(&seed) % 4)
+ map_and_fault_range(start, len);
+ else
+ punch_range(start, len);
+ }
+}
+
+/*
+ * Safe, single-process functional check. It fragments the shared resv_map
+ * (reservations for a shared mapping persist on the inode after munmap) so
+ * that a subsequent whole-file mmap makes region_chg compute
+ * regions_needed > 1, forcing allocate_file_region_entries to allocate and
+ * splice several entries. This exercises the buggy line, but without
+ * concurrency it always makes exactly one loop iteration, so it cannot trip
+ * the race and is safe on any kernel. Returns 0 on success.
+ */
+static int functional_check(void)
+{
+ unsigned long i;
+ void *addr;
+
+ /* Punch everything so we start from an empty resv_map. */
+ punch_range(0, file_pages);
+
+ /* Reserve every other huge page -> ~file_pages/2 disjoint regions. */
+ for (i = 0; i < file_pages; i += 2)
+ map_and_fault_range(i, 1);
+
+ /*
+ * One mapping spanning the whole file must fill all the holes in a
+ * single region_chg -> a multi-entry region_cache refill.
+ */
+ addr = mmap(NULL, file_pages * huge_page_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, memfd, 0);
+ if (addr == MAP_FAILED) {
+ ksft_print_msg("whole-file mmap failed: %s\n", strerror(errno));
+ return -1;
+ }
+ munmap(addr, file_pages * huge_page_size);
+ punch_range(0, file_pages);
+ return 0;
+}
+
+static int run_trigger(void)
+{
+ pid_t *pids;
+ unsigned int i, started = 0;
+
+ stop = mmap(NULL, sizeof(*stop), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (stop == MAP_FAILED)
+ ksft_exit_fail_msg("mmap(stop flag): %s\n", strerror(errno));
+ *stop = 0;
+
+ pids = calloc(nr_procs, sizeof(*pids));
+ if (!pids)
+ ksft_exit_fail_msg("calloc: %s\n", strerror(errno));
+
+ ksft_print_msg("stressing %u processes for %us on %lu huge pages (%lu MiB)\n",
+ nr_procs, duration_sec, file_pages,
+ (file_pages * huge_page_size) >> 20);
+ ksft_print_msg("WARNING: a vulnerable kernel will panic here\n");
+
+ for (i = 0; i < nr_procs; i++) {
+ pid_t pid = fork();
+
+ if (pid < 0) {
+ ksft_print_msg("fork: %s\n", strerror(errno));
+ break;
+ }
+ if (pid == 0) {
+ free(pids);
+ child_worker(i);
+ _exit(0);
+ }
+ pids[started++] = pid;
+ }
+
+ if (!started) {
+ free(pids);
+ munmap((void *)stop, sizeof(*stop));
+ ksft_exit_fail_msg("no worker processes started\n");
+ }
+
+ sleep(duration_sec);
+ *stop = 1;
+
+ for (i = 0; i < started; i++)
+ waitpid(pids[i], NULL, 0);
+ free(pids);
+ munmap((void *)stop, sizeof(*stop));
+ return 0;
+}
+
+static void usage(const char *prog)
+{
+ fprintf(stderr,
+ "usage: %s [--trigger] [-s huge_pages] [-t procs] [-d seconds]\n"
+ " (default) run a safe single-process functional check\n"
+ " --trigger fork+fault concurrent reproducer (CAN PANIC A BUGGY HOST)\n"
+ " -s huge pages backing the shared file (default %lu)\n"
+ " -t worker processes for --trigger (default 2 * nr_cpus)\n"
+ " -d --trigger run time in seconds (default %u)\n",
+ prog, file_pages, duration_sec);
+}
+
+int main(int argc, char *argv[])
+{
+ bool trigger = false;
+ int i, ret;
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--trigger")) {
+ trigger = true;
+ } else if (!strcmp(argv[i], "-s") && i + 1 < argc) {
+ file_pages = strtoul(argv[++i], NULL, 0);
+ } else if (!strcmp(argv[i], "-t") && i + 1 < argc) {
+ nr_procs = strtoul(argv[++i], NULL, 0);
+ } else if (!strcmp(argv[i], "-d") && i + 1 < argc) {
+ duration_sec = strtoul(argv[++i], NULL, 0);
+ } else {
+ usage(argv[0]);
+ return KSFT_FAIL;
+ }
+ }
+
+ ksft_print_header();
+ ksft_set_plan(1);
+
+ if (file_pages < 4)
+ file_pages = 4;
+ if (!nr_procs) {
+ long cpus = sysconf(_SC_NPROCESSORS_ONLN);
+
+ nr_procs = cpus > 0 ? (unsigned int)cpus * 2 : 4;
+ }
+ if (nr_procs < 2)
+ nr_procs = 2;
+
+ huge_page_size = default_huge_page_size();
+ if (!huge_page_size)
+ ksft_exit_skip("no hugetlbfs / huge page size available\n");
+
+ /*
+ * Both modes fault pages in (functional_check touches every page it
+ * maps, and --trigger's workers fault the ranges they map), so the
+ * whole file may become resident; require that many *free* huge pages,
+ * not just reservations.
+ */
+ if (hugetlb_free_default_pages() < file_pages)
+ ksft_exit_skip("need %lu free huge pages, have %lu (set nr_hugepages)\n",
+ file_pages, hugetlb_free_default_pages());
+
+ memfd = sys_memfd_create("hugetlb_region_cache_race",
+ MFD_HUGETLB |
+ (__builtin_ctzl(huge_page_size) << MFD_HUGE_SHIFT));
+ if (memfd < 0)
+ ksft_exit_skip("memfd_create(MFD_HUGETLB): %s\n",
+ strerror(errno));
+ if (ftruncate(memfd, file_pages * huge_page_size)) {
+ close(memfd);
+ ksft_exit_fail_msg("ftruncate: %s\n", strerror(errno));
+ }
+
+ if (trigger)
+ ret = run_trigger();
+ else
+ ret = functional_check();
+
+ close(memfd);
+
+ if (ret)
+ ksft_test_result_fail("hugetlb region_cache race check\n");
+ else if (trigger)
+ ksft_test_result_pass("survived concurrent region_cache churn\n");
+ else
+ ksft_test_result_pass("multi-entry region_cache refill works\n");
+
+ ksft_finished();
+}
--- a/tools/testing/selftests/mm/Makefile~selftests-mm-add-hugetlb_region_cache_race-regression-test
+++ a/tools/testing/selftests/mm/Makefile
@@ -64,6 +64,7 @@ TEST_GEN_FILES += hugetlb-madvise
TEST_GEN_FILES += hugetlb-mmap
TEST_GEN_FILES += hugetlb-mremap
TEST_GEN_FILES += hugetlb-read-hwpoison
+TEST_GEN_FILES += hugetlb_region_cache_race
TEST_GEN_FILES += hugetlb-shm
TEST_GEN_FILES += hugetlb-soft-offline
TEST_GEN_FILES += khugepaged
--- a/tools/testing/selftests/mm/run_vmtests.sh~selftests-mm-add-hugetlb_region_cache_race-regression-test
+++ a/tools/testing/selftests/mm/run_vmtests.sh
@@ -266,6 +266,7 @@ CATEGORY="hugetlb" run_test ./hugetlb-ma
CATEGORY="hugetlb" run_test ./hugetlb_dio
CATEGORY="hugetlb" run_test ./hugetlb_fault_after_madv
CATEGORY="hugetlb" run_test ./hugetlb_madv_vs_map
+CATEGORY="hugetlb" run_test ./hugetlb_region_cache_race
if test_selected "hugetlb"; then
echo "NOTE: These hugetlb tests provide minimal coverage. Use" | tap_prefix
_
Patches currently in -mm which might be from caixiangfeng@bytedance.com are
mm-hugetlb-fix-list-corruption-in-allocate_file_region_entries.patch
reply other threads:[~2026-07-15 4:04 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260715040447.76AA71F000E9@smtp.kernel.org \
--to=akpm@linux-foundation.org \
--cc=baoquan.he@linux.dev \
--cc=caixiangfeng@bytedance.com \
--cc=david@kernel.org \
--cc=mm-commits@vger.kernel.org \
--cc=muchun.song@linux.dev \
--cc=osalvador@suse.de \
--cc=richard.weiyang@linux.alibaba.com \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.