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 21 static 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 30 static jmp_buf hashchk_detected_buf; 31 static const char *hashchk_failure_msg; 32 33 static 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 */ 47 static 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 64 out: 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 72 static unsigned long hash_values[HASH_COUNT + 1]; 73 74 static 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 83 static 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 100 static 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 110 static 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 */ 116 static 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 */ 155 static 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 179 static 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 */ 189 static 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 214 int 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