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/userfaultfd02.c b/testcases/kernel/syscalls/userfaultfd/userfaultfd02.c new file mode 100644 index 00000000000..304ab4e858c --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd02.c @@ -0,0 +1,119 @@ +// 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" + +static int page_size; +static char *page; +static void *move_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); + 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); +} + +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)); + + 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; + 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 * (*)(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..e0801c23e6b --- /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); +} + +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 * (*)(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..6a0e0c9f7d0 --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd04.c @@ -0,0 +1,125 @@ +// 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. + */ + +#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); +} + +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 * (*)(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..5e93ba08080 --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd05.c @@ -0,0 +1,116 @@ +// 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" + +static int page_size; +static char *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); +} + +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); +} + +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)); + + 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; + 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 * (*)(void *)) handle_thread, NULL); + + (void)page[0xf]; + + 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..a56f71e155f --- /dev/null +++ b/testcases/kernel/syscalls/userfaultfd/userfaultfd06.c @@ -0,0 +1,142 @@ +// 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" + +static int page_size; +static char *page; +static int uffd; +static volatile int wp_fault_seen; + +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); + + 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(TBROK | TERRNO, "Received unexpected UFFD_EVENT"); + + if (!(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) || + !(msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE)) { + tst_brk(TBROK, + "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); +} + +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)); + + 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 = 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 * (*)(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", +};