| // 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; |
| } |