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
21static int require_nphie(void)
22{
23 SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
24 SKIP_IF_MSG(!(get_dexcr(source: EFFECTIVE) & DEXCR_PR_NPHIE),
25 "DEXCR[NPHIE] not enabled");
26
27 return 0;
28}
29
30static jmp_buf hashchk_detected_buf;
31static const char *hashchk_failure_msg;
32
33static 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 */
47static 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
64out:
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
72static unsigned long hash_values[HASH_COUNT + 1];
73
74static void fill_hash_values(void)
75{
76 for (unsigned long i = 0; i < HASH_COUNT; i++)
77 hashst(lr: i, sp: &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
83static 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(lr: i, sp: &hash_values[i]);
92
93 if (hash_values[i] == orig_hash)
94 matches++;
95 }
96
97 return matches;
98}
99
100static 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
110static 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 */
116static 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 */
155static 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
179static 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 */
189static 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
214int 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

source code of linux/tools/testing/selftests/powerpc/dexcr/hashchk_test.c