diff --git a/runtest/syscalls b/runtest/syscalls index a1ef7548b58..0f7ec722bb4 100644 --- a/runtest/syscalls +++ b/runtest/syscalls @@ -1770,6 +1770,11 @@ umount2_01 umount2_01 umount2_02 umount2_02 userfaultfd01 userfaultfd01 +userfaultfd02 userfaultfd02 +userfaultfd03 userfaultfd03 +userfaultfd04 userfaultfd04 +userfaultfd05 userfaultfd05 +userfaultfd06 userfaultfd06 ustat01 ustat01 ustat02 ustat02 diff --git a/testcases/kernel/syscalls/userfaultfd/.gitignore b/testcases/kernel/syscalls/userfaultfd/.gitignore index d819a2a7c82..bc32fdf3ba8 100644 --- a/testcases/kernel/syscalls/userfaultfd/.gitignore +++ b/testcases/kernel/syscalls/userfaultfd/.gitignore @@ -1 +1,6 @@ /userfaultfd01 +/userfaultfd02 +/userfaultfd03 +/userfaultfd04 +/userfaultfd05 +/userfaultfd06 diff --git a/testcases/kernel/syscalls/userfaultfd/Makefile b/testcases/kernel/syscalls/userfaultfd/Makefile index 31ddc4a4217..3252e47dfe3 100644 --- a/testcases/kernel/syscalls/userfaultfd/Makefile +++ b/testcases/kernel/syscalls/userfaultfd/Makefile @@ -12,3 +12,8 @@ include $(top_srcdir)/include/mk/testcases.mk include $(top_srcdir)/include/mk/generic_leaf_target.mk userfaultfd01: CFLAGS += -pthread +userfaultfd02: CFLAGS += -pthread +userfaultfd03: CFLAGS += -pthread +userfaultfd04: CFLAGS += -pthread +userfaultfd05: CFLAGS += -pthread +userfaultfd06: CFLAGS += -pthread diff --git a/testcases/kernel/syscalls/userfaultfd/tst_userfaultfd.h b/testcases/kernel/syscalls/userfaultfd/tst_userfaultfd.h new file mode 100644 index 00000000000..39db4b34790 --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/tst_userfaultfd.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 SUSE LLC + * Author: Christian Amann + * Author: Ricardo Branco + */ + +static inline int sys_userfaultfd(int flags) +{ + TEST(tst_syscall(__NR_userfaultfd, flags)); + + if (TST_RET == -1) { + if (TST_ERR == EPERM) { + tst_res(TCONF, "Hint: check /proc/sys/vm/unprivileged_userfaultfd"); + tst_brk(TCONF | TTERRNO, + "userfaultfd() requires CAP_SYS_PTRACE on this system"); + } else + tst_brk(TBROK | TTERRNO, + "Could not create userfault file descriptor"); + } + + return TST_RET; +} diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c index cf2e927d551..a9b2a583fb7 100644 --- a/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd01.c @@ -15,17 +15,13 @@ #include "tst_safe_macros.h" #include "tst_safe_pthread.h" #include "lapi/userfaultfd.h" +#include "tst_userfaultfd.h" static int page_size; static char *page; static void *copy_page; static int uffd; -static int sys_userfaultfd(int flags) -{ - return tst_syscall(__NR_userfaultfd, flags); -} - static void set_pages(void) { page_size = sysconf(_SC_PAGE_SIZE); @@ -35,7 +31,7 @@ static void set_pages(void) MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); } -static void handle_thread(void) +static void *handle_thread(void) { static struct uffd_msg msg; struct uffdio_copy uffdio_copy; @@ -66,6 +62,7 @@ static void handle_thread(void) SAFE_IOCTL(uffd, UFFDIO_COPY, &uffdio_copy); close(uffd); + return NULL; } static void run(void) @@ -78,16 +75,6 @@ static void run(void) TEST(sys_userfaultfd(O_CLOEXEC | O_NONBLOCK)); - if (TST_RET == -1) { - if (TST_ERR == EPERM) { - tst_res(TCONF, "Hint: check /proc/sys/vm/unprivileged_userfaultfd"); - tst_brk(TCONF | TTERRNO, - "userfaultfd() requires CAP_SYS_PTRACE on this system"); - } else - tst_brk(TBROK | TTERRNO, - "Could not create userfault file descriptor"); - } - uffd = TST_RET; uffdio_api.api = UFFD_API; uffdio_api.features = 0; @@ -99,8 +86,7 @@ static void run(void) SAFE_IOCTL(uffd, UFFDIO_REGISTER, &uffdio_register); - SAFE_PTHREAD_CREATE(&thr, NULL, - (void * (*)(void *)) handle_thread, NULL); + SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL); char c = page[0xf]; diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd02.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd02.c new file mode 100644 index 00000000000..dcad43bf46c --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd02.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 SUSE LLC + * Author: Christian Amann + * Author: Ricardo Branco + */ + +/*\ + * Force a pagefault event and handle it using :man2:`userfaultfd` + * from a different thread using UFFDIO_MOVE + */ + +#include "config.h" +#include +#include "tst_test.h" +#include "tst_safe_macros.h" +#include "tst_safe_pthread.h" +#include "lapi/userfaultfd.h" +#include "tst_userfaultfd.h" + +static int page_size; +static char *page; +static void *move_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); + move_page = SAFE_MMAP(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); +} + +static void *handle_thread(void) +{ + static struct uffd_msg msg; + struct uffdio_move uffdio_move; + + 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"); + + 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; + uffdio_move.mode = 0; + uffdio_move.move = 0; + SAFE_IOCTL(uffd, UFFDIO_MOVE, &uffdio_move); + + close(uffd); + return NULL; +} + +static void run(void) +{ + pthread_t thr; + struct uffdio_api uffdio_api; + struct uffdio_register uffdio_register; + + set_pages(); + + TEST(sys_userfaultfd(O_CLOEXEC | O_NONBLOCK)); + + uffd = TST_RET; + uffdio_api.api = UFFD_API; + uffdio_api.features = 0; + 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); + + SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL); + + 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_PTHREAD_JOIN(thr, NULL); +} + +static struct tst_test test = { + .test_all = run, + .min_kver = "6.8", +}; diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c new file mode 100644 index 00000000000..b69874b7a89 --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd03.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 SUSE LLC + * Author: Christian Amann + * Author: Ricardo Branco + */ + +/*\ + * Force a pagefault event and handle it using :man2:`userfaultfd` + * from a different thread opening uffd with UFFD_USER_MODE_ONLY. + */ + +#include "config.h" +#include +#include "tst_test.h" +#include "tst_safe_macros.h" +#include "tst_safe_pthread.h" +#include "lapi/userfaultfd.h" + +static int page_size; +static char *page; +static void *copy_page; +static int uffd; + +static int sys_userfaultfd(int flags) +{ + return tst_syscall(__NR_userfaultfd, flags); +} + +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 *handle_thread(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"); + + 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; + uffdio_copy.mode = 0; + uffdio_copy.copy = 0; + SAFE_IOCTL(uffd, UFFDIO_COPY, &uffdio_copy); + + close(uffd); + return NULL; +} + +static void run(void) +{ + pthread_t thr; + struct uffdio_api uffdio_api; + struct uffdio_register uffdio_register; + + set_pages(); + + TEST(sys_userfaultfd(O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY)); + + if (TST_RET == -1) { + tst_brk(TBROK | TTERRNO, + "Could not create userfault file descriptor"); + } + + uffd = TST_RET; + uffdio_api.api = UFFD_API; + uffdio_api.features = 0; + 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); + + SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL); + + char c = page[0xf]; + + if (c == 'X') + tst_res(TPASS, "Pagefault handled in user-mode!"); + else + tst_res(TFAIL, "Pagefault not handled in user-mode!"); + + SAFE_PTHREAD_JOIN(thr, NULL); +} + +static struct tst_test test = { + .test_all = run, + .min_kver = "5.11", +}; diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c new file mode 100644 index 00000000000..d982252d9a9 --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 SUSE LLC + * Author: Christian Amann + * Author: Ricardo Branco + */ + +/*\ + * Force a pagefault event and handle it using :man2:`userfaultfd` + * from a different thread using /dev/userfaultfd instead of syscall, + * using USERFAULTFD_IOC_NEW ioctl to create the uffd & UFFDIO_COPY. + */ + +#include "config.h" +#include +#include "tst_test.h" +#include "tst_safe_macros.h" +#include "tst_safe_pthread.h" +#include "lapi/userfaultfd.h" + +static int page_size; +static char *page; +static void *copy_page; +static int uffd; + +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 *handle_thread(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"); + + 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; + uffdio_copy.mode = 0; + uffdio_copy.copy = 0; + SAFE_IOCTL(uffd, UFFDIO_COPY, &uffdio_copy); + + close(uffd); + return NULL; +} + +static void run(void) +{ + pthread_t thr; + struct uffdio_api uffdio_api; + struct uffdio_register uffdio_register; + + set_pages(); + + TEST(open_userfaultfd(O_CLOEXEC | O_NONBLOCK)); + + if (TST_RET == -1) { + if (TST_ERR == EPERM) { + tst_brk(TCONF, "Hint: check /dev/userfaultfd permissions"); + } else + tst_brk(TBROK | TTERRNO, + "Could not create userfault file descriptor"); + } + + uffd = TST_RET; + uffdio_api.api = UFFD_API; + uffdio_api.features = 0; + 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); + + SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL); + + 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_PTHREAD_JOIN(thr, NULL); +} + +static struct tst_test test = { + .test_all = run, + .min_kver = "5.11", +}; diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd05.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd05.c new file mode 100644 index 00000000000..3dd29f9cf22 --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd05.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 SUSE LLC + * Author: Christian Amann + * Author: Ricardo Branco + */ + +/*\ + * Force a pagefault event and handle it using :man2:`userfaultfd` + * from a different thread using UFFDIO_ZEROPAGE + */ + +#include "config.h" +#include +#include "tst_test.h" +#include "tst_safe_macros.h" +#include "tst_safe_pthread.h" +#include "lapi/userfaultfd.h" +#include "tst_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 *handle_thread(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"); + + uffdio_zeropage.range.start = msg.arg.pagefault.address + & ~(page_size - 1); + uffdio_zeropage.range.len = page_size; + uffdio_zeropage.mode = 0; + + SAFE_IOCTL(uffd, UFFDIO_ZEROPAGE, &uffdio_zeropage); + + close(uffd); + return NULL; +} + +static void run(void) +{ + pthread_t thr; + struct uffdio_api uffdio_api; + struct uffdio_register uffdio_register; + + set_pages(); + + TEST(sys_userfaultfd(O_CLOEXEC | O_NONBLOCK)); + + uffd = TST_RET; + uffdio_api.api = UFFD_API; + uffdio_api.features = 0; + 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); + + SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL); + + 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_PTHREAD_JOIN(thr, NULL); +} + +static struct tst_test test = { + .test_all = run, + .min_kver = "4.3", +}; diff --git a/testcases/kernel/syscalls/userfaultfd/userfaultfd06.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd06.c new file mode 100644 index 00000000000..a44e1b1c09d --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd06.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 SUSE LLC + * Author: Christian Amann + * Author: Ricardo Branco + */ + +/*\ + * Force a pagefault event and handle it using :man2:`userfaultfd` + * from a different thread testing UFFDIO_WRITEPROTECT_MODE_WP + */ + +#include "config.h" +#include +#include "tst_test.h" +#include "tst_safe_macros.h" +#include "tst_safe_pthread.h" +#include "lapi/userfaultfd.h" +#include "tst_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 *handle_thread(void) +{ + static 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"); + + 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; + uffdio_writeprotect.mode = 0; /* clear write-protect */ + + SAFE_IOCTL(uffd, UFFDIO_WRITEPROTECT, &uffdio_writeprotect); + + close(uffd); + return NULL; +} + +static void run(void) +{ + pthread_t thr; + struct uffdio_api uffdio_api; + struct uffdio_register uffdio_register; + struct uffdio_writeprotect uffdio_writeprotect; + + set_pages(); + + TEST(sys_userfaultfd(O_CLOEXEC | O_NONBLOCK)); + + uffd = TST_RET; + uffdio_api.api = UFFD_API; + uffdio_api.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP; + SAFE_IOCTL(uffd, UFFDIO_API, &uffdio_api); + + if (!(uffdio_api.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP)) { + tst_brk(TCONF, "UFFD write-protect unsupported"); + return; + } + + 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); + + SAFE_PTHREAD_CREATE(&thr, NULL, (void *) handle_thread, NULL); + + /* Try to write */ + page[0xf] = 'W'; + + SAFE_PTHREAD_JOIN(thr, NULL); + + 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", +};