Commit bdb07f35 authored by Benjamin Gray's avatar Benjamin Gray Committed by Michael Ellerman

selftests/powerpc/dexcr: Add hashst/hashchk test

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: default avatarBenjamin Gray <bgray@linux.ibm.com>
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20230616034846.311705-11-bgray@linux.ibm.com
parent b9125c9a
...@@ -17,6 +17,7 @@ SUB_DIRS = alignment \ ...@@ -17,6 +17,7 @@ SUB_DIRS = alignment \
benchmarks \ benchmarks \
cache_shape \ cache_shape \
copyloops \ copyloops \
dexcr \
dscr \ dscr \
mm \ mm \
nx-gzip \ nx-gzip \
......
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
// 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);
}
/* 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 */
// 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;
}
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#define mb() asm volatile("sync" : : : "memory"); #define mb() asm volatile("sync" : : : "memory");
#define barrier() asm volatile("" : : : "memory"); #define barrier() asm volatile("" : : : "memory");
#define SPRN_HDEXCR_RO 455 /* Userspace readonly view of SPRN_HDEXCR (471) */
#define SPRN_MMCR2 769 #define SPRN_MMCR2 769
#define SPRN_MMCRA 770 #define SPRN_MMCRA 770
#define SPRN_MMCR0 779 #define SPRN_MMCR0 779
...@@ -47,6 +49,8 @@ ...@@ -47,6 +49,8 @@
#define SPRN_SDAR 781 #define SPRN_SDAR 781
#define SPRN_SIER 768 #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_TEXASR 0x82 /* Transaction Exception and Status Register */
#define SPRN_TFIAR 0x81 /* Transaction Failure Inst Addr */ #define SPRN_TFIAR 0x81 /* Transaction Failure Inst Addr */
#define SPRN_TFHAR 0x80 /* Transaction Failure Handler Addr */ #define SPRN_TFHAR 0x80 /* Transaction Failure Handler Addr */
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdbool.h> #include <stdbool.h>
#include <sys/signal.h>
#include <linux/auxvec.h> #include <linux/auxvec.h>
#include <linux/perf_event.h> #include <linux/perf_event.h>
#include <asm/cputable.h> #include <asm/cputable.h>
...@@ -111,6 +112,9 @@ static inline char *auxv_platform(void) ...@@ -111,6 +112,9 @@ static inline char *auxv_platform(void)
bool is_ppc64le(void); bool is_ppc64le(void);
int using_hash_mmu(bool *using_hash); 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 */ /* Yes, this is evil */
#define FAIL_IF(x) \ #define FAIL_IF(x) \
do { \ do { \
......
...@@ -618,3 +618,27 @@ int using_hash_mmu(bool *using_hash) ...@@ -618,3 +618,27 @@ int using_hash_mmu(bool *using_hash)
fclose(f); fclose(f);
return rc; 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment