blob: f29bac6362c36b86cec856c5a7145625d34d272b [file]
/*
* Copyright (c) 2021, Google Inc. All rights reserved
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <err.h>
#include <kernel/thread.h>
#include <kernel/vm.h>
#include <lib/mmutest/mmutest.h>
#include <lib/trusty/trusty_app.h>
#include <lib/unittest/unittest.h>
#if USER_SCS_ENABLED
#define FEATURE_GATED_TEST_NAME(name) name
/**
* translate_uspace_ptr() - Translate userspace pointer to shadow stack
* @uspace: An address space of a trust app
* @uspace_ptr: Pointer into above user space.
* @kspace_ptr_out: Pointer translated into the kernel's address space. The
* value of this output parameter should not be consumed
* if this function returned a negative error code.
*
* Attempt to translate userspace pointer into its kernel equivalent. We
* cannot simply dereference a userspace pointer because the TLB isn't
* loaded with the correct page table. Instead, we translate the pointer
* to physical memory and then into the kernel virtual address space.
* Translation can fail the userspace address is not backed by physical
* memory; this is the case for guard pages.
*
* Return:
* * NO_ERROR - if translation succeeded
* * ERR_OUT_OF_RANGE - if address translation failed
* * ERR_NOT_FOUND - if pointer is not backed by physical memory
* * ERR_INVALID_ARGS - if kspace_ptr_out is NULL
*/
static int translate_uspace_ptr(struct vmm_aspace* uspace,
vaddr_t uspace_ptr,
vaddr_t* kspace_ptr_out) {
paddr_t phys_ptr;
uint flags;
if (kspace_ptr_out == NULL) {
return ERR_INVALID_ARGS;
}
*kspace_ptr_out = 0;
/* translate userspace virtual address to physical address */
status_t res =
arch_mmu_query(&uspace->arch_aspace, uspace_ptr, &phys_ptr, &flags);
/*
* failures can happen if the pointer is invalid or points to a guard page
* not backed by physical memory (in which case res is ERR_NOT_FOUND).
*/
if (res) {
return res;
}
EXPECT_EQ(flags, ARCH_MMU_FLAG_PERM_NO_EXECUTE | ARCH_MMU_FLAG_PERM_USER,
"Shadow call stack must point to non-executable user memory");
/* translate physical address to kernel virtual address */
*kspace_ptr_out = (vaddr_t)paddr_to_kvaddr(phys_ptr);
ASSERT_NE(0, *kspace_ptr_out,
"Failed to map phys addr to kernel virtual addr");
return NO_ERROR;
test_abort:
return ERR_OUT_OF_RANGE;
}
struct scs_test_status {
uint32_t running_apps;
uint32_t invalid_apps;
};
/**
* trusty_app_callback() - Test that app has a valid user shadow call stack
*
* @ta: Application to test
* @status: Pointer to a structure tracking the current test status.
*/
static void trusty_app_callback(struct trusty_app* ta, void* status) {
struct scs_test_status* status_ptr = (struct scs_test_status*)status;
if (strcmp(ta->props.app_name, "userscs-custom") == 0) {
/* were we able to request a custom shadow stack size? */
ASSERT_EQ(ta->props.min_shadow_stack_size, 128);
} else if (strcmp(ta->props.app_name, "userscs-disabled") == 0) {
/* were we able to opt out of shadow stacks? */
ASSERT_EQ(ta->props.min_shadow_stack_size, 0);
/* userscs-* apps loop infinitely so they'll always have a thread */
ASSERT_NE((void*)ta->thread, NULL, "App has thread");
ASSERT_EQ((void*)ta->thread->shadow_stack_base, NULL,
"Shadow call stack was disabled");
return;
} else if (strcmp(ta->props.app_name, "userscs-default") == 0) {
/* did default scs app get the default shadow stack size? */
ASSERT_EQ(ta->props.min_shadow_stack_size, DEFAULT_SHADOW_STACK_SIZE,
"Expected shadow call stack to have the default size");
}
/* size must be a multiple of the pointer size */
ASSERT_EQ(0, ta->props.min_shadow_stack_size % sizeof(vaddr_t),
"Shadow call stack size is not a multiple of the pointer size");
/*
* Apps that aren't running may not have a thread allocated. Moreover,
* apps that opt out of shadow call stacks need no further inspection.
*/
if (ta->state != APP_RUNNING || ta->props.min_shadow_stack_size == 0) {
return;
}
status_ptr->running_apps++;
struct trusty_thread* tt = ta->thread;
ASSERT_NE((void*)tt, NULL, "Running trusty app must have a valid thread");
ASSERT_NE((void*)tt->shadow_stack_base, NULL,
"Shadow call stack must point to allocation");
ASSERT_EQ(false, is_kernel_address(tt->shadow_stack_base),
"Shadow stack on user thread points to kernel memory");
ASSERT_NE(tt->stack_start, tt->shadow_stack_base,
"Shadow stack on user thread aliases the regular stack");
/*
* Check the shadow stack size by examining the last element and one past
* the last element. Note that these pointers are valid in the address
* space of the trusty app, not in the current address space of a kernel
* app. Therefore, we translate from the app's address space to the kernel
* addres space before dereferencing lest we generate an access violation.
*/
vaddr_t past_last = (vaddr_t)tt->shadow_stack_base + tt->shadow_stack_size;
vaddr_t last_elem = past_last - sizeof(vaddr_t);
/* a whole number of pages is allocated no matter the shadow stack size */
vaddr_t pre_first = last_elem - round_up(tt->shadow_stack_size, PAGE_SIZE);
vaddr_t elem_translated, ignored;
struct vmm_aspace* uspace = ta->aspace;
ASSERT_EQ(NO_ERROR,
translate_uspace_ptr(uspace, last_elem, &elem_translated),
"Actual size of shadow stack differs from recorded size");
/*
* Check that test app uses its shadow stack as expected. The shadow call
* stack is zero-initialized and once a stack slot is used it can never
* become zero again.
*/
if (strcmp(ta->props.app_name, "userscs-default") == 0) {
EXPECT_EQ(0, *(vaddr_t*)elem_translated,
"Expected last element of shadow stack to be zero "
"(unused)");
ASSERT_EQ(NO_ERROR, translate_uspace_ptr(uspace, tt->shadow_stack_base,
&elem_translated));
/*
* The link register is initially zero so when shadow call stacks are
* enabled for libc, the second element holds the first non-zero entry
*/
EXPECT_NE(0, *(vaddr_t*)elem_translated + sizeof(vaddr_t),
"Expected second element of shadow stack to be non-zero "
"(used)");
}
ASSERT_EQ(ERR_NOT_FOUND, translate_uspace_ptr(uspace, past_last, &ignored),
"Expected guard page after shadow stack on user thread");
ASSERT_EQ(ERR_NOT_FOUND, translate_uspace_ptr(uspace, pre_first, &ignored),
"Expected guard page before shadow stack on user thread");
return;
test_abort:
status_ptr->invalid_apps++;
}
static int inspect_trusty_threads(void) {
struct scs_test_status status = {0};
trusty_app_forall(trusty_app_callback, &status);
ASSERT_NE(0, status.running_apps);
return status.invalid_apps;
test_abort:
return -1;
}
#else
#define FEATURE_GATED_TEST_NAME(name) DISABLED_##name
static int inspect_trusty_threads(void) {
return 0;
}
#endif
TEST(userscstest, FEATURE_GATED_TEST_NAME(user_threads_have_scs)) {
int res = inspect_trusty_threads();
EXPECT_NE(-1, res, "There were no running apps with threads to inspect");
EXPECT_EQ(0, res,
"One or more apps did not have the expected shadow call stack");
}
PORT_TEST(userscstest, "com.android.kernel.userscstest");