root/tools/testing/selftests/powerpc/tm/tm-signal-pagefault.c
// 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");
}