All of lore.kernel.org
 help / color / mirror / Atom feed
* [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
* [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
* [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

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 16:21 [LTP] [PATCH v2] userfaultfd: Add tests for UFFDIO_RWPROTECT and UFFDIO_SET_MODE Ricardo Branco
2026-10-24 17:55 ` [LTP] " linuxtestproject.agent
  -- strict thread matches above, loose matches on Subject: below --
2026-10-24 18:06 [LTP] [PATCH v3] " Ricardo Branco
2026-10-24 20:22 ` [LTP] " linuxtestproject.agent
2026-10-25  8:35   ` Ricardo Branco
2026-10-24 12:07 [LTP] [PATCH] " Ricardo Branco
2026-10-24 14:21 ` [LTP] " linuxtestproject.agent

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.