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