Test the kernel DEXCR[NPHIE] interface and hashchk exception handling.

Introduces with it a DEXCR utils library for common DEXCR operations.

Volatile is used to prevent the compiler optimising away the signal
tests.

Signed-off-by: Benjamin Gray <bg...@linux.ibm.com>

---
v1:     * Clean up dexcr makefile
        * Include kernel headers in CFLAGS
        * Use numeric literals for hashst/hashchk to support older
          toolchains
        * A lot of other refactoring
---
 tools/testing/selftests/powerpc/Makefile      |   1 +
 .../selftests/powerpc/dexcr/.gitignore        |   1 +
 .../testing/selftests/powerpc/dexcr/Makefile  |   7 +
 tools/testing/selftests/powerpc/dexcr/dexcr.c | 132 ++++++++++
 tools/testing/selftests/powerpc/dexcr/dexcr.h |  49 ++++
 .../selftests/powerpc/dexcr/hashchk_test.c    | 227 ++++++++++++++++++
 tools/testing/selftests/powerpc/include/reg.h |   4 +
 .../testing/selftests/powerpc/include/utils.h |   4 +
 tools/testing/selftests/powerpc/utils.c       |  24 ++
 9 files changed, 449 insertions(+)
 create mode 100644 tools/testing/selftests/powerpc/dexcr/.gitignore
 create mode 100644 tools/testing/selftests/powerpc/dexcr/Makefile
 create mode 100644 tools/testing/selftests/powerpc/dexcr/dexcr.c
 create mode 100644 tools/testing/selftests/powerpc/dexcr/dexcr.h
 create mode 100644 tools/testing/selftests/powerpc/dexcr/hashchk_test.c

diff --git a/tools/testing/selftests/powerpc/Makefile 
b/tools/testing/selftests/powerpc/Makefile
index 6ba95cd19e42..00dbd000ee01 100644
--- a/tools/testing/selftests/powerpc/Makefile
+++ b/tools/testing/selftests/powerpc/Makefile
@@ -17,6 +17,7 @@ SUB_DIRS = alignment          \
           benchmarks           \
           cache_shape          \
           copyloops            \
+          dexcr                \
           dscr                 \
           mm                   \
           nx-gzip              \
diff --git a/tools/testing/selftests/powerpc/dexcr/.gitignore 
b/tools/testing/selftests/powerpc/dexcr/.gitignore
new file mode 100644
index 000000000000..d12e4560aca9
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/.gitignore
@@ -0,0 +1 @@
+hashchk_test
diff --git a/tools/testing/selftests/powerpc/dexcr/Makefile 
b/tools/testing/selftests/powerpc/dexcr/Makefile
new file mode 100644
index 000000000000..16c8b489948a
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/Makefile
@@ -0,0 +1,7 @@
+TEST_GEN_PROGS := hashchk_test
+
+include ../../lib.mk
+
+$(OUTPUT)/hashchk_test: CFLAGS += -fno-pie $(call cc-option,-mno-rop-protect)
+
+$(TEST_GEN_PROGS): ../harness.c ../utils.c ./dexcr.c
diff --git a/tools/testing/selftests/powerpc/dexcr/dexcr.c 
b/tools/testing/selftests/powerpc/dexcr/dexcr.c
new file mode 100644
index 000000000000..65ec5347de98
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/dexcr.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "dexcr.h"
+#include "reg.h"
+#include "utils.h"
+
+static jmp_buf generic_signal_jump_buf;
+
+static void generic_signal_handler(int signum, siginfo_t *info, void *context)
+{
+       longjmp(generic_signal_jump_buf, 0);
+}
+
+bool dexcr_exists(void)
+{
+       struct sigaction old;
+       volatile bool exists;
+
+       old = push_signal_handler(SIGILL, generic_signal_handler);
+       if (setjmp(generic_signal_jump_buf))
+               goto out;
+
+       /*
+        * If the SPR is not recognised by the hardware it triggers
+        * a hypervisor emulation interrupt. If the kernel does not
+        * recognise/try to emulate it, we receive a SIGILL signal.
+        *
+        * If we do not receive a signal, assume we have the SPR or the
+        * kernel is trying to emulate it correctly.
+        */
+       exists = false;
+       mfspr(SPRN_DEXCR_RO);
+       exists = true;
+
+out:
+       pop_signal_handler(SIGILL, old);
+       return exists;
+}
+
+/*
+ * Just test if a bad hashchk triggers a signal, without checking
+ * for support or if the NPHIE aspect is enabled.
+ */
+bool hashchk_triggers(void)
+{
+       struct sigaction old;
+       volatile bool triggers;
+
+       old = push_signal_handler(SIGILL, generic_signal_handler);
+       if (setjmp(generic_signal_jump_buf))
+               goto out;
+
+       triggers = true;
+       do_bad_hashchk();
+       triggers = false;
+
+out:
+       pop_signal_handler(SIGILL, old);
+       return triggers;
+}
+
+unsigned int get_dexcr(enum dexcr_source source)
+{
+       switch (source) {
+       case DEXCR:
+               return mfspr(SPRN_DEXCR_RO);
+       case HDEXCR:
+               return mfspr(SPRN_HDEXCR_RO);
+       case EFFECTIVE:
+               return mfspr(SPRN_DEXCR_RO) | mfspr(SPRN_HDEXCR_RO);
+       default:
+               FAIL_IF_EXIT_MSG(true, "bad enum dexcr_source");
+       }
+}
+
+void await_child_success(pid_t pid)
+{
+       int wstatus;
+
+       FAIL_IF_EXIT_MSG(pid == -1, "fork failed");
+       FAIL_IF_EXIT_MSG(waitpid(pid, &wstatus, 0) == -1, "wait failed");
+       FAIL_IF_EXIT_MSG(!WIFEXITED(wstatus), "child did not exit cleanly");
+       FAIL_IF_EXIT_MSG(WEXITSTATUS(wstatus) != 0, "child exit error");
+}
+
+/*
+ * Perform a hashst instruction. The following components determine the result
+ *
+ * 1. The LR value (any register technically)
+ * 2. The SP value (also any register, but it must be a valid address)
+ * 3. A secret key managed by the kernel
+ *
+ * The result is stored to the address held in SP.
+ */
+void hashst(unsigned long lr, void *sp)
+{
+       asm volatile ("addi 31, %0, 0;"         /* set r31 (pretend LR) to lr */
+                     "addi 30, %1, 8;"         /* set r30 (pretend SP) to sp + 
8 */
+                     PPC_RAW_HASHST(31, -8, 30)        /* compute hash into 
stack location */
+                     : : "r" (lr), "r" (sp) : "r31", "r30", "memory");
+}
+
+/*
+ * Perform a hashchk instruction. A hash is computed as per hashst(),
+ * however the result is not stored to memory. Instead the existing
+ * value is read and compared against the computed hash.
+ *
+ * If they match, execution continues.
+ * If they differ, an interrupt triggers.
+ */
+void hashchk(unsigned long lr, void *sp)
+{
+       asm volatile ("addi 31, %0, 0;"         /* set r31 (pretend LR) to lr */
+                     "addi 30, %1, 8;"         /* set r30 (pretend SP) to sp + 
8 */
+                     PPC_RAW_HASHCHK(31, -8, 30)       /* check hash at stack 
location */
+                     : : "r" (lr), "r" (sp) : "r31", "r30", "memory");
+}
+
+void do_bad_hashchk(void)
+{
+       unsigned long hash = 0;
+
+       hashst(0, &hash);
+       hash += 1;
+       hashchk(0, &hash);
+}
diff --git a/tools/testing/selftests/powerpc/dexcr/dexcr.h 
b/tools/testing/selftests/powerpc/dexcr/dexcr.h
new file mode 100644
index 000000000000..f55cbbc8643b
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/dexcr.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * POWER Dynamic Execution Control Facility (DEXCR)
+ *
+ * This header file contains helper functions and macros
+ * required for all the DEXCR related test cases.
+ */
+#ifndef _SELFTESTS_POWERPC_DEXCR_DEXCR_H
+#define _SELFTESTS_POWERPC_DEXCR_DEXCR_H
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "reg.h"
+
+#define DEXCR_PR_BIT(aspect)   __MASK(63 - (32 + (aspect)))
+#define DEXCR_PR_SBHE          DEXCR_PR_BIT(0)
+#define DEXCR_PR_IBRTPD                DEXCR_PR_BIT(3)
+#define DEXCR_PR_SRAPD         DEXCR_PR_BIT(4)
+#define DEXCR_PR_NPHIE         DEXCR_PR_BIT(5)
+
+#define PPC_RAW_HASH_ARGS(b, i, a) \
+       ((((i) >> 3) & 0x1F) << 21 | (a) << 16 | (b) << 11 | (((i) >> 8) & 0x1))
+#define PPC_RAW_HASHST(b, i, a) \
+       str(.long (0x7C0005A4 | PPC_RAW_HASH_ARGS(b, i, a));)
+#define PPC_RAW_HASHCHK(b, i, a) \
+       str(.long (0x7C0005E4 | PPC_RAW_HASH_ARGS(b, i, a));)
+
+bool dexcr_exists(void);
+
+bool hashchk_triggers(void);
+
+enum dexcr_source {
+       DEXCR,          /* Userspace DEXCR value */
+       HDEXCR,         /* Hypervisor enforced DEXCR value */
+       EFFECTIVE,      /* Bitwise OR of UDEXCR and ENFORCED DEXCR bits */
+};
+
+unsigned int get_dexcr(enum dexcr_source source);
+
+void await_child_success(pid_t pid);
+
+void hashst(unsigned long lr, void *sp);
+
+void hashchk(unsigned long lr, void *sp);
+
+void do_bad_hashchk(void);
+
+#endif  /* _SELFTESTS_POWERPC_DEXCR_DEXCR_H */
diff --git a/tools/testing/selftests/powerpc/dexcr/hashchk_test.c 
b/tools/testing/selftests/powerpc/dexcr/hashchk_test.c
new file mode 100644
index 000000000000..7d5658c9ebe4
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/hashchk_test.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "dexcr.h"
+#include "utils.h"
+
+static int require_nphie(void)
+{
+       SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
+       SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE),
+                   "DEXCR[NPHIE] not enabled");
+
+       return 0;
+}
+
+static jmp_buf hashchk_detected_buf;
+static const char *hashchk_failure_msg;
+
+static void hashchk_handler(int signum, siginfo_t *info, void *context)
+{
+       if (signum != SIGILL)
+               hashchk_failure_msg = "wrong signal received";
+       else if (info->si_code != ILL_ILLOPN)
+               hashchk_failure_msg = "wrong signal code received";
+
+       longjmp(hashchk_detected_buf, 0);
+}
+
+/*
+ * Check that hashchk triggers when DEXCR[NPHIE] is enabled
+ * and is detected as such by the kernel exception handler
+ */
+static int hashchk_detected_test(void)
+{
+       struct sigaction old;
+       int err;
+
+       err = require_nphie();
+       if (err)
+               return err;
+
+       old = push_signal_handler(SIGILL, hashchk_handler);
+       if (setjmp(hashchk_detected_buf))
+               goto out;
+
+       hashchk_failure_msg = NULL;
+       do_bad_hashchk();
+       hashchk_failure_msg = "hashchk failed to trigger";
+
+out:
+       pop_signal_handler(SIGILL, old);
+       FAIL_IF_MSG(hashchk_failure_msg, hashchk_failure_msg);
+       return 0;
+}
+
+#define HASH_COUNT 8
+
+static unsigned long hash_values[HASH_COUNT + 1];
+
+static void fill_hash_values(void)
+{
+       for (unsigned long i = 0; i < HASH_COUNT; i++)
+               hashst(i, &hash_values[i]);
+
+       /* Used to ensure the checks uses the same addresses as the hashes */
+       hash_values[HASH_COUNT] = (unsigned long)&hash_values;
+}
+
+static unsigned int count_hash_values_matches(void)
+{
+       unsigned long matches = 0;
+
+       for (unsigned long i = 0; i < HASH_COUNT; i++) {
+               unsigned long orig_hash = hash_values[i];
+               hash_values[i] = 0;
+
+               hashst(i, &hash_values[i]);
+
+               if (hash_values[i] == orig_hash)
+                       matches++;
+       }
+
+       return matches;
+}
+
+static int hashchk_exec_child(void)
+{
+       ssize_t count;
+
+       fill_hash_values();
+
+       count = write(STDOUT_FILENO, hash_values, sizeof(hash_values));
+       return count == sizeof(hash_values) ? 0 : EOVERFLOW;
+}
+
+static char *hashchk_exec_child_args[] = { "hashchk_exec_child", NULL };
+
+/*
+ * Check that new programs get different keys so a malicious process
+ * can't recreate a victim's hash values.
+ */
+static int hashchk_exec_random_key_test(void)
+{
+       pid_t pid;
+       int err;
+       int pipefd[2];
+
+       err = require_nphie();
+       if (err)
+               return err;
+
+       FAIL_IF_MSG(pipe(pipefd), "failed to create pipe");
+
+       pid = fork();
+       if (pid == 0) {
+               if (dup2(pipefd[1], STDOUT_FILENO) == -1)
+                       _exit(errno);
+
+               execve("/proc/self/exe", hashchk_exec_child_args, NULL);
+               _exit(errno);
+       }
+
+       await_child_success(pid);
+       FAIL_IF_MSG(read(pipefd[0], hash_values, sizeof(hash_values)) != 
sizeof(hash_values),
+                   "missing expected child output");
+
+       /* Verify the child used the same hash_values address */
+       FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)&hash_values,
+                        "bad address check");
+
+       /* If all hashes are the same it means (most likely) same key */
+       FAIL_IF_MSG(count_hash_values_matches() == HASH_COUNT, "shared key 
detected");
+
+       return 0;
+}
+
+/*
+ * Check that forks share the same key so that existing hash values
+ * remain valid.
+ */
+static int hashchk_fork_share_key_test(void)
+{
+       pid_t pid;
+       int err;
+
+       err = require_nphie();
+       if (err)
+               return err;
+
+       fill_hash_values();
+
+       pid = fork();
+       if (pid == 0) {
+               if (count_hash_values_matches() != HASH_COUNT)
+                       _exit(1);
+               _exit(0);
+       }
+
+       await_child_success(pid);
+       return 0;
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+static int hashchk_clone_child_fn(void *args)
+{
+       fill_hash_values();
+       return 0;
+}
+
+/*
+ * Check that threads share the same key so that existing hash values
+ * remain valid.
+ */
+static int hashchk_clone_share_key_test(void)
+{
+       void *child_stack;
+       pid_t pid;
+       int err;
+
+       err = require_nphie();
+       if (err)
+               return err;
+
+       child_stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
+                          MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+
+       FAIL_IF_MSG(child_stack == MAP_FAILED, "failed to map child stack");
+
+       pid = clone(hashchk_clone_child_fn, child_stack + STACK_SIZE,
+                   CLONE_VM | SIGCHLD, NULL);
+
+       await_child_success(pid);
+       FAIL_IF_MSG(count_hash_values_matches() != HASH_COUNT,
+                   "different key detected");
+
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       int err = 0;
+
+       if (argc >= 1 && !strcmp(argv[0], hashchk_exec_child_args[0]))
+               return hashchk_exec_child();
+
+       err |= test_harness(hashchk_detected_test, "hashchk_detected");
+       err |= test_harness(hashchk_exec_random_key_test, 
"hashchk_exec_random_key");
+       err |= test_harness(hashchk_fork_share_key_test, 
"hashchk_fork_share_key");
+       err |= test_harness(hashchk_clone_share_key_test, 
"hashchk_clone_share_key");
+
+       return err;
+}
diff --git a/tools/testing/selftests/powerpc/include/reg.h 
b/tools/testing/selftests/powerpc/include/reg.h
index d5a547f72669..fad09c9d3387 100644
--- a/tools/testing/selftests/powerpc/include/reg.h
+++ b/tools/testing/selftests/powerpc/include/reg.h
@@ -19,6 +19,8 @@
 #define mb()           asm volatile("sync" : : : "memory");
 #define barrier()      asm volatile("" : : : "memory");
 
+#define SPRN_HDEXCR_RO 455     /* Userspace readonly view of SPRN_HDEXCR (471) 
*/
+
 #define SPRN_MMCR2     769
 #define SPRN_MMCRA     770
 #define SPRN_MMCR0     779
@@ -47,6 +49,8 @@
 #define SPRN_SDAR      781
 #define SPRN_SIER      768
 
+#define SPRN_DEXCR_RO  812     /* Userspace readonly view of SPRN_DEXCR (828) 
*/
+
 #define SPRN_TEXASR     0x82    /* Transaction Exception and Status Register */
 #define SPRN_TFIAR      0x81    /* Transaction Failure Inst Addr    */
 #define SPRN_TFHAR      0x80    /* Transaction Failure Handler Addr */
diff --git a/tools/testing/selftests/powerpc/include/utils.h 
b/tools/testing/selftests/powerpc/include/utils.h
index 65b242842ff5..85fa6fdbeafa 100644
--- a/tools/testing/selftests/powerpc/include/utils.h
+++ b/tools/testing/selftests/powerpc/include/utils.h
@@ -11,6 +11,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <stdbool.h>
+#include <sys/signal.h>
 #include <linux/auxvec.h>
 #include <linux/perf_event.h>
 #include <asm/cputable.h>
@@ -108,6 +109,9 @@ static inline char *auxv_platform(void)
 bool is_ppc64le(void);
 int using_hash_mmu(bool *using_hash);
 
+struct sigaction push_signal_handler(int sig, void (*fn)(int, siginfo_t *, 
void *));
+struct sigaction pop_signal_handler(int sig, struct sigaction old_handler);
+
 /* Yes, this is evil */
 #define FAIL_IF(x)                                             \
 do {                                                           \
diff --git a/tools/testing/selftests/powerpc/utils.c 
b/tools/testing/selftests/powerpc/utils.c
index 7c8cfedb012a..b1d01a10f143 100644
--- a/tools/testing/selftests/powerpc/utils.c
+++ b/tools/testing/selftests/powerpc/utils.c
@@ -595,3 +595,27 @@ int using_hash_mmu(bool *using_hash)
        fclose(f);
        return rc;
 }
+
+struct sigaction push_signal_handler(int sig, void (*fn)(int, siginfo_t *, 
void *))
+{
+       struct sigaction sa;
+       struct sigaction old_handler;
+
+       sa.sa_sigaction = fn;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_SIGINFO;
+       FAIL_IF_EXIT_MSG(sigaction(sig, &sa, &old_handler),
+                        "failed to push signal handler");
+
+       return old_handler;
+}
+
+struct sigaction pop_signal_handler(int sig, struct sigaction old_handler)
+{
+       struct sigaction popped;
+
+       FAIL_IF_EXIT_MSG(sigaction(sig, &old_handler, &popped),
+                        "failed to pop signal handler");
+
+       return popped;
+}
-- 
2.39.2

Reply via email to