Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,4 @@
dexcr_test
hashchk_test
chdexcr
lsdexcr
@@ -0,0 +1,12 @@
TEST_GEN_PROGS := dexcr_test hashchk_test
TEST_GEN_FILES := lsdexcr chdexcr
include ../../lib.mk
include ../flags.mk
CFLAGS += $(KHDR_INCLUDES)
$(OUTPUT)/hashchk_test: CFLAGS += -fno-pie -no-pie $(call cc-option,-mno-rop-protect)
$(TEST_GEN_PROGS): ../harness.c ../utils.c ./dexcr.c
$(TEST_GEN_FILES): ../utils.c ./dexcr.c
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include "dexcr.h"
#include "utils.h"
static void die(const char *msg)
{
printf("%s\n", msg);
exit(1);
}
static void help(void)
{
printf("Invoke a provided program with a custom DEXCR on-exec reset value\n"
"\n"
"usage: chdexcr [CHDEXCR OPTIONS] -- PROGRAM [ARGS...]\n"
"\n"
"Each configurable DEXCR aspect is exposed as an option.\n"
"\n"
"The normal option sets the aspect in the DEXCR. The --no- variant\n"
"clears that aspect. For example, --ibrtpd sets the IBRTPD aspect bit,\n"
"so indirect branch prediction will be disabled in the provided program.\n"
"Conversely, --no-ibrtpd clears the aspect bit, so indirect branch\n"
"prediction may occur.\n"
"\n"
"CHDEXCR OPTIONS:\n");
for (int i = 0; i < ARRAY_SIZE(aspects); i++) {
const struct dexcr_aspect *aspect = &aspects[i];
if (aspect->prctl == -1)
continue;
printf(" --%-6s / --no-%-6s : %s\n", aspect->opt, aspect->opt, aspect->desc);
}
}
static const struct dexcr_aspect *opt_to_aspect(const char *opt)
{
for (int i = 0; i < ARRAY_SIZE(aspects); i++)
if (aspects[i].prctl != -1 && !strcmp(aspects[i].opt, opt))
return &aspects[i];
return NULL;
}
static int apply_option(const char *option)
{
const struct dexcr_aspect *aspect;
const char *opt = NULL;
const char *set_prefix = "--";
const char *clear_prefix = "--no-";
unsigned long ctrl = 0;
int err;
if (!strcmp(option, "-h") || !strcmp(option, "--help")) {
help();
exit(0);
}
/* Strip out --(no-) prefix and determine ctrl value */
if (!strncmp(option, clear_prefix, strlen(clear_prefix))) {
opt = &option[strlen(clear_prefix)];
ctrl |= PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC;
} else if (!strncmp(option, set_prefix, strlen(set_prefix))) {
opt = &option[strlen(set_prefix)];
ctrl |= PR_PPC_DEXCR_CTRL_SET_ONEXEC;
}
if (!opt || !*opt)
return 1;
aspect = opt_to_aspect(opt);
if (!aspect)
die("unknown aspect");
err = pr_set_dexcr(aspect->prctl, ctrl);
if (err)
die("failed to apply option");
return 0;
}
int main(int argc, char *const argv[])
{
int i;
if (!dexcr_exists())
die("DEXCR not detected on this hardware");
for (i = 1; i < argc; i++)
if (apply_option(argv[i]))
break;
if (i < argc && !strcmp(argv[i], "--"))
i++;
if (i >= argc)
die("missing command");
execvp(argv[i], &argv[i]);
perror("execve");
return errno;
}
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0+
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/prctl.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;
}
unsigned int pr_which_to_aspect(unsigned long which)
{
switch (which) {
case PR_PPC_DEXCR_SBHE:
return DEXCR_PR_SBHE;
case PR_PPC_DEXCR_IBRTPD:
return DEXCR_PR_IBRTPD;
case PR_PPC_DEXCR_SRAPD:
return DEXCR_PR_SRAPD;
case PR_PPC_DEXCR_NPHIE:
return DEXCR_PR_NPHIE;
default:
FAIL_IF_EXIT_MSG(true, "unknown PR aspect");
}
}
int pr_get_dexcr(unsigned long which)
{
return prctl(PR_PPC_GET_DEXCR, which, 0UL, 0UL, 0UL);
}
int pr_set_dexcr(unsigned long which, unsigned long ctrl)
{
return prctl(PR_PPC_SET_DEXCR, which, ctrl, 0UL, 0UL);
}
bool pr_dexcr_aspect_supported(unsigned long which)
{
if (pr_get_dexcr(which) == -1)
return errno == ENODEV;
return true;
}
bool pr_dexcr_aspect_editable(unsigned long which)
{
return pr_get_dexcr(which) & PR_PPC_DEXCR_CTRL_EDITABLE;
}
/*
* 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,106 @@
/* 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/prctl.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));)
struct dexcr_aspect {
const char *name; /* Short display name */
const char *opt; /* Option name for chdexcr */
const char *desc; /* Expanded aspect meaning */
unsigned int index; /* Aspect bit index in DEXCR */
unsigned long prctl; /* 'which' value for get/set prctl */
};
static const struct dexcr_aspect aspects[] = {
{
.name = "SBHE",
.opt = "sbhe",
.desc = "Speculative branch hint enable",
.index = 0,
.prctl = PR_PPC_DEXCR_SBHE,
},
{
.name = "IBRTPD",
.opt = "ibrtpd",
.desc = "Indirect branch recurrent target prediction disable",
.index = 3,
.prctl = PR_PPC_DEXCR_IBRTPD,
},
{
.name = "SRAPD",
.opt = "srapd",
.desc = "Subroutine return address prediction disable",
.index = 4,
.prctl = PR_PPC_DEXCR_SRAPD,
},
{
.name = "NPHIE",
.opt = "nphie",
.desc = "Non-privileged hash instruction enable",
.index = 5,
.prctl = PR_PPC_DEXCR_NPHIE,
},
{
.name = "PHIE",
.opt = "phie",
.desc = "Privileged hash instruction enable",
.index = 6,
.prctl = -1,
},
};
bool dexcr_exists(void);
bool pr_dexcr_aspect_supported(unsigned long which);
bool pr_dexcr_aspect_editable(unsigned long which);
int pr_get_dexcr(unsigned long pr_aspect);
int pr_set_dexcr(unsigned long pr_aspect, unsigned long ctrl);
unsigned int pr_which_to_aspect(unsigned long which);
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,215 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <unistd.h>
#include "dexcr.h"
#include "utils.h"
/*
* Helper function for testing the behaviour of a newly exec-ed process
*/
static int dexcr_prctl_onexec_test_child(unsigned long which, const char *status)
{
unsigned long dexcr = mfspr(SPRN_DEXCR_RO);
unsigned long aspect = pr_which_to_aspect(which);
int ctrl = pr_get_dexcr(which);
if (!strcmp(status, "set")) {
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET),
"setting aspect across exec not applied");
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC),
"setting aspect across exec not inherited");
FAIL_IF_EXIT_MSG(!(aspect & dexcr), "setting aspect across exec did not take effect");
} else if (!strcmp(status, "clear")) {
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR),
"clearing aspect across exec not applied");
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC),
"clearing aspect across exec not inherited");
FAIL_IF_EXIT_MSG(aspect & dexcr, "clearing aspect across exec did not take effect");
} else {
FAIL_IF_EXIT_MSG(true, "unknown expected status");
}
return 0;
}
/*
* Test that the given prctl value can be manipulated freely
*/
static int dexcr_prctl_aspect_test(unsigned long which)
{
unsigned long aspect = pr_which_to_aspect(which);
pid_t pid;
int ctrl;
int err;
int errno_save;
SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
SKIP_IF_MSG(!pr_dexcr_aspect_supported(which), "DEXCR aspect not supported");
SKIP_IF_MSG(!pr_dexcr_aspect_editable(which), "DEXCR aspect not editable with prctl");
/* We reject invalid combinations of arguments */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR);
errno_save = errno;
FAIL_IF_MSG(err != -1, "simultaneous set and clear should be rejected");
FAIL_IF_MSG(errno_save != EINVAL, "simultaneous set and clear should be rejected with EINVAL");
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET_ONEXEC | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
errno_save = errno;
FAIL_IF_MSG(err != -1, "simultaneous set and clear on exec should be rejected");
FAIL_IF_MSG(errno_save != EINVAL, "simultaneous set and clear on exec should be rejected with EINVAL");
/* We set the aspect */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET), "config value not PR_PPC_DEXCR_CTRL_SET");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_CLEAR, "config value unexpected clear flag");
FAIL_IF_MSG(!(aspect & mfspr(SPRN_DEXCR_RO)), "setting aspect did not take effect");
/* We clear the aspect */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "config value not PR_PPC_DEXCR_CTRL_CLEAR");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_SET, "config value unexpected set flag");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "clearing aspect did not take effect");
/* We make it set on exec (doesn't change our current value) */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "process aspect should still be cleared");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_SET_ONEXEC");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC, "config value unexpected clear on exec flag");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "scheduling aspect to set on exec should not change it now");
/* We make it clear on exec (doesn't change our current value) */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "process aspect config should still be cleared");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC");
FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC, "config value unexpected set on exec flag");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "process aspect should still be cleared");
/* We allow setting the current and on-exec value in a single call */
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET), "config value not PR_PPC_DEXCR_CTRL_SET");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC");
FAIL_IF_MSG(!(aspect & mfspr(SPRN_DEXCR_RO)), "process aspect should be set");
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR | PR_PPC_DEXCR_CTRL_SET_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR | PR_PPC_DEXCR_CTRL_SET_ONEXEC failed");
ctrl = pr_get_dexcr(which);
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "config value not PR_PPC_DEXCR_CTRL_CLEAR");
FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_SET_ONEXEC");
FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "process aspect should be clear");
/* Verify the onexec value is applied across exec */
pid = fork();
if (!pid) {
char which_str[32] = {};
char *args[] = { "dexcr_prctl_onexec_test_child", which_str, "set", NULL };
unsigned int ctrl = pr_get_dexcr(which);
sprintf(which_str, "%lu", which);
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC),
"setting aspect on exec not copied across fork");
FAIL_IF_EXIT_MSG(mfspr(SPRN_DEXCR_RO) & aspect,
"setting aspect on exec wrongly applied to fork");
execve("/proc/self/exe", args, NULL);
_exit(errno);
}
await_child_success(pid);
err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");
pid = fork();
if (!pid) {
char which_str[32] = {};
char *args[] = { "dexcr_prctl_onexec_test_child", which_str, "clear", NULL };
unsigned int ctrl = pr_get_dexcr(which);
sprintf(which_str, "%lu", which);
FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC),
"clearing aspect on exec not copied across fork");
FAIL_IF_EXIT_MSG(!(mfspr(SPRN_DEXCR_RO) & aspect),
"clearing aspect on exec wrongly applied to fork");
execve("/proc/self/exe", args, NULL);
_exit(errno);
}
await_child_success(pid);
return 0;
}
static int dexcr_prctl_ibrtpd_test(void)
{
return dexcr_prctl_aspect_test(PR_PPC_DEXCR_IBRTPD);
}
static int dexcr_prctl_srapd_test(void)
{
return dexcr_prctl_aspect_test(PR_PPC_DEXCR_SRAPD);
}
static int dexcr_prctl_nphie_test(void)
{
return dexcr_prctl_aspect_test(PR_PPC_DEXCR_NPHIE);
}
int main(int argc, char *argv[])
{
int err = 0;
/*
* Some tests require checking what happens across exec, so we may be
* invoked as the child of a particular test
*/
if (argc > 1) {
if (argc == 3 && !strcmp(argv[0], "dexcr_prctl_onexec_test_child")) {
unsigned long which;
err = parse_ulong(argv[1], strlen(argv[1]), &which, 10);
FAIL_IF_MSG(err, "failed to parse which value for child");
return dexcr_prctl_onexec_test_child(which, argv[2]);
}
FAIL_IF_MSG(true, "unknown test case");
}
/*
* Otherwise we are the main test invocation and run the full suite
*/
err |= test_harness(dexcr_prctl_ibrtpd_test, "dexcr_prctl_ibrtpd");
err |= test_harness(dexcr_prctl_srapd_test, "dexcr_prctl_srapd");
err |= test_harness(dexcr_prctl_nphie_test, "dexcr_prctl_nphie");
return err;
}
@@ -0,0 +1,233 @@
// 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");
pr_set_dexcr(PR_PPC_DEXCR_NPHIE, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_SET_ONEXEC);
if (get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE)
return 0;
SKIP_IF_MSG(!(get_dexcr(EFFECTIVE) & DEXCR_PR_NPHIE),
"Failed to enable DEXCR[NPHIE]");
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;
}
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0+
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>
#include "dexcr.h"
#include "utils.h"
static unsigned int dexcr;
static unsigned int hdexcr;
static unsigned int effective;
static void print_list(const char *list[], size_t len)
{
for (size_t i = 0; i < len; i++) {
printf("%s", list[i]);
if (i + 1 < len)
printf(", ");
}
}
static void print_dexcr(char *name, unsigned int bits)
{
const char *enabled_aspects[ARRAY_SIZE(aspects) + 1] = {NULL};
size_t j = 0;
printf("%s: 0x%08x", name, bits);
if (bits == 0) {
printf("\n");
return;
}
for (size_t i = 0; i < ARRAY_SIZE(aspects); i++) {
unsigned int mask = DEXCR_PR_BIT(aspects[i].index);
if (bits & mask) {
enabled_aspects[j++] = aspects[i].name;
bits &= ~mask;
}
}
if (bits)
enabled_aspects[j++] = "unknown";
printf(" (");
print_list(enabled_aspects, j);
printf(")\n");
}
static void print_aspect(const struct dexcr_aspect *aspect)
{
const char *attributes[8] = {NULL};
size_t j = 0;
unsigned long mask;
mask = DEXCR_PR_BIT(aspect->index);
if (dexcr & mask)
attributes[j++] = "set";
if (hdexcr & mask)
attributes[j++] = "set (hypervisor)";
if (!(effective & mask))
attributes[j++] = "clear";
printf("%12s %c (%d): ", aspect->name, effective & mask ? '*' : ' ', aspect->index);
print_list(attributes, j);
printf(" \t(%s)\n", aspect->desc);
}
static void print_aspect_config(const struct dexcr_aspect *aspect)
{
const char *reason = NULL;
const char *reason_hyp = NULL;
const char *reason_prctl = "no prctl";
bool actual = effective & DEXCR_PR_BIT(aspect->index);
bool expected = actual; /* Assume it's fine if we don't expect a specific set/clear value */
if (actual)
reason = "set by unknown";
else
reason = "cleared by unknown";
if (aspect->prctl != -1) {
int ctrl = pr_get_dexcr(aspect->prctl);
if (ctrl < 0) {
reason_prctl = "failed to read prctl";
} else {
if (ctrl & PR_PPC_DEXCR_CTRL_SET) {
reason_prctl = "set by prctl";
expected = true;
} else if (ctrl & PR_PPC_DEXCR_CTRL_CLEAR) {
reason_prctl = "cleared by prctl";
expected = false;
} else {
reason_prctl = "unknown prctl";
}
reason = reason_prctl;
}
}
if (hdexcr & DEXCR_PR_BIT(aspect->index)) {
reason_hyp = "set by hypervisor";
reason = reason_hyp;
expected = true;
} else {
reason_hyp = "not modified by hypervisor";
}
printf("%12s (%d): %-28s (%s, %s)\n",
aspect->name,
aspect->index,
reason,
reason_hyp,
reason_prctl);
/*
* The checks are not atomic, so this can technically trigger if the
* hypervisor makes a change while we are checking each source. It's
* far more likely to be a bug if we see this though.
*/
if (actual != expected)
printf(" : ! actual %s does not match config\n", aspect->name);
}
int main(int argc, char *argv[])
{
if (!dexcr_exists()) {
printf("DEXCR not detected on this hardware\n");
return 1;
}
dexcr = get_dexcr(DEXCR);
hdexcr = get_dexcr(HDEXCR);
effective = dexcr | hdexcr;
printf("current status:\n");
print_dexcr(" DEXCR", dexcr);
print_dexcr(" HDEXCR", hdexcr);
print_dexcr("Effective", effective);
printf("\n");
for (size_t i = 0; i < ARRAY_SIZE(aspects); i++)
print_aspect(&aspects[i]);
printf("\n");
if (effective & DEXCR_PR_NPHIE) {
printf("DEXCR[NPHIE] enabled: hashst/hashchk ");
if (hashchk_triggers())
printf("working\n");
else
printf("failed to trigger\n");
} else {
printf("DEXCR[NPHIE] disabled: hashst/hashchk ");
if (hashchk_triggers())
printf("unexpectedly triggered\n");
else
printf("ignored\n");
}
printf("\n");
printf("configuration:\n");
for (size_t i = 0; i < ARRAY_SIZE(aspects); i++)
print_aspect_config(&aspects[i]);
printf("\n");
return 0;
}
@@ -0,0 +1 @@
timeout=130