162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#define _GNU_SOURCE
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci#include <errno.h>
662306a36Sopenharmony_ci#include <fcntl.h>
762306a36Sopenharmony_ci#include <limits.h>
862306a36Sopenharmony_ci#include <sched.h>
962306a36Sopenharmony_ci#include <setjmp.h>
1062306a36Sopenharmony_ci#include <signal.h>
1162306a36Sopenharmony_ci#include <stdio.h>
1262306a36Sopenharmony_ci#include <stdlib.h>
1362306a36Sopenharmony_ci#include <string.h>
1462306a36Sopenharmony_ci#include <sys/mman.h>
1562306a36Sopenharmony_ci#include <sys/prctl.h>
1662306a36Sopenharmony_ci#include <unistd.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "dexcr.h"
1962306a36Sopenharmony_ci#include "utils.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic int require_nphie(void)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
2462306a36Sopenharmony_ci	SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE),
2562306a36Sopenharmony_ci		    "DEXCR[NPHIE] not enabled");
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	return 0;
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic jmp_buf hashchk_detected_buf;
3162306a36Sopenharmony_cistatic const char *hashchk_failure_msg;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic void hashchk_handler(int signum, siginfo_t *info, void *context)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	if (signum != SIGILL)
3662306a36Sopenharmony_ci		hashchk_failure_msg = "wrong signal received";
3762306a36Sopenharmony_ci	else if (info->si_code != ILL_ILLOPN)
3862306a36Sopenharmony_ci		hashchk_failure_msg = "wrong signal code received";
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	longjmp(hashchk_detected_buf, 0);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/*
4462306a36Sopenharmony_ci * Check that hashchk triggers when DEXCR[NPHIE] is enabled
4562306a36Sopenharmony_ci * and is detected as such by the kernel exception handler
4662306a36Sopenharmony_ci */
4762306a36Sopenharmony_cistatic int hashchk_detected_test(void)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct sigaction old;
5062306a36Sopenharmony_ci	int err;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	err = require_nphie();
5362306a36Sopenharmony_ci	if (err)
5462306a36Sopenharmony_ci		return err;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	old = push_signal_handler(SIGILL, hashchk_handler);
5762306a36Sopenharmony_ci	if (setjmp(hashchk_detected_buf))
5862306a36Sopenharmony_ci		goto out;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	hashchk_failure_msg = NULL;
6162306a36Sopenharmony_ci	do_bad_hashchk();
6262306a36Sopenharmony_ci	hashchk_failure_msg = "hashchk failed to trigger";
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ciout:
6562306a36Sopenharmony_ci	pop_signal_handler(SIGILL, old);
6662306a36Sopenharmony_ci	FAIL_IF_MSG(hashchk_failure_msg, hashchk_failure_msg);
6762306a36Sopenharmony_ci	return 0;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci#define HASH_COUNT 8
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic unsigned long hash_values[HASH_COUNT + 1];
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic void fill_hash_values(void)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	for (unsigned long i = 0; i < HASH_COUNT; i++)
7762306a36Sopenharmony_ci		hashst(i, &hash_values[i]);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	/* Used to ensure the checks uses the same addresses as the hashes */
8062306a36Sopenharmony_ci	hash_values[HASH_COUNT] = (unsigned long)&hash_values;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic unsigned int count_hash_values_matches(void)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	unsigned long matches = 0;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	for (unsigned long i = 0; i < HASH_COUNT; i++) {
8862306a36Sopenharmony_ci		unsigned long orig_hash = hash_values[i];
8962306a36Sopenharmony_ci		hash_values[i] = 0;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci		hashst(i, &hash_values[i]);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		if (hash_values[i] == orig_hash)
9462306a36Sopenharmony_ci			matches++;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return matches;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int hashchk_exec_child(void)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	ssize_t count;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	fill_hash_values();
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	count = write(STDOUT_FILENO, hash_values, sizeof(hash_values));
10762306a36Sopenharmony_ci	return count == sizeof(hash_values) ? 0 : EOVERFLOW;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic char *hashchk_exec_child_args[] = { "hashchk_exec_child", NULL };
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci/*
11362306a36Sopenharmony_ci * Check that new programs get different keys so a malicious process
11462306a36Sopenharmony_ci * can't recreate a victim's hash values.
11562306a36Sopenharmony_ci */
11662306a36Sopenharmony_cistatic int hashchk_exec_random_key_test(void)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	pid_t pid;
11962306a36Sopenharmony_ci	int err;
12062306a36Sopenharmony_ci	int pipefd[2];
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	err = require_nphie();
12362306a36Sopenharmony_ci	if (err)
12462306a36Sopenharmony_ci		return err;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	FAIL_IF_MSG(pipe(pipefd), "failed to create pipe");
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	pid = fork();
12962306a36Sopenharmony_ci	if (pid == 0) {
13062306a36Sopenharmony_ci		if (dup2(pipefd[1], STDOUT_FILENO) == -1)
13162306a36Sopenharmony_ci			_exit(errno);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci		execve("/proc/self/exe", hashchk_exec_child_args, NULL);
13462306a36Sopenharmony_ci		_exit(errno);
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	await_child_success(pid);
13862306a36Sopenharmony_ci	FAIL_IF_MSG(read(pipefd[0], hash_values, sizeof(hash_values)) != sizeof(hash_values),
13962306a36Sopenharmony_ci		    "missing expected child output");
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/* Verify the child used the same hash_values address */
14262306a36Sopenharmony_ci	FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)&hash_values,
14362306a36Sopenharmony_ci			 "bad address check");
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	/* If all hashes are the same it means (most likely) same key */
14662306a36Sopenharmony_ci	FAIL_IF_MSG(count_hash_values_matches() == HASH_COUNT, "shared key detected");
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return 0;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci/*
15262306a36Sopenharmony_ci * Check that forks share the same key so that existing hash values
15362306a36Sopenharmony_ci * remain valid.
15462306a36Sopenharmony_ci */
15562306a36Sopenharmony_cistatic int hashchk_fork_share_key_test(void)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	pid_t pid;
15862306a36Sopenharmony_ci	int err;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	err = require_nphie();
16162306a36Sopenharmony_ci	if (err)
16262306a36Sopenharmony_ci		return err;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	fill_hash_values();
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	pid = fork();
16762306a36Sopenharmony_ci	if (pid == 0) {
16862306a36Sopenharmony_ci		if (count_hash_values_matches() != HASH_COUNT)
16962306a36Sopenharmony_ci			_exit(1);
17062306a36Sopenharmony_ci		_exit(0);
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	await_child_success(pid);
17462306a36Sopenharmony_ci	return 0;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci#define STACK_SIZE (1024 * 1024)
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int hashchk_clone_child_fn(void *args)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	fill_hash_values();
18262306a36Sopenharmony_ci	return 0;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci/*
18662306a36Sopenharmony_ci * Check that threads share the same key so that existing hash values
18762306a36Sopenharmony_ci * remain valid.
18862306a36Sopenharmony_ci */
18962306a36Sopenharmony_cistatic int hashchk_clone_share_key_test(void)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	void *child_stack;
19262306a36Sopenharmony_ci	pid_t pid;
19362306a36Sopenharmony_ci	int err;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	err = require_nphie();
19662306a36Sopenharmony_ci	if (err)
19762306a36Sopenharmony_ci		return err;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	child_stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
20062306a36Sopenharmony_ci			   MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	FAIL_IF_MSG(child_stack == MAP_FAILED, "failed to map child stack");
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	pid = clone(hashchk_clone_child_fn, child_stack + STACK_SIZE,
20562306a36Sopenharmony_ci		    CLONE_VM | SIGCHLD, NULL);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	await_child_success(pid);
20862306a36Sopenharmony_ci	FAIL_IF_MSG(count_hash_values_matches() != HASH_COUNT,
20962306a36Sopenharmony_ci		    "different key detected");
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	return 0;
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ciint main(int argc, char *argv[])
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	int err = 0;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	if (argc >= 1 && !strcmp(argv[0], hashchk_exec_child_args[0]))
21962306a36Sopenharmony_ci		return hashchk_exec_child();
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	err |= test_harness(hashchk_detected_test, "hashchk_detected");
22262306a36Sopenharmony_ci	err |= test_harness(hashchk_exec_random_key_test, "hashchk_exec_random_key");
22362306a36Sopenharmony_ci	err |= test_harness(hashchk_fork_share_key_test, "hashchk_fork_share_key");
22462306a36Sopenharmony_ci	err |= test_harness(hashchk_clone_share_key_test, "hashchk_clone_share_key");
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return err;
22762306a36Sopenharmony_ci}
228