* [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs
@ 2026-09-10 2:34 James Houghton
2026-09-10 2:34 ` [PATCH v3 2/2] mm: selftests: Adjust the MADV_COLLAPSE uffd-minor selftests James Houghton
2026-09-10 23:24 ` [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs Andrew Morton
0 siblings, 2 replies; 6+ messages in thread
From: James Houghton @ 2026-09-10 2:34 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Zi Yan, Baolin Wang, liam,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Yang Shi, zokeefe, hughd, Kiryl Shutsemau, jthoughton,
linux-mm, linux-kernel
Userfaultfd minor faults provides userspace with the ability to manually
install PTEs with UFFDIO_CONTINUE. Right now, MADV_COLLAPSE can map
holes in the VMA when a naturally-aligned THP is present. This is not
true for khugepaged collapse: the PTEs will be retracted, but a PMD will
not be installed.
When MADV_COLLAPSE installs a PMD that mapped holes in the VMA,
userspace is likely to expect UFFDIO_CONTINUE to succeed on the
should-be holes. UFFDIO_CONTINUE will fail and return EEXIST.
This is not inherently a problem, as MADV_COLLAPSE is an explicit
userspace action. But, especially because MADV_COLLAPSE can be invoked
by an external process via process_madvise(), a rogue caller could break
a userfaultfd-minor resolver thread. If khugepaged is later updated to
install PMDs for khugepaged collapsing, that would be a genuine problem.
Userspace cannot generally use MADV_COLLAPSE to resolve userfault minor
faults, as MADV_COLLAPSE will only resolve such faults if a
naturally-aligned THP is present.
The naturally-aligned THP case is the only case where this quirk exists.
Collapsing otherwise requires all PTEs to be present for
userfaultfd-registered VMAs (i.e., max none PTEs is 0), which is
correct. This check is essentially bypassed for naturally-aligned THPs.
Suggested-by: Lance Yang <lance.yang@linux.dev>
Tested-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: James Houghton <jthoughton@google.com>
---
v2: https://lore.kernel.org/linux-mm/20260828222640.1638457-1-jthoughton@google.com/
Changes since v2:
- Drop cc:stable and update the changelog for the main patch.
khugepaged doesn't actually install PMDs, so it is impossible for the
kernel to silently install mappings that userspace didn't ask for.
Therefore this patch does not need to be backported.
- Update the uffd-unit-tests selftest to account for the new behavior
(new patch). This patched selftest is equivalent to the reproducer I
provided in v1.
v1: https://lore.kernel.org/linux-mm/20260828005004.2870750-1-jthoughton@google.com/
---
mm/khugepaged.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 75639298efc2..e6947fe142ee 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1894,6 +1894,13 @@ static enum scan_result try_collapse_pte_mapped_thp(struct mm_struct *mm, unsign
if (userfaultfd_protected(vma))
return SCAN_PTE_UFFD;
+ /*
+ * Userfaultfd-minor-registered VMAs should not be collapsed, as
+ * userspace is expecting to explicitly install PTEs.
+ */
+ if (userfaultfd_minor(vma))
+ return SCAN_PTE_UFFD;
+
folio = filemap_lock_folio(vma->vm_file->f_mapping,
linear_page_index(vma, haddr));
if (IS_ERR(folio))
base-commit: 5acbae5f7eb3d5275120abfe698c394b7325dcec
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 2/2] mm: selftests: Adjust the MADV_COLLAPSE uffd-minor selftests
2026-09-10 2:34 [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs James Houghton
@ 2026-09-10 2:34 ` James Houghton
2026-09-10 23:24 ` [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs Andrew Morton
1 sibling, 0 replies; 6+ messages in thread
From: James Houghton @ 2026-09-10 2:34 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Zi Yan, Baolin Wang, liam,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Yang Shi, zokeefe, hughd, Kiryl Shutsemau, jthoughton,
linux-mm, linux-kernel
The behavior of MADV_COLLAPSE with uffd-minor VMAs has changed:
MADV_COLLAPSE will no longer install PTEs where none existed before.
Update the selftest to demonstrate this new behavior.
On an unpatched kernel, the test will hit "unexpected memory contents
after collapse".
If the selftest is left unpatched but the kernel is patched, the
selftest will SKIP when it gets EINVAL back from MADV_COLLAPSE.
Signed-off-by: James Houghton <jthoughton@google.com>
---
tools/testing/selftests/mm/uffd-unit-tests.c | 75 ++++++++++++++++++--
1 file changed, 68 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
index ef9b3956bdcf..6f2360f9b75d 100644
--- a/tools/testing/selftests/mm/uffd-unit-tests.c
+++ b/tools/testing/selftests/mm/uffd-unit-tests.c
@@ -518,19 +518,34 @@ static void uffd_wp_fork_pin_with_event_test(uffd_global_test_opts_t *gopts, uff
uffd_wp_fork_pin_test_common(gopts, args, true);
}
-static void check_memory_contents(uffd_global_test_opts_t *gopts, char *p)
+static int __check_memory_contents(unsigned long offset,
+ unsigned long nr_pages,
+ uffd_global_test_opts_t *gopts,
+ char *p)
{
unsigned long i, j;
uint8_t expected_byte;
- for (i = 0; i < gopts->nr_pages; ++i) {
+ if (nr_pages + offset < nr_pages)
+ err("overflow in memory check");
+ if (nr_pages + offset > gopts->nr_pages)
+ err("out of bounds memory check");
+
+ for (i = offset; i < offset + nr_pages; ++i) {
expected_byte = ~((uint8_t)(i % ((uint8_t)-1)));
for (j = 0; j < gopts->page_size; j++) {
uint8_t v = *(uint8_t *)(p + (i * gopts->page_size) + j);
if (v != expected_byte)
- err("unexpected page contents");
+ return 1;
}
}
+
+ return 0;
+}
+
+static int check_memory_contents(uffd_global_test_opts_t *gopts, char *p)
+{
+ return __check_memory_contents(0, gopts->nr_pages, gopts, p);
}
static void uffd_minor_test_common(uffd_global_test_opts_t *gopts, bool test_collapse, bool test_wp)
@@ -539,6 +554,8 @@ static void uffd_minor_test_common(uffd_global_test_opts_t *gopts, bool test_col
pthread_t uffd_mon;
char c = '\0';
struct uffd_args args = { 0 };
+ unsigned long checked = 0;
+ bool bad_contents;
args.gopts = gopts;
/*
@@ -564,24 +581,65 @@ static void uffd_minor_test_common(uffd_global_test_opts_t *gopts, bool test_col
if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
err("uffd_poll_thread create");
+ if (test_collapse) {
+ /*
+ * Read just a single page and try collapsing. The collapse
+ * should either be rejected or be a no-op.
+ */
+ if (__check_memory_contents(0, 1, gopts, gopts->area_dst_alias))
+ err("unexpected memory contents before collapse");
+
+ /* MADV_COLLAPSE might return EINVAL for uffd-minor VMAs. */
+ madvise(gopts->area_dst_alias, gopts->nr_pages * gopts->page_size,
+ MADV_COLLAPSE);
+ /*
+ * If the above collapse mapped pages that were not explicitly
+ * CONTINUE'd, the below __check_memory_contents() will not
+ * fault on some pages, resulting in incorrect contents. The
+ * PTE for the first page may get retracted, so avoid checking
+ * that page, as we might take a second fault, flipping the
+ * contents a second time.
+ */
+ checked = 1;
+ }
+
/*
* Read each of the pages back using the UFFD-registered mapping. We
* expect that the first time we touch a page, it will result in a minor
* fault. uffd_poll_thread will resolve the fault by bit-flipping the
* page's contents, and then issuing a CONTINUE ioctl.
*/
- check_memory_contents(gopts, gopts->area_dst_alias);
+ bad_contents = !!__check_memory_contents(checked, gopts->nr_pages - checked,
+ gopts, gopts->area_dst_alias);
if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
err("pipe write");
if (pthread_join(uffd_mon, NULL))
err("join() failed");
+ if (bad_contents && test_collapse) {
+ uffd_test_fail("unexpected memory contents after collapse");
+ return;
+ }
+
+ if (bad_contents) {
+ uffd_test_fail("unexpected memory contents");
+ return;
+ }
+
if (test_collapse) {
+ /*
+ * MADV_COLLAPSE will fail unless userfaultfd-minor is
+ * unregistered.
+ */
+ if (uffd_unregister(gopts->uffd, gopts->area_dst_alias,
+ gopts->nr_pages * gopts->page_size))
+ err("uffd_unregister before MADV_COLLAPSE failed");
+
+ /* MADV_COLLAPSE should succeed with userfaultfd unregistered. */
if (madvise(gopts->area_dst_alias, gopts->nr_pages * gopts->page_size,
MADV_COLLAPSE)) {
- /* It's fine to fail for this one... */
- uffd_test_skip("MADV_COLLAPSE failed");
+ uffd_test_fail("MADV_COLLAPSE failed");
return;
}
@@ -593,7 +651,10 @@ static void uffd_minor_test_common(uffd_global_test_opts_t *gopts, bool test_col
* This won't cause uffd-fault - it purely just makes sure there
* was no corruption.
*/
- check_memory_contents(gopts, gopts->area_dst_alias);
+ if (check_memory_contents(gopts, gopts->area_dst_alias)) {
+ uffd_test_fail("unexpected memory contents");
+ return;
+ }
}
if (args.missing_faults != 0 || args.minor_faults != gopts->nr_pages)
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs
2026-09-10 2:34 [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs James Houghton
2026-09-10 2:34 ` [PATCH v3 2/2] mm: selftests: Adjust the MADV_COLLAPSE uffd-minor selftests James Houghton
@ 2026-09-10 23:24 ` Andrew Morton
2026-09-11 0:02 ` James Houghton
1 sibling, 1 reply; 6+ messages in thread
From: Andrew Morton @ 2026-09-10 23:24 UTC (permalink / raw)
To: James Houghton
Cc: David Hildenbrand, Lorenzo Stoakes, Zi Yan, Baolin Wang, liam,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Yang Shi, zokeefe, hughd, Kiryl Shutsemau, linux-mm,
linux-kernel
On Thu, 10 Sep 2026 02:34:10 +0000 James Houghton <jthoughton@google.com> wrote:
> Userfaultfd minor faults provides userspace with the ability to manually
> install PTEs with UFFDIO_CONTINUE. Right now, MADV_COLLAPSE can map
> holes in the VMA when a naturally-aligned THP is present. This is not
> true for khugepaged collapse: the PTEs will be retracted, but a PMD will
> not be installed.
>
> When MADV_COLLAPSE installs a PMD that mapped holes in the VMA,
> userspace is likely to expect UFFDIO_CONTINUE to succeed on the
> should-be holes. UFFDIO_CONTINUE will fail and return EEXIST.
>
> This is not inherently a problem, as MADV_COLLAPSE is an explicit
> userspace action. But, especially because MADV_COLLAPSE can be invoked
> by an external process via process_madvise(), a rogue caller could break
> a userfaultfd-minor resolver thread. If khugepaged is later updated to
> install PMDs for khugepaged collapsing, that would be a genuine problem.
>
> Userspace cannot generally use MADV_COLLAPSE to resolve userfault minor
> faults, as MADV_COLLAPSE will only resolve such faults if a
> naturally-aligned THP is present.
>
> The naturally-aligned THP case is the only case where this quirk exists.
> Collapsing otherwise requires all PTEs to be present for
> userfaultfd-registered VMAs (i.e., max none PTEs is 0), which is
> correct. This check is essentially bypassed for naturally-aligned THPs.
>
> ...
>
> - Drop cc:stable and update the changelog for the main patch.
> khugepaged doesn't actually install PMDs, so it is impossible for the
> kernel to silently install mappings that userspace didn't ask for.
> Therefore this patch does not need to be backported.
Thanks for explaining that. It helps. My life :(
Sashiko had a stupid niggle about the selftest change and made quite
disturbing claims about the existing try_collapse_pte_mapped_thp() code:
https://sashiko.dev/#/patchset/20260910023411.514987-1-jthoughton@google.com
I'll queue the series for test-n-review.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs
2026-09-10 23:24 ` [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs Andrew Morton
@ 2026-09-11 0:02 ` James Houghton
2026-09-11 0:06 ` James Houghton
2026-09-11 0:57 ` James Houghton
0 siblings, 2 replies; 6+ messages in thread
From: James Houghton @ 2026-09-11 0:02 UTC (permalink / raw)
To: akpm
Cc: baohua, baolin.wang, david, dev.jain, hughd, jthoughton, kas,
lance.yang, liam, linux-kernel, linux-mm, ljs, nico.pache,
ryan.roberts, shy828301, usama.arif, ziy, zokeefe
On Thu, Sep 10, 2026 at 4:24 PM Andrew Morton <akpm@linux-foundation.org> wrote:
> Sashiko had a stupid niggle about the selftest change and made quite
> disturbing claims about the existing try_collapse_pte_mapped_thp() code:
>
> https://sashiko.dev/#/patchset/20260910023411.514987-1-jthoughton@google.com
Thanks, it looks like the note it wrote about the selftest is genuine; it is
okay for MADV_COLLAPSE to fail. So I shouldn't have changed uffd_test_skip() to
uffd_test_fail().
Andrew, if you can, please add this diff as part of the patch to queue. (It's
a partial revert.) Or I could send the a new version of the patch as a reply
to patch #2 email. Or a whole v4, whatever's easiest.
diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
index 6f2360f9b75d..d8e40b770d88 100644
--- a/tools/testing/selftests/mm/uffd-unit-tests.c
+++ b/tools/testing/selftests/mm/uffd-unit-tests.c
@@ -639,7 +639,8 @@ static void uffd_minor_test_common(uffd_global_test_opts_t *gopts, bool test_col
/* MADV_COLLAPSE should succeed with userfaultfd unregistered. */
if (madvise(gopts->area_dst_alias, gopts->nr_pages * gopts->page_size,
MADV_COLLAPSE)) {
- uffd_test_fail("MADV_COLLAPSE failed");
+ /* It's fine to fail for this one... */
+ uffd_test_skip("MADV_COLLAPSE failed");
return;
}
With respect to the note about try_collapse_pte_mapped_thp() breaking POSIX
SIGBUS semantics, it definitely seems plausible. I'll look into it.
> I'll queue the series for test-n-review.
Thanks!
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs
2026-09-11 0:02 ` James Houghton
@ 2026-09-11 0:06 ` James Houghton
2026-09-11 0:57 ` James Houghton
1 sibling, 0 replies; 6+ messages in thread
From: James Houghton @ 2026-09-11 0:06 UTC (permalink / raw)
To: akpm
Cc: baohua, baolin.wang, david, dev.jain, hughd, kas, lance.yang,
liam, linux-kernel, linux-mm, ljs, nico.pache, ryan.roberts,
shy828301, usama.arif, ziy, zokeefe
On Thu, Sep 10, 2026 at 5:03 PM James Houghton <jthoughton@google.com> wrote:
>
> On Thu, Sep 10, 2026 at 4:24 PM Andrew Morton <akpm@linux-foundation.org> wrote:
> > Sashiko had a stupid niggle about the selftest change and made quite
> > disturbing claims about the existing try_collapse_pte_mapped_thp() code:
> >
> > https://sashiko.dev/#/patchset/20260910023411.514987-1-jthoughton@google.com
>
> Thanks, it looks like the note it wrote about the selftest is genuine; it is
> okay for MADV_COLLAPSE to fail. So I shouldn't have changed uffd_test_skip() to
> uffd_test_fail().
>
> Andrew, if you can, please add this diff as part of the patch to queue. (It's
> a partial revert.) Or I could send the a new version of the patch as a reply
> to patch #2 email. Or a whole v4, whatever's easiest.
>
> diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
> index 6f2360f9b75d..d8e40b770d88 100644
> --- a/tools/testing/selftests/mm/uffd-unit-tests.c
> +++ b/tools/testing/selftests/mm/uffd-unit-tests.c
> @@ -639,7 +639,8 @@ static void uffd_minor_test_common(uffd_global_test_opts_t *gopts, bool test_col
> /* MADV_COLLAPSE should succeed with userfaultfd unregistered. */
> if (madvise(gopts->area_dst_alias, gopts->nr_pages * gopts->page_size,
> MADV_COLLAPSE)) {
> - uffd_test_fail("MADV_COLLAPSE failed");
> + /* It's fine to fail for this one... */
> + uffd_test_skip("MADV_COLLAPSE failed");
> return;
> }
Ah, the
/* MADV_COLLAPSE should succeed with userfaultfd unregistered. */
comment at the top of that hunk should also be removed.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs
2026-09-11 0:02 ` James Houghton
2026-09-11 0:06 ` James Houghton
@ 2026-09-11 0:57 ` James Houghton
1 sibling, 0 replies; 6+ messages in thread
From: James Houghton @ 2026-09-11 0:57 UTC (permalink / raw)
To: akpm
Cc: baohua, baolin.wang, david, dev.jain, hughd, jthoughton, kas,
lance.yang, liam, linux-kernel, linux-mm, ljs, nico.pache,
ryan.roberts, shy828301, usama.arif, ziy, zokeefe
Okay so it appears to be a real bug. Here's a diff of what I think would fix it,
though I haven't tested it. I've also included an AI-generated selftest that
checks the behavior when you MADV_COLLAPSE a VMA that maps a shmem file that
had a THP and got truncated.
I'm happy to send this patch on its own to see what others think. I'll test it
properly tomorrow.
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index e6947fe142ee..db660cd9d75d 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1863,6 +1863,7 @@ static enum scan_result try_collapse_pte_mapped_thp(struct mm_struct *mm, unsign
pte_t *start_pte, *pte;
pmd_t *pmd, pgt_pmd;
spinlock_t *pml = NULL, *ptl;
+ pgoff_t file_end;
int i;
mmap_assert_locked(mm);
@@ -1901,6 +1902,11 @@ static enum scan_result try_collapse_pte_mapped_thp(struct mm_struct *mm, unsign
if (userfaultfd_minor(vma))
return SCAN_PTE_UFFD;
+ /* Do not map pages past the end of the file. */
+ file_end = DIV_ROUND_UP(i_size_read(file_inode(vma->vm_file)), PAGE_SIZE);
+ if (linear_page_index(vma, haddr) + HPAGE_PMD_NR > file_end)
+ return SCAN_TRUNCATED;
+
folio = filemap_lock_folio(vma->vm_file->f_mapping,
linear_page_index(vma, haddr));
if (IS_ERR(folio))
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index 2d5366196e30..794636dfa485 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -104,6 +104,7 @@ TEST_GEN_FILES += guard-regions
TEST_GEN_FILES += merge
TEST_GEN_FILES += rmap
TEST_GEN_FILES += folio_split_race_test
+TEST_GEN_FILES += collapse_truncate
ifneq ($(ARCH),arm64)
TEST_GEN_FILES += soft-dirty
diff --git a/tools/testing/selftests/mm/collapse_truncate.c b/tools/testing/selftests/mm/collapse_truncate.c
new file mode 100644
index 000000000000..dd3415c51246
--- /dev/null
+++ b/tools/testing/selftests/mm/collapse_truncate.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test creating a 2M tmpfs file, mapping it 2M-aligned, MADV_NOHUGEPAGE,
+ * writing to each page, truncating 4K off the file, MADV_COLLAPSE, and
+ * attempting to read from the truncated 4K.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+
+#include "kselftest.h"
+#include "vm_util.h"
+
+#ifndef MADV_NOHUGEPAGE
+#define MADV_NOHUGEPAGE 15
+#endif
+
+#ifndef MADV_HUGEPAGE
+#define MADV_HUGEPAGE 14
+#endif
+
+#ifndef MADV_COLLAPSE
+#define MADV_COLLAPSE 25
+#endif
+
+#ifndef MFD_CLOEXEC
+#define MFD_CLOEXEC 0x0001U
+#endif
+
+#ifndef __NR_memfd_create
+#if defined(__x86_64__)
+#define __NR_memfd_create 319
+#elif defined(__aarch64__)
+#define __NR_memfd_create 279
+#elif defined(__i386__)
+#define __NR_memfd_create 356
+#endif
+#endif
+
+#define SIZE_2MB (2 * 1024 * 1024UL)
+#define PAGE_SIZE_DEFAULT 4096UL
+
+static inline int sys_memfd_create(const char *name, unsigned int flags)
+{
+#ifdef SYS_memfd_create
+ return syscall(SYS_memfd_create, name, flags);
+#elif defined(__NR_memfd_create)
+ return syscall(__NR_memfd_create, name, flags);
+#else
+ return memfd_create(name, flags);
+#endif
+}
+
+static sigjmp_buf jmpbuf;
+static volatile sig_atomic_t got_sigbus;
+static volatile sig_atomic_t got_sigsegv;
+
+static void sig_handler(int sig, siginfo_t *si, void *unused)
+{
+ if (sig == SIGBUS)
+ got_sigbus = 1;
+ else if (sig == SIGSEGV)
+ got_sigsegv = 1;
+ siglongjmp(jmpbuf, 1);
+}
+
+static void setup_sighandlers(void)
+{
+ struct sigaction act = {
+ .sa_sigaction = sig_handler,
+ .sa_flags = SA_SIGINFO | SA_NODEFER,
+ };
+ sigemptyset(&act.sa_mask);
+ if (sigaction(SIGBUS, &act, NULL))
+ ksft_exit_fail_msg("sigaction(SIGBUS) failed: %s\n", strerror(errno));
+ if (sigaction(SIGSEGV, &act, NULL))
+ ksft_exit_fail_msg("sigaction(SIGSEGV) failed: %s\n", strerror(errno));
+}
+
+static void test_collapse_truncate(bool clear_nohugepage)
+{
+ unsigned long hpage_size = read_pmd_pagesize();
+ unsigned long page_size = psize();
+ size_t truncated_size;
+ const char *test_name;
+ int fd, collapse_ret, collapse_err;
+ void *res, *p;
+ uintptr_t addr, aligned_addr;
+ bool read_succeeded = false;
+ char val = 0;
+
+ if (!hpage_size)
+ hpage_size = SIZE_2MB;
+ if (!page_size)
+ page_size = PAGE_SIZE_DEFAULT;
+
+ truncated_size = hpage_size - page_size;
+ test_name = clear_nohugepage ?
+ "collapse_truncate (clear MADV_NOHUGEPAGE)" :
+ "collapse_truncate (with MADV_NOHUGEPAGE)";
+
+ ksft_print_msg("[RUN] %s\n", test_name);
+
+ /* 1. Create a 2M tmpfs file */
+ fd = sys_memfd_create("collapse_truncate_tmpfs", MFD_CLOEXEC);
+ if (fd < 0) {
+ ksft_test_result_skip("- %s: memfd_create failed: %s\n",
+ test_name, strerror(errno));
+ return;
+ }
+
+ if (ftruncate(fd, hpage_size) < 0) {
+ ksft_print_msg("ftruncate to %lu failed: %s\n", hpage_size, strerror(errno));
+ close(fd);
+ ksft_test_result_fail("- %s: initial ftruncate failed\n", test_name);
+ return;
+ }
+
+ /* 2. Map it such that the mapping is 2M-aligned */
+ res = mmap(NULL, 2 * hpage_size, PROT_NONE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (res == MAP_FAILED) {
+ ksft_print_msg("reservation mmap failed: %s\n", strerror(errno));
+ close(fd);
+ ksft_test_result_fail("- %s: reservation mmap failed\n", test_name);
+ return;
+ }
+
+ addr = (uintptr_t)res;
+ aligned_addr = (addr + hpage_size - 1) & ~(hpage_size - 1);
+ munmap(res, 2 * hpage_size);
+
+ p = mmap((void *)aligned_addr, hpage_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_FIXED, fd, 0);
+ if (p == MAP_FAILED) {
+ ksft_print_msg("aligned mmap failed: %s\n", strerror(errno));
+ close(fd);
+ ksft_test_result_fail("- %s: aligned mmap failed\n", test_name);
+ return;
+ }
+
+ /* 3. MADV_NOHUGEPAGE the mapping */
+ if (madvise(p, hpage_size, MADV_NOHUGEPAGE) < 0) {
+ ksft_print_msg("madvise(MADV_NOHUGEPAGE) failed: %s\n", strerror(errno));
+ munmap(p, hpage_size);
+ close(fd);
+ ksft_test_result_fail("- %s: madvise(MADV_NOHUGEPAGE) failed\n", test_name);
+ return;
+ }
+
+ /* 4. Write to each page of the mapping */
+ for (size_t offset = 0; offset < hpage_size; offset += page_size) {
+ *(volatile char *)((char *)p + offset) = 'A';
+ }
+
+ /* 5. Truncate 4K off the file */
+ if (ftruncate(fd, truncated_size) < 0) {
+ ksft_print_msg("ftruncate to %lu failed: %s\n", truncated_size, strerror(errno));
+ munmap(p, hpage_size);
+ close(fd);
+ ksft_test_result_fail("- %s: ftruncate -4K failed\n", test_name);
+ return;
+ }
+
+ /*
+ * If clear_nohugepage is requested, clear VM_NOHUGEPAGE using
+ * MADV_HUGEPAGE to allow MADV_COLLAPSE to proceed without EINVAL.
+ */
+ if (clear_nohugepage) {
+ if (madvise(p, hpage_size, MADV_HUGEPAGE) < 0) {
+ ksft_print_msg("madvise(MADV_HUGEPAGE) failed: %s\n", strerror(errno));
+ }
+ }
+
+ /* 6. MADV_COLLAPSE the mapping */
+ collapse_ret = madvise(p, hpage_size, MADV_COLLAPSE);
+ collapse_err = (collapse_ret == 0) ? 0 : errno;
+ ksft_print_msg("madvise(MADV_COLLAPSE) ret=%d (errno=%d: %s)\n",
+ collapse_ret, collapse_err,
+ collapse_ret == 0 ? "Success" : strerror(collapse_err));
+
+ if (collapse_ret == 0) {
+ bool is_huge = check_huge_shmem(p, hpage_size, 1, hpage_size);
+ ksft_print_msg("check_huge_shmem: %s\n",
+ is_huge ? "huge PMD mapped" : "not huge PMD mapped");
+ }
+
+ /* 7. Attempt to read from the last 4K (the one that was truncated) */
+ got_sigbus = 0;
+ got_sigsegv = 0;
+
+ if (sigsetjmp(jmpbuf, 1) == 0) {
+ val = *(volatile char *)((char *)p + truncated_size);
+ read_succeeded = true;
+ }
+
+ if (read_succeeded) {
+ ksft_print_msg("Read from truncated 4K succeeded without SIGBUS! val=0x%02x ('%c')\n",
+ (unsigned char)val, val ? val : ' ');
+ /*
+ * Accessing beyond EOF of a file-backed mapping MUST result
+ * in SIGBUS. If the read succeeded, a huge PMD erroneously
+ * mapped beyond the end of the file.
+ */
+ ksft_test_result_fail("- %s: read beyond EOF succeeded without SIGBUS (PMD mapped past EOF)\n",
+ test_name);
+ } else if (got_sigbus) {
+ ksft_print_msg("Caught SIGBUS on reading truncated 4K page as expected\n");
+ ksft_test_result_pass("- %s: SIGBUS received on reading truncated page\n",
+ test_name);
+ } else if (got_sigsegv) {
+ ksft_print_msg("Caught unexpected SIGSEGV on reading truncated 4K page\n");
+ ksft_test_result_fail("- %s: caught SIGSEGV instead of SIGBUS\n",
+ test_name);
+ } else {
+ ksft_test_result_fail("- %s: unknown error reading truncated page\n",
+ test_name);
+ }
+
+ munmap(p, hpage_size);
+ close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+ int opt;
+ bool run_all = true;
+ bool only_nohugepage = false;
+ bool only_clear = false;
+
+ while ((opt = getopt(argc, argv, "nc12h")) != -1) {
+ switch (opt) {
+ case 'n':
+ case '1':
+ only_nohugepage = true;
+ run_all = false;
+ break;
+ case 'c':
+ case '2':
+ only_clear = true;
+ run_all = false;
+ break;
+ case 'h':
+ printf("Usage: %s [-n|-1] [-c|-2] [-h]\n", argv[0]);
+ printf(" -n, -1: Run only test with MADV_NOHUGEPAGE retained\n");
+ printf(" -c, -2: Run only test with MADV_NOHUGEPAGE cleared before collapse\n");
+ printf(" -h: Show this help message\n");
+ return 0;
+ default:
+ return 1;
+ }
+ }
+
+ ksft_print_header();
+ ksft_set_plan(run_all ? 2 : 1);
+
+ setup_sighandlers();
+
+ if (run_all || only_nohugepage)
+ test_collapse_truncate(false);
+ if (run_all || only_clear)
+ test_collapse_truncate(true);
+
+ ksft_finished();
+}
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-11 0:57 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-10 2:34 [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs James Houghton
2026-09-10 2:34 ` [PATCH v3 2/2] mm: selftests: Adjust the MADV_COLLAPSE uffd-minor selftests James Houghton
2026-09-10 23:24 ` [PATCH v3 1/2] mm/khugepaged: Never install PMDs in uffd-minor-registered VMAs Andrew Morton
2026-09-11 0:02 ` James Houghton
2026-09-11 0:06 ` James Houghton
2026-09-11 0:57 ` James Houghton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox