xref: /linux/tools/testing/selftests/powerpc/dexcr/hashchk_test.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1 // SPDX-License-Identifier: GPL-2.0+
2 
3 #define _GNU_SOURCE
4 
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <limits.h>
8 #include <sched.h>
9 #include <setjmp.h>
10 #include <signal.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/mman.h>
15 #include <sys/prctl.h>
16 #include <unistd.h>
17 
18 #include "dexcr.h"
19 #include "utils.h"
20 
21 static int require_nphie(void)
22 {
23 	SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
24 
25 	pr_set_dexcr(PR_PPC_DEXCR_NPHIE, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_SET_ONEXEC);
26 
27 	if (get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE)
28 		return 0;
29 
30 	SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE),
31 		    "Failed to enable DEXCR[NPHIE]");
32 
33 	return 0;
34 }
35 
36 static jmp_buf hashchk_detected_buf;
37 static const char *hashchk_failure_msg;
38 
39 static void hashchk_handler(int signum, siginfo_t *info, void *context)
40 {
41 	if (signum != SIGILL)
42 		hashchk_failure_msg = "wrong signal received";
43 	else if (info->si_code != ILL_ILLOPN)
44 		hashchk_failure_msg = "wrong signal code received";
45 
46 	longjmp(hashchk_detected_buf, 0);
47 }
48 
49 /*
50  * Check that hashchk triggers when DEXCR[NPHIE] is enabled
51  * and is detected as such by the kernel exception handler
52  */
53 static int hashchk_detected_test(void)
54 {
55 	struct sigaction old;
56 	int err;
57 
58 	err = require_nphie();
59 	if (err)
60 		return err;
61 
62 	old = push_signal_handler(SIGILL, hashchk_handler);
63 	if (setjmp(hashchk_detected_buf))
64 		goto out;
65 
66 	hashchk_failure_msg = NULL;
67 	do_bad_hashchk();
68 	hashchk_failure_msg = "hashchk failed to trigger";
69 
70 out:
71 	pop_signal_handler(SIGILL, old);
72 	FAIL_IF_MSG(hashchk_failure_msg, hashchk_failure_msg);
73 	return 0;
74 }
75 
76 #define HASH_COUNT 8
77 
78 static unsigned long hash_values[HASH_COUNT + 1];
79 
80 static void fill_hash_values(void)
81 {
82 	for (unsigned long i = 0; i < HASH_COUNT; i++)
83 		hashst(i, &hash_values[i]);
84 
85 	/* Used to ensure the checks uses the same addresses as the hashes */
86 	hash_values[HASH_COUNT] = (unsigned long)&hash_values;
87 }
88 
89 static unsigned int count_hash_values_matches(void)
90 {
91 	unsigned long matches = 0;
92 
93 	for (unsigned long i = 0; i < HASH_COUNT; i++) {
94 		unsigned long orig_hash = hash_values[i];
95 		hash_values[i] = 0;
96 
97 		hashst(i, &hash_values[i]);
98 
99 		if (hash_values[i] == orig_hash)
100 			matches++;
101 	}
102 
103 	return matches;
104 }
105 
106 static int hashchk_exec_child(void)
107 {
108 	ssize_t count;
109 
110 	fill_hash_values();
111 
112 	count = write(STDOUT_FILENO, hash_values, sizeof(hash_values));
113 	return count == sizeof(hash_values) ? 0 : EOVERFLOW;
114 }
115 
116 static char *hashchk_exec_child_args[] = { "hashchk_exec_child", NULL };
117 
118 /*
119  * Check that new programs get different keys so a malicious process
120  * can't recreate a victim's hash values.
121  */
122 static int hashchk_exec_random_key_test(void)
123 {
124 	pid_t pid;
125 	int err;
126 	int pipefd[2];
127 
128 	err = require_nphie();
129 	if (err)
130 		return err;
131 
132 	FAIL_IF_MSG(pipe(pipefd), "failed to create pipe");
133 
134 	pid = fork();
135 	if (pid == 0) {
136 		if (dup2(pipefd[1], STDOUT_FILENO) == -1)
137 			_exit(errno);
138 
139 		execve("/proc/self/exe", hashchk_exec_child_args, NULL);
140 		_exit(errno);
141 	}
142 
143 	await_child_success(pid);
144 	FAIL_IF_MSG(read(pipefd[0], hash_values, sizeof(hash_values)) != sizeof(hash_values),
145 		    "missing expected child output");
146 
147 	/* Verify the child used the same hash_values address */
148 	FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)&hash_values,
149 			 "bad address check");
150 
151 	/* If all hashes are the same it means (most likely) same key */
152 	FAIL_IF_MSG(count_hash_values_matches() == HASH_COUNT, "shared key detected");
153 
154 	return 0;
155 }
156 
157 /*
158  * Check that forks share the same key so that existing hash values
159  * remain valid.
160  */
161 static int hashchk_fork_share_key_test(void)
162 {
163 	pid_t pid;
164 	int err;
165 
166 	err = require_nphie();
167 	if (err)
168 		return err;
169 
170 	fill_hash_values();
171 
172 	pid = fork();
173 	if (pid == 0) {
174 		if (count_hash_values_matches() != HASH_COUNT)
175 			_exit(1);
176 		_exit(0);
177 	}
178 
179 	await_child_success(pid);
180 	return 0;
181 }
182 
183 #define STACK_SIZE (1024 * 1024)
184 
185 static int hashchk_clone_child_fn(void *args)
186 {
187 	fill_hash_values();
188 	return 0;
189 }
190 
191 /*
192  * Check that threads share the same key so that existing hash values
193  * remain valid.
194  */
195 static int hashchk_clone_share_key_test(void)
196 {
197 	void *child_stack;
198 	pid_t pid;
199 	int err;
200 
201 	err = require_nphie();
202 	if (err)
203 		return err;
204 
205 	child_stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
206 			   MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
207 
208 	FAIL_IF_MSG(child_stack == MAP_FAILED, "failed to map child stack");
209 
210 	pid = clone(hashchk_clone_child_fn, child_stack + STACK_SIZE,
211 		    CLONE_VM | SIGCHLD, NULL);
212 
213 	await_child_success(pid);
214 	FAIL_IF_MSG(count_hash_values_matches() != HASH_COUNT,
215 		    "different key detected");
216 
217 	return 0;
218 }
219 
220 int main(int argc, char *argv[])
221 {
222 	int err = 0;
223 
224 	if (argc >= 1 && !strcmp(argv[0], hashchk_exec_child_args[0]))
225 		return hashchk_exec_child();
226 
227 	err |= test_harness(hashchk_detected_test, "hashchk_detected");
228 	err |= test_harness(hashchk_exec_random_key_test, "hashchk_exec_random_key");
229 	err |= test_harness(hashchk_fork_share_key_test, "hashchk_fork_share_key");
230 	err |= test_harness(hashchk_clone_share_key_test, "hashchk_clone_share_key");
231 
232 	return err;
233 }
234