aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/testing/selftests/powerpc/signal/sigreturn_kernel.c
blob: 0a1b6e591eeed38be7b97e0099e3126f66af3857 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 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");
}