123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright 2020, Gustavo Luiz Duarte, IBM Corp.
- *
- * This test starts a transaction and triggers a signal, forcing a pagefault to
- * happen when the kernel signal handling code touches the user signal stack.
- *
- * In order to avoid pre-faulting the signal stack memory and to force the
- * pagefault to happen precisely in the kernel signal handling code, the
- * pagefault handling is done in userspace using the userfaultfd facility.
- *
- * Further pagefaults are triggered by crafting the signal handler's ucontext
- * to point to additional memory regions managed by the userfaultfd, so using
- * the same mechanism used to avoid pre-faulting the signal stack memory.
- *
- * On failure (bug is present) kernel crashes or never returns control back to
- * userspace. If bug is not present, tests completes almost immediately.
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <linux/userfaultfd.h>
- #include <poll.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <sys/syscall.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <pthread.h>
- #include <signal.h>
- #include <errno.h>
- #include "tm.h"
- #define UF_MEM_SIZE 655360 /* 10 x 64k pages */
- /* Memory handled by userfaultfd */
- static char *uf_mem;
- static size_t uf_mem_offset = 0;
- /*
- * Data that will be copied into the faulting pages (instead of zero-filled
- * pages). This is used to make the test more reliable and avoid segfaulting
- * when we return from the signal handler. Since we are making the signal
- * handler's ucontext point to newly allocated memory, when that memory is
- * paged-in it will contain the expected content.
- */
- static char backing_mem[UF_MEM_SIZE];
- static size_t pagesize;
- /*
- * Return a chunk of at least 'size' bytes of memory that will be handled by
- * userfaultfd. If 'backing_data' is not NULL, its content will be save to
- * 'backing_mem' and then copied into the faulting pages when the page fault
- * is handled.
- */
- void *get_uf_mem(size_t size, void *backing_data)
- {
- void *ret;
- if (uf_mem_offset + size > UF_MEM_SIZE) {
- fprintf(stderr, "Requesting more uf_mem than expected!\n");
- exit(EXIT_FAILURE);
- }
- ret = &uf_mem[uf_mem_offset];
- /* Save the data that will be copied into the faulting page */
- if (backing_data != NULL)
- memcpy(&backing_mem[uf_mem_offset], backing_data, size);
- /* Reserve the requested amount of uf_mem */
- uf_mem_offset += size;
- /* Keep uf_mem_offset aligned to the page size (round up) */
- uf_mem_offset = (uf_mem_offset + pagesize - 1) & ~(pagesize - 1);
- return ret;
- }
- void *fault_handler_thread(void *arg)
- {
- struct uffd_msg msg; /* Data read from userfaultfd */
- long uffd; /* userfaultfd file descriptor */
- struct uffdio_copy uffdio_copy;
- struct pollfd pollfd;
- ssize_t nread, offset;
- uffd = (long) arg;
- for (;;) {
- pollfd.fd = uffd;
- pollfd.events = POLLIN;
- if (poll(&pollfd, 1, -1) == -1) {
- perror("poll() failed");
- exit(EXIT_FAILURE);
- }
- nread = read(uffd, &msg, sizeof(msg));
- if (nread == 0) {
- fprintf(stderr, "read(): EOF on userfaultfd\n");
- exit(EXIT_FAILURE);
- }
- if (nread == -1) {
- perror("read() failed");
- exit(EXIT_FAILURE);
- }
- /* We expect only one kind of event */
- if (msg.event != UFFD_EVENT_PAGEFAULT) {
- fprintf(stderr, "Unexpected event on userfaultfd\n");
- exit(EXIT_FAILURE);
- }
- /*
- * We need to handle page faults in units of pages(!).
- * So, round faulting address down to page boundary.
- */
- uffdio_copy.dst = msg.arg.pagefault.address & ~(pagesize-1);
- offset = (char *) uffdio_copy.dst - uf_mem;
- uffdio_copy.src = (unsigned long) &backing_mem[offset];
- uffdio_copy.len = pagesize;
- uffdio_copy.mode = 0;
- uffdio_copy.copy = 0;
- if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
- perror("ioctl-UFFDIO_COPY failed");
- exit(EXIT_FAILURE);
- }
- }
- }
- void setup_uf_mem(void)
- {
- long uffd; /* userfaultfd file descriptor */
- pthread_t thr;
- struct uffdio_api uffdio_api;
- struct uffdio_register uffdio_register;
- int ret;
- pagesize = sysconf(_SC_PAGE_SIZE);
- /* Create and enable userfaultfd object */
- uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
- if (uffd == -1) {
- perror("userfaultfd() failed");
- exit(EXIT_FAILURE);
- }
- uffdio_api.api = UFFD_API;
- uffdio_api.features = 0;
- if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) {
- perror("ioctl-UFFDIO_API failed");
- exit(EXIT_FAILURE);
- }
- /*
- * Create a private anonymous mapping. The memory will be demand-zero
- * paged, that is, not yet allocated. When we actually touch the memory
- * the related page will be allocated via the userfaultfd mechanism.
- */
- uf_mem = mmap(NULL, UF_MEM_SIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (uf_mem == MAP_FAILED) {
- perror("mmap() failed");
- exit(EXIT_FAILURE);
- }
- /*
- * Register the memory range of the mapping we've just mapped to be
- * handled by the userfaultfd object. In 'mode' we request to track
- * missing pages (i.e. pages that have not yet been faulted-in).
- */
- uffdio_register.range.start = (unsigned long) uf_mem;
- uffdio_register.range.len = UF_MEM_SIZE;
- uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
- if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
- perror("ioctl-UFFDIO_REGISTER");
- exit(EXIT_FAILURE);
- }
- /* Create a thread that will process the userfaultfd events */
- ret = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
- if (ret != 0) {
- fprintf(stderr, "pthread_create(): Error. Returned %d\n", ret);
- exit(EXIT_FAILURE);
- }
- }
- /*
- * Assumption: the signal was delivered while userspace was in transactional or
- * suspended state, i.e. uc->uc_link != NULL.
- */
- void signal_handler(int signo, siginfo_t *si, void *uc)
- {
- ucontext_t *ucp = uc;
- /* Skip 'trap' after returning, otherwise we get a SIGTRAP again */
- ucp->uc_link->uc_mcontext.regs->nip += 4;
- ucp->uc_mcontext.v_regs =
- get_uf_mem(sizeof(elf_vrreg_t), ucp->uc_mcontext.v_regs);
- ucp->uc_link->uc_mcontext.v_regs =
- get_uf_mem(sizeof(elf_vrreg_t), ucp->uc_link->uc_mcontext.v_regs);
- ucp->uc_link = get_uf_mem(sizeof(ucontext_t), ucp->uc_link);
- }
- bool have_userfaultfd(void)
- {
- long rc;
- errno = 0;
- rc = syscall(__NR_userfaultfd, -1);
- return rc == 0 || errno != ENOSYS;
- }
- int tm_signal_pagefault(void)
- {
- struct sigaction sa;
- stack_t ss;
- SKIP_IF(!have_htm());
- SKIP_IF(htm_is_synthetic());
- SKIP_IF(!have_userfaultfd());
- setup_uf_mem();
- /*
- * Set an alternative stack that will generate a page fault when the
- * signal is raised. The page fault will be treated via userfaultfd,
- * i.e. via fault_handler_thread.
- */
- ss.ss_sp = get_uf_mem(SIGSTKSZ, NULL);
- ss.ss_size = SIGSTKSZ;
- ss.ss_flags = 0;
- if (sigaltstack(&ss, NULL) == -1) {
- perror("sigaltstack() failed");
- exit(EXIT_FAILURE);
- }
- sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
- sa.sa_sigaction = signal_handler;
- if (sigaction(SIGTRAP, &sa, NULL) == -1) {
- perror("sigaction() failed");
- exit(EXIT_FAILURE);
- }
- /* Trigger a SIGTRAP in transactional state */
- asm __volatile__(
- "tbegin.;"
- "beq 1f;"
- "trap;"
- "1: ;"
- : : : "memory");
- /* Trigger a SIGTRAP in suspended state */
- asm __volatile__(
- "tbegin.;"
- "beq 1f;"
- "tsuspend.;"
- "trap;"
- "tresume.;"
- "1: ;"
- : : : "memory");
- return EXIT_SUCCESS;
- }
- int main(int argc, char **argv)
- {
- /*
- * Depending on kernel config, the TM Bad Thing might not result in a
- * crash, instead the kernel never returns control back to userspace, so
- * set a tight timeout. If the test passes it completes almost
- * immediately.
- */
- test_harness_set_timeout(2);
- return test_harness(tm_signal_pagefault, "tm_signal_pagefault");
- }
|