Linux Audio

Check our new training course

Loading...
  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(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(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
 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(i, &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}