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} 228