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: Benjamin Gray <bgray@linux.ibm.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://msgid.link/20230616034846.311705-11-bgray@linux.ibm.com
This commit is contained in:
parent
b9125c9aa0
commit
bdb07f35a5
|
@ -17,6 +17,7 @@ SUB_DIRS = alignment \
|
|||
benchmarks \
|
||||
cache_shape \
|
||||
copyloops \
|
||||
dexcr \
|
||||
dscr \
|
||||
mm \
|
||||
nx-gzip \
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
hashchk_test
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
||||
|
|
|
@ -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>
|
||||
|
@ -111,6 +112,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 { \
|
||||
|
|
|
@ -618,3 +618,27 @@ out:
|
|||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue