Re-register the destination without WP while a UFFDIO_COPY_MODE_WP copy waits for its source page, then check that the copy fails with -EINVAL and leaves no uffd-wp bit behind. This covers all memory types. Signed-off-by: Jose A. Perez de Azpillaga --- Changes in v2: - "all memory types" in the commit message - rename copy to result v1: https://lore.kernel.org/20261029221129.89068-3-azpijr@gmail.com tools/testing/selftests/mm/uffd-unit-tests.c | 105 +++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c index 2a97ba1d6feb..cc22b4cde621 100644 --- a/tools/testing/selftests/mm/uffd-unit-tests.c +++ b/tools/testing/selftests/mm/uffd-unit-tests.c @@ -375,6 +375,104 @@ static void uffd_wp_unpopulated_test(uffd_global_test_opts_t *gopts, uffd_test_a uffd_test_pass(); } +struct wp_copy_args { + uffd_global_test_opts_t *gopts; + char *src; + __s64 result; +}; + +static void *wp_copy_thread(void *data) +{ + struct wp_copy_args *args = data; + struct uffdio_copy copy = { + .dst = (unsigned long)args->gopts->area_dst, + .src = (unsigned long)args->src, + .len = args->gopts->page_size, + .mode = UFFDIO_COPY_MODE_WP, + }; + + ioctl(args->gopts->uffd, UFFDIO_COPY, ©); + args->result = copy.copy; + + return NULL; +} + +/* + * UFFDIO_COPY drops its locks to fault in the source page. Re-register the + * destination without WP while it waits there: the MODE_WP copy must then + * fail instead of leaving a uffd-wp bit in a VMA that no longer has WP. + */ +static void uffd_wp_copy_reregister_test(uffd_global_test_opts_t *gopts, + uffd_test_args_t *args) +{ + struct uffdio_api api = { .api = UFFD_API }; + struct wp_copy_args copy_args = { .gopts = gopts }; + unsigned long len = gopts->page_size; + struct uffdio_zeropage zeropage; + struct uffd_msg msg; + struct pollfd pfd; + pthread_t thread; + uint64_t value; + int pagemap_fd; + int src_uffd; + + /* The source fault comes from the kernel, so no UFFD_USER_MODE_ONLY */ + src_uffd = uffd_open(O_CLOEXEC); + if (src_uffd < 0) { + uffd_test_skip("cannot open a kernel-mode userfaultfd"); + return; + } + if (ioctl(src_uffd, UFFDIO_API, &api)) + err("UFFDIO_API failed"); + + copy_args.src = mmap(NULL, len, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (copy_args.src == MAP_FAILED) + err("mmap() failed"); + + if (uffd_register(src_uffd, copy_args.src, len, true, false, false)) + err("register src failed"); + if (uffd_register(gopts->uffd, gopts->area_dst, len, true, true, false)) + err("register dst failed"); + + if (pthread_create(&thread, NULL, wp_copy_thread, ©_args)) + err("pthread_create() failed"); + + /* Wait until UFFDIO_COPY blocks on the source page */ + pfd = (struct pollfd){ .fd = src_uffd, .events = POLLIN }; + if (poll(&pfd, 1, 10000) != 1) + err("no fault on the source page"); + if (read(src_uffd, &msg, sizeof(msg)) != sizeof(msg)) + err("read() failed"); + + if (uffd_unregister(gopts->uffd, gopts->area_dst, len)) + err("unregister dst failed"); + if (uffd_register(gopts->uffd, gopts->area_dst, len, true, false, false)) + err("re-register dst failed"); + + zeropage.range.start = (unsigned long)copy_args.src; + zeropage.range.len = len; + zeropage.mode = 0; + if (ioctl(src_uffd, UFFDIO_ZEROPAGE, &zeropage)) + err("UFFDIO_ZEROPAGE failed"); + + if (pthread_join(thread, NULL)) + err("pthread_join() failed"); + + if (copy_args.result != -EINVAL) + err("UFFDIO_COPY returned %lld, expected %d", + (long long)copy_args.result, -EINVAL); + + pagemap_fd = pagemap_open(); + value = pagemap_get_entry(pagemap_fd, gopts->area_dst); + pagemap_check_wp(value, false); + close(pagemap_fd); + + munmap(copy_args.src, len); + close(src_uffd); + uffd_test_pass(); +} + static void uffd_wp_fork_test_common(uffd_global_test_opts_t *gopts, uffd_test_args_t *args, bool with_event) { @@ -2403,6 +2501,13 @@ uffd_test_case_t uffd_tests[] = { .uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP | UFFD_FEATURE_WP_UNPOPULATED, }, + { + .name = "wp-copy-reregister", + .uffd_fn = uffd_wp_copy_reregister_test, + .mem_targets = MEM_ALL, + .uffd_feature_required = UFFD_FEATURE_PAGEFAULT_FLAG_WP | + UFFD_FEATURE_WP_HUGETLBFS_SHMEM, + }, { .name = "minor", .uffd_fn = uffd_minor_test, -- 2.56.0