326 lines
8.7 KiB
C
326 lines
8.7 KiB
C
|
// 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");
|
||
|
}
|