Messages in this thread Patch in this message | | | From | Benjamin Gray <> | Subject | [RFC PATCH 10/13] selftests/powerpc: Add hashst/hashchk test | Date | Mon, 28 Nov 2022 13:44:55 +1100 |
| |
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@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
| |