root/tools/testing/selftests/x86/sigaltstack.c
// SPDX-License-Identifier: GPL-2.0-only

#define _GNU_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <sys/mman.h>
#include <sys/auxv.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <setjmp.h>

#include "helpers.h"

/* sigaltstack()-enforced minimum stack */
#define ENFORCED_MINSIGSTKSZ    2048

#ifndef AT_MINSIGSTKSZ
#  define AT_MINSIGSTKSZ        51
#endif

static int nerrs;

static bool sigalrm_expected;

static unsigned long at_minstack_size;

static int setup_altstack(void *start, unsigned long size)
{
        stack_t ss;

        memset(&ss, 0, sizeof(ss));
        ss.ss_size = size;
        ss.ss_sp = start;

        return sigaltstack(&ss, NULL);
}

static jmp_buf jmpbuf;

static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
{
        if (sigalrm_expected) {
                printf("[FAIL]\tWrong signal delivered: SIGSEGV (expected SIGALRM).");
                nerrs++;
        } else {
                printf("[OK]\tSIGSEGV signal delivered.\n");
        }

        siglongjmp(jmpbuf, 1);
}

static void sigalrm(int sig, siginfo_t *info, void *ctx_void)
{
        if (!sigalrm_expected) {
                printf("[FAIL]\tWrong signal delivered: SIGALRM (expected SIGSEGV).");
                nerrs++;
        } else {
                printf("[OK]\tSIGALRM signal delivered.\n");
        }
}

static void test_sigaltstack(void *altstack, unsigned long size)
{
        if (setup_altstack(altstack, size))
                err(1, "sigaltstack()");

        sigalrm_expected = (size > at_minstack_size) ? true : false;

        sethandler(SIGSEGV, sigsegv, 0);
        sethandler(SIGALRM, sigalrm, SA_ONSTACK);

        if (!sigsetjmp(jmpbuf, 1)) {
                printf("[RUN]\tTest an alternate signal stack of %ssufficient size.\n",
                       sigalrm_expected ? "" : "in");
                printf("\tRaise SIGALRM. %s is expected to be delivered.\n",
                       sigalrm_expected ? "It" : "SIGSEGV");
                raise(SIGALRM);
        }

        clearhandler(SIGALRM);
        clearhandler(SIGSEGV);
}

int main(void)
{
        void *altstack;

        at_minstack_size = getauxval(AT_MINSIGSTKSZ);

        altstack = mmap(NULL, at_minstack_size + SIGSTKSZ, PROT_READ | PROT_WRITE,
                        MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
        if (altstack == MAP_FAILED)
                err(1, "mmap()");

        if ((ENFORCED_MINSIGSTKSZ + 1) < at_minstack_size)
                test_sigaltstack(altstack, ENFORCED_MINSIGSTKSZ + 1);

        test_sigaltstack(altstack, at_minstack_size + SIGSTKSZ);

        return nerrs == 0 ? 0 : 1;
}