* [LTP] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
@ 2026-04-21 15:23 Martin Cermak via ltp
2026-04-21 17:06 ` [LTP] " linuxtestproject.agent
2026-04-22 5:37 ` [LTP] [Valgrind-developers] [PATCH] " Petr Vorel
0 siblings, 2 replies; 8+ messages in thread
From: Martin Cermak via ltp @ 2026-04-21 15:23 UTC (permalink / raw)
To: ltp; +Cc: valgrind-developers, mcermak
Historically a page fault is handled in the linux kernel. The
userfaultfd mechanism [1] was introduced in Linux 4.3 (2015) to allow
custom handling page faults in userspace. It allows for custom page
fault handling strategies such as lazy loading, live migration of qemu
VMs, etc. With userfaultfd, kernel communicates with userspace via a
file descriptor (userfaultfd). Userfaultfd LTP tests cover this.
These tests set up a main thread and a page fault handler thread. Once
a page fault happens in the main thread, kernel stops it, and let's the
handler take care of the page fault.
This is a problem for Valgrind, because it serializes the threads
execution (using one "big mutex", VG_(acquire_BigLock)). Once kernel
stops the main thread, under valgrind, the handler thread doesn't have
a chance to handle the page fault. Valgrind stalls till the timeout
(adjustable via LTP_TIMEOUT_MUL ;)
However, some of the testcases can be easily changed to use forked
processes instead of threads. That's what this patch does. When
client program forks, Valgrind forks too, and that allows for the needed
parallelism to handle the page fault.
This redesign can't be easily done with some of the tests, such as e.g.
userfaultfd02 where both the main thread and the handler thread need to
share the address space. That could be done via clone() and is proven
to work. But unfortunately there is another Valgrind limitation in clone
flags it supports, so this also isn't a practical way to go.
That said, this update tweaks userfaultfd01, userfaultfd03, and
userfaultfd04 in a way that they become "compatible" with Valgrind.
It's a test coverage improvement.
[1] https://www.kernel.org/doc/html/latest/admin-guide/mm/userfaultfd.html
---
.../kernel/syscalls/userfaultfd/userfaultfd01.c | 13 +++++++++----
.../kernel/syscalls/userfaultfd/userfaultfd03.c | 15 ++++++++++-----
.../kernel/syscalls/userfaultfd/userfaultfd04.c | 13 +++++++++----
3 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c
index 7368d3863..c927cda95 100644
--- a/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c
@@ -57,7 +57,7 @@ static void reset_pages(void)
SAFE_MUNMAP(copy_page, page_size);
}
-static void *handle_thread(void)
+static void *pagefault_handler(void)
{
static struct uffd_msg msg;
struct uffdio_copy uffdio_copy = {};
@@ -91,7 +91,7 @@ static void *handle_thread(void)
static void run(unsigned int i)
{
- pthread_t thr;
+ pid_t pid;
struct uffdio_api uffdio_api = {};
struct uffdio_register uffdio_register;
struct tcase *tc = &tcases[i];
@@ -112,7 +112,11 @@ static void run(unsigned int i)
SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
- SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL);
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
char c = page[0xf];
@@ -121,7 +125,7 @@ static void run(unsigned int i)
else
tst_res(TFAIL, "Pagefault not handled!");
- SAFE_PTHREAD_JOIN(thr, NULL);
+ SAFE_WAITPID(pid, NULL, 0);
reset_pages();
}
@@ -129,4 +133,5 @@ static struct tst_test test = {
.setup = setup,
.test = run,
.tcnt = ARRAY_SIZE(tcases),
+ .forks_child = 1,
};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c
index b65f39eca..f5d3be1ae 100644
--- a/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c
@@ -61,7 +61,7 @@ static void reset_pages(void)
SAFE_MUNMAP(copy_page, page_size);
}
-static void *handle_thread(void)
+static void *pagefault_handler(void)
{
static struct uffd_msg msg;
struct uffdio_copy uffdio_copy = {};
@@ -95,7 +95,7 @@ static void *handle_thread(void)
static void run(void)
{
- pthread_t thr;
+ pid_t pid;
struct uffdio_api uffdio_api = {};
struct uffdio_register uffdio_register;
@@ -112,7 +112,11 @@ static void run(void)
SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
- SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL);
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
char c = page[0xf];
@@ -121,7 +125,7 @@ static void run(void)
else
tst_res(TFAIL, "Pagefault not handled via /dev/userfaultfd");
- SAFE_PTHREAD_JOIN(thr, NULL);
+ SAFE_WAITPID(pid, NULL, 0);
reset_pages();
}
@@ -132,5 +136,6 @@ static struct tst_test test = {
.needs_kconfigs = (const char *[]) {
"CONFIG_USERFAULTFD=y",
NULL
- }
+ },
+ .forks_child = 1,
};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c
index 4eb811e45..13883a711 100644
--- a/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c
@@ -33,7 +33,7 @@ static void reset_pages(void)
SAFE_MUNMAP(page, page_size);
}
-static void *handle_thread(void)
+static void *pagefault_handler(void)
{
static struct uffd_msg msg;
struct uffdio_zeropage uffdio_zeropage = {};
@@ -64,7 +64,7 @@ static void *handle_thread(void)
static void run(void)
{
- pthread_t thr;
+ pid_t pid;
struct uffdio_api uffdio_api = {};
struct uffdio_register uffdio_register;
@@ -81,7 +81,11 @@ static void run(void)
SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
- SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL);
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
for (int i = 0; i < page_size; i++) {
if (page[i] != 0) {
@@ -92,10 +96,11 @@ static void run(void)
tst_res(TPASS, "Pagefault handled with UFFDIO_ZEROPAGE");
- SAFE_PTHREAD_JOIN(thr, NULL);
+ SAFE_WAITPID(pid, NULL, 0);
reset_pages();
}
static struct tst_test test = {
.test_all = run,
+ .forks_child = 1,
};
--
2.53.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [LTP] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-21 15:23 [LTP] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible Martin Cermak via ltp
@ 2026-04-21 17:06 ` linuxtestproject.agent
2026-04-22 5:37 ` [LTP] [Valgrind-developers] [PATCH] " Petr Vorel
1 sibling, 0 replies; 8+ messages in thread
From: linuxtestproject.agent @ 2026-04-21 17:06 UTC (permalink / raw)
To: Martin Cermak; +Cc: ltp
Hi Martin,
On Tue, 21 Apr 2026, Martin Cermak wrote:
> Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
The commit message is missing a Signed-off-by: tag.
> -static void *handle_thread(void)
> +static void *pagefault_handler(void)
Since this is no longer a thread entry point, change the return type to
void and drop the return NULL.
> /*\
> * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
> * from a different thread.
> */
The handler now runs in a forked child, not a thread. Update the doc
comment in all three files.
[...]
> + pid = SAFE_FORK();
> + if (pid == 0) {
> + pagefault_handler();
userfaultfd03.c: extra leading tab on the `if` line.
[...]
> + SAFE_WAITPID(pid, NULL, 0);
> reset_pages();
In the original code the thread closed uffd for the whole process (threads
share the fd table). With fork, the child closes only its own copy; the
parent's uffd is never closed. Add SAFE_CLOSE(uffd) before reset_pages()
in all three tests.
Also, tst_safe_pthread.h is no longer used in userfaultfd03.c and
userfaultfd04.c after this change; remove the include.
---
Note:
Our agent completed the review of the patch.
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] 8+ messages in thread
* Re: [LTP] [Valgrind-developers] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-21 15:23 [LTP] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible Martin Cermak via ltp
2026-04-21 17:06 ` [LTP] " linuxtestproject.agent
@ 2026-04-22 5:37 ` Petr Vorel
2026-04-22 7:54 ` Ricardo Branco
1 sibling, 1 reply; 8+ messages in thread
From: Petr Vorel @ 2026-04-22 5:37 UTC (permalink / raw)
To: Martin Cermak; +Cc: valgrind-developers, ltp
Hi Martin,
[ Cc Ricardo ]
> Historically a page fault is handled in the linux kernel. The
> userfaultfd mechanism [1] was introduced in Linux 4.3 (2015) to allow
> custom handling page faults in userspace. It allows for custom page
> fault handling strategies such as lazy loading, live migration of qemu
> VMs, etc. With userfaultfd, kernel communicates with userspace via a
> file descriptor (userfaultfd). Userfaultfd LTP tests cover this.
> These tests set up a main thread and a page fault handler thread. Once
> a page fault happens in the main thread, kernel stops it, and let's the
> handler take care of the page fault.
> This is a problem for Valgrind, because it serializes the threads
> execution (using one "big mutex", VG_(acquire_BigLock)). Once kernel
> stops the main thread, under valgrind, the handler thread doesn't have
> a chance to handle the page fault. Valgrind stalls till the timeout
> (adjustable via LTP_TIMEOUT_MUL ;)
> However, some of the testcases can be easily changed to use forked
> processes instead of threads. That's what this patch does. When
> client program forks, Valgrind forks too, and that allows for the needed
> parallelism to handle the page fault.
You understand process vs. threads more than me. But shouldn't mmap() use
MAP_SHARED instead of MAP_PRIVATE for those which aren't using /dev/userfaultfd?
> This redesign can't be easily done with some of the tests, such as e.g.
> userfaultfd02 where both the main thread and the handler thread need to
> share the address space. That could be done via clone() and is proven
What is the difference with userfaultfd02 then?
> to work. But unfortunately there is another Valgrind limitation in clone
> flags it supports, so this also isn't a practical way to go.
> That said, this update tweaks userfaultfd01, userfaultfd03, and
> userfaultfd04 in a way that they become "compatible" with Valgrind.
> It's a test coverage improvement.
Also doc should be updated (mentions thread).
Please don't forget to add it next time:
Signed-off-by: Martin Cermak <mcermak@redhat.com>
> [1] https://www.kernel.org/doc/html/latest/admin-guide/mm/userfaultfd.html
> ---
> .../kernel/syscalls/userfaultfd/userfaultfd01.c | 13 +++++++++----
> .../kernel/syscalls/userfaultfd/userfaultfd03.c | 15 ++++++++++-----
> .../kernel/syscalls/userfaultfd/userfaultfd04.c | 13 +++++++++----
> 3 files changed, 28 insertions(+), 13 deletions(-)
> diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c
> index 7368d3863..c927cda95 100644
> --- a/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c
> +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c
> @@ -57,7 +57,7 @@ static void reset_pages(void)
> SAFE_MUNMAP(copy_page, page_size);
> }
> -static void *handle_thread(void)
> +static void *pagefault_handler(void)
> {
> static struct uffd_msg msg;
> struct uffdio_copy uffdio_copy = {};
> @@ -91,7 +91,7 @@ static void *handle_thread(void)
> static void run(unsigned int i)
> {
> - pthread_t thr;
> + pid_t pid;
> struct uffdio_api uffdio_api = {};
> struct uffdio_register uffdio_register;
> struct tcase *tc = &tcases[i];
> @@ -112,7 +112,11 @@ static void run(unsigned int i)
> SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
> - SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL);
> + pid = SAFE_FORK();
> + if (pid == 0) {
> + pagefault_handler();
> + _exit(0);
> + }
> char c = page[0xf];
> @@ -121,7 +125,7 @@ static void run(unsigned int i)
> else
> tst_res(TFAIL, "Pagefault not handled!");
> - SAFE_PTHREAD_JOIN(thr, NULL);
> + SAFE_WAITPID(pid, NULL, 0);
> reset_pages();
> }
> @@ -129,4 +133,5 @@ static struct tst_test test = {
> .setup = setup,
> .test = run,
> .tcnt = ARRAY_SIZE(tcases),
> + .forks_child = 1,
> };
> diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c
> index b65f39eca..f5d3be1ae 100644
> --- a/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c
> +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c
> @@ -61,7 +61,7 @@ static void reset_pages(void)
> SAFE_MUNMAP(copy_page, page_size);
> }
> -static void *handle_thread(void)
> +static void *pagefault_handler(void)
> {
> static struct uffd_msg msg;
> struct uffdio_copy uffdio_copy = {};
> @@ -95,7 +95,7 @@ static void *handle_thread(void)
> static void run(void)
> {
> - pthread_t thr;
> + pid_t pid;
> struct uffdio_api uffdio_api = {};
> struct uffdio_register uffdio_register;
> @@ -112,7 +112,11 @@ static void run(void)
> SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
> - SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL);
> + pid = SAFE_FORK();
> + if (pid == 0) {
> + pagefault_handler();
> + _exit(0);
> + }
> char c = page[0xf];
> @@ -121,7 +125,7 @@ static void run(void)
> else
> tst_res(TFAIL, "Pagefault not handled via /dev/userfaultfd");
> - SAFE_PTHREAD_JOIN(thr, NULL);
> + SAFE_WAITPID(pid, NULL, 0);
> reset_pages();
> }
> @@ -132,5 +136,6 @@ static struct tst_test test = {
> .needs_kconfigs = (const char *[]) {
> "CONFIG_USERFAULTFD=y",
> NULL
> - }
> + },
> + .forks_child = 1,
> };
> diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c
> index 4eb811e45..13883a711 100644
> --- a/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c
> +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c
> @@ -33,7 +33,7 @@ static void reset_pages(void)
> SAFE_MUNMAP(page, page_size);
> }
> -static void *handle_thread(void)
> +static void *pagefault_handler(void)
> {
> static struct uffd_msg msg;
> struct uffdio_zeropage uffdio_zeropage = {};
> @@ -64,7 +64,7 @@ static void *handle_thread(void)
> static void run(void)
> {
> - pthread_t thr;
> + pid_t pid;
> struct uffdio_api uffdio_api = {};
> struct uffdio_register uffdio_register;
> @@ -81,7 +81,11 @@ static void run(void)
> SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
> - SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL);
> + pid = SAFE_FORK();
> + if (pid == 0) {
> + pagefault_handler();
> + _exit(0);
> + }
> for (int i = 0; i < page_size; i++) {
> if (page[i] != 0) {
> @@ -92,10 +96,11 @@ static void run(void)
> tst_res(TPASS, "Pagefault handled with UFFDIO_ZEROPAGE");
> - SAFE_PTHREAD_JOIN(thr, NULL);
> + SAFE_WAITPID(pid, NULL, 0);
> reset_pages();
> }
> static struct tst_test test = {
> .test_all = run,
> + .forks_child = 1,
> };
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] [Valgrind-developers] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-22 5:37 ` [LTP] [Valgrind-developers] [PATCH] " Petr Vorel
@ 2026-04-22 7:54 ` Ricardo Branco
2026-04-22 12:17 ` Petr Vorel
0 siblings, 1 reply; 8+ messages in thread
From: Ricardo Branco @ 2026-04-22 7:54 UTC (permalink / raw)
To: Petr Vorel, Martin Cermak; +Cc: valgrind-developers, ltp
On 4/22/26 7:37 AM, Petr Vorel wrote:
>> However, some of the testcases can be easily changed to use forked
>> processes instead of threads. That's what this patch does. When
>> client program forks, Valgrind forks too, and that allows for the needed
>> parallelism to handle the page fault.
> You understand process vs. threads more than me. But shouldn't mmap() use
> MAP_SHARED instead of MAP_PRIVATE for those which aren't using /dev/userfaultfd?
The documentation for userfaultfd mentions threads. I'm afraid we'll
lose vital coverage if we move to forked processes.
Perhaps we should cover both?
Also checkout UFFD_FEATURE_EVENT_FORK and the BUGS section for this in
userfaultfd(2).
I have like 3 patches for userfaultfd that I'm afraid I'd have to rebase
then.\ Best,
Ricardo
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] [Valgrind-developers] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-22 7:54 ` Ricardo Branco
@ 2026-04-22 12:17 ` Petr Vorel
2026-04-22 12:42 ` Ricardo Branco
2026-04-22 13:08 ` Martin Cermak via ltp
0 siblings, 2 replies; 8+ messages in thread
From: Petr Vorel @ 2026-04-22 12:17 UTC (permalink / raw)
To: Ricardo Branco; +Cc: valgrind-developers, Martin Cermak, ltp
> On 4/22/26 7:37 AM, Petr Vorel wrote:
> > > However, some of the testcases can be easily changed to use forked
> > > processes instead of threads. That's what this patch does. When
> > > client program forks, Valgrind forks too, and that allows for the needed
> > > parallelism to handle the page fault.
> > You understand process vs. threads more than me. But shouldn't mmap() use
> > MAP_SHARED instead of MAP_PRIVATE for those which aren't using /dev/userfaultfd?
> The documentation for userfaultfd mentions threads. I'm afraid we'll
> lose vital coverage if we move to forked processes.
+1, at least some tests would keep threads (not all userfaultfd tests would be
converted, but yes, that's why I suggested to use MAP_SHARED, which could be
similar to threads (yes, the difference between processes and threads in Linux
kernel is not that huge as both are created in clone(), it's mostly about what
is shared).
> Perhaps we should cover both?
What do you mean by "both"?
Run test with both processes and threads e.g. via .test_variants?
Because combine MAP_SHARED | MAP_PRIVATE is not possible (mutually exclusive).
> Also checkout UFFD_FEATURE_EVENT_FORK and the BUGS section for this in
> userfaultfd(2).
+1
> I have like 3 patches for userfaultfd that I'm afraid I'd have to rebase
> then.\ Best,
Yeah, that's life of an open source development.
Kind regards,
Petr
> Ricardo
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] [Valgrind-developers] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-22 12:17 ` Petr Vorel
@ 2026-04-22 12:42 ` Ricardo Branco
2026-04-22 13:08 ` Martin Cermak via ltp
1 sibling, 0 replies; 8+ messages in thread
From: Ricardo Branco @ 2026-04-22 12:42 UTC (permalink / raw)
To: Petr Vorel; +Cc: valgrind-developers, Martin Cermak, ltp
On 4/22/26 2:17 PM, Petr Vorel wrote:
>> Perhaps we should cover both?
> What do you mean by "both"?
>
> Run test with both processes and threads e.g. via .test_variants?
Yes.
Best,
R
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] [Valgrind-developers] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-22 12:17 ` Petr Vorel
2026-04-22 12:42 ` Ricardo Branco
@ 2026-04-22 13:08 ` Martin Cermak via ltp
2026-04-23 14:44 ` Martin Cermak via ltp
1 sibling, 1 reply; 8+ messages in thread
From: Martin Cermak via ltp @ 2026-04-22 13:08 UTC (permalink / raw)
To: Petr Vorel; +Cc: valgrind-developers, ltp
On Wed 2026-04-22 14:17 , Petr Vorel wrote:
>
>
> > On 4/22/26 7:37 AM, Petr Vorel wrote:
> > > > However, some of the testcases can be easily changed to use forked
> > > > processes instead of threads. That's what this patch does. When
> > > > client program forks, Valgrind forks too, and that allows for the needed
> > > > parallelism to handle the page fault.
> > > You understand process vs. threads more than me. But shouldn't mmap() use
> > > MAP_SHARED instead of MAP_PRIVATE for those which aren't using /dev/userfaultfd?
>
> > The documentation for userfaultfd mentions threads. I'm afraid we'll
> > lose vital coverage if we move to forked processes.
>
> +1, at least some tests would keep threads (not all userfaultfd tests would be
> converted, but yes, that's why I suggested to use MAP_SHARED, which could be
> similar to threads (yes, the difference between processes and threads in Linux
> kernel is not that huge as both are created in clone(), it's mostly about what
> is shared).
My goal is to make at least part of these userfaultfd tests
compatible with Valgrind. I've experimented with clone() and it
seems like Valgrind has limited support for it. That said,
adding another set of userfaultfd tests using fork() or clone()
instead of threads might help. Some of such new tests will be
"incompatible" with valgrind, but it might be a test coverage
improvement.
m.
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] [Valgrind-developers] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible
2026-04-22 13:08 ` Martin Cermak via ltp
@ 2026-04-23 14:44 ` Martin Cermak via ltp
0 siblings, 0 replies; 8+ messages in thread
From: Martin Cermak via ltp @ 2026-04-23 14:44 UTC (permalink / raw)
To: Petr Vorel; +Cc: valgrind-developers, ltp
On Wed 2026-04-22 15:08 , Martin Cermak wrote:
> On Wed 2026-04-22 14:17 , Petr Vorel wrote:
> >
> >
> > > On 4/22/26 7:37 AM, Petr Vorel wrote:
> > > > > However, some of the testcases can be easily changed to use forked
> > > > > processes instead of threads. That's what this patch does. When
> > > > > client program forks, Valgrind forks too, and that allows for the needed
> > > > > parallelism to handle the page fault.
> > > > You understand process vs. threads more than me. But shouldn't mmap() use
> > > > MAP_SHARED instead of MAP_PRIVATE for those which aren't using /dev/userfaultfd?
> >
> > > The documentation for userfaultfd mentions threads. I'm afraid we'll
> > > lose vital coverage if we move to forked processes.
> >
> > +1, at least some tests would keep threads (not all userfaultfd tests would be
> > converted, but yes, that's why I suggested to use MAP_SHARED, which could be
> > similar to threads (yes, the difference between processes and threads in Linux
> > kernel is not that huge as both are created in clone(), it's mostly about what
> > is shared).
>
> My goal is to make at least part of these userfaultfd tests
> compatible with Valgrind. I've experimented with clone() and it
> seems like Valgrind has limited support for it. That said,
> adding another set of userfaultfd tests using fork() or clone()
> instead of threads might help. Some of such new tests will be
> "incompatible" with valgrind, but it might be a test coverage
> improvement.
Here is an ai-assisted patch that works for me:
From 693f3015d39b39e2c263aac57c183e3e4cf7df4d Mon Sep 17 00:00:00 2001
From: Martin Cermak <mcermak@redhat.com>
Date: Thu, 23 Apr 2026 15:51:10 +0200
Subject: [PATCH] Provide Valgrind-compatible userfaultfd tests
The userfaultfd tests use threads. Threads are executed serially
in Valgrind, but parallelism is important for userfaultfd tests.
When a page fault happens in parent thread, kernel stops it, and
waits for the handler thread to handle the page fault. That said,
userfaultfd tests 01 - 06 stall in Valgrind.
This update comes with second set of userfaultfd tests 07 - 12.
These additional tests use fork() or clone(), thus a separate
process instead of threads. Support for clone() is also limited
in Valgrind. For that reason only tests 07, 09 and 10 are "Valgrind
compatible". But it's a test coverage improvement.
Valgrind can filter LTP tests on the per test binary basis. That's
why this update adds separate tests 07 - 12.
Assisted-by: Anthropic Claude
Reviewed-by: Martin Cermak <mcermak@redhat.com>
---
include/lapi/sched.h | 8 +
.../kernel/syscalls/userfaultfd/.gitignore | 6 +
.../kernel/syscalls/userfaultfd/Makefile | 3 +
.../syscalls/userfaultfd/userfaultfd07.c | 136 +++++++++++++++
.../syscalls/userfaultfd/userfaultfd08.c | 113 +++++++++++++
.../syscalls/userfaultfd/userfaultfd09.c | 141 ++++++++++++++++
.../syscalls/userfaultfd/userfaultfd10.c | 106 ++++++++++++
.../syscalls/userfaultfd/userfaultfd11.c | 140 ++++++++++++++++
.../syscalls/userfaultfd/userfaultfd12.c | 157 ++++++++++++++++++
9 files changed, 810 insertions(+)
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd07.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd10.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd11.c
create mode 100644 testcases/kernel/syscalls/userfaultfd/userfaultfd12.c
diff --git a/include/lapi/sched.h b/include/lapi/sched.h
index 05b322c1c..b33de6657 100644
--- a/include/lapi/sched.h
+++ b/include/lapi/sched.h
@@ -121,6 +121,14 @@ static inline int getcpu(unsigned *cpu, unsigned *node)
# define CLONE_FS 0x00000200
#endif
+#ifndef CLONE_FILES
+# define CLONE_FILES 0x00000400
+#endif
+
+#ifndef CLONE_SIGHAND
+# define CLONE_SIGHAND 0x00000800
+#endif
+
#ifndef CLONE_PIDFD
# define CLONE_PIDFD 0x00001000
#endif
diff --git a/testcases/kernel/syscalls/userfaultfd/.gitignore b/testcases/kernel/syscalls/userfaultfd/.gitignore
index bc32fdf3b..59d28c6f3 100644
--- a/testcases/kernel/syscalls/userfaultfd/.gitignore
+++ b/testcases/kernel/syscalls/userfaultfd/.gitignore
@@ -4,3 +4,9 @@
/userfaultfd04
/userfaultfd05
/userfaultfd06
+/userfaultfd07
+/userfaultfd08
+/userfaultfd09
+/userfaultfd10
+/userfaultfd11
+/userfaultfd12
diff --git a/testcases/kernel/syscalls/userfaultfd/Makefile b/testcases/kernel/syscalls/userfaultfd/Makefile
index 3252e47df..e4ed56b4b 100644
--- a/testcases/kernel/syscalls/userfaultfd/Makefile
+++ b/testcases/kernel/syscalls/userfaultfd/Makefile
@@ -17,3 +17,6 @@ userfaultfd03: CFLAGS += -pthread
userfaultfd04: CFLAGS += -pthread
userfaultfd05: CFLAGS += -pthread
userfaultfd06: CFLAGS += -pthread
+# Tests 07-12: Valgrind-compatible variants using fork/clone
+# Tests 07, 09, 10: fork-based (no pthread needed)
+# Tests 08, 11, 12: clone-based (no pthread needed)
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd07.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd07.c
new file mode 100644
index 000000000..bebf0e921
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd07.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using fork instead of threads for Valgrind compatibility).
+ */
+
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/userfaultfd.h"
+
+#define BEFORE_5_11 1
+#define AFTER_5_11 2
+#define DESC(x) .flags = x, .desc = #x
+
+static struct tcase {
+ int flags;
+ const char *desc;
+ int kver;
+} tcases[] = {
+ { DESC(O_CLOEXEC | O_NONBLOCK) },
+ { DESC(O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY), .kver = AFTER_5_11, },
+};
+
+static int page_size;
+static char *page;
+static void *copy_page;
+static int uffd;
+static int kver;
+
+static void setup(void)
+{
+ if (tst_kvercmp(5, 11, 0) >= 0)
+ kver = AFTER_5_11;
+ else
+ kver = BEFORE_5_11;
+}
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ copy_page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+ SAFE_MUNMAP(copy_page, page_size);
+}
+
+static void *pagefault_handler(void)
+{
+ static struct uffd_msg msg;
+ struct uffdio_copy uffdio_copy = {};
+
+ 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(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ memset(copy_page, 'X', page_size);
+
+ uffdio_copy.src = (unsigned long) copy_page;
+
+ uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_copy.len = page_size;
+ SAFE_IOCTL(uffd, UFFDIO_COPY, &uffdio_copy);
+
+ close(uffd);
+ return NULL;
+}
+
+static void run(unsigned int i)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+ struct tcase *tc = &tcases[i];
+
+ if (tc->kver == AFTER_5_11 && kver == BEFORE_5_11)
+ tst_brk(TCONF, "%s requires kernel >= 5.11", tc->desc);
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(tc->flags, false);
+
+ uffdio_api.api = UFFD_API;
+ 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_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ char c = page[0xf];
+
+ if (c == 'X')
+ tst_res(TPASS, "Pagefault handled!");
+ else
+ tst_res(TFAIL, "Pagefault not handled!");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .test = run,
+ .tcnt = ARRAY_SIZE(tcases),
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
new file mode 100644
index 000000000..f6bde86cc
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd08.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using clone with CLONE_VM instead of threads
+ * for Valgrind compatibility) using UFFDIO_MOVE.
+ */
+
+#include "config.h"
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "tst_clone.h"
+#include "lapi/sched.h"
+#include "lapi/userfaultfd.h"
+
+#define CHILD_STACK_SIZE (1024 * 1024)
+
+static int page_size;
+static char *page;
+static void *move_page;
+static int uffd;
+static void *child_stack;
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ move_page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+ /*
+ * After UFFDIO_MOVE, move_page is invalid and should not be unmapped.
+ * The kernel has already removed the mapping during the move operation.
+ */
+}
+
+static int pagefault_handler(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ struct uffd_msg msg;
+ struct uffdio_move uffdio_move = {};
+
+ SAFE_READ(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ memset(move_page, 'X', page_size);
+
+ uffdio_move.src = (unsigned long) move_page;
+
+ uffdio_move.dst = (unsigned long) msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_move.len = page_size;
+ SAFE_IOCTL(uffd, UFFDIO_MOVE, &uffdio_move);
+
+ close(uffd);
+ return 0;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC, false);
+
+ uffdio_api.api = UFFD_API;
+ 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_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = ltp_clone(CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND | SIGCHLD,
+ pagefault_handler, NULL, CHILD_STACK_SIZE, child_stack);
+ if (pid == -1)
+ tst_brk(TBROK | TERRNO, "ltp_clone failed");
+
+ char c = page[0xf];
+
+ if (c == 'X')
+ tst_res(TPASS, "Pagefault handled via UFFDIO_MOVE");
+ else
+ tst_res(TFAIL, "Pagefault not handled via UFFDIO_MOVE");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .min_kver = "6.8",
+ .forks_child = 1,
+ .bufs = (struct tst_buffers []) {
+ {&child_stack, .size = CHILD_STACK_SIZE},
+ {},
+ },
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
new file mode 100644
index 000000000..e1190a4ce
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd09.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using fork instead of threads for Valgrind compatibility)
+ * using /dev/userfaultfd instead of syscall, using USERFAULTFD_IOC_NEW ioctl to create
+ * the uffd & UFFDIO_COPY.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/userfaultfd.h"
+
+static int page_size;
+static char *page;
+static void *copy_page;
+static int uffd;
+
+static void setup(void)
+{
+ if (access("/dev/userfaultfd", F_OK) != 0) {
+ int res = (tst_kvercmp(6, 1, 0) < 0) ? TCONF : TBROK;
+
+ tst_brk(res, "/dev/userfaultfd not found");
+ }
+}
+
+static int open_userfaultfd(int flags)
+{
+ int fd, fd2;
+
+ fd = SAFE_OPEN("/dev/userfaultfd", O_RDWR);
+
+ fd2 = SAFE_IOCTL(fd, USERFAULTFD_IOC_NEW, flags);
+
+ SAFE_CLOSE(fd);
+
+ return fd2;
+}
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ copy_page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+ SAFE_MUNMAP(copy_page, page_size);
+}
+
+static void *pagefault_handler(void)
+{
+ static struct uffd_msg msg;
+ struct uffdio_copy uffdio_copy = {};
+
+ 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(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ memset(copy_page, 'X', page_size);
+
+ uffdio_copy.src = (unsigned long) copy_page;
+
+ uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_copy.len = page_size;
+ SAFE_IOCTL(uffd, UFFDIO_COPY, &uffdio_copy);
+
+ close(uffd);
+ return NULL;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+
+ set_pages();
+
+ uffd = open_userfaultfd(O_CLOEXEC | O_NONBLOCK);
+
+ uffdio_api.api = UFFD_API;
+ 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_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ char c = page[0xf];
+
+ if (c == 'X')
+ tst_res(TPASS, "Pagefault handled via /dev/userfaultfd");
+ else
+ tst_res(TFAIL, "Pagefault not handled via /dev/userfaultfd");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .needs_root = 1,
+ .setup = setup,
+ .test_all = run,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_USERFAULTFD=y",
+ NULL
+ },
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd10.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd10.c
new file mode 100644
index 000000000..8e9bd0b0c
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd10.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using fork instead of threads for Valgrind compatibility)
+ * using UFFDIO_ZEROPAGE.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/userfaultfd.h"
+
+static int page_size;
+static char *page;
+static int uffd;
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+}
+
+static void *pagefault_handler(void)
+{
+ static struct uffd_msg msg;
+ struct uffdio_zeropage uffdio_zeropage = {};
+
+ 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(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TBROK | TERRNO, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ uffdio_zeropage.range.start = msg.arg.pagefault.address
+ & ~(page_size - 1);
+ uffdio_zeropage.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_ZEROPAGE, &uffdio_zeropage);
+
+ close(uffd);
+ return NULL;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+
+ uffdio_api.api = UFFD_API;
+ 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_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = SAFE_FORK();
+ if (pid == 0) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ for (int i = 0; i < page_size; i++) {
+ if (page[i] != 0) {
+ tst_res(TFAIL, "page[%d]=0x%x not zero", i, page[i]);
+ return;
+ }
+ }
+
+ tst_res(TPASS, "Pagefault handled with UFFDIO_ZEROPAGE");
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd11.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd11.c
new file mode 100644
index 000000000..52590d117
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd11.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC
+ * Author: Christian Amann <camann@suse.com>
+ * Author: Ricardo Branco <rbranco@suse.com>
+ */
+
+/*\
+ * Force a pagefault event and handle it using :manpage:`userfaultfd(2)`
+ * from a different process (using clone with CLONE_VM instead of threads
+ * for Valgrind compatibility) testing UFFDIO_WRITEPROTECT_MODE_WP.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "lapi/sched.h"
+#include "lapi/userfaultfd.h"
+
+static int page_size;
+static char *page;
+static int uffd;
+static volatile int wp_fault_seen;
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ memset(page, 0, page_size);
+}
+
+static void reset_pages(void)
+{
+ SAFE_MUNMAP(page, page_size);
+}
+
+static void pagefault_handler(void)
+{
+ struct uffd_msg msg;
+ struct uffdio_writeprotect uffdio_writeprotect = {};
+
+ 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(1, 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_WP) ||
+ !(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE)) {
+ tst_brk(TFAIL,
+ "Expected WP+WRITE fault but flags=%lx",
+ (unsigned long)msg.arg.pagefault.flags);
+ }
+
+ /* While the WP fault is pending, the write must NOT be visible. */
+ if (page[0xf] != 0)
+ tst_brk(TFAIL,
+ "Write became visible while page was write-protected!");
+
+ wp_fault_seen = 1;
+
+ /* Resolve the fault by clearing WP so the writer can resume. */
+ uffdio_writeprotect.range.start = msg.arg.pagefault.address & ~(page_size - 1);
+ uffdio_writeprotect.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_WRITEPROTECT, &uffdio_writeprotect);
+
+ close(uffd);
+}
+
+static void run(void)
+{
+ const struct tst_clone_args clone_args = {
+ .flags = CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND,
+ .exit_signal = SIGCHLD,
+ };
+ pid_t pid;
+ struct uffdio_api uffdio_api;
+ struct uffdio_register uffdio_register;
+ struct uffdio_writeprotect uffdio_writeprotect;
+
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP;
+
+ 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_WP;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ uffdio_writeprotect.range.start = (unsigned long)page;
+ uffdio_writeprotect.range.len = page_size;
+ uffdio_writeprotect.mode = UFFDIO_WRITEPROTECT_MODE_WP;
+
+ SAFE_IOCTL(uffd, UFFDIO_WRITEPROTECT, &uffdio_writeprotect);
+
+ pid = SAFE_CLONE(&clone_args);
+ if (!pid) {
+ pagefault_handler();
+ _exit(0);
+ }
+
+ /* Try to write */
+ page[0xf] = 'W';
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+
+ if (wp_fault_seen)
+ tst_res(TPASS, "WRITEPROTECT pagefault handled!");
+ else
+ tst_res(TFAIL, "No WRITEPROTECT pagefault observed");
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .min_kver = "5.7",
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_HAVE_ARCH_USERFAULTFD_WP=y",
+ NULL
+ },
+ .forks_child = 1,
+};
diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd12.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd12.c
new file mode 100644
index 000000000..3fd4e3652
--- /dev/null
+++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd12.c
@@ -0,0 +1,157 @@
+// 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 process (using clone with CLONE_VM instead of threads
+ * for Valgrind compatibility) testing UFFDIO_POISON.
+ */
+
+#include "config.h"
+#include <poll.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <unistd.h>
+#include "tst_test.h"
+#include "tst_safe_macros.h"
+#include "tst_clone.h"
+#include "lapi/sched.h"
+#include "lapi/userfaultfd.h"
+
+#define CHILD_STACK_SIZE (1024 * 1024)
+
+static int page_size;
+static char *page;
+static int uffd;
+static int poison_fault_seen;
+static volatile int sigbus_seen;
+static sigjmp_buf jmpbuf;
+static void *child_stack;
+
+static void sigbus_handler(int sig)
+{
+ if (sig == SIGBUS) {
+ sigbus_seen = 1;
+ siglongjmp(jmpbuf, 1);
+ }
+}
+
+static void setup(void)
+{
+ struct sigaction sa = {};
+
+ sa.sa_handler = sigbus_handler;
+ sigemptyset(&sa.sa_mask);
+ SAFE_SIGACTION(SIGBUS, &sa, NULL);
+}
+
+static void set_pages(void)
+{
+ page_size = sysconf(_SC_PAGE_SIZE);
+ page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+}
+
+static void reset_pages(void)
+{
+ if (page) {
+ SAFE_MUNMAP(page, page_size);
+ page = NULL;
+ }
+}
+
+static int pagefault_handler(void *arg LTP_ATTRIBUTE_UNUSED)
+{
+ struct uffd_msg msg;
+ struct uffdio_poison uffdio_poison = {};
+ 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(1, uffd, &msg, sizeof(msg));
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ tst_brk(TFAIL, "Received unexpected UFFD_EVENT %d", msg.event);
+
+ tst_atomic_store(1, &poison_fault_seen);
+
+ /* Poison the page that triggered the fault */
+ uffdio_poison.range.start = msg.arg.pagefault.address & ~(page_size - 1);
+ uffdio_poison.range.len = page_size;
+
+ SAFE_IOCTL(uffd, UFFDIO_POISON, &uffdio_poison);
+
+ close(uffd);
+ return 0;
+}
+
+static void run(void)
+{
+ pid_t pid;
+ struct uffdio_api uffdio_api = {};
+ struct uffdio_register uffdio_register;
+ char dummy;
+
+ poison_fault_seen = 0;
+ sigbus_seen = 0;
+ set_pages();
+
+ uffd = SAFE_USERFAULTFD(O_CLOEXEC | O_NONBLOCK, false);
+
+ uffdio_api.api = UFFD_API;
+ uffdio_api.features = UFFD_FEATURE_POISON;
+
+ SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api);
+
+ if (!(uffdio_api.features & UFFD_FEATURE_POISON))
+ tst_brk(TCONF, "UFFD_FEATURE_POISON not supported");
+
+ uffdio_register.range.start = (unsigned long) page;
+ uffdio_register.range.len = page_size;
+ uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+
+ SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register);
+
+ pid = ltp_clone(CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND | SIGCHLD,
+ pagefault_handler, NULL, CHILD_STACK_SIZE, child_stack);
+ if (pid == -1)
+ tst_brk(TBROK | TERRNO, "ltp_clone failed");
+
+ /* Try to read from the page: should trigger fault, get poisoned, then SIGBUS */
+ if (sigsetjmp(jmpbuf, 1) == 0) {
+ LTP_VAR_USED(dummy) = page[0];
+ }
+
+ SAFE_WAITPID(pid, NULL, 0);
+ reset_pages();
+
+ int poisoned = tst_atomic_load(&poison_fault_seen);
+
+ if (poisoned && sigbus_seen)
+ tst_res(TPASS, "POISON successfully triggered SIGBUS");
+ else if (poisoned && !sigbus_seen)
+ tst_res(TFAIL, "POISON fault seen but no SIGBUS received");
+ else if (!poisoned && sigbus_seen)
+ tst_res(TFAIL, "SIGBUS received but no poison fault seen");
+ else
+ tst_res(TFAIL, "No poison fault or SIGBUS observed");
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = setup,
+ .cleanup = reset_pages,
+ .forks_child = 1,
+ .bufs = (struct tst_buffers []) {
+ {&child_stack, .size = CHILD_STACK_SIZE},
+ {},
+ },
+};
--
2.53.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-04-23 14:45 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-21 15:23 [LTP] [PATCH] Make userfaultfd0{1, 3, 4} LTP tests valgrind compatible Martin Cermak via ltp
2026-04-21 17:06 ` [LTP] " linuxtestproject.agent
2026-04-22 5:37 ` [LTP] [Valgrind-developers] [PATCH] " Petr Vorel
2026-04-22 7:54 ` Ricardo Branco
2026-04-22 12:17 ` Petr Vorel
2026-04-22 12:42 ` Ricardo Branco
2026-04-22 13:08 ` Martin Cermak via ltp
2026-04-23 14:44 ` Martin Cermak via ltp
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox