From: Ricardo Branco <rbranco@suse.de>
To: ltp@lists.linux.it
Cc: "Claude Opus 5.5" <noreply@anthropic.com>
Subject: [LTP] [PATCH v2] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
Date: Sat, 24 Oct 2026 18:21:45 +0200 [thread overview]
Message-ID: <20261024162146.609312-1-rbranco@suse.de> (raw)
Add userfaultfd08 to exercise UFFDIO_RWPROTECT: a page is registered
with UFFDIO_REGISTER_MODE_RWP, protected with UFFDIO_RWPROTECT_MODE_RWP,
and a plain read from a second thread is verified to raise a
UFFD_PAGEFAULT_FLAG_RWP fault (without UFFD_PAGEFAULT_FLAG_WRITE) that
is resolved by clearing the RWP mode, preserving page contents.
Add userfaultfd09 to exercise UFFDIO_SET_MODE toggling
UFFD_FEATURE_RWP_ASYNC at runtime: with async mode enabled at
UFFDIO_API time, a protected page is accessed and auto-resolved with
no fault message delivered; UFFDIO_SET_MODE then disables async mode,
and the same access on a freshly protected page now blocks and is
delivered as a fault, handled from a second thread as above.
Add the UFFDIO_RWPROTECT and UFFDIO_SET_MODE ioctls, their associated
structs, and the UFFD_FEATURE_RWP/UFFD_FEATURE_RWP_ASYNC feature bits
to lapi/userfaultfd.h for systems whose kernel headers predate them.
Signed-off-by: Ricardo Branco <rbranco@suse.de>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
configure.ac | 2 +
include/lapi/userfaultfd.h | 30 ++-
runtest/syscalls | 2 +
.../kernel/syscalls/userfaultfd/.gitignore | 2 +
.../kernel/syscalls/userfaultfd/Makefile | 2 +
.../syscalls/userfaultfd/userfaultfd08.c | 148 +++++++++++++++
.../syscalls/userfaultfd/userfaultfd09.c | 179 ++++++++++++++++++
7 files changed, 364 insertions(+), 1 deletion(-)
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
diff --git a/configure.ac b/configure.ac
index d75e94162..359a584f5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -298,6 +298,8 @@ AC_CHECK_TYPES([struct sockaddr_vm],,,[
AC_CHECK_TYPES([struct uffdio_move],,,[#include <linux/userfaultfd.h>])
AC_CHECK_TYPES([struct uffdio_poison],,,[#include <linux/userfaultfd.h>])
+AC_CHECK_TYPES([struct uffdio_rwprotect],,,[#include <linux/userfaultfd.h>])
+AC_CHECK_TYPES([struct uffdio_set_mode],,,[#include <linux/userfaultfd.h>])
AC_CHECK_TYPES([struct uffdio_writeprotect],,,[#include <linux/userfaultfd.h>])
AC_CHECK_TYPES([struct keyctl_dh_params, struct keyctl_kdf_params,
diff --git a/include/lapi/userfaultfd.h b/include/lapi/userfaultfd.h
index 4a25e7338..36a28bd7a 100644
--- a/include/lapi/userfaultfd.h
+++ b/include/lapi/userfaultfd.h
@@ -247,6 +247,34 @@ struct uffdio_poison {
};
#endif /* HAVE_STRUCT_UFFDIO_POISON */
+#ifndef HAVE_STRUCT_UFFDIO_RWPROTECT
+#define UFFD_FEATURE_RWP (1<<17)
+#define UFFD_FEATURE_RWP_ASYNC (1<<18)
+#define UFFD_PAGEFAULT_FLAG_RWP (1<<3)
+#define UFFDIO_REGISTER_MODE_RWP ((__u64)1<<3)
+#define _UFFDIO_RWPROTECT (0x09)
+#define UFFDIO_RWPROTECT _IOWR(UFFDIO, _UFFDIO_RWPROTECT, \
+ struct uffdio_rwprotect)
+
+struct uffdio_rwprotect {
+ struct uffdio_range range;
+#define UFFDIO_RWPROTECT_MODE_RWP ((__u64)1<<0)
+#define UFFDIO_RWPROTECT_MODE_DONTWAKE ((__u64)1<<1)
+ __u64 mode;
+};
+#endif /* HAVE_STRUCT_UFFDIO_RWPROTECT */
+
+#ifndef HAVE_STRUCT_UFFDIO_SET_MODE
+#define _UFFDIO_SET_MODE (0x0A)
+#define UFFDIO_SET_MODE _IOW(UFFDIO, _UFFDIO_SET_MODE, \
+ struct uffdio_set_mode)
+
+struct uffdio_set_mode {
+ __u64 enable;
+ __u64 disable;
+};
+#endif /* HAVE_STRUCT_UFFDIO_SET_MODE */
+
#define SAFE_USERFAULTFD(flags, retry) \
safe_userfaultfd(__FILE__, __LINE__, (flags), (retry))
@@ -285,7 +313,7 @@ static inline void check_uffd_feature(uint64_t feature, const char *name)
struct uffdio_api uffdio_api = {};
int uffd;
- uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, true);
uffdio_api.api = UFFD_API;
SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
diff --git a/runtest/syscalls b/runtest/syscalls
index 2bdb4a9a2..e01c62d76 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1843,6 +1843,8 @@ userfaultfd04 userfaultfd04
userfaultfd05 userfaultfd05
userfaultfd06 userfaultfd06
userfaultfd07 userfaultfd07
+userfaultfd08 userfaultfd08
+userfaultfd09 userfaultfd09
ustat01 ustat01
ustat02 ustat02
diff --git a/testcases/kernel/syscalls/userfaultfd/.gitignore b/testcases/kernel/syscalls/userfaultfd/.gitignore
index 3478a162e..7a9b2edb6 100644
--- a/testcases/kernel/syscalls/userfaultfd/.gitignore
+++ b/testcases/kernel/syscalls/userfaultfd/.gitignore
@@ -5,3 +5,5 @@
/userfaultfd05
/userfaultfd06
/userfaultfd07
+/userfaultfd08
+/userfaultfd09
diff --git a/testcases/kernel/syscalls/userfaultfd/Makefile b/testcases/kernel/syscalls/userfaultfd/Makefile
index 257249e88..48fa533ad 100644
--- a/testcases/kernel/syscalls/userfaultfd/Makefile
+++ b/testcases/kernel/syscalls/userfaultfd/Makefile
@@ -18,3 +18,5 @@ userfaultfd04: CFLAGS += -pthread
userfaultfd05: CFLAGS += -pthread
userfaultfd06: CFLAGS += -pthread
userfaultfd07: CFLAGS += -pthread
+userfaultfd08: CFLAGS += -pthread
+userfaultfd09: CFLAGS += -pthread
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
new file mode 100644
index 000000000..fd7c2f73e
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
@@ -0,0 +1,148 @@
+// 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 thread testing UFFDIO_RWPROTECT.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "tst_safe_pthread.h"
+#include "lapi/userfaultfd.h"
+
+#define PAGE_BYTE 0x5a
+
+static long page_size;
+static char *page;
+static int uffd = -1;
+static volatile int rwp_fault_seen;
+
+static void setup(void)
+{
+ CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
+}
+
+static void set_pages(void)
+{
+ page_size = SAFE_SYSCONF(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ memset(page, PAGE_BYTE, page_size);
+}
+
+static void reset_pages(void)
+{
+ if (page) {
+ SAFE_MUNMAP(page, page_size);
+ page = NULL;
+ }
+ if (uffd != -1)
+ SAFE_CLOSE(uffd);
+}
+
+static void rwprotect(unsigned long start, __u64 mode)
+{
+ struct uffdio_rwprotect uffdio_rwprotect = {};
+
+ uffdio_rwprotect.range.start = start;
+ uffdio_rwprotect.range.len = page_size;
+ uffdio_rwprotect.mode = mode;
+
+ TEST(ioctl(uffd, UFFDIO_RWPROTECT, &uffdio_rwprotect));
+ if (TST_RET)
+ tst_brk(TFAIL | TTERRNO, "UFFDIO_RWPROTECT failed");
+}
+
+static void *handle_thread(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ static struct uffd_msg msg;
+ 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(SAFE_READ_ALL, 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_RWP)) {
+ tst_brk(TFAIL, "Expected RWP fault, got flags=0x%llx",
+ (unsigned long long)msg.arg.pagefault.flags);
+ }
+
+ /* The access below is a read, so WRITE must not be set. */
+ if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE)
+ tst_brk(TFAIL, "Unexpected WRITE flag on a read-only access");
+
+ rwp_fault_seen = 1;
+
+ /* Resolve the fault by clearing RWP so the reader can resume. */
+ rwprotect(msg.arg.pagefault.address & ~((unsigned long)page_size - 1), 0);
+
+ return NULL;
+}
+
+static void run(void)
+{
+ pthread_t thr;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+ unsigned char val;
+
+ set_pages();
+ rwp_fault_seen = 0;
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, true);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = UFFD_FEATURE_RWP;
+
+ 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_RWP;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ rwprotect((unsigned long)page, UFFDIO_RWPROTECT_MODE_RWP);
+
+ SAFE_PTHREAD_CREATE(&thr, NULL, (void *)handle_thread, NULL);
+
+ /* Try to read the RWP-protected page. */
+ val = page[0];
+
+ SAFE_PTHREAD_JOIN(thr, NULL);
+ reset_pages();
+
+ if (!rwp_fault_seen) {
+ tst_res(TFAIL, "No RWPROTECT pagefault observed");
+ return;
+ }
+
+ if (val != PAGE_BYTE) {
+ tst_res(TFAIL, "Page content corrupted: got 0x%02x, expected 0x%02x",
+ val, PAGE_BYTE);
+ return;
+ }
+
+ tst_res(TPASS, "RWPROTECT pagefault handled!");
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .cleanup = reset_pages,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
new file mode 100644
index 000000000..d418c7b27
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
@@ -0,0 +1,179 @@
+// 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 thread testing UFFDIO_SET_MODE toggling
+ * UFFD_FEATURE_RWP_ASYNC at runtime.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "tst_safe_pthread.h"
+#include "lapi/userfaultfd.h"
+
+#define PAGE_BYTE 0x5a
+
+static long page_size;
+static char *page;
+static int uffd = -1;
+static volatile int rwp_fault_seen;
+
+static void setup(void)
+{
+ CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
+ CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP_ASYNC);
+}
+
+static void set_pages(void)
+{
+ page_size = SAFE_SYSCONF(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ memset(page, PAGE_BYTE, page_size);
+}
+
+static void reset_pages(void)
+{
+ if (page) {
+ SAFE_MUNMAP(page, page_size);
+ page = NULL;
+ }
+ if (uffd != -1)
+ SAFE_CLOSE(uffd);
+}
+
+static void rwprotect(unsigned long start, __u64 mode)
+{
+ struct uffdio_rwprotect uffdio_rwprotect = {};
+
+ uffdio_rwprotect.range.start = start;
+ uffdio_rwprotect.range.len = page_size;
+ uffdio_rwprotect.mode = mode;
+
+ TEST(ioctl(uffd, UFFDIO_RWPROTECT, &uffdio_rwprotect));
+ if (TST_RET)
+ tst_brk(TFAIL | TTERRNO, "UFFDIO_RWPROTECT failed");
+}
+
+static void *handle_thread(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ static struct uffd_msg msg;
+ 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(SAFE_READ_ALL, 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_RWP)) {
+ tst_brk(TFAIL, "Expected RWP fault, got flags=0x%llx",
+ (unsigned long long)msg.arg.pagefault.flags);
+ }
+
+ /* The access below is a read, so WRITE must not be set. */
+ if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE)
+ tst_brk(TFAIL, "Unexpected WRITE flag on a read-only access");
+
+ rwp_fault_seen = 1;
+
+ rwprotect(msg.arg.pagefault.address & ~((unsigned long)page_size - 1), 0);
+
+ return NULL;
+}
+
+static void run(void)
+{
+ pthread_t thr;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+ struct uffdio_set_mode uffdio_set_mode = {};
+ struct pollfd pollfd;
+ unsigned char val;
+
+ set_pages();
+ rwp_fault_seen = 0;
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, true);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC;
+
+ 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_RWP;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ /* Phase 1: async mode is active, access must auto-resolve. */
+ rwprotect((unsigned long)page, UFFDIO_RWPROTECT_MODE_RWP);
+
+ val = page[0];
+
+ if (val != PAGE_BYTE) {
+ tst_res(TFAIL, "Async access corrupted content: got 0x%02x, expected 0x%02x",
+ val, PAGE_BYTE);
+ goto out;
+ }
+
+ pollfd.fd = uffd;
+ pollfd.events = POLLIN;
+ if (poll(&pollfd, 1, 0) != 0) {
+ tst_res(TFAIL, "Unexpected pagefault message in async mode");
+ goto out;
+ }
+
+ /* Phase 2: switch to sync mode via UFFDIO_SET_MODE. */
+ uffdio_set_mode.disable = UFFD_FEATURE_RWP_ASYNC;
+
+ TEST(ioctl(uffd, UFFDIO_SET_MODE, &uffdio_set_mode));
+ if (TST_RET) {
+ tst_res(TFAIL | TTERRNO, "UFFDIO_SET_MODE failed");
+ goto out;
+ }
+
+ rwprotect((unsigned long)page, UFFDIO_RWPROTECT_MODE_RWP);
+
+ SAFE_PTHREAD_CREATE(&thr, NULL, (void *)handle_thread, NULL);
+
+ val = page[0];
+
+ SAFE_PTHREAD_JOIN(thr, NULL);
+
+ if (!rwp_fault_seen) {
+ tst_res(TFAIL, "No RWPROTECT pagefault observed after switching to sync mode");
+ goto out;
+ }
+
+ if (val != PAGE_BYTE) {
+ tst_res(TFAIL, "Sync access corrupted content: got 0x%02x, expected 0x%02x",
+ val, PAGE_BYTE);
+ goto out;
+ }
+
+ tst_res(TPASS, "UFFDIO_SET_MODE toggled RWP_ASYNC correctly!");
+
+out:
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test_all = run,
+ .cleanup = reset_pages,
+};
--
2.56.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
next reply other threads:[~2026-10-24 16:22 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 16:21 Ricardo Branco [this message]
2026-10-24 17:55 ` [LTP] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE linuxtestproject.agent
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=20261024162146.609312-1-rbranco@suse.de \
--to=rbranco@suse.de \
--cc=ltp@lists.linux.it \
--cc=noreply@anthropic.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox