* [LTP] [PATCH v3] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
@ 2026-10-24 18:06 Ricardo Branco
2026-10-24 20:22 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 5+ messages in thread
From: Ricardo Branco @ 2026-10-24 18:06 UTC (permalink / raw)
To: ltp; +Cc: Claude Opus 5.5
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 | 148 +++++++++++++++
.../syscalls/userfaultfd/userfaultfd09.c | 179 ++++++++++++++++++
7 files changed, 363 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..2b7d6fb75
--- /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, 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);
+
+ 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..eb5f9dd37
--- /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, 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((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
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [LTP] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
2026-10-24 18:06 [LTP] [PATCH v3] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE Ricardo Branco
@ 2026-10-24 20:22 ` linuxtestproject.agent
2026-10-25 8:35 ` Ricardo Branco
0 siblings, 1 reply; 5+ messages in thread
From: linuxtestproject.agent @ 2026-10-24 20:22 UTC (permalink / raw)
To: Ricardo Branco; +Cc: ltp
Hi Ricardo,
On 24 Oct 2026, Ricardo Branco wrote:
> userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
> +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
> + uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
> +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP_ASYNC);
> + uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
Medium: With vm.unprivileged_userfaultfd=0 and no CAP_SYS_PTRACE, both
tests report TCONF even when RWP is supported, because CHECK_UFFD_FEATURE()
and run() request kernel-fault-capable descriptors.
These tests only need userspace faults; use UFFD_USER_MODE_ONLY or enable
the existing EPERM retry in both the feature probe and test descriptor
creation.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [LTP] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
2026-10-24 20:22 ` [LTP] " linuxtestproject.agent
@ 2026-10-25 8:35 ` Ricardo Branco
0 siblings, 0 replies; 5+ messages in thread
From: Ricardo Branco @ 2026-10-25 8:35 UTC (permalink / raw)
To: linuxtestproject.agent; +Cc: ltp
On 10/24/26 10:22 PM, linuxtestproject.agent@gmail.com wrote:
> Hi Ricardo,
>
> On 24 Oct 2026, Ricardo Branco wrote:
>> userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
>> +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
>> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
>> + uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
>> +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
>> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
>> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP_ASYNC);
>> + uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
> Medium: With vm.unprivileged_userfaultfd=0 and no CAP_SYS_PTRACE, both
> tests report TCONF even when RWP is supported, because CHECK_UFFD_FEATURE()
> and run() request kernel-fault-capable descriptors.
> These tests only need userspace faults; use UFFD_USER_MODE_ONLY or enable
> the existing EPERM retry in both the feature probe and test descriptor
> creation.
>
> Verdict - Needs revision
This is intentional. The existing userfaultfd tests (userfaultfd01-07)
all create the fd with retry=false and TCONF in this configuration, so
the new tests behave the same way.
Enabling the retry in the shared check_uffd_feature() was tried in an
earlier revision and dropped. On Linux 5.2-5.10, without
CAP_SYS_PTRACE, the retry adds UFFD_USER_MODE_ONLY, which that kernel
doesn't support. The call then fails with EINVAL, so the test reports
TBROK instead of TCONF. That would also regress the existing callers.
Handling unprivileged userfaultfd properly across all the tests would
be a separate change to the shared helper, and doesn't belong in this
series.
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 5+ messages in thread
* [LTP] [PATCH v2] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
@ 2026-10-24 16:21 Ricardo Branco
2026-10-24 17:55 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 5+ messages in thread
From: Ricardo Branco @ 2026-10-24 16:21 UTC (permalink / raw)
To: ltp; +Cc: Claude Opus 5.5
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
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [LTP] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
2026-10-24 16:21 [LTP] [PATCH v2] " Ricardo Branco
@ 2026-10-24 17:55 ` linuxtestproject.agent
0 siblings, 0 replies; 5+ messages in thread
From: linuxtestproject.agent @ 2026-10-24 17:55 UTC (permalink / raw)
To: Ricardo Branco; +Cc: ltp
Hi Ricardo,
On Sat, 24 Oct 2026 18:21:45 +0200, Ricardo Branco wrote:
> userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
> diff --git a/include/lapi/userfaultfd.h b/include/lapi/userfaultfd.h
> - uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
> + uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, true);
Medium: On Linux 5.2-5.10, without CAP_SYS_PTRACE and with
vm.unprivileged_userfaultfd=0, the retry adds the unsupported
UFFD_USER_MODE_ONLY flag and returns EINVAL, producing TBROK instead of
TCONF. This also regresses existing callers such as userfaultfd05 on
Linux 5.10. Handle EINVAL from the unsupported retry flag as TCONF while
retaining the fallback for kernels that support it.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 5+ messages in thread
* [LTP] [PATCH] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
@ 2026-10-24 12:07 Ricardo Branco
2026-10-24 14:21 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 5+ messages in thread
From: Ricardo Branco @ 2026-10-24 12:07 UTC (permalink / raw)
To: ltp; +Cc: Claude Opus 5.5
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
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [LTP] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
2026-10-24 12:07 [LTP] [PATCH] " Ricardo Branco
@ 2026-10-24 14:21 ` linuxtestproject.agent
0 siblings, 0 replies; 5+ messages in thread
From: linuxtestproject.agent @ 2026-10-24 14:21 UTC (permalink / raw)
To: Ricardo Branco; +Cc: ltp
Hi Ricardo,
On Sat, 24 Oct 2026, Ricardo Branco wrote:
> userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE
> diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
> + SAFE_IOCTL(uffd, UFFDIO_RWPROTECT, &uffdio_rwprotect);
> +
> + SAFE_CLOSE(uffd);
High: Closing the last userfaultfd independently restores permissions and
wakes faulting threads, so this test can pass even if the unprotect ioctl
returns success without resolving the fault. Remove this close and let
reset_pages() close the fd after the reader resumes and the handler is
joined.
> diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
> + uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
> diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP);
> + CHECK_UFFD_FEATURE(UFFD_FEATURE_RWP_ASYNC);
> + uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
Medium: Both tests skip with TCONF when vm.unprivileged_userfaultfd=0 and
CAP_SYS_PTRACE is absent, although these accesses only cause user-mode
faults. Request UFFD_USER_MODE_ONLY, or enable the existing retry, in both
run() calls and the feature probes; check_uffd_feature() currently
hard-codes retry=false too.
> diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
> + SAFE_IOCTL(uffd, UFFDIO_SET_MODE, &uffdio_set_mode);
Medium: These are the ioctls under test, but SAFE_IOCTL accepts any
nonnegative result and reports negative results as TBROK rather than
TFAIL. Use TEST with an explicit zero-return assertion, or TST_EXP_PASS
with appropriate failure control flow, for UFFDIO_RWPROTECT and
UFFDIO_SET_MODE in both tests.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-25 8:35 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-24 18:06 [LTP] [PATCH v3] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE Ricardo Branco
2026-10-24 20:22 ` [LTP] " linuxtestproject.agent
2026-10-25 8:35 ` Ricardo Branco
-- strict thread matches above, loose matches on Subject: below --
2026-10-24 16:21 [LTP] [PATCH v2] " Ricardo Branco
2026-10-24 17:55 ` [LTP] " linuxtestproject.agent
2026-10-24 12:07 [LTP] [PATCH] " Ricardo Branco
2026-10-24 14:21 ` [LTP] " linuxtestproject.agent
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox