root/tools/testing/selftests/powerpc/signal/sigreturn_kernel.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Test that we can't sigreturn to kernel addresses, or to kernel mode.
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "utils.h"

#define MSR_PR (1ul << 14)

static volatile unsigned long long sigreturn_addr;
static volatile unsigned long long sigreturn_msr_mask;

static void sigusr1_handler(int signo, siginfo_t *si, void *uc_ptr)
{
        ucontext_t *uc = (ucontext_t *)uc_ptr;

        if (sigreturn_addr)
                UCONTEXT_NIA(uc) = sigreturn_addr;

        if (sigreturn_msr_mask)
                UCONTEXT_MSR(uc) &= sigreturn_msr_mask;
}

static pid_t fork_child(void)
{
        pid_t pid;

        pid = fork();
        if (pid == 0) {
                raise(SIGUSR1);
                exit(0);
        }

        return pid;
}

static int expect_segv(pid_t pid)
{
        int child_ret;

        waitpid(pid, &child_ret, 0);
        FAIL_IF(WIFEXITED(child_ret));
        FAIL_IF(!WIFSIGNALED(child_ret));
        FAIL_IF(WTERMSIG(child_ret) != 11);

        return 0;
}

int test_sigreturn_kernel(void)
{
        struct sigaction act;
        int child_ret, i;
        pid_t pid;

        act.sa_sigaction = sigusr1_handler;
        act.sa_flags = SA_SIGINFO;
        sigemptyset(&act.sa_mask);

        FAIL_IF(sigaction(SIGUSR1, &act, NULL));

        for (i = 0; i < 2; i++) {
                // Return to kernel
                sigreturn_addr = 0xcull << 60;
                pid = fork_child();
                expect_segv(pid);

                // Return to kernel virtual
                sigreturn_addr = 0xc008ull << 48;
                pid = fork_child();
                expect_segv(pid);

                // Return out of range
                sigreturn_addr = 0xc010ull << 48;
                pid = fork_child();
                expect_segv(pid);

                // Return to no-man's land, just below PAGE_OFFSET
                sigreturn_addr = (0xcull << 60) - (64 * 1024);
                pid = fork_child();
                expect_segv(pid);

                // Return to no-man's land, above TASK_SIZE_4PB
                sigreturn_addr = 0x1ull << 52;
                pid = fork_child();
                expect_segv(pid);

                // Return to 0xd space
                sigreturn_addr = 0xdull << 60;
                pid = fork_child();
                expect_segv(pid);

                // Return to 0xe space
                sigreturn_addr = 0xeull << 60;
                pid = fork_child();
                expect_segv(pid);

                // Return to 0xf space
                sigreturn_addr = 0xfull << 60;
                pid = fork_child();
                expect_segv(pid);

                // Attempt to set PR=0 for 2nd loop (should be blocked by kernel)
                sigreturn_msr_mask = ~MSR_PR;
        }

        printf("All children killed as expected\n");

        // Don't change address, just MSR, should return to user as normal
        sigreturn_addr = 0;
        sigreturn_msr_mask = ~MSR_PR;
        pid = fork_child();
        waitpid(pid, &child_ret, 0);
        FAIL_IF(!WIFEXITED(child_ret));
        FAIL_IF(WIFSIGNALED(child_ret));
        FAIL_IF(WEXITSTATUS(child_ret) != 0);

        return 0;
}

int main(void)
{
        return test_harness(test_sigreturn_kernel, "sigreturn_kernel");
}