| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright 2018, Breno Leitao, IBM Corp. |
| * Licensed under GPLv2. |
| * |
| * Sigfuz(tm): A PowerPC TM-aware signal fuzzer. |
| * |
| * This is a new selftest that raises SIGUSR1 signals and handles it in a set |
| * of different ways, trying to create different scenario for testing |
| * purpose. |
| * |
| * This test works raising a signal and calling sigreturn interleaved with |
| * TM operations, as starting, suspending and terminating a transaction. The |
| * test depends on random numbers, and, based on them, it sets different TM |
| * states. |
| * |
| * Other than that, the test fills out the user context struct that is passed |
| * to the sigreturn system call with random data, in order to make sure that |
| * the signal handler syscall can handle different and invalid states |
| * properly. |
| * |
| * This selftest has command line parameters to control what kind of tests the |
| * user wants to run, as for example, if a transaction should be started prior |
| * to signal being raised, or, after the signal being raised and before the |
| * sigreturn. If no parameter is given, the default is enabling all options. |
| * |
| * This test does not check if the user context is being read and set |
| * properly by the kernel. Its purpose, at this time, is basically |
| * guaranteeing that the kernel does not crash on invalid scenarios. |
| */ |
| |
| #include <stdio.h> |
| #include <limits.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <ucontext.h> |
| #include <sys/mman.h> |
| #include <pthread.h> |
| #include "utils.h" |
| |
| /* Selftest defaults */ |
| #define COUNT_MAX 600 /* Number of interactions */ |
| #define THREADS 16 /* Number of threads */ |
| |
| /* Arguments options */ |
| #define ARG_MESS_WITH_TM_AT 0x1 |
| #define ARG_MESS_WITH_TM_BEFORE 0x2 |
| #define ARG_MESS_WITH_MSR_AT 0x4 |
| #define ARG_FOREVER 0x10 |
| #define ARG_COMPLETE (ARG_MESS_WITH_TM_AT | \ |
| ARG_MESS_WITH_TM_BEFORE | \ |
| ARG_MESS_WITH_MSR_AT) |
| |
| static int args; |
| static int nthread = THREADS; |
| static int count_max = COUNT_MAX; |
| |
| /* checkpoint context */ |
| static ucontext_t *tmp_uc; |
| |
| /* Return true with 1/x probability */ |
| static int one_in_chance(int x) |
| { |
| return rand() % x == 0; |
| } |
| |
| /* Change TM states */ |
| static void mess_with_tm(void) |
| { |
| /* Starts a transaction 33% of the time */ |
| if (one_in_chance(3)) { |
| asm ("tbegin. ;" |
| "beq 8 ;"); |
| |
| /* And suspended half of them */ |
| if (one_in_chance(2)) |
| asm("tsuspend. ;"); |
| } |
| |
| /* Call 'tend' in 5% of the runs */ |
| if (one_in_chance(20)) |
| asm("tend. ;"); |
| } |
| |
| /* Signal handler that will be invoked with raise() */ |
| static void trap_signal_handler(int signo, siginfo_t *si, void *uc) |
| { |
| ucontext_t *ucp = uc; |
| |
| ucp->uc_link = tmp_uc; |
| |
| /* |
| * Set uc_link in three possible ways: |
| * - Setting a single 'int' in the whole chunk |
| * - Cloning ucp into uc_link |
| * - Allocating a new memory chunk |
| */ |
| if (one_in_chance(3)) { |
| memset(ucp->uc_link, rand(), sizeof(ucontext_t)); |
| } else if (one_in_chance(2)) { |
| memcpy(ucp->uc_link, uc, sizeof(ucontext_t)); |
| } else if (one_in_chance(2)) { |
| if (tmp_uc) { |
| free(tmp_uc); |
| tmp_uc = NULL; |
| } |
| tmp_uc = malloc(sizeof(ucontext_t)); |
| ucp->uc_link = tmp_uc; |
| /* Trying to cause a major page fault at Kernel level */ |
| madvise(ucp->uc_link, sizeof(ucontext_t), MADV_DONTNEED); |
| } |
| |
| if (args & ARG_MESS_WITH_MSR_AT) { |
| /* Changing the checkpointed registers */ |
| if (one_in_chance(4)) { |
| ucp->uc_link->uc_mcontext.gp_regs[PT_MSR] |= MSR_TS_S; |
| } else { |
| if (one_in_chance(2)) { |
| ucp->uc_link->uc_mcontext.gp_regs[PT_MSR] |= |
| MSR_TS_T; |
| } else if (one_in_chance(2)) { |
| ucp->uc_link->uc_mcontext.gp_regs[PT_MSR] |= |
| MSR_TS_T | MSR_TS_S; |
| } |
| } |
| |
| /* Checking the current register context */ |
| if (one_in_chance(2)) { |
| ucp->uc_mcontext.gp_regs[PT_MSR] |= MSR_TS_S; |
| } else if (one_in_chance(2)) { |
| if (one_in_chance(2)) |
| ucp->uc_mcontext.gp_regs[PT_MSR] |= |
| MSR_TS_T; |
| else if (one_in_chance(2)) |
| ucp->uc_mcontext.gp_regs[PT_MSR] |= |
| MSR_TS_T | MSR_TS_S; |
| } |
| } |
| |
| if (one_in_chance(20)) { |
| /* Nested transaction start */ |
| if (one_in_chance(5)) |
| mess_with_tm(); |
| |
| /* Return without changing any other context info */ |
| return; |
| } |
| |
| if (one_in_chance(10)) |
| ucp->uc_mcontext.gp_regs[PT_MSR] = random(); |
| if (one_in_chance(10)) |
| ucp->uc_mcontext.gp_regs[PT_NIP] = random(); |
| if (one_in_chance(10)) |
| ucp->uc_link->uc_mcontext.gp_regs[PT_MSR] = random(); |
| if (one_in_chance(10)) |
| ucp->uc_link->uc_mcontext.gp_regs[PT_NIP] = random(); |
| |
| ucp->uc_mcontext.gp_regs[PT_TRAP] = random(); |
| ucp->uc_mcontext.gp_regs[PT_DSISR] = random(); |
| ucp->uc_mcontext.gp_regs[PT_DAR] = random(); |
| ucp->uc_mcontext.gp_regs[PT_ORIG_R3] = random(); |
| ucp->uc_mcontext.gp_regs[PT_XER] = random(); |
| ucp->uc_mcontext.gp_regs[PT_RESULT] = random(); |
| ucp->uc_mcontext.gp_regs[PT_SOFTE] = random(); |
| ucp->uc_mcontext.gp_regs[PT_DSCR] = random(); |
| ucp->uc_mcontext.gp_regs[PT_CTR] = random(); |
| ucp->uc_mcontext.gp_regs[PT_LNK] = random(); |
| ucp->uc_mcontext.gp_regs[PT_CCR] = random(); |
| ucp->uc_mcontext.gp_regs[PT_REGS_COUNT] = random(); |
| |
| ucp->uc_link->uc_mcontext.gp_regs[PT_TRAP] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_DSISR] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_DAR] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_ORIG_R3] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_XER] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_RESULT] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_SOFTE] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_DSCR] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_CTR] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_LNK] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_CCR] = random(); |
| ucp->uc_link->uc_mcontext.gp_regs[PT_REGS_COUNT] = random(); |
| |
| if (args & ARG_MESS_WITH_TM_BEFORE) { |
| if (one_in_chance(2)) |
| mess_with_tm(); |
| } |
| } |
| |
| static void seg_signal_handler(int signo, siginfo_t *si, void *uc) |
| { |
| /* Clear exit for process that segfaults */ |
| exit(0); |
| } |
| |
| static void *sigfuz_test(void *thrid) |
| { |
| struct sigaction trap_sa, seg_sa; |
| int ret, i = 0; |
| pid_t t; |
| |
| tmp_uc = malloc(sizeof(ucontext_t)); |
| |
| /* Main signal handler */ |
| trap_sa.sa_flags = SA_SIGINFO; |
| trap_sa.sa_sigaction = trap_signal_handler; |
| |
| /* SIGSEGV signal handler */ |
| seg_sa.sa_flags = SA_SIGINFO; |
| seg_sa.sa_sigaction = seg_signal_handler; |
| |
| /* The signal handler will enable MSR_TS */ |
| sigaction(SIGUSR1, &trap_sa, NULL); |
| |
| /* If it does not crash, it will segfault, avoid it to retest */ |
| sigaction(SIGSEGV, &seg_sa, NULL); |
| |
| while (i < count_max) { |
| t = fork(); |
| |
| if (t == 0) { |
| /* Once seed per process */ |
| srand(time(NULL) + getpid()); |
| if (args & ARG_MESS_WITH_TM_AT) { |
| if (one_in_chance(2)) |
| mess_with_tm(); |
| } |
| raise(SIGUSR1); |
| exit(0); |
| } else { |
| waitpid(t, &ret, 0); |
| } |
| if (!(args & ARG_FOREVER)) |
| i++; |
| } |
| |
| /* If not freed already, free now */ |
| if (tmp_uc) { |
| free(tmp_uc); |
| tmp_uc = NULL; |
| } |
| |
| return NULL; |
| } |
| |
| static int signal_fuzzer(void) |
| { |
| int t, rc; |
| pthread_t *threads; |
| |
| threads = malloc(nthread * sizeof(pthread_t)); |
| |
| for (t = 0; t < nthread; t++) { |
| rc = pthread_create(&threads[t], NULL, sigfuz_test, |
| (void *)&t); |
| if (rc) |
| perror("Thread creation error\n"); |
| } |
| |
| for (t = 0; t < nthread; t++) { |
| rc = pthread_join(threads[t], NULL); |
| if (rc) |
| perror("Thread join error\n"); |
| } |
| |
| free(threads); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| static void show_help(char *name) |
| { |
| printf("%s: Sigfuzzer for powerpc\n", name); |
| printf("Usage:\n"); |
| printf("\t-b\t Mess with TM before raising a SIGUSR1 signal\n"); |
| printf("\t-a\t Mess with TM after raising a SIGUSR1 signal\n"); |
| printf("\t-m\t Mess with MSR[TS] bits at mcontext\n"); |
| printf("\t-x\t Mess with everything above\n"); |
| printf("\t-f\t Run forever (Press ^C to Quit)\n"); |
| printf("\t-i\t Amount of interactions. (Default = %d)\n", COUNT_MAX); |
| printf("\t-t\t Amount of threads. (Default = %d)\n", THREADS); |
| exit(-1); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int opt; |
| |
| while ((opt = getopt(argc, argv, "bamxt:fi:h")) != -1) { |
| if (opt == 'b') { |
| printf("Mess with TM before signal\n"); |
| args |= ARG_MESS_WITH_TM_BEFORE; |
| } else if (opt == 'a') { |
| printf("Mess with TM at signal handler\n"); |
| args |= ARG_MESS_WITH_TM_AT; |
| } else if (opt == 'm') { |
| printf("Mess with MSR[TS] bits in mcontext\n"); |
| args |= ARG_MESS_WITH_MSR_AT; |
| } else if (opt == 'x') { |
| printf("Running with all options enabled\n"); |
| args |= ARG_COMPLETE; |
| } else if (opt == 't') { |
| nthread = atoi(optarg); |
| printf("Threads = %d\n", nthread); |
| } else if (opt == 'f') { |
| args |= ARG_FOREVER; |
| printf("Press ^C to stop\n"); |
| test_harness_set_timeout(-1); |
| } else if (opt == 'i') { |
| count_max = atoi(optarg); |
| printf("Running for %d interactions\n", count_max); |
| } else if (opt == 'h') { |
| show_help(argv[0]); |
| } |
| } |
| |
| /* Default test suite */ |
| if (!args) |
| args = ARG_COMPLETE; |
| |
| test_harness(signal_fuzzer, "signal_fuzzer"); |
| } |