* [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; as well as URLs for NNTP newsgroup(s).