kselftests/arm64: add PAuth test for whether exec() changes keys
authorBoyan Karatotev <boyan.karatotev@arm.com>
Fri, 18 Sep 2020 10:47:14 +0000 (11:47 +0100)
committerWill Deacon <will@kernel.org>
Fri, 18 Sep 2020 13:07:20 +0000 (14:07 +0100)
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>
tools/testing/selftests/arm64/pauth/Makefile
tools/testing/selftests/arm64/pauth/exec_target.c [new file with mode: 0644]
tools/testing/selftests/arm64/pauth/helper.h
tools/testing/selftests/arm64/pauth/pac.c

index 5c0dd12..72e290b 100644 (file)
@@ -13,6 +13,7 @@ pauth_cc_support := $(shell if ($(CC) $(CFLAGS) -march=armv8.3-a -E -x c /dev/nu
 ifeq ($(pauth_cc_support),1)
 TEST_GEN_PROGS := pac
 TEST_GEN_FILES := pac_corruptor.o helper.o
+TEST_GEN_PROGS_EXTENDED := exec_target
 endif
 
 include ../../lib.mk
@@ -30,6 +31,9 @@ $(OUTPUT)/helper.o: helper.c
 # greater, gcc emits pac* instructions which are not in HINT NOP space,
 # preventing the tests from occurring at all. Compile for ARMv8.2 so tests can
 # run on earlier targets and print a meaningful error messages
+$(OUTPUT)/exec_target: exec_target.c $(OUTPUT)/helper.o
+       $(CC) $^ -o $@ $(CFLAGS) -march=armv8.2-a
+
 $(OUTPUT)/pac: pac.c $(OUTPUT)/pac_corruptor.o $(OUTPUT)/helper.o
        $(CC) $^ -o $@ $(CFLAGS) -march=armv8.2-a
 endif
diff --git a/tools/testing/selftests/arm64/pauth/exec_target.c b/tools/testing/selftests/arm64/pauth/exec_target.c
new file mode 100644 (file)
index 0000000..4435600
--- /dev/null
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2020 ARM Limited
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/auxv.h>
+
+#include "helper.h"
+
+int main(void)
+{
+       struct signatures signed_vals;
+       unsigned long hwcaps;
+       size_t val;
+
+       fread(&val, sizeof(size_t), 1, stdin);
+
+       /* don't try to execute illegal (unimplemented) instructions) caller
+        * should have checked this and keep worker simple
+        */
+       hwcaps = getauxval(AT_HWCAP);
+
+       if (hwcaps & HWCAP_PACA) {
+               signed_vals.keyia = keyia_sign(val);
+               signed_vals.keyib = keyib_sign(val);
+               signed_vals.keyda = keyda_sign(val);
+               signed_vals.keydb = keydb_sign(val);
+       }
+       signed_vals.keyg = (hwcaps & HWCAP_PACG) ?  keyg_sign(val) : 0;
+
+       fwrite(&signed_vals, sizeof(struct signatures), 1, stdout);
+
+       return 0;
+}
index 35c4f33..652496c 100644 (file)
@@ -6,6 +6,16 @@
 
 #include <stdlib.h>
 
+#define NKEYS 5
+
+struct signatures {
+       size_t keyia;
+       size_t keyib;
+       size_t keyda;
+       size_t keydb;
+       size_t keyg;
+};
+
 void pac_corruptor(void);
 
 /* PAuth sign a value with key ia and modifier value 0 */
index bd3d4c0..b363ad6 100644 (file)
@@ -2,6 +2,8 @@
 // Copyright (C) 2020 ARM Limited
 
 #include <sys/auxv.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 #include <signal.h>
 #include <setjmp.h>
 
@@ -28,6 +30,117 @@ do { \
        ASSERT_NE(0, hwcaps & HWCAP_PACG) TH_LOG("Generic PAUTH not enabled"); \
 } while (0)
 
+void sign_specific(struct signatures *sign, size_t val)
+{
+       sign->keyia = keyia_sign(val);
+       sign->keyib = keyib_sign(val);
+       sign->keyda = keyda_sign(val);
+       sign->keydb = keydb_sign(val);
+}
+
+void sign_all(struct signatures *sign, size_t val)
+{
+       sign->keyia = keyia_sign(val);
+       sign->keyib = keyib_sign(val);
+       sign->keyda = keyda_sign(val);
+       sign->keydb = keydb_sign(val);
+       sign->keyg  = keyg_sign(val);
+}
+
+int n_same(struct signatures *old, struct signatures *new, int nkeys)
+{
+       int res = 0;
+
+       res += old->keyia == new->keyia;
+       res += old->keyib == new->keyib;
+       res += old->keyda == new->keyda;
+       res += old->keydb == new->keydb;
+       if (nkeys == NKEYS)
+               res += old->keyg == new->keyg;
+
+       return res;
+}
+
+int exec_sign_all(struct signatures *signed_vals, size_t val)
+{
+       int new_stdin[2];
+       int new_stdout[2];
+       int status;
+       ssize_t ret;
+       pid_t pid;
+
+       ret = pipe(new_stdin);
+       if (ret == -1) {
+               perror("pipe returned error");
+               return -1;
+       }
+
+       ret = pipe(new_stdout);
+       if (ret == -1) {
+               perror("pipe returned error");
+               return -1;
+       }
+
+       pid = fork();
+       // child
+       if (pid == 0) {
+               dup2(new_stdin[0], STDIN_FILENO);
+               if (ret == -1) {
+                       perror("dup2 returned error");
+                       exit(1);
+               }
+
+               dup2(new_stdout[1], STDOUT_FILENO);
+               if (ret == -1) {
+                       perror("dup2 returned error");
+                       exit(1);
+               }
+
+               close(new_stdin[0]);
+               close(new_stdin[1]);
+               close(new_stdout[0]);
+               close(new_stdout[1]);
+
+               ret = execl("exec_target", "exec_target", (char *)NULL);
+               if (ret == -1) {
+                       perror("exec returned error");
+                       exit(1);
+               }
+       }
+
+       close(new_stdin[0]);
+       close(new_stdout[1]);
+
+       ret = write(new_stdin[1], &val, sizeof(size_t));
+       if (ret == -1) {
+               perror("write returned error");
+               return -1;
+       }
+
+       /*
+        * wait for the worker to finish, so that read() reads all data
+        * will also context switch with worker so that this function can be used
+        * for context switch tests
+        */
+       waitpid(pid, &status, 0);
+       if (WIFEXITED(status) == 0) {
+               fprintf(stderr, "worker exited unexpectedly\n");
+               return -1;
+       }
+       if (WEXITSTATUS(status) != 0) {
+               fprintf(stderr, "worker exited with error\n");
+               return -1;
+       }
+
+       ret = read(new_stdout[0], signed_vals, sizeof(struct signatures));
+       if (ret == -1) {
+               perror("read returned error");
+               return -1;
+       }
+
+       return 0;
+}
+
 sigjmp_buf jmpbuf;
 void pac_signal_handler(int signum, siginfo_t *si, void *uc)
 {
@@ -92,4 +205,41 @@ TEST(pac_instructions_not_nop_generic)
        ASSERT_NE(0, keyg)  TH_LOG("keyg instructions did nothing");
 }
 
+/*
+ * fork() does not change keys. Only exec() does so call a worker program.
+ * Its only job is to sign a value and report back the resutls
+ */
+TEST(exec_changed_keys)
+{
+       struct signatures new_keys;
+       struct signatures old_keys;
+       int ret;
+       int same = 10;
+       int nkeys = NKEYS;
+       unsigned long hwcaps = getauxval(AT_HWCAP);
+
+       /* generic and data key instructions are not in NOP space. This prevents a SIGILL */
+       ASSERT_NE(0, hwcaps & HWCAP_PACA) TH_LOG("PAUTH not enabled");
+       if (!(hwcaps & HWCAP_PACG)) {
+               TH_LOG("WARNING: Generic PAUTH not enabled. Skipping generic key checks");
+               nkeys = NKEYS - 1;
+       }
+
+       for (int i = 0; i < PAC_COLLISION_ATTEMPTS; i++) {
+               ret = exec_sign_all(&new_keys, i);
+               ASSERT_EQ(0, ret) TH_LOG("failed to run worker");
+
+               if (nkeys == NKEYS)
+                       sign_all(&old_keys, i);
+               else
+                       sign_specific(&old_keys, i);
+
+               ret = n_same(&old_keys, &new_keys, nkeys);
+               if (ret < same)
+                       same = ret;
+       }
+
+       ASSERT_EQ(0, same) TH_LOG("exec() did not change %d keys", same);
+}
+
 TEST_HARNESS_MAIN