729 lines
17 KiB
C
729 lines
17 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2023 ARM Limited.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <sys/auxv.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <asm/hwcap.h>
|
|
#include <asm/mman.h>
|
|
|
|
#include <linux/compiler.h>
|
|
|
|
#include "kselftest_harness.h"
|
|
|
|
#include "gcs-util.h"
|
|
|
|
#define my_syscall2(num, arg1, arg2) \
|
|
({ \
|
|
register long _num __asm__ ("x8") = (num); \
|
|
register long _arg1 __asm__ ("x0") = (long)(arg1); \
|
|
register long _arg2 __asm__ ("x1") = (long)(arg2); \
|
|
register long _arg3 __asm__ ("x2") = 0; \
|
|
register long _arg4 __asm__ ("x3") = 0; \
|
|
register long _arg5 __asm__ ("x4") = 0; \
|
|
\
|
|
__asm__ volatile ( \
|
|
"svc #0\n" \
|
|
: "=r"(_arg1) \
|
|
: "r"(_arg1), "r"(_arg2), \
|
|
"r"(_arg3), "r"(_arg4), \
|
|
"r"(_arg5), "r"(_num) \
|
|
: "memory", "cc" \
|
|
); \
|
|
_arg1; \
|
|
})
|
|
|
|
static noinline void gcs_recurse(int depth)
|
|
{
|
|
if (depth)
|
|
gcs_recurse(depth - 1);
|
|
|
|
/* Prevent tail call optimization so we actually recurse */
|
|
asm volatile("dsb sy" : : : "memory");
|
|
}
|
|
|
|
/* Smoke test that a function call and return works*/
|
|
TEST(can_call_function)
|
|
{
|
|
gcs_recurse(0);
|
|
}
|
|
|
|
static void *gcs_test_thread(void *arg)
|
|
{
|
|
int ret;
|
|
unsigned long mode;
|
|
|
|
/*
|
|
* Some libcs don't seem to fill unused arguments with 0 but
|
|
* the kernel validates this so we supply all 5 arguments.
|
|
*/
|
|
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
|
|
if (ret != 0) {
|
|
ksft_print_msg("PR_GET_SHADOW_STACK_STATUS failed: %d\n", ret);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(mode & PR_SHADOW_STACK_ENABLE)) {
|
|
ksft_print_msg("GCS not enabled in thread, mode is %lu\n",
|
|
mode);
|
|
return NULL;
|
|
}
|
|
|
|
/* Just in case... */
|
|
gcs_recurse(0);
|
|
|
|
/* Use a non-NULL value to indicate a pass */
|
|
return &gcs_test_thread;
|
|
}
|
|
|
|
/* Verify that if we start a new thread it has GCS enabled */
|
|
TEST(gcs_enabled_thread)
|
|
{
|
|
pthread_t thread;
|
|
void *thread_ret;
|
|
int ret;
|
|
|
|
ret = pthread_create(&thread, NULL, gcs_test_thread, NULL);
|
|
ASSERT_TRUE(ret == 0);
|
|
if (ret != 0)
|
|
return;
|
|
|
|
ret = pthread_join(thread, &thread_ret);
|
|
ASSERT_TRUE(ret == 0);
|
|
if (ret != 0)
|
|
return;
|
|
|
|
ASSERT_TRUE(thread_ret != NULL);
|
|
}
|
|
|
|
/* Read the GCS until we find the terminator */
|
|
TEST(gcs_find_terminator)
|
|
{
|
|
unsigned long *gcs, *cur;
|
|
|
|
gcs = get_gcspr();
|
|
cur = gcs;
|
|
while (*cur)
|
|
cur++;
|
|
|
|
ksft_print_msg("GCS in use from %p-%p\n", gcs, cur);
|
|
|
|
/*
|
|
* We should have at least whatever called into this test so
|
|
* the two pointer should differ.
|
|
*/
|
|
ASSERT_TRUE(gcs != cur);
|
|
}
|
|
|
|
/*
|
|
* We can access a GCS via ptrace
|
|
*
|
|
* This could usefully have a fixture but note that each test is
|
|
* fork()ed into a new child whcih causes issues. Might be better to
|
|
* lift at least some of this out into a separate, non-harness, test
|
|
* program.
|
|
*/
|
|
TEST(ptrace_read_write)
|
|
{
|
|
pid_t child, pid;
|
|
int ret, status;
|
|
siginfo_t si;
|
|
uint64_t val, rval, gcspr;
|
|
struct user_gcs child_gcs;
|
|
struct iovec iov, local_iov, remote_iov;
|
|
|
|
child = fork();
|
|
if (child == -1) {
|
|
ksft_print_msg("fork() failed: %d (%s)\n",
|
|
errno, strerror(errno));
|
|
ASSERT_NE(child, -1);
|
|
}
|
|
|
|
if (child == 0) {
|
|
/*
|
|
* In child, make sure there's something on the stack and
|
|
* ask to be traced.
|
|
*/
|
|
gcs_recurse(0);
|
|
if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
|
|
ksft_exit_fail_msg("PTRACE_TRACEME %s",
|
|
strerror(errno));
|
|
|
|
if (raise(SIGSTOP))
|
|
ksft_exit_fail_msg("raise(SIGSTOP) %s",
|
|
strerror(errno));
|
|
|
|
return;
|
|
}
|
|
|
|
ksft_print_msg("Child: %d\n", child);
|
|
|
|
/* Attach to the child */
|
|
while (1) {
|
|
int sig;
|
|
|
|
pid = wait(&status);
|
|
if (pid == -1) {
|
|
ksft_print_msg("wait() failed: %s",
|
|
strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* This should never happen but it's hard to flag in
|
|
* the framework.
|
|
*/
|
|
if (pid != child)
|
|
continue;
|
|
|
|
if (WIFEXITED(status) || WIFSIGNALED(status))
|
|
ksft_exit_fail_msg("Child died unexpectedly\n");
|
|
|
|
if (!WIFSTOPPED(status))
|
|
goto error;
|
|
|
|
sig = WSTOPSIG(status);
|
|
|
|
if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {
|
|
if (errno == ESRCH) {
|
|
ASSERT_NE(errno, ESRCH);
|
|
return;
|
|
}
|
|
|
|
if (errno == EINVAL) {
|
|
sig = 0; /* bust group-stop */
|
|
goto cont;
|
|
}
|
|
|
|
ksft_print_msg("PTRACE_GETSIGINFO: %s\n",
|
|
strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
if (sig == SIGSTOP && si.si_code == SI_TKILL &&
|
|
si.si_pid == pid)
|
|
break;
|
|
|
|
cont:
|
|
if (ptrace(PTRACE_CONT, pid, NULL, sig)) {
|
|
if (errno == ESRCH) {
|
|
ASSERT_NE(errno, ESRCH);
|
|
return;
|
|
}
|
|
|
|
ksft_print_msg("PTRACE_CONT: %s\n", strerror(errno));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Where is the child GCS? */
|
|
iov.iov_base = &child_gcs;
|
|
iov.iov_len = sizeof(child_gcs);
|
|
ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_GCS, &iov);
|
|
if (ret != 0) {
|
|
ksft_print_msg("Failed to read child GCS state: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
goto error;
|
|
}
|
|
|
|
/* We should have inherited GCS over fork(), confirm */
|
|
if (!(child_gcs.features_enabled & PR_SHADOW_STACK_ENABLE)) {
|
|
ASSERT_TRUE(child_gcs.features_enabled &
|
|
PR_SHADOW_STACK_ENABLE);
|
|
goto error;
|
|
}
|
|
|
|
gcspr = child_gcs.gcspr_el0;
|
|
ksft_print_msg("Child GCSPR 0x%lx, flags %llx, locked %llx\n",
|
|
gcspr, child_gcs.features_enabled,
|
|
child_gcs.features_locked);
|
|
|
|
/* Ideally we'd cross check with the child memory map */
|
|
|
|
errno = 0;
|
|
val = ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL);
|
|
ret = errno;
|
|
if (ret != 0)
|
|
ksft_print_msg("PTRACE_PEEKDATA failed: %s (%d)\n",
|
|
strerror(ret), ret);
|
|
EXPECT_EQ(ret, 0);
|
|
|
|
/* The child should be in a function, the GCSPR shouldn't be 0 */
|
|
EXPECT_NE(val, 0);
|
|
|
|
/* Same thing via process_vm_readv() */
|
|
local_iov.iov_base = &rval;
|
|
local_iov.iov_len = sizeof(rval);
|
|
remote_iov.iov_base = (void *)gcspr;
|
|
remote_iov.iov_len = sizeof(rval);
|
|
ret = process_vm_readv(child, &local_iov, 1, &remote_iov, 1, 0);
|
|
if (ret == -1)
|
|
ksft_print_msg("process_vm_readv() failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
EXPECT_EQ(ret, sizeof(rval));
|
|
EXPECT_EQ(val, rval);
|
|
|
|
/* Write data via a peek */
|
|
ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, NULL);
|
|
if (ret == -1)
|
|
ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
EXPECT_EQ(ret, 0);
|
|
EXPECT_EQ(0, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));
|
|
|
|
/* Restore what we had before */
|
|
ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, val);
|
|
if (ret == -1)
|
|
ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
EXPECT_EQ(ret, 0);
|
|
EXPECT_EQ(val, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));
|
|
|
|
/* That's all, folks */
|
|
kill(child, SIGKILL);
|
|
return;
|
|
|
|
error:
|
|
kill(child, SIGKILL);
|
|
ASSERT_FALSE(true);
|
|
}
|
|
|
|
FIXTURE(map_gcs)
|
|
{
|
|
unsigned long *stack;
|
|
};
|
|
|
|
FIXTURE_VARIANT(map_gcs)
|
|
{
|
|
size_t stack_size;
|
|
unsigned long flags;
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s2k_cap_marker)
|
|
{
|
|
.stack_size = 2 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s2k_cap)
|
|
{
|
|
.stack_size = 2 * 1024,
|
|
.flags = SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s2k_marker)
|
|
{
|
|
.stack_size = 2 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s2k)
|
|
{
|
|
.stack_size = 2 * 1024,
|
|
.flags = 0,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s4k_cap_marker)
|
|
{
|
|
.stack_size = 4 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s4k_cap)
|
|
{
|
|
.stack_size = 4 * 1024,
|
|
.flags = SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s3k_marker)
|
|
{
|
|
.stack_size = 4 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s4k)
|
|
{
|
|
.stack_size = 4 * 1024,
|
|
.flags = 0,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s16k_cap_marker)
|
|
{
|
|
.stack_size = 16 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s16k_cap)
|
|
{
|
|
.stack_size = 16 * 1024,
|
|
.flags = SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s16k_marker)
|
|
{
|
|
.stack_size = 16 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s16k)
|
|
{
|
|
.stack_size = 16 * 1024,
|
|
.flags = 0,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s64k_cap_marker)
|
|
{
|
|
.stack_size = 64 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s64k_cap)
|
|
{
|
|
.stack_size = 64 * 1024,
|
|
.flags = SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s64k_marker)
|
|
{
|
|
.stack_size = 64 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s64k)
|
|
{
|
|
.stack_size = 64 * 1024,
|
|
.flags = 0,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s128k_cap_marker)
|
|
{
|
|
.stack_size = 128 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s128k_cap)
|
|
{
|
|
.stack_size = 128 * 1024,
|
|
.flags = SHADOW_STACK_SET_TOKEN,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s128k_marker)
|
|
{
|
|
.stack_size = 128 * 1024,
|
|
.flags = SHADOW_STACK_SET_MARKER,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(map_gcs, s128k)
|
|
{
|
|
.stack_size = 128 * 1024,
|
|
.flags = 0,
|
|
};
|
|
|
|
FIXTURE_SETUP(map_gcs)
|
|
{
|
|
self->stack = (void *)syscall(__NR_map_shadow_stack, 0,
|
|
variant->stack_size,
|
|
variant->flags);
|
|
ASSERT_FALSE(self->stack == MAP_FAILED);
|
|
ksft_print_msg("Allocated stack from %p-%p\n", self->stack,
|
|
self->stack + variant->stack_size);
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(map_gcs)
|
|
{
|
|
int ret;
|
|
|
|
if (self->stack != MAP_FAILED) {
|
|
ret = munmap(self->stack, variant->stack_size);
|
|
ASSERT_EQ(ret, 0);
|
|
}
|
|
}
|
|
|
|
/* The stack has a cap token */
|
|
TEST_F(map_gcs, stack_capped)
|
|
{
|
|
unsigned long *stack = self->stack;
|
|
size_t cap_index;
|
|
|
|
cap_index = (variant->stack_size / sizeof(unsigned long));
|
|
|
|
switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
|
|
case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
|
|
cap_index -= 2;
|
|
break;
|
|
case SHADOW_STACK_SET_TOKEN:
|
|
cap_index -= 1;
|
|
break;
|
|
case SHADOW_STACK_SET_MARKER:
|
|
case 0:
|
|
/* No cap, no test */
|
|
return;
|
|
}
|
|
|
|
ASSERT_EQ(stack[cap_index], GCS_CAP(&stack[cap_index]));
|
|
}
|
|
|
|
/* The top of the stack is 0 */
|
|
TEST_F(map_gcs, stack_terminated)
|
|
{
|
|
unsigned long *stack = self->stack;
|
|
size_t term_index;
|
|
|
|
if (!(variant->flags & SHADOW_STACK_SET_MARKER))
|
|
return;
|
|
|
|
term_index = (variant->stack_size / sizeof(unsigned long)) - 1;
|
|
|
|
ASSERT_EQ(stack[term_index], 0);
|
|
}
|
|
|
|
/* Writes should fault */
|
|
TEST_F_SIGNAL(map_gcs, not_writeable, SIGSEGV)
|
|
{
|
|
self->stack[0] = 0;
|
|
}
|
|
|
|
/* Put it all together, we can safely switch to and from the stack */
|
|
TEST_F(map_gcs, stack_switch)
|
|
{
|
|
size_t cap_index;
|
|
cap_index = (variant->stack_size / sizeof(unsigned long));
|
|
unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;
|
|
|
|
/* Skip over the stack terminator and point at the cap */
|
|
switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
|
|
case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
|
|
cap_index -= 2;
|
|
break;
|
|
case SHADOW_STACK_SET_TOKEN:
|
|
cap_index -= 1;
|
|
break;
|
|
case SHADOW_STACK_SET_MARKER:
|
|
case 0:
|
|
/* No cap, no test */
|
|
return;
|
|
}
|
|
pivot_gcspr_el0 = &self->stack[cap_index];
|
|
|
|
/* Pivot to the new GCS */
|
|
ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",
|
|
pivot_gcspr_el0, get_gcspr(),
|
|
*pivot_gcspr_el0);
|
|
gcsss1(pivot_gcspr_el0);
|
|
orig_gcspr_el0 = gcsss2();
|
|
ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",
|
|
get_gcspr(), orig_gcspr_el0,
|
|
*pivot_gcspr_el0);
|
|
|
|
ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());
|
|
|
|
/* New GCS must be in the new buffer */
|
|
ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);
|
|
ASSERT_TRUE((unsigned long)get_gcspr() <=
|
|
(unsigned long)self->stack + variant->stack_size);
|
|
|
|
/* We should be able to use all but 2 slots of the new stack */
|
|
ksft_print_msg("Recursing %zu levels\n", cap_index - 1);
|
|
gcs_recurse(cap_index - 1);
|
|
|
|
/* Pivot back to the original GCS */
|
|
gcsss1(orig_gcspr_el0);
|
|
pivot_gcspr_el0 = gcsss2();
|
|
|
|
gcs_recurse(0);
|
|
ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());
|
|
}
|
|
|
|
/* We fault if we try to go beyond the end of the stack */
|
|
TEST_F_SIGNAL(map_gcs, stack_overflow, SIGSEGV)
|
|
{
|
|
size_t cap_index;
|
|
cap_index = (variant->stack_size / sizeof(unsigned long));
|
|
unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;
|
|
|
|
/* Skip over the stack terminator and point at the cap */
|
|
switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
|
|
case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
|
|
cap_index -= 2;
|
|
break;
|
|
case SHADOW_STACK_SET_TOKEN:
|
|
cap_index -= 1;
|
|
break;
|
|
case SHADOW_STACK_SET_MARKER:
|
|
case 0:
|
|
/* No cap, no test but we need to SEGV to avoid a false fail */
|
|
orig_gcspr_el0 = get_gcspr();
|
|
*orig_gcspr_el0 = 0;
|
|
return;
|
|
}
|
|
pivot_gcspr_el0 = &self->stack[cap_index];
|
|
|
|
/* Pivot to the new GCS */
|
|
ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",
|
|
pivot_gcspr_el0, get_gcspr(),
|
|
*pivot_gcspr_el0);
|
|
gcsss1(pivot_gcspr_el0);
|
|
orig_gcspr_el0 = gcsss2();
|
|
ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",
|
|
pivot_gcspr_el0, orig_gcspr_el0,
|
|
*pivot_gcspr_el0);
|
|
|
|
ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());
|
|
|
|
/* New GCS must be in the new buffer */
|
|
ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);
|
|
ASSERT_TRUE((unsigned long)get_gcspr() <=
|
|
(unsigned long)self->stack + variant->stack_size);
|
|
|
|
/* Now try to recurse, we should fault doing this. */
|
|
ksft_print_msg("Recursing %zu levels...\n", cap_index + 1);
|
|
gcs_recurse(cap_index + 1);
|
|
ksft_print_msg("...done\n");
|
|
|
|
/* Clean up properly to try to guard against spurious passes. */
|
|
gcsss1(orig_gcspr_el0);
|
|
pivot_gcspr_el0 = gcsss2();
|
|
ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());
|
|
}
|
|
|
|
FIXTURE(map_invalid_gcs)
|
|
{
|
|
};
|
|
|
|
FIXTURE_VARIANT(map_invalid_gcs)
|
|
{
|
|
size_t stack_size;
|
|
};
|
|
|
|
FIXTURE_SETUP(map_invalid_gcs)
|
|
{
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(map_invalid_gcs)
|
|
{
|
|
}
|
|
|
|
/* GCS must be larger than 16 bytes */
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, too_small)
|
|
{
|
|
.stack_size = 8,
|
|
};
|
|
|
|
/* GCS size must be 16 byte aligned */
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_1) { .stack_size = 1024 + 1 };
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_2) { .stack_size = 1024 + 2 };
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_3) { .stack_size = 1024 + 3 };
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_4) { .stack_size = 1024 + 4 };
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_5) { .stack_size = 1024 + 5 };
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_6) { .stack_size = 1024 + 6 };
|
|
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_7) { .stack_size = 1024 + 7 };
|
|
|
|
TEST_F(map_invalid_gcs, do_map)
|
|
{
|
|
void *stack;
|
|
|
|
stack = (void *)syscall(__NR_map_shadow_stack, 0,
|
|
variant->stack_size, 0);
|
|
ASSERT_TRUE(stack == MAP_FAILED);
|
|
if (stack != MAP_FAILED)
|
|
munmap(stack, variant->stack_size);
|
|
}
|
|
|
|
FIXTURE(invalid_mprotect)
|
|
{
|
|
unsigned long *stack;
|
|
size_t stack_size;
|
|
};
|
|
|
|
FIXTURE_VARIANT(invalid_mprotect)
|
|
{
|
|
unsigned long flags;
|
|
};
|
|
|
|
FIXTURE_SETUP(invalid_mprotect)
|
|
{
|
|
self->stack_size = sysconf(_SC_PAGE_SIZE);
|
|
self->stack = (void *)syscall(__NR_map_shadow_stack, 0,
|
|
self->stack_size, 0);
|
|
ASSERT_FALSE(self->stack == MAP_FAILED);
|
|
ksft_print_msg("Allocated stack from %p-%p\n", self->stack,
|
|
self->stack + self->stack_size);
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(invalid_mprotect)
|
|
{
|
|
int ret;
|
|
|
|
if (self->stack != MAP_FAILED) {
|
|
ret = munmap(self->stack, self->stack_size);
|
|
ASSERT_EQ(ret, 0);
|
|
}
|
|
}
|
|
|
|
FIXTURE_VARIANT_ADD(invalid_mprotect, exec)
|
|
{
|
|
.flags = PROT_EXEC,
|
|
};
|
|
|
|
TEST_F(invalid_mprotect, do_map)
|
|
{
|
|
int ret;
|
|
|
|
ret = mprotect(self->stack, self->stack_size, variant->flags);
|
|
ASSERT_EQ(ret, -1);
|
|
}
|
|
|
|
TEST_F(invalid_mprotect, do_map_read)
|
|
{
|
|
int ret;
|
|
|
|
ret = mprotect(self->stack, self->stack_size,
|
|
variant->flags | PROT_READ);
|
|
ASSERT_EQ(ret, -1);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
unsigned long gcs_mode;
|
|
int ret;
|
|
|
|
if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
|
|
ksft_exit_skip("SKIP GCS not supported\n");
|
|
|
|
/*
|
|
* Force shadow stacks on, our tests *should* be fine with or
|
|
* without libc support and with or without this having ended
|
|
* up tagged for GCS and enabled by the dynamic linker. We
|
|
* can't use the libc prctl() function since we can't return
|
|
* from enabling the stack.
|
|
*/
|
|
ret = my_syscall2(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode);
|
|
if (ret) {
|
|
ksft_print_msg("Failed to read GCS state: %d\n", ret);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
|
|
gcs_mode = PR_SHADOW_STACK_ENABLE;
|
|
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
|
|
gcs_mode);
|
|
if (ret) {
|
|
ksft_print_msg("Failed to configure GCS: %d\n", ret);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
/* Avoid returning in case libc doesn't understand GCS */
|
|
exit(test_harness_run(argc, argv));
|
|
}
|