/* Prevent tail call optimization so we actually recurse */ asmvolatile("dsb sy" : : : "memory");
}
/* Smoke test that a function call and return works*/
TEST(can_call_function)
{
gcs_recurse(0);
}
staticvoid *gcs_test_thread(void *arg)
{ int ret; unsignedlong 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)
{ unsignedlong *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;
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));
/* 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;
}
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;
}
/* 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(unsignedlong)); unsignedlong *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((unsignedlong)get_gcspr() > (unsignedlong)self->stack);
ASSERT_TRUE((unsignedlong)get_gcspr() <=
(unsignedlong)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(unsignedlong)); unsignedlong *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((unsignedlong)get_gcspr() > (unsignedlong)self->stack);
ASSERT_TRUE((unsignedlong)get_gcspr() <=
(unsignedlong)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());
}
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)
{ unsignedlong 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));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.