* Re: [LTP] [Valgrind-developers] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-22 13:08 ` Martin Cermak via ltp
@ 2026-04-23 14:44 ` Martin Cermak via ltp
0 siblings, 0 replies; 8+ messages in thread
From: Martin Cermak via ltp @ 2026-04-23 14:44 UTC (permalink / raw)
To: Petr Vorel; +Cc: valgrind-developers, ltp
On Wed 2026-04-22 15:08 , Martin Cermak wrote:
> On Wed 2026-04-22 14:17 , Petr Vorel wrote:
> >
> >
> > > On 4/22/26 7:37 AM, Petr Vorel wrote:
> > > > > However, some of the testcases can be easily changed to use forked
> > > > > processes instead of threads. That's what this patch does. When
> > > > > client program forks, Valgrind forks too, and that allows for the needed
> > > > > parallelism to handle the page fault.
> > > > You understand process vs. threads more than me. But shouldn't mmap() use
> > > > MAP_SHARED instead of MAP_PRIVATE for those which aren't using /dev/userfaultfd?
> >
> > > The documentation for userfaultfd mentions threads. I'm afraid we'll
> > > lose vital coverage if we move to forked processes.
> >
> > +1, at least some tests would keep threads (not all userfaultfd tests would be
> > converted, but yes, that's why I suggested to use MAP_SHARED, which could be
> > similar to threads (yes, the difference between processes and threads in Linux
> > kernel is not that huge as both are created in clone(), it's mostly about what
> > is shared).
>
> My goal is to make at least part of these userfaultfd tests
> compatible with Valgrind. I've experimented with clone() and it
> seems like Valgrind has limited support for it. That said,
> adding another set of userfaultfd tests using fork() or clone()
> instead of threads might help. Some of such new tests will be
> "incompatible" with valgrind, but it might be a test coverage
> improvement.
Here is an ai-assisted patch that works for me:
From 693f3015d39b39e2c263aac57c183e3e4cf7df4d Mon Sep 17 00:00:00 2001
From: Martin Cermak <mcermak@redhat.com>
Date: Thu, 23 Apr 2026 15:51:10 +0200
Subject: [PATCH] Provide Valgrind-compatible userfaultfd tests
The userfaultfd tests use threads. Threads are executed serially
in Valgrind, but parallelism is important for userfaultfd tests.
When a page fault happens in parent thread, kernel stops it, and
waits for the handler thread to handle the page fault. That said,
userfaultfd tests 01 - 06 stall in Valgrind.
This update comes with second set of userfaultfd tests 07 - 12.
These additional tests use fork() or clone(), thus a separate
process instead of threads. Support for clone() is also limited
in Valgrind. For that reason only tests 07, 09 and 10 are "Valgrind
compatible". But it's a test coverage improvement.
Valgrind can filter LTP tests on the per test binary basis. That's
why this update adds separate tests 07 - 12.
Assisted-by: Anthropic Claude
Reviewed-by: Martin Cermak <mcermak@redhat.com>
---
include/lapi/sched.h | 8 +
.../kernel/syscalls/userfaultfd/.gitignore | 6 +
.../kernel/syscalls/userfaultfd/Makefile | 3 +
.../syscalls/userfaultfd/userfaultfd07.c | 136 +++++++++++++++
.../syscalls/userfaultfd/userfaultfd08.c | 113 +++++++++++++
.../syscalls/userfaultfd/userfaultfd09.c | 141 ++++++++++++++++
.../syscalls/userfaultfd/userfaultfd10.c | 106 ++++++++++++
.../syscalls/userfaultfd/userfaultfd11.c | 140 ++++++++++++++++
.../syscalls/userfaultfd/userfaultfd12.c | 157 ++++++++++++++++++
9 files changed, 810 insertions(+)
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd07.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd10.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd11.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd12.c
diff --git a/include/lapi/sched.h b/include/lapi/sched.h
index 05b322c1c..b33de6657 100644
--- a/include/lapi/sched.h
+++ b/include/lapi/sched.h
@@ -121,6 +121,14 @@ static inline int getcpu(unsigned *cpu, unsigned *node)
# define CLONE_FS 0x00000200
#endif
+#ifndef CLONE_FILES
+# define CLONE_FILES 0x00000400
+#endif
+
+#ifndef CLONE_SIGHAND
+# define CLONE_SIGHAND 0x00000800
+#endif
+
#ifndef CLONE_PIDFD
# define CLONE_PIDFD 0x00001000
#endif
diff --git a/testcases/kernel/syscalls/userfaultfd/.gitignore b/testcases/kernel/syscalls/userfaultfd/.gitignore
index bc32fdf3b..59d28c6f3 100644
--- a/testcases/kernel/syscalls/userfaultfd/.gitignore
+++ b/testcases/kernel/syscalls/userfaultfd/.gitignore
@@ -4,3 +4,9 @@
/userfaultfd04
/userfaultfd05
/userfaultfd06
+/userfaultfd07
+/userfaultfd08
+/userfaultfd09
+/userfaultfd10
+/userfaultfd11
+/userfaultfd12
diff --git a/testcases/kernel/syscalls/userfaultfd/Makefile b/testcases/kernel/syscalls/userfaultfd/Makefile
index 3252e47df..e4ed56b4b 100644
--- a/testcases/kernel/syscalls/userfaultfd/Makefile
+++ b/testcases/kernel/syscalls/userfaultfd/Makefile
@@ -17,3 +17,6 @@ userfaultfd03: CFLAGS += -pthread
userfaultfd04: CFLAGS += -pthread
userfaultfd05: CFLAGS += -pthread
userfaultfd06: CFLAGS += -pthread
+# Tests 07-12: Valgrind-compatible variants using fork/clone
+# Tests 07, 09, 10: fork-based (no pthread needed)
+# Tests 08, 11, 12: clone-based (no pthread needed)
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd07.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd07.c
new file mode 100644
index 000000000..bebf0e921
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd07.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using fork instead of threads for Valgrind compatibility).
+ */
+
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/userfaultfd.h"
+
+#define BEFORE_5_11 1
+#define AFTER_5_11 2
+#define DESC(x) .flags = x, .desc = #x
+
+static struct tcase {
+ int flags;
+ const char *desc;
+ int kver;
+} tcases[] = {
+ { DESC(O_CLOEXEC | O_NONBLOCK) },
+ { DESC(O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY), .kver = AFTER_5_11, },
+};
+
+static int page_size;
+static char *page;
+static void *copy_page;
+static int uffd;
+static int kver;
+
+static void setup(void)
+{
+ if (tst_kvercmp(5, 11, 0) >= 0)
+ kver = AFTER_5_11;
+ else
+ kver = BEFORE_5_11;
+}
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ copy_page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+ SAFE_MUNMAP(copy_page, page_size);
+}
+
+static void *pagefault_handler(void)
+{
+ static struct uffd_msg msg;
+ struct uffdio_copy uffdio_copy = {};
+
+ struct pollfd pollfd;
+ int nready;
+
+ pollfd.fd = uffd;
+ pollfd.events = POLLIN;
+ nready = poll(&pollfd, 1, -1);
+ if (nready == -1)
+ tst_brk(TBROK | TERRNO, "Error on poll");
+
+ SAFE_READ(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ memset(copy_page, 'X', page_size);
+
+ uffdio_copy.src = (unsigned long) copy_page;
+
+ uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_copy.len = page_size;
+ SAFE_IOCTL(uffd, UFFDIO_COPY, &uffdio_copy);
+
+ close(uffd);
+ return NULL;
+}
+
+static void run(unsigned int i)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+ struct tcase *tc = &tcases[i];
+
+ if (tc->kver == AFTER_5_11 && kver == BEFORE_5_11)
+ tst_brk(TCONF, "%s requires kernel >= 5.11", tc->desc);
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(tc->flags, false);
+
+ uffdio_api.api = UFFD_API;
+ SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
+
+ uffdio_register.range.start = (unsigned long) page;
+ uffdio_register.range.len = page_size;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ char c = page[0xf];
+
+ if (c == 'X')
+ tst_res(TPASS, "Pagefault handled!");
+ else
+ tst_res(TFAIL, "Pagefault not handled!");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test = run,
+ .tcnt = ARRAY_SIZE(tcases),
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
new file mode 100644
index 000000000..f6bde86cc
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using clone with CLONE_VM instead of threads
+ * for Valgrind compatibility) using UFFDIO_MOVE.
+ */
+
+#include "config.h"
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "tst_clone.h"
+#include "lapi/sched.h"
+#include "lapi/userfaultfd.h"
+
+#define CHILD_STACK_SIZE (1024 * 1024)
+
+static int page_size;
+static char *page;
+static void *move_page;
+static int uffd;
+static void *child_stack;
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ move_page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+ /*
+ * After UFFDIO_MOVE, move_page is invalid and should not be unmapped.
+ * The kernel has already removed the mapping during the move operation.
+ */
+}
+
+static int pagefault_handler(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ struct uffd_msg msg;
+ struct uffdio_move uffdio_move = {};
+
+ SAFE_READ(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ memset(move_page, 'X', page_size);
+
+ uffdio_move.src = (unsigned long) move_page;
+
+ uffdio_move.dst = (unsigned long) msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_move.len = page_size;
+ SAFE_IOCTL(uffd, UFFDIO_MOVE, &uffdio_move);
+
+ close(uffd);
+ return 0;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC, false);
+
+ uffdio_api.api = UFFD_API;
+ SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
+
+ uffdio_register.range.start = (unsigned long) page;
+ uffdio_register.range.len = page_size;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = ltp_clone(CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND | SIGCHLD,
+ pagefault_handler, NULL, CHILD_STACK_SIZE, child_stack);
+ if (pid == -1)
+ tst_brk(TBROK | TERRNO, "ltp_clone failed");
+
+ char c = page[0xf];
+
+ if (c == 'X')
+ tst_res(TPASS, "Pagefault handled via UFFDIO_MOVE");
+ else
+ tst_res(TFAIL, "Pagefault not handled via UFFDIO_MOVE");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .min_kver = "6.8",
+ .forks_child = 1,
+ .bufs = (struct tst_buffers []) {
+ {&child_stack, .size = CHILD_STACK_SIZE},
+ {},
+ },
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
new file mode 100644
index 000000000..e1190a4ce
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using fork instead of threads for Valgrind compatibility)
+ * using /dev/userfaultfd instead of syscall, using USERFAULTFD_IOC_NEW ioctl to create
+ * the uffd & UFFDIO_COPY.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/userfaultfd.h"
+
+static int page_size;
+static char *page;
+static void *copy_page;
+static int uffd;
+
+static void setup(void)
+{
+ if (access("/dev/userfaultfd", F_OK) != 0) {
+ int res = (tst_kvercmp(6, 1, 0) < 0) ? TCONF : TBROK;
+
+ tst_brk(res, "/dev/userfaultfd not found");
+ }
+}
+
+static int open_userfaultfd(int flags)
+{
+ int fd, fd2;
+
+ fd = SAFE_OPEN("/dev/userfaultfd", O_RDWR);
+
+ fd2 = SAFE_IOCTL(fd, USERFAULTFD_IOC_NEW, flags);
+
+ SAFE_CLOSE(fd);
+
+ return fd2;
+}
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ copy_page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+ SAFE_MUNMAP(copy_page, page_size);
+}
+
+static void *pagefault_handler(void)
+{
+ static struct uffd_msg msg;
+ struct uffdio_copy uffdio_copy = {};
+
+ struct pollfd pollfd;
+ int nready;
+
+ pollfd.fd = uffd;
+ pollfd.events = POLLIN;
+ nready = poll(&pollfd, 1, -1);
+ if (nready == -1)
+ tst_brk(TBROK | TERRNO, "Error on poll");
+
+ SAFE_READ(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ memset(copy_page, 'X', page_size);
+
+ uffdio_copy.src = (unsigned long) copy_page;
+
+ uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_copy.len = page_size;
+ SAFE_IOCTL(uffd, UFFDIO_COPY, &uffdio_copy);
+
+ close(uffd);
+ return NULL;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+
+ set_pages();
+
+ uffd = open_userfaultfd(O_CLOEXEC | O_NONBLOCK);
+
+ uffdio_api.api = UFFD_API;
+ SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
+
+ uffdio_register.range.start = (unsigned long) page;
+ uffdio_register.range.len = page_size;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ char c = page[0xf];
+
+ if (c == 'X')
+ tst_res(TPASS, "Pagefault handled via /dev/userfaultfd");
+ else
+ tst_res(TFAIL, "Pagefault not handled via /dev/userfaultfd");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .needs_root = 1,
+ .setup = setup,
+ .test_all = run,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_USERFAULTFD=y",
+ NULL
+ },
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd10.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd10.c
new file mode 100644
index 000000000..8e9bd0b0c
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd10.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using fork instead of threads for Valgrind compatibility)
+ * using UFFDIO_ZEROPAGE.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/userfaultfd.h"
+
+static int page_size;
+static char *page;
+static int uffd;
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+}
+
+static void *pagefault_handler(void)
+{
+ static struct uffd_msg msg;
+ struct uffdio_zeropage uffdio_zeropage = {};
+
+ struct pollfd pollfd;
+ int nready;
+
+ pollfd.fd = uffd;
+ pollfd.events = POLLIN;
+ nready = poll(&pollfd, 1, -1);
+ if (nready == -1)
+ tst_brk(TBROK | TERRNO, "Error on poll");
+
+ SAFE_READ(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ uffdio_zeropage.range.start = msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_zeropage.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_ZEROPAGE, &uffdio_zeropage);
+
+ close(uffd);
+ return NULL;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+
+ uffdio_api.api = UFFD_API;
+ SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
+
+ uffdio_register.range.start = (unsigned long) page;
+ uffdio_register.range.len = page_size;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ for (int i = 0; i < page_size; i++) {
+ if (page[i] != 0) {
+ tst_res(TFAIL, "page[%d]=0x%x not zero", i, page[i]);
+ return;
+ }
+ }
+
+ tst_res(TPASS, "Pagefault handled with UFFDIO_ZEROPAGE");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd11.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd11.c
new file mode 100644
index 000000000..52590d117
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd11.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using clone with CLONE_VM instead of threads
+ * for Valgrind compatibility) testing UFFDIO_WRITEPROTECT_MODE_WP.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/sched.h"
+#include "lapi/userfaultfd.h"
+
+static int page_size;
+static char *page;
+static int uffd;
+static volatile int wp_fault_seen;
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ memset(page, 0, page_size);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+}
+
+static void pagefault_handler(void)
+{
+ struct uffd_msg msg;
+ struct uffdio_writeprotect uffdio_writeprotect = {};
+
+ struct pollfd pollfd;
+ int nready;
+
+ pollfd.fd = uffd;
+ pollfd.events = POLLIN;
+ nready = poll(&pollfd, 1, -1);
+ if (nready == -1)
+ tst_brk(TBROK | TERRNO, "Error on poll");
+
+ SAFE_READ(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TFAIL, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ if (!(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) ||
+ !(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE)) {
+ tst_brk(TFAIL,
+ "Expected WP+WRITE fault but flags=%lx",
+ (unsigned long)msg.arg.pagefault.flags);
+ }
+
+ /* While the WP fault is pending, the write must NOT be visible. */
+ if (page[0xf] != 0)
+ tst_brk(TFAIL,
+ "Write became visible while page was write-protected!");
+
+ wp_fault_seen = 1;
+
+ /* Resolve the fault by clearing WP so the writer can resume. */
+ uffdio_writeprotect.range.start = msg.arg.pagefault.address & ~(page_size - 1);
+ uffdio_writeprotect.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_WRITEPROTECT, &uffdio_writeprotect);
+
+ close(uffd);
+}
+
+static void run(void)
+{
+ const struct tst_clone_args clone_args = {
+ .flags = CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND,
+ .exit_signal = SIGCHLD,
+ };
+ pid_t pid;
+ struct uffdio_api uffdio_api;
+ struct uffdio_register uffdio_register;
+ struct uffdio_writeprotect uffdio_writeprotect;
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP;
+
+ SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
+
+ uffdio_register.range.start = (unsigned long) page;
+ uffdio_register.range.len = page_size;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_WP;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ uffdio_writeprotect.range.start = (unsigned long)page;
+ uffdio_writeprotect.range.len = page_size;
+ uffdio_writeprotect.mode = UFFDIO_WRITEPROTECT_MODE_WP;
+
+ SAFE_IOCTL(uffd, UFFDIO_WRITEPROTECT, &uffdio_writeprotect);
+
+ pid = SAFE_CLONE(&clone_args);
+ if (!pid) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ /* Try to write */
+ page[0xf] = 'W';
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+
+ if (wp_fault_seen)
+ tst_res(TPASS, "WRITEPROTECT pagefault handled!");
+ else
+ tst_res(TFAIL, "No WRITEPROTECT pagefault observed");
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .min_kver = "5.7",
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_HAVE_ARCH_USERFAULTFD_WP=y",
+ NULL
+ },
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd12.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd12.c
new file mode 100644
index 000000000..3fd4e3652
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd12.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 SUSE LLC
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using clone with CLONE_VM instead of threads
+ * for Valgrind compatibility) testing UFFDIO_POISON.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "tst_clone.h"
+#include "lapi/sched.h"
+#include "lapi/userfaultfd.h"
+
+#define CHILD_STACK_SIZE (1024 * 1024)
+
+static int page_size;
+static char *page;
+static int uffd;
+static int poison_fault_seen;
+static volatile int sigbus_seen;
+static sigjmp_buf jmpbuf;
+static void *child_stack;
+
+static void sigbus_handler(int sig)
+{
+ if (sig == SIGBUS) {
+ sigbus_seen = 1;
+ siglongjmp(jmpbuf, 1);
+ }
+}
+
+static void setup(void)
+{
+ struct sigaction sa = {};
+
+ sa.sa_handler = sigbus_handler;
+ sigemptyset(&sa.sa_mask);
+ SAFE_SIGACTION(SIGBUS, &sa, NULL);
+}
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ if (page) {
+ SAFE_MUNMAP(page, page_size);
+ page = NULL;
+ }
+}
+
+static int pagefault_handler(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ struct uffd_msg msg;
+ struct uffdio_poison uffdio_poison = {};
+ struct pollfd pollfd;
+ int nready;
+
+ pollfd.fd = uffd;
+ pollfd.events = POLLIN;
+ nready = poll(&pollfd, 1, -1);
+ if (nready == -1)
+ tst_brk(TBROK | TERRNO, "Error on poll");
+
+ SAFE_READ(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TFAIL, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ tst_atomic_store(1, &poison_fault_seen);
+
+ /* Poison the page that triggered the fault */
+ uffdio_poison.range.start = msg.arg.pagefault.address & ~(page_size - 1);
+ uffdio_poison.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_POISON, &uffdio_poison);
+
+ close(uffd);
+ return 0;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+ char dummy;
+
+ poison_fault_seen = 0;
+ sigbus_seen = 0;
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = UFFD_FEATURE_POISON;
+
+ SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
+
+ if (!(uffdio_api.features & UFFD_FEATURE_POISON))
+ tst_brk(TCONF, "UFFD_FEATURE_POISON not supported");
+
+ uffdio_register.range.start = (unsigned long) page;
+ uffdio_register.range.len = page_size;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = ltp_clone(CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND | SIGCHLD,
+ pagefault_handler, NULL, CHILD_STACK_SIZE, child_stack);
+ if (pid == -1)
+ tst_brk(TBROK | TERRNO, "ltp_clone failed");
+
+ /* Try to read from the page: should trigger fault, get poisoned, then SIGBUS */
+ if (sigsetjmp(jmpbuf, 1) == 0) {
+ LTP_VAR_USED(dummy) = page[0];
+ }
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+
+ int poisoned = tst_atomic_load(&poison_fault_seen);
+
+ if (poisoned && sigbus_seen)
+ tst_res(TPASS, "POISON successfully triggered SIGBUS");
+ else if (poisoned && !sigbus_seen)
+ tst_res(TFAIL, "POISON fault seen but no SIGBUS received");
+ else if (!poisoned && sigbus_seen)
+ tst_res(TFAIL, "SIGBUS received but no poison fault seen");
+ else
+ tst_res(TFAIL, "No poison fault or SIGBUS observed");
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = setup,
+ .cleanup = reset_pages,
+ .forks_child = 1,
+ .bufs = (struct tst_buffers []) {
+ {&child_stack, .size = CHILD_STACK_SIZE},
+ {},
+ },
+};
--
2.53.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread