// SPDX-License-Identifier: GPL-2.0+ #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <limits.h> #include <sched.h> #include <setjmp.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/prctl.h> #include <unistd.h> #include "dexcr.h" #include "utils.h" static int require_nphie(void) { SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported"); SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE), "DEXCR[NPHIE] not enabled"); return 0; } static jmp_buf hashchk_detected_buf; static const char *hashchk_failure_msg; static void hashchk_handler(int signum, siginfo_t *info, void *context) { if (signum != SIGILL) hashchk_failure_msg = "wrong signal received"; else if (info->si_code != ILL_ILLOPN) hashchk_failure_msg = "wrong signal code received"; longjmp(hashchk_detected_buf, 0); } /* * Check that hashchk triggers when DEXCR[NPHIE] is enabled * and is detected as such by the kernel exception handler */ static int hashchk_detected_test(void) { struct sigaction old; int err; err = require_nphie(); if (err) return err; old = push_signal_handler(SIGILL, hashchk_handler); if (setjmp(hashchk_detected_buf)) goto out; hashchk_failure_msg = NULL; do_bad_hashchk(); hashchk_failure_msg = "hashchk failed to trigger"; out: pop_signal_handler(SIGILL, old); FAIL_IF_MSG(hashchk_failure_msg, hashchk_failure_msg); return 0; } #define HASH_COUNT 8 static unsigned long hash_values[HASH_COUNT + 1]; static void fill_hash_values(void) { for (unsigned long i = 0; i < HASH_COUNT; i++) hashst(i, &hash_values[i]); /* Used to ensure the checks uses the same addresses as the hashes */ hash_values[HASH_COUNT] = (unsigned long)&hash_values; } static unsigned int count_hash_values_matches(void) { unsigned long matches = 0; for (unsigned long i = 0; i < HASH_COUNT; i++) { unsigned long orig_hash = hash_values[i]; hash_values[i] = 0; hashst(i, &hash_values[i]); if (hash_values[i] == orig_hash) matches++; } return matches; } static int hashchk_exec_child(void) { ssize_t count; fill_hash_values(); count = write(STDOUT_FILENO, hash_values, sizeof(hash_values)); return count == sizeof(hash_values) ? 0 : EOVERFLOW; } static char *hashchk_exec_child_args[] = { "hashchk_exec_child", NULL }; /* * Check that new programs get different keys so a malicious process * can't recreate a victim's hash values. */ static int hashchk_exec_random_key_test(void) { pid_t pid; int err; int pipefd[2]; err = require_nphie(); if (err) return err; FAIL_IF_MSG(pipe(pipefd), "failed to create pipe"); pid = fork(); if (pid == 0) { if (dup2(pipefd[1], STDOUT_FILENO) == -1) _exit(errno); execve("/proc/self/exe", hashchk_exec_child_args, NULL); _exit(errno); } await_child_success(pid); FAIL_IF_MSG(read(pipefd[0], hash_values, sizeof(hash_values)) != sizeof(hash_values), "missing expected child output"); /* Verify the child used the same hash_values address */ FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)&hash_values, "bad address check"); /* If all hashes are the same it means (most likely) same key */ FAIL_IF_MSG(count_hash_values_matches() == HASH_COUNT, "shared key detected"); return 0; } /* * Check that forks share the same key so that existing hash values * remain valid. */ static int hashchk_fork_share_key_test(void) { pid_t pid; int err; err = require_nphie(); if (err) return err; fill_hash_values(); pid = fork(); if (pid == 0) { if (count_hash_values_matches() != HASH_COUNT) _exit(1); _exit(0); } await_child_success(pid); return 0; } #define STACK_SIZE (1024 * 1024) static int hashchk_clone_child_fn(void *args) { fill_hash_values(); return 0; } /* * Check that threads share the same key so that existing hash values * remain valid. */ static int hashchk_clone_share_key_test(void) { void *child_stack; pid_t pid; int err; err = require_nphie(); if (err) return err; child_stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); FAIL_IF_MSG(child_stack == MAP_FAILED, "failed to map child stack"); pid = clone(hashchk_clone_child_fn, child_stack + STACK_SIZE, CLONE_VM | SIGCHLD, NULL); await_child_success(pid); FAIL_IF_MSG(count_hash_values_matches() != HASH_COUNT, "different key detected"); return 0; } int main(int argc, char *argv[]) { int err = 0; if (argc >= 1 && !strcmp(argv[0], hashchk_exec_child_args[0])) return hashchk_exec_child(); err |= test_harness(hashchk_detected_test, "hashchk_detected"); err |= test_harness(hashchk_exec_random_key_test, "hashchk_exec_random_key"); err |= test_harness(hashchk_fork_share_key_test, "hashchk_fork_share_key"); err |= test_harness(hashchk_clone_share_key_test, "hashchk_clone_share_key"); return err; }