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

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

Signed-off-by: Benjamin Gray <bg...@linux.ibm.com>
---
 tools/testing/selftests/powerpc/Makefile      |   1 +
 .../selftests/powerpc/dexcr/.gitignore        |   1 +
 .../testing/selftests/powerpc/dexcr/Makefile  |   9 +
 tools/testing/selftests/powerpc/dexcr/dexcr.c | 118 +++++++++
 tools/testing/selftests/powerpc/dexcr/dexcr.h |  52 ++++
 .../selftests/powerpc/dexcr/hashchk_test.c    | 229 ++++++++++++++++++
 tools/testing/selftests/powerpc/include/reg.h |   4 +
 7 files changed, 414 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..37adb7f47832
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/.gitignore
@@ -0,0 +1 @@
+hashchk_user
diff --git a/tools/testing/selftests/powerpc/dexcr/Makefile 
b/tools/testing/selftests/powerpc/dexcr/Makefile
new file mode 100644
index 000000000000..4b4380d4d986
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/Makefile
@@ -0,0 +1,9 @@
+TEST_GEN_PROGS := hashchk_test
+
+TEST_FILES := settings
+top_srcdir = ../../../../..
+include ../../lib.mk
+
+HASHCHK_TEST_CFLAGS = -no-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..3e7cb581d4a2
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/dexcr.c
@@ -0,0 +1,118 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+
+#include "dexcr.h"
+#include "reg.h"
+#include "utils.h"
+
+long sysctl_get_sbhe(void)
+{
+       long value;
+
+       FAIL_IF_EXIT_MSG(read_long(SYSCTL_DEXCR_SBHE, &value, 10),
+                        "failed to read " SYSCTL_DEXCR_SBHE);
+
+       return value;
+}
+
+void sysctl_set_sbhe(long value)
+{
+       FAIL_IF_EXIT_MSG(write_long(SYSCTL_DEXCR_SBHE, value, 10),
+                        "failed to write to " SYSCTL_DEXCR_SBHE);
+}
+
+unsigned int pr_aspect_to_dexcr_mask(unsigned long which)
+{
+       switch (which) {
+       case PR_PPC_DEXCR_SBHE:
+               return DEXCR_PRO_SBHE;
+       case PR_PPC_DEXCR_IBRTPD:
+               return DEXCR_PRO_IBRTPD;
+       case PR_PPC_DEXCR_SRAPD:
+               return DEXCR_PRO_SRAPD;
+       case PR_PPC_DEXCR_NPHIE:
+               return DEXCR_PRO_NPHIE;
+       default:
+               FAIL_IF_EXIT_MSG(true, "unknown PR aspect");
+       }
+}
+
+static inline unsigned int get_dexcr_pro(void)
+{
+       return mfspr(SPRN_DEXCR);
+}
+
+static inline unsigned int get_dexcr_enf(void)
+{
+       return mfspr(SPRN_HDEXCR);
+}
+
+static inline unsigned int get_dexcr_eff(void)
+{
+       return get_dexcr_pro() | get_dexcr_enf();
+}
+
+unsigned int get_dexcr(enum DexcrSource source)
+{
+       switch (source) {
+       case UDEXCR:
+               return get_dexcr_pro();
+       case ENFORCED:
+               return get_dexcr_enf();
+       case EFFECTIVE:
+               return get_dexcr_eff();
+       default:
+               FAIL_IF_EXIT_MSG(true, "bad DEXCR source");
+       }
+}
+
+bool pr_aspect_supported(unsigned long which)
+{
+       return prctl(PR_PPC_GET_DEXCR, which, 0, 0, 0) >= 0;
+}
+
+bool pr_aspect_editable(unsigned long which)
+{
+       int ret = prctl(PR_PPC_GET_DEXCR, which, 0, 0, 0);
+       return ret > 0 && (ret & PR_PPC_DEXCR_PRCTL) > 0;
+}
+
+bool pr_aspect_edit(unsigned long which, unsigned long ctrl)
+{
+       return prctl(PR_PPC_SET_DEXCR, which, ctrl, 0, 0) == 0;
+}
+
+bool pr_aspect_check(unsigned long which, enum DexcrSource source)
+{
+       unsigned int dexcr = get_dexcr(source);
+       unsigned int aspect = pr_aspect_to_dexcr_mask(which);
+       return (dexcr & aspect) != 0;
+}
+
+int pr_aspect_get(unsigned long pr_aspect)
+{
+       int ret = prctl(PR_PPC_GET_DEXCR, pr_aspect, 0, 0, 0);
+       FAIL_IF_EXIT_MSG(ret < 0, "prctl failed");
+       return ret;
+}
+
+bool dexcr_pro_check(unsigned int pro, enum DexcrSource source)
+{
+       return (get_dexcr(source) & pro) != 0;
+}
+
+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");
+}
diff --git a/tools/testing/selftests/powerpc/dexcr/dexcr.h 
b/tools/testing/selftests/powerpc/dexcr/dexcr.h
new file mode 100644
index 000000000000..fb8007bf19f8
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/dexcr.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * 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/prctl.h>
+#include <unistd.h>
+
+#include "reg.h"
+#include "utils.h"
+
+#define DEXCR_PRO_MASK(aspect) __MASK(63 - (32 + (aspect)))
+#define DEXCR_PRO_SBHE         DEXCR_PRO_MASK(0)
+#define DEXCR_PRO_IBRTPD       DEXCR_PRO_MASK(3)
+#define DEXCR_PRO_SRAPD                DEXCR_PRO_MASK(4)
+#define DEXCR_PRO_NPHIE                DEXCR_PRO_MASK(5)
+
+enum DexcrSource {
+       UDEXCR,         /* Userspace DEXCR value */
+       ENFORCED,       /* Enforced by hypervisor */
+       EFFECTIVE,      /* Bitwise OR of requested and enforced DEXCR bits */
+};
+
+unsigned int get_dexcr(enum DexcrSource source);
+
+bool pr_aspect_supported(unsigned long which);
+
+bool pr_aspect_editable(unsigned long which);
+
+bool pr_aspect_edit(unsigned long which, unsigned long ctrl);
+
+bool pr_aspect_check(unsigned long which, enum DexcrSource source);
+
+int pr_aspect_get(unsigned long which);
+
+unsigned int pr_aspect_to_dexcr_mask(unsigned long which);
+
+bool dexcr_pro_check(unsigned int pro, enum DexcrSource source);
+
+long sysctl_get_sbhe(void);
+
+void sysctl_set_sbhe(long value);
+
+void await_child_success(pid_t pid);
+
+#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..3351bdbdaf13
--- /dev/null
+++ b/tools/testing/selftests/powerpc/dexcr/hashchk_test.c
@@ -0,0 +1,229 @@
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.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(!pr_aspect_supported(PR_PPC_DEXCR_NPHIE),
+                   "DEXCR[NPHIE] not supported");
+
+       if (dexcr_pro_check(DEXCR_PRO_NPHIE, EFFECTIVE))
+               return 0;
+
+       pr_aspect_edit(PR_PPC_DEXCR_NPHIE, PR_PPC_DEXCR_FORCE_SET_ASPECT);
+       FAIL_IF_EXIT_MSG(!dexcr_pro_check(DEXCR_PRO_NPHIE, EFFECTIVE),
+                        "failed to enable DEXCR[NPIHE]");
+
+       return 0;
+}
+
+static void sigill_handler_enabled(int signum, siginfo_t *info, void *context)
+{
+       SIGSAFE_FAIL_IF_EXIT_MSG(signum != SIGILL, "wrong signal received");
+       SIGSAFE_FAIL_IF_EXIT_MSG(info->si_code != ILL_ILLOPN, "wrong 
signal-code received");
+       exit(0);
+}
+
+static void do_bad_hashchk(void)
+{
+       unsigned long hash = 0;
+       void *hash_p = ((void *)&hash) + 8;     /* hash* offset must be at 
least -8 */
+
+       asm ("li 3, 0;"                 /* set r3 (pretend LR) to known value */
+            "hashst 3, -8(%1);"        /* compute good hash */
+            "addi 3, 3, 1;"            /* modify hash */
+            "hashchk 3, -8(%1);"       /* check bad hash */
+            : "+m" (hash) : "r" (hash_p) : "r3");
+}
+
+/*
+ * Check that hashchk triggers when DEXCR[NPHIE] is enabled
+ * and is detected as such by the kernel exception handler
+ */
+static int hashchk_enabled_test(void)
+{
+       int err;
+       struct sigaction sa;
+
+       if ((err = require_nphie()))
+               return err;
+
+       sa.sa_sigaction = sigill_handler_enabled;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_SIGINFO;
+       FAIL_IF_MSG(sigaction(SIGILL, &sa, NULL), "cannot install signal 
handler");
+
+       do_bad_hashchk();
+
+       FAIL_IF_MSG(true, "hashchk failed to trigger");
+}
+
+#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++) {
+               void *hash_addr = ((void*)&hash_values[i]) + 8;
+
+               asm volatile ("hashst %2, -8(%1);"
+                             : "+m" (hash_values[i]) : "r" (hash_addr), "r" 
(i));
+       }
+
+       hash_values[HASH_COUNT] = (unsigned long)&hash_values;
+}
+
+static unsigned int count_hash_values_matches(void)
+{
+       unsigned long matches = 0;
+
+       FAIL_IF_EXIT_MSG(hash_values[HASH_COUNT] != (unsigned long)hash_values,
+                        "bad address check");
+
+       for (unsigned long i = 0; i < HASH_COUNT; i++) {
+               unsigned long orig_hash = hash_values[i];
+               void *hash_addr = ((void*)&hash_values[i]) + 8;
+
+               asm volatile ("hashst %2, -8(%1);"
+                             : "+m" (hash_values[i]) : "r" (hash_addr), "r" 
(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;
+}
+
+/*
+ * 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];
+
+       if ((err = require_nphie()))
+               return err;
+
+       FAIL_IF_MSG(pipe(pipefd), "failed to create pipe");
+
+       pid = fork();
+       if (pid == 0) {
+               char *args[] = { "hashchk_exec_child", NULL };
+
+               if (dup2(pipefd[1], STDOUT_FILENO) == -1)
+                       _exit(errno);
+
+               execve("/proc/self/exe", 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");
+
+       /* 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;
+
+       if ((err = require_nphie()))
+               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;
+
+       if ((err = require_nphie()))
+               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"))
+               return hashchk_exec_child();
+
+       err |= test_harness(hashchk_enabled_test, "hashchk_enabled");
+       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..cbb5979cb3e2 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    455
+
 #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     812
+
 #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 */
-- 
2.38.1

Reply via email to