From: Ricardo Branco <rbranco@suse.de>
To: ltp@lists.linux.it
Cc: "Claude Opus 5.5" <noreply@anthropic.com>
Subject: [LTP] [PATCH] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
Date: Sat, 24 Oct 2026 14:07:36 +0200 [thread overview]
Message-ID: <20261024120743.583665-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 | 28 +++
runtest/syscalls | 2 +
.../kernel/syscalls/userfaultfd/.gitignore | 2 +
.../kernel/syscalls/userfaultfd/Makefile | 2 +
.../syscalls/userfaultfd/userfaultfd08.c | 147 ++++++++++++++
.../syscalls/userfaultfd/userfaultfd09.c | 179 ++++++++++++++++++
7 files changed, 362 insertions(+)
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..a4b3d0763 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))
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..95fbffa1f
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
@@ -0,0 +1,147 @@
+// 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 *handle_thread(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ static struct uffd_msg msg;
+ struct uffdio_rwprotect uffdio_rwprotect = {};
+
+ 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. */
+ uffdio_rwprotect.range.start =
+ msg.arg.pagefault.address & ~((unsigned long)page_size - 1);
+ uffdio_rwprotect.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_RWPROTECT, &uffdio_rwprotect);
+
+ SAFE_CLOSE(uffd);
+ return NULL;
+}
+
+static void run(void)
+{
+ pthread_t thr;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+ struct uffdio_rwprotect uffdio_rwprotect;
+ unsigned char val;
+
+ set_pages();
+ rwp_fault_seen = 0;
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+
+ 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);
+
+ uffdio_rwprotect.range.start = (unsigned long)page;
+ uffdio_rwprotect.range.len = page_size;
+ uffdio_rwprotect.mode = UFFDIO_RWPROTECT_MODE_RWP;
+
+ SAFE_IOCTL(uffd, UFFDIO_RWPROTECT, &uffdio_rwprotect);
+
+ 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..244f4c532
--- /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_page(void)
+{
+ struct uffdio_rwprotect uffdio_rwprotect = {};
+
+ uffdio_rwprotect.range.start = (unsigned long)page;
+ uffdio_rwprotect.range.len = page_size;
+ uffdio_rwprotect.mode = UFFDIO_RWPROTECT_MODE_RWP;
+
+ SAFE_IOCTL(uffd, UFFDIO_RWPROTECT, &uffdio_rwprotect);
+}
+
+static void *handle_thread(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ static struct uffd_msg msg;
+ struct uffdio_rwprotect uffdio_rwprotect = {};
+
+ 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;
+
+ uffdio_rwprotect.range.start =
+ msg.arg.pagefault.address & ~((unsigned long)page_size - 1);
+ uffdio_rwprotect.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_RWPROTECT, &uffdio_rwprotect);
+
+ 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, false);
+
+ 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_page();
+
+ 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;
+
+ SAFE_IOCTL(uffd, UFFDIO_SET_MODE, &uffdio_set_mode);
+
+ rwprotect_page();
+
+ 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 12:08 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 12:07 Ricardo Branco [this message]
2026-10-24 14:21 ` [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=20261024120743.583665-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.