root/tools/testing/selftests/signal/sas.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Stas Sergeev <stsp@users.sourceforge.net>
 *
 * test sigaltstack(SS_ONSTACK | SS_AUTODISARM)
 * If that succeeds, then swapcontext() can be used inside sighandler safely.
 *
 */

#define _GNU_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <ucontext.h>
#include <alloca.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <sys/auxv.h>

#include "kselftest.h"
#include "current_stack_pointer.h"

#ifndef SS_AUTODISARM
#define SS_AUTODISARM  (1U << 31)
#endif

#ifndef AT_MINSIGSTKSZ
#define AT_MINSIGSTKSZ  51
#endif

static unsigned int stack_size;
static void *sstack, *ustack;
static ucontext_t uc, sc;
static const char *msg = "[OK]\tStack preserved";
static const char *msg2 = "[FAIL]\tStack corrupted";
struct stk_data {
        char msg[128];
        int flag;
};

void my_usr1(int sig, siginfo_t *si, void *u)
{
        char *aa;
        int err;
        stack_t stk;
        struct stk_data *p;

        if (sp < (unsigned long)sstack ||
                        sp >= (unsigned long)sstack + stack_size) {
                ksft_exit_fail_msg("SP is not on sigaltstack\n");
        }
        /* put some data on stack. other sighandler will try to overwrite it */
        aa = alloca(1024);
        assert(aa);
        p = (struct stk_data *)(aa + 512);
        strcpy(p->msg, msg);
        p->flag = 1;
        ksft_print_msg("[RUN]\tsignal USR1\n");
        err = sigaltstack(NULL, &stk);
        if (err) {
                ksft_exit_fail_msg("sigaltstack() - %s\n", strerror(errno));
                exit(EXIT_FAILURE);
        }
        if (stk.ss_flags != SS_DISABLE)
                ksft_test_result_fail("tss_flags=%x, should be SS_DISABLE\n",
                                stk.ss_flags);
        else
                ksft_test_result_pass(
                                "sigaltstack is disabled in sighandler\n");
        swapcontext(&sc, &uc);
        ksft_print_msg("%s\n", p->msg);
        if (!p->flag) {
                ksft_exit_fail_msg("[RUN]\tAborting\n");
                exit(EXIT_FAILURE);
        }
}

void my_usr2(int sig, siginfo_t *si, void *u)
{
        char *aa;
        struct stk_data *p;

        ksft_print_msg("[RUN]\tsignal USR2\n");
        aa = alloca(1024);
        /* dont run valgrind on this */
        /* try to find the data stored by previous sighandler */
        p = memmem(aa, 1024, msg, strlen(msg));
        if (p) {
                ksft_test_result_fail("sigaltstack re-used\n");
                /* corrupt the data */
                strcpy(p->msg, msg2);
                /* tell other sighandler that his data is corrupted */
                p->flag = 0;
        }
}

static void switch_fn(void)
{
        ksft_print_msg("[RUN]\tswitched to user ctx\n");
        raise(SIGUSR2);
        setcontext(&sc);
}

int main(void)
{
        struct sigaction act;
        stack_t stk;
        int err;

        /* Make sure more than the required minimum. */
        stack_size = getauxval(AT_MINSIGSTKSZ) + SIGSTKSZ;
        ksft_print_msg("[NOTE]\tthe stack size is %u\n", stack_size);

        ksft_print_header();
        ksft_set_plan(3);

        sigemptyset(&act.sa_mask);
        act.sa_flags = SA_ONSTACK | SA_SIGINFO;
        act.sa_sigaction = my_usr1;
        sigaction(SIGUSR1, &act, NULL);
        act.sa_sigaction = my_usr2;
        sigaction(SIGUSR2, &act, NULL);
        sstack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
        if (sstack == MAP_FAILED) {
                ksft_exit_fail_msg("mmap() - %s\n", strerror(errno));
                return EXIT_FAILURE;
        }

        err = sigaltstack(NULL, &stk);
        if (err) {
                ksft_exit_fail_msg("sigaltstack() - %s\n", strerror(errno));
                exit(EXIT_FAILURE);
        }
        if (stk.ss_flags == SS_DISABLE) {
                ksft_test_result_pass(
                                "Initial sigaltstack state was SS_DISABLE\n");
        } else {
                ksft_exit_fail_msg("Initial sigaltstack state was %x; "
                       "should have been SS_DISABLE\n", stk.ss_flags);
                return EXIT_FAILURE;
        }

        stk.ss_sp = sstack;
        stk.ss_size = stack_size;
        stk.ss_flags = SS_ONSTACK | SS_AUTODISARM;
        err = sigaltstack(&stk, NULL);
        if (err) {
                if (errno == EINVAL) {
                        ksft_test_result_skip(
                                "[NOTE]\tThe running kernel doesn't support SS_AUTODISARM\n");
                        /*
                         * If test cases for the !SS_AUTODISARM variant were
                         * added, we could still run them.  We don't have any
                         * test cases like that yet, so just exit and report
                         * success.
                         */
                        return 0;
                } else {
                        ksft_exit_fail_msg(
                                "sigaltstack(SS_ONSTACK | SS_AUTODISARM)  %s\n",
                                        strerror(errno));
                        return EXIT_FAILURE;
                }
        }

        ustack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
        if (ustack == MAP_FAILED) {
                ksft_exit_fail_msg("mmap() - %s\n", strerror(errno));
                return EXIT_FAILURE;
        }
        getcontext(&uc);
        uc.uc_link = NULL;
        uc.uc_stack.ss_sp = ustack;
        uc.uc_stack.ss_size = stack_size;
        makecontext(&uc, switch_fn, 0);
        raise(SIGUSR1);

        err = sigaltstack(NULL, &stk);
        if (err) {
                ksft_exit_fail_msg("sigaltstack() - %s\n", strerror(errno));
                exit(EXIT_FAILURE);
        }
        if (stk.ss_flags != SS_AUTODISARM) {
                ksft_exit_fail_msg("ss_flags=%x, should be SS_AUTODISARM\n",
                                stk.ss_flags);
                exit(EXIT_FAILURE);
        }
        ksft_test_result_pass(
                        "sigaltstack is still SS_AUTODISARM after signal\n");

        ksft_exit_pass();
        return 0;
}