Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

kselftests/arm64: add PAuth test for whether exec() changes keys

Kernel documentation states that it will change PAuth keys on exec() calls.

Verify that all keys are correctly switched to new ones.

Signed-off-by: Boyan Karatotev <boyan.karatotev@arm.com>
Reviewed-by: Vincenzo Frascino <Vincenzo.Frascino@arm.com>
Reviewed-by: Amit Daniel Kachhap <amit.kachhap@arm.com>
Acked-by: Shuah Khan <skhan@linuxfoundation.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20200918104715.182310-4-boian4o1@gmail.com
Signed-off-by: Will Deacon <will@kernel.org>

authored by

Boyan Karatotev and committed by
Will Deacon
806a15b2 766d95b1

+198
+4
tools/testing/selftests/arm64/pauth/Makefile
··· 13 13 ifeq ($(pauth_cc_support),1) 14 14 TEST_GEN_PROGS := pac 15 15 TEST_GEN_FILES := pac_corruptor.o helper.o 16 + TEST_GEN_PROGS_EXTENDED := exec_target 16 17 endif 17 18 18 19 include ../../lib.mk ··· 31 30 # greater, gcc emits pac* instructions which are not in HINT NOP space, 32 31 # preventing the tests from occurring at all. Compile for ARMv8.2 so tests can 33 32 # run on earlier targets and print a meaningful error messages 33 + $(OUTPUT)/exec_target: exec_target.c $(OUTPUT)/helper.o 34 + $(CC) $^ -o $@ $(CFLAGS) -march=armv8.2-a 35 + 34 36 $(OUTPUT)/pac: pac.c $(OUTPUT)/pac_corruptor.o $(OUTPUT)/helper.o 35 37 $(CC) $^ -o $@ $(CFLAGS) -march=armv8.2-a 36 38 endif
+34
tools/testing/selftests/arm64/pauth/exec_target.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // Copyright (C) 2020 ARM Limited 3 + 4 + #include <stdio.h> 5 + #include <stdlib.h> 6 + #include <sys/auxv.h> 7 + 8 + #include "helper.h" 9 + 10 + int main(void) 11 + { 12 + struct signatures signed_vals; 13 + unsigned long hwcaps; 14 + size_t val; 15 + 16 + fread(&val, sizeof(size_t), 1, stdin); 17 + 18 + /* don't try to execute illegal (unimplemented) instructions) caller 19 + * should have checked this and keep worker simple 20 + */ 21 + hwcaps = getauxval(AT_HWCAP); 22 + 23 + if (hwcaps & HWCAP_PACA) { 24 + signed_vals.keyia = keyia_sign(val); 25 + signed_vals.keyib = keyib_sign(val); 26 + signed_vals.keyda = keyda_sign(val); 27 + signed_vals.keydb = keydb_sign(val); 28 + } 29 + signed_vals.keyg = (hwcaps & HWCAP_PACG) ? keyg_sign(val) : 0; 30 + 31 + fwrite(&signed_vals, sizeof(struct signatures), 1, stdout); 32 + 33 + return 0; 34 + }
+10
tools/testing/selftests/arm64/pauth/helper.h
··· 6 6 7 7 #include <stdlib.h> 8 8 9 + #define NKEYS 5 10 + 11 + struct signatures { 12 + size_t keyia; 13 + size_t keyib; 14 + size_t keyda; 15 + size_t keydb; 16 + size_t keyg; 17 + }; 18 + 9 19 void pac_corruptor(void); 10 20 11 21 /* PAuth sign a value with key ia and modifier value 0 */
+150
tools/testing/selftests/arm64/pauth/pac.c
··· 2 2 // Copyright (C) 2020 ARM Limited 3 3 4 4 #include <sys/auxv.h> 5 + #include <sys/types.h> 6 + #include <sys/wait.h> 5 7 #include <signal.h> 6 8 #include <setjmp.h> 7 9 ··· 29 27 /* generic key instructions are not in NOP space. This prevents a SIGILL */ \ 30 28 ASSERT_NE(0, hwcaps & HWCAP_PACG) TH_LOG("Generic PAUTH not enabled"); \ 31 29 } while (0) 30 + 31 + void sign_specific(struct signatures *sign, size_t val) 32 + { 33 + sign->keyia = keyia_sign(val); 34 + sign->keyib = keyib_sign(val); 35 + sign->keyda = keyda_sign(val); 36 + sign->keydb = keydb_sign(val); 37 + } 38 + 39 + void sign_all(struct signatures *sign, size_t val) 40 + { 41 + sign->keyia = keyia_sign(val); 42 + sign->keyib = keyib_sign(val); 43 + sign->keyda = keyda_sign(val); 44 + sign->keydb = keydb_sign(val); 45 + sign->keyg = keyg_sign(val); 46 + } 47 + 48 + int n_same(struct signatures *old, struct signatures *new, int nkeys) 49 + { 50 + int res = 0; 51 + 52 + res += old->keyia == new->keyia; 53 + res += old->keyib == new->keyib; 54 + res += old->keyda == new->keyda; 55 + res += old->keydb == new->keydb; 56 + if (nkeys == NKEYS) 57 + res += old->keyg == new->keyg; 58 + 59 + return res; 60 + } 61 + 62 + int exec_sign_all(struct signatures *signed_vals, size_t val) 63 + { 64 + int new_stdin[2]; 65 + int new_stdout[2]; 66 + int status; 67 + ssize_t ret; 68 + pid_t pid; 69 + 70 + ret = pipe(new_stdin); 71 + if (ret == -1) { 72 + perror("pipe returned error"); 73 + return -1; 74 + } 75 + 76 + ret = pipe(new_stdout); 77 + if (ret == -1) { 78 + perror("pipe returned error"); 79 + return -1; 80 + } 81 + 82 + pid = fork(); 83 + // child 84 + if (pid == 0) { 85 + dup2(new_stdin[0], STDIN_FILENO); 86 + if (ret == -1) { 87 + perror("dup2 returned error"); 88 + exit(1); 89 + } 90 + 91 + dup2(new_stdout[1], STDOUT_FILENO); 92 + if (ret == -1) { 93 + perror("dup2 returned error"); 94 + exit(1); 95 + } 96 + 97 + close(new_stdin[0]); 98 + close(new_stdin[1]); 99 + close(new_stdout[0]); 100 + close(new_stdout[1]); 101 + 102 + ret = execl("exec_target", "exec_target", (char *)NULL); 103 + if (ret == -1) { 104 + perror("exec returned error"); 105 + exit(1); 106 + } 107 + } 108 + 109 + close(new_stdin[0]); 110 + close(new_stdout[1]); 111 + 112 + ret = write(new_stdin[1], &val, sizeof(size_t)); 113 + if (ret == -1) { 114 + perror("write returned error"); 115 + return -1; 116 + } 117 + 118 + /* 119 + * wait for the worker to finish, so that read() reads all data 120 + * will also context switch with worker so that this function can be used 121 + * for context switch tests 122 + */ 123 + waitpid(pid, &status, 0); 124 + if (WIFEXITED(status) == 0) { 125 + fprintf(stderr, "worker exited unexpectedly\n"); 126 + return -1; 127 + } 128 + if (WEXITSTATUS(status) != 0) { 129 + fprintf(stderr, "worker exited with error\n"); 130 + return -1; 131 + } 132 + 133 + ret = read(new_stdout[0], signed_vals, sizeof(struct signatures)); 134 + if (ret == -1) { 135 + perror("read returned error"); 136 + return -1; 137 + } 138 + 139 + return 0; 140 + } 32 141 33 142 sigjmp_buf jmpbuf; 34 143 void pac_signal_handler(int signum, siginfo_t *si, void *uc) ··· 203 90 keyg |= keyg_sign(i) & PAC_MASK; 204 91 205 92 ASSERT_NE(0, keyg) TH_LOG("keyg instructions did nothing"); 93 + } 94 + 95 + /* 96 + * fork() does not change keys. Only exec() does so call a worker program. 97 + * Its only job is to sign a value and report back the resutls 98 + */ 99 + TEST(exec_changed_keys) 100 + { 101 + struct signatures new_keys; 102 + struct signatures old_keys; 103 + int ret; 104 + int same = 10; 105 + int nkeys = NKEYS; 106 + unsigned long hwcaps = getauxval(AT_HWCAP); 107 + 108 + /* generic and data key instructions are not in NOP space. This prevents a SIGILL */ 109 + ASSERT_NE(0, hwcaps & HWCAP_PACA) TH_LOG("PAUTH not enabled"); 110 + if (!(hwcaps & HWCAP_PACG)) { 111 + TH_LOG("WARNING: Generic PAUTH not enabled. Skipping generic key checks"); 112 + nkeys = NKEYS - 1; 113 + } 114 + 115 + for (int i = 0; i < PAC_COLLISION_ATTEMPTS; i++) { 116 + ret = exec_sign_all(&new_keys, i); 117 + ASSERT_EQ(0, ret) TH_LOG("failed to run worker"); 118 + 119 + if (nkeys == NKEYS) 120 + sign_all(&old_keys, i); 121 + else 122 + sign_specific(&old_keys, i); 123 + 124 + ret = n_same(&old_keys, &new_keys, nkeys); 125 + if (ret < same) 126 + same = ret; 127 + } 128 + 129 + ASSERT_EQ(0, same) TH_LOG("exec() did not change %d keys", same); 206 130 } 207 131 208 132 TEST_HARNESS_MAIN