[RFC PATCH 10/13] selftests/powerpc: Add hashst/hashchk test

From: Benjamin Gray
Date: Sun Nov 27 2022 - 21:46:47 EST


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 <bgray@xxxxxxxxxxxxx>
---
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