228 lines
4.8 KiB
C
228 lines
4.8 KiB
C
// 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;
|
|
}
|