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 25 pr_set_dexcr(PR_PPC_DEXCR_NPHIE, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_SET_ONEXEC); 26 27 if (get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE) 28 return 0; 29 30 SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE), 31 "Failed to enable DEXCR[NPHIE]"); 32 33 return 0; 34 } 35 36 static jmp_buf hashchk_detected_buf; 37 static const char *hashchk_failure_msg; 38 39 static void hashchk_handler(int signum, siginfo_t *info, void *context) 40 { 41 if (signum != SIGILL) 42 hashchk_failure_msg = "wrong signal received"; 43 else if (info->si_code != ILL_ILLOPN) 44 hashchk_failure_msg = "wrong signal code received"; 45 46 longjmp(hashchk_detected_buf, 0); 47 } 48 49 /* 50 * Check that hashchk triggers when DEXCR[NPHIE] is enabled 51 * and is detected as such by the kernel exception handler 52 */ 53 static int hashchk_detected_test(void) 54 { 55 struct sigaction old; 56 int err; 57 58 err = require_nphie(); 59 if (err) 60 return err; 61 62 old = push_signal_handler(SIGILL, hashchk_handler); 63 if (setjmp(hashchk_detected_buf)) 64 goto out; 65 66 hashchk_failure_msg = NULL; 67 do_bad_hashchk(); 68 hashchk_failure_msg = "hashchk failed to trigger"; 69 70 out: 71 pop_signal_handler(SIGILL, old); 72 FAIL_IF_MSG(hashchk_failure_msg, hashchk_failure_msg); 73 return 0; 74 } 75 76 #define HASH_COUNT 8 77 78 static unsigned long hash_values[HASH_COUNT + 1]; 79 80 static void fill_hash_values(void) 81 { 82 for (unsigned long i = 0; i < HASH_COUNT; i++) 83 hashst(i, &hash_values[i]); 84 85 /* Used to ensure the checks uses the same addresses as the hashes */ 86 hash_values[HASH_COUNT] = (unsigned long)&hash_values; 87 } 88 89 static unsigned int count_hash_values_matches(void) 90 { 91 unsigned long matches = 0; 92 93 for (unsigned long i = 0; i < HASH_COUNT; i++) { 94 unsigned long orig_hash = hash_values[i]; 95 hash_values[i] = 0; 96 97 hashst(i, &hash_values[i]); 98 99 if (hash_values[i] == orig_hash) 100 matches++; 101 } 102 103 return matches; 104 } 105 106 static int hashchk_exec_child(void) 107 { 108 ssize_t count; 109 110 fill_hash_values(); 111 112 count = write(STDOUT_FILENO, hash_values, sizeof(hash_values)); 113 return count == sizeof(hash_values) ? 0 : EOVERFLOW; 114 } 115 116 static char *hashchk_exec_child_args[] = { "hashchk_exec_child", NULL }; 117 118 /* 119 * Check that new programs get different keys so a malicious process 120 * can't recreate a victim's hash values. 121 */ 122 static int hashchk_exec_random_key_test(void) 123 { 124 pid_t pid; 125 int err; 126 int pipefd[2]; 127 128 err = require_nphie(); 129 if (err) 130 return err; 131 132 FAIL_IF_MSG(pipe(pipefd), "failed to create pipe"); 133 134 pid = fork(); 135 if (pid == 0) { 136 if (dup2(pipefd[1], STDOUT_FILENO) == -1) 137 _exit(errno); 138 139 execve("/proc/self/exe", hashchk_exec_child_args, NULL); 140 _exit(errno); 141 } 142 143 await_child_success(pid); 144 FAIL_IF_MSG(read(pipefd[0], hash_values, sizeof(hash_values)) != sizeof(hash_values), 145 "missing expected child output"); 146 147 /* Verify the child used the same hash_values address */ 148 FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)&hash_values, 149 "bad address check"); 150 151 /* If all hashes are the same it means (most likely) same key */ 152 FAIL_IF_MSG(count_hash_values_matches() == HASH_COUNT, "shared key detected"); 153 154 return 0; 155 } 156 157 /* 158 * Check that forks share the same key so that existing hash values 159 * remain valid. 160 */ 161 static int hashchk_fork_share_key_test(void) 162 { 163 pid_t pid; 164 int err; 165 166 err = require_nphie(); 167 if (err) 168 return err; 169 170 fill_hash_values(); 171 172 pid = fork(); 173 if (pid == 0) { 174 if (count_hash_values_matches() != HASH_COUNT) 175 _exit(1); 176 _exit(0); 177 } 178 179 await_child_success(pid); 180 return 0; 181 } 182 183 #define STACK_SIZE (1024 * 1024) 184 185 static int hashchk_clone_child_fn(void *args) 186 { 187 fill_hash_values(); 188 return 0; 189 } 190 191 /* 192 * Check that threads share the same key so that existing hash values 193 * remain valid. 194 */ 195 static int hashchk_clone_share_key_test(void) 196 { 197 void *child_stack; 198 pid_t pid; 199 int err; 200 201 err = require_nphie(); 202 if (err) 203 return err; 204 205 child_stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, 206 MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); 207 208 FAIL_IF_MSG(child_stack == MAP_FAILED, "failed to map child stack"); 209 210 pid = clone(hashchk_clone_child_fn, child_stack + STACK_SIZE, 211 CLONE_VM | SIGCHLD, NULL); 212 213 await_child_success(pid); 214 FAIL_IF_MSG(count_hash_values_matches() != HASH_COUNT, 215 "different key detected"); 216 217 return 0; 218 } 219 220 int main(int argc, char *argv[]) 221 { 222 int err = 0; 223 224 if (argc >= 1 && !strcmp(argv[0], hashchk_exec_child_args[0])) 225 return hashchk_exec_child(); 226 227 err |= test_harness(hashchk_detected_test, "hashchk_detected"); 228 err |= test_harness(hashchk_exec_random_key_test, "hashchk_exec_random_key"); 229 err |= test_harness(hashchk_fork_share_key_test, "hashchk_fork_share_key"); 230 err |= test_harness(hashchk_clone_share_key_test, "hashchk_clone_share_key"); 231 232 return err; 233 } 234