// 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 #include #include #include #include #include #include #include #include #include #include #include #include #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(!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"); }