Skip to content

Commit efd8e51

Browse files
ricardobranco777pevik
authored andcommitted
userfaultfd: Add test using UFFDIO_WRITEPROTECT
Signed-off-by: Ricardo Branco <rbranco@suse.de> Reviewed-by: Petr Vorel <pvorel@suse.cz> Reviewed-by: Cyril Hrubis <chrubis@suse.cz> Closes: #1280
1 parent 8a17c3e commit efd8e51

6 files changed

Lines changed: 149 additions & 0 deletions

File tree

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ AC_CHECK_TYPES([struct sockaddr_vm],,,[
278278
])
279279

280280
AC_CHECK_TYPES([struct uffdio_move],,,[#include <linux/userfaultfd.h>])
281+
AC_CHECK_TYPES([struct uffdio_writeprotect],,,[#include <linux/userfaultfd.h>])
281282

282283
# Tools knobs
283284

include/lapi/userfaultfd.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,20 @@ struct uffdio_move {
219219
};
220220
#endif /* HAVE_STRUCT_UFFDIO_MOVE */
221221

222+
#ifndef HAVE_STRUCT_UFFDIO_WRITEPROTECT
223+
#define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0)
224+
#define _UFFDIO_WRITEPROTECT (0x06)
225+
#define UFFDIO_WRITEPROTECT _IOWR(UFFDIO, _UFFDIO_WRITEPROTECT, \
226+
struct uffdio_writeprotect)
227+
228+
struct uffdio_writeprotect {
229+
struct uffdio_range range;
230+
#define UFFDIO_WRITEPROTECT_MODE_WP ((__u64)1<<0)
231+
#define UFFDIO_WRITEPROTECT_MODE_DONTWAKE ((__u64)1<<1)
232+
__u64 mode;
233+
};
234+
#endif /* HAVE_STRUCT_UFFDIO_WRITEPROTECT */
235+
222236
#define SAFE_USERFAULTFD(flags, retry) \
223237
safe_userfaultfd(__FILE__, __LINE__, (flags), (retry))
224238

runtest/syscalls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,7 @@ userfaultfd01 userfaultfd01
17731773
userfaultfd02 userfaultfd02
17741774
userfaultfd03 userfaultfd03
17751775
userfaultfd04 userfaultfd04
1776+
userfaultfd05 userfaultfd05
17761777

17771778
ustat01 ustat01
17781779
ustat02 ustat02

testcases/kernel/syscalls/userfaultfd/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/userfaultfd02
33
/userfaultfd03
44
/userfaultfd04
5+
/userfaultfd05

testcases/kernel/syscalls/userfaultfd/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ userfaultfd01: CFLAGS += -pthread
1515
userfaultfd02: CFLAGS += -pthread
1616
userfaultfd03: CFLAGS += -pthread
1717
userfaultfd04: CFLAGS += -pthread
18+
userfaultfd05: CFLAGS += -pthread
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (c) 2025 SUSE LLC
4+
* Author: Christian Amann <camann@suse.com>
5+
* Author: Ricardo Branco <rbranco@suse.com>
6+
*/
7+
8+
/*\
9+
* Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
10+
* from a different thread testing UFFDIO_WRITEPROTECT_MODE_WP.
11+
*/
12+
13+
#include "config.h"
14+
#include <poll.h>
15+
#include "tst_test.h"
16+
#include "tst_safe_macros.h"
17+
#include "tst_safe_pthread.h"
18+
#include "lapi/userfaultfd.h"
19+
20+
static int page_size;
21+
static char *page;
22+
static int uffd;
23+
static volatile int wp_fault_seen;
24+
25+
static void set_pages(void)
26+
{
27+
page_size = sysconf(_SC_PAGE_SIZE);
28+
page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
29+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
30+
31+
memset(page, 0, page_size);
32+
}
33+
34+
static void reset_pages(void)
35+
{
36+
SAFE_MUNMAP(page, page_size);
37+
}
38+
39+
static void *handle_thread(void)
40+
{
41+
static struct uffd_msg msg;
42+
struct uffdio_writeprotect uffdio_writeprotect = {};
43+
44+
struct pollfd pollfd;
45+
int nready;
46+
47+
pollfd.fd = uffd;
48+
pollfd.events = POLLIN;
49+
nready = poll(&pollfd, 1, -1);
50+
if (nready == -1)
51+
tst_brk(TBROK | TERRNO, "Error on poll");
52+
53+
SAFE_READ(1, uffd, &msg, sizeof(msg));
54+
55+
if (msg.event != UFFD_EVENT_PAGEFAULT)
56+
tst_brk(TFAIL, "Received unexpected UFFD_EVENT %d", msg.event);
57+
58+
if (!(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) ||
59+
!(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE)) {
60+
tst_brk(TFAIL,
61+
"Expected WP+WRITE fault but flags=%lx",
62+
(unsigned long)msg.arg.pagefault.flags);
63+
}
64+
65+
/* While the WP fault is pending, the write must NOT be visible. */
66+
if (page[0xf] != 0)
67+
tst_brk(TFAIL,
68+
"Write became visible while page was write-protected!");
69+
70+
wp_fault_seen = 1;
71+
72+
/* Resolve the fault by clearing WP so the writer can resume. */
73+
uffdio_writeprotect.range.start = msg.arg.pagefault.address & ~(page_size - 1);
74+
uffdio_writeprotect.range.len = page_size;
75+
76+
SAFE_IOCTL(uffd, UFFDIO_WRITEPROTECT, &uffdio_writeprotect);
77+
78+
close(uffd);
79+
return NULL;
80+
}
81+
82+
static void run(void)
83+
{
84+
pthread_t thr;
85+
struct uffdio_api uffdio_api;
86+
struct uffdio_register uffdio_register;
87+
struct uffdio_writeprotect uffdio_writeprotect;
88+
89+
set_pages();
90+
91+
uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
92+
93+
uffdio_api.api = UFFD_API;
94+
uffdio_api.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP;
95+
SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
96+
97+
if (!(uffdio_api.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP)) {
98+
tst_brk(TCONF, "UFFD write-protect unsupported");
99+
return;
100+
}
101+
102+
uffdio_register.range.start = (unsigned long) page;
103+
uffdio_register.range.len = page_size;
104+
uffdio_register.mode = UFFDIO_REGISTER_MODE_WP;
105+
106+
SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
107+
108+
uffdio_writeprotect.range.start = (unsigned long)page;
109+
uffdio_writeprotect.range.len = page_size;
110+
uffdio_writeprotect.mode = UFFDIO_WRITEPROTECT_MODE_WP;
111+
112+
SAFE_IOCTL(uffd, UFFDIO_WRITEPROTECT, &uffdio_writeprotect);
113+
114+
SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL);
115+
116+
/* Try to write */
117+
page[0xf] = 'W';
118+
119+
SAFE_PTHREAD_JOIN(thr, NULL);
120+
reset_pages();
121+
122+
if (wp_fault_seen)
123+
tst_res(TPASS, "WRITEPROTECT pagefault handled!");
124+
else
125+
tst_res(TFAIL, "No WRITEPROTECT pagefault observed");
126+
}
127+
128+
static struct tst_test test = {
129+
.test_all = run,
130+
.min_kver = "5.7",
131+
};

0 commit comments

Comments
 (0)