blob: 19584a2c9269a30902b02ffb43264545588d66b4 [file]
/*
* Copyright (c) 2015, 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/unittest/unittest.h>
#include <lk/init.h>
#include <pow2.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
/*
* These below declarations are made to avoid issues with CFI
* while copying heap allocated method, this is to reduce the
* probability of it breaking in future toolchain versions
*/
extern uint8_t mmutest_arch_nop[];
extern uint8_t mmutest_arch_nop_end[];
static int mmutest_run_in_thread(const char* thread_name,
int (*func)(void* arg),
void* arg) {
int ret;
int thread_ret;
struct thread* thread;
uint8_t* canary;
vmm_aspace_t* aspace = vmm_get_kernel_aspace();
thread = thread_create("mmu_test_execute", func, arg, DEFAULT_PRIORITY,
DEFAULT_STACK_SIZE);
if (!thread) {
return ERR_NO_MEMORY;
}
canary = (uint8_t*)thread->stack - PAGE_SIZE * 2;
ret = vmm_alloc(aspace, "canary", PAGE_SIZE, (void**)&canary, 0,
VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret) {
canary = NULL;
} else {
memset(canary, 0x55, PAGE_SIZE);
}
thread_set_flag_exit_on_panic(thread, true);
ret = thread_resume(thread);
if (ret) {
return ret;
}
ret = thread_join(thread, &thread_ret, INFINITE_TIME);
if (ret) {
return ret;
}
if (canary) {
size_t i;
for (i = 0; i < PAGE_SIZE; i++) {
if (canary[i] != 0x55)
break;
}
EXPECT_EQ(i, PAGE_SIZE, "memory below stack corrupted\n");
vmm_free_region(aspace, (vaddr_t)canary);
}
return thread_ret;
}
static int mmutest_alloc(void** ptrp, uint arch_mmu_flags) {
int ret;
uint arch_mmu_flags_query = ~0U;
vmm_aspace_t* aspace = vmm_get_kernel_aspace();
ret = vmm_alloc_contiguous(aspace, "mmutest", PAGE_SIZE, ptrp, 0, 0,
arch_mmu_flags);
EXPECT_EQ(NO_ERROR, ret, "vmm_alloc_contiguous failed\n");
if (ret) {
return ret;
}
arch_mmu_query(&aspace->arch_aspace, (vaddr_t)*ptrp, NULL,
&arch_mmu_flags_query);
EXPECT_EQ(arch_mmu_flags_query, arch_mmu_flags,
"arch_mmu_query, 0x%x, does not match requested flags, 0x%x\n",
arch_mmu_flags_query, arch_mmu_flags);
return 0;
}
static int mmutest_vmm_store_uint32(uint arch_mmu_flags, bool user) {
int ret;
void* ptr;
ret = mmutest_alloc(&ptr, arch_mmu_flags);
if (ret) {
return ret;
}
ret = mmutest_arch_store_uint32(ptr, user);
vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)ptr);
return ret;
}
static int mmutest_vmm_store_uint32_kernel(uint arch_mmu_flags) {
return mmutest_vmm_store_uint32(arch_mmu_flags, false);
}
static int mmutest_vmm_store_uint32_user(uint arch_mmu_flags) {
return mmutest_vmm_store_uint32(arch_mmu_flags, true);
}
/*
* disabling the cfi-icall as a workaround to avoid cfi check
* failure errors while calling heap allocated functions
*/
static int mmu_test_execute_thread_func(void* arg)
__attribute__((no_sanitize("cfi-icall"))) {
void (*func)(void) = arg;
func();
return 0;
}
/*
* Executes 'mmutest_arch_nop' code from a memory mapped with the passed flags.
* To simplify test writing, this first creates a writable allocation and vmm
* mapping before making a second mapping with the requested arch_mmu_flags and
* executing the test thread. This avoids violating W^X semantics which are
* enforced on some architectures.
*/
static int mmu_test_execute(uint arch_mmu_flags) {
const size_t len = mmutest_arch_nop_end - mmutest_arch_nop;
const size_t alloc_len = round_up(len, PAGE_SIZE);
vmm_aspace_t* aspace = vmm_get_kernel_aspace();
struct obj_ref vmm_obj_ref = OBJ_REF_INITIAL_VALUE(vmm_obj_ref);
struct vmm_obj* vmm_obj = NULL;
void *ptr = NULL, *execute_ptr = NULL;
uint arch_mmu_flags_query;
int ret;
/* Allocate pages to hold the test code and create writable mapping */
ret = pmm_alloc(&vmm_obj, &vmm_obj_ref, alloc_len / PAGE_SIZE,
PMM_ALLOC_FLAG_CONTIGUOUS, 0);
ASSERT_EQ(NO_ERROR, ret, "pmm_alloc failed\n");
ret = vmm_alloc_obj(aspace, "mmutest_w", vmm_obj, 0, alloc_len, &ptr, 0, 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret, "vmm_alloc_obj failed\n");
/* Populate the memory */
memcpy(ptr, mmutest_arch_nop, len);
arch_sync_cache_range((addr_t)ptr, len);
/* Now create a new mapping with the desired test arch_mmu_flags */
ret = vmm_alloc_obj(aspace, "mmutest_flags", vmm_obj, 0, alloc_len,
&execute_ptr, 0, 0, arch_mmu_flags);
ASSERT_EQ(NO_ERROR, ret, "vmm_alloc_obj failed\n");
/* Ensure the new mapping reflects the initialised memory */
EXPECT_EQ(0, memcmp(ptr, execute_ptr, alloc_len),
"mapping contents mismatch\n");
/* Double check the flags are as expected on the new memory */
arch_mmu_query(&aspace->arch_aspace, (vaddr_t)execute_ptr, NULL,
&arch_mmu_flags_query);
ASSERT_EQ(arch_mmu_flags_query, arch_mmu_flags,
"arch_mmu_query, 0x%x, does not match requested flags, 0x%x\n",
arch_mmu_flags_query, arch_mmu_flags);
/* Execute the test */
ret = mmutest_run_in_thread("mmu_test_execute",
mmu_test_execute_thread_func, execute_ptr);
test_abort:
if (execute_ptr) {
int tmp_ret = vmm_free_region(aspace, (vaddr_t)execute_ptr);
EXPECT_EQ(NO_ERROR, tmp_ret, "vmm_free_region failed\n");
}
if (ptr) {
int tmp_ret = vmm_free_region(aspace, (vaddr_t)ptr);
EXPECT_EQ(NO_ERROR, tmp_ret, "vmm_free_region failed\n");
}
if (vmm_obj) {
vmm_obj_del_ref(vmm_obj, &vmm_obj_ref);
}
return ret;
}
/* Skip kernel permission tests on ARM as it uses 1MB mappings */
#if ARCH_ARM
#define DISABLED_ON_ARM_NAME(name) DISABLED_##name
#else
#define DISABLED_ON_ARM_NAME(name) name
#endif
typedef struct {
vmm_aspace_t* aspace;
size_t allocation_size;
} mmutestvmm_t;
TEST_F_SETUP(mmutestvmm) {
int ret;
const void* const* params = GetParam();
const size_t* allocation_size_p = params[0];
const bool* is_kernel_aspace = params[1];
_state->allocation_size = *allocation_size_p;
if (*is_kernel_aspace) {
_state->aspace = vmm_get_kernel_aspace();
} else {
ret = vmm_create_aspace(&_state->aspace, "mmutestvmm", 0);
ASSERT_EQ(NO_ERROR, ret);
}
ASSERT_GE(_state->allocation_size, PAGE_SIZE);
ASSERT_LT(_state->allocation_size, _state->aspace->size);
test_abort:;
}
static size_t mmutestvmm_allocation_sizes[] = {
PAGE_SIZE,
2 * 1024 * 1024, /* large enough to use section/block mapping on arm */
};
TEST_F_TEARDOWN(mmutestvmm) {
if (!(_state->aspace->flags & VMM_ASPACE_FLAG_KERNEL)) {
vmm_free_aspace(_state->aspace);
}
}
/* Smoke test for vmm_alloc */
TEST_P(mmutestvmm, vmm_alloc) {
int ret;
void* ptr = NULL;
ret = vmm_alloc(_state->aspace, "mmutest", _state->allocation_size, &ptr, 0,
0, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
EXPECT_EQ(NO_ERROR, ret);
EXPECT_NE(NULL, ptr);
ret = vmm_free_region(_state->aspace, (vaddr_t)ptr);
EXPECT_EQ(NO_ERROR, ret, "vmm_free_region failed\n");
}
/* Smoke test for vmm_alloc_contiguous */
TEST_P(mmutestvmm, vmm_alloc_contiguous) {
int ret;
void* ptr = NULL;
ret = vmm_alloc_contiguous(_state->aspace, "mmutest",
_state->allocation_size, &ptr,
log2_uint(_state->allocation_size), 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
EXPECT_EQ(NO_ERROR, ret);
EXPECT_NE(NULL, ptr);
ret = vmm_free_region(_state->aspace, (vaddr_t)ptr);
EXPECT_EQ(NO_ERROR, ret, "vmm_free_region failed\n");
}
INSTANTIATE_TEST_SUITE_P(
allocationsize,
mmutestvmm,
testing_Combine(testing_ValuesIn(mmutestvmm_allocation_sizes),
/* user(false) and kernel(true) aspaces */
testing_Bool()));
static int mmutest_panic_thread_func(void* _unused) {
panic("mmutest-panic");
}
TEST(mmutest, panic) {
/* Check thread_set_flag_exit_on_panic feature needed by other tests */
int ret = mmutest_run_in_thread("mmutest-panic", mmutest_panic_thread_func,
NULL);
EXPECT_EQ(ERR_FAULT, ret);
}
static int mmutest_panic_thread_lock_thread_func(void* _unused) {
THREAD_LOCK(state);
panic("mmutest-panic-thread-lock");
}
TEST(mmutest, panic_thread_lock) {
/*
* Test panic with thread locked. Both _panic and platform_halt locks the
* thread_lock, so _panic needs to release it if it was already held by the
* current CPU.
*/
int ret =
mmutest_run_in_thread("mmutest-panic-thread-lock",
mmutest_panic_thread_lock_thread_func, NULL);
EXPECT_EQ(ERR_FAULT, ret);
}
TEST(mmutest, alloc_last_kernel_page) {
int ret;
void* ptr1;
void* ptr2;
void* ptr3;
vmm_aspace_t* aspace = vmm_get_kernel_aspace();
struct vmm_obj_slice slice;
vmm_obj_slice_init(&slice);
/*
* Perform allocations at a specific address and at a vmm chosen address
* with and without the last page allocated. There are different code paths
* in the vmm allocator where the virtual address can overflow for the
* region that is being allocated and for regions already allocated.
*/
/* Allocate last kernel aspace page. */
ptr1 = (void*)(aspace->base + (aspace->size - PAGE_SIZE));
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr1, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD |
VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
/* TODO: allow this to fail as page could already be in use */
ASSERT_EQ(NO_ERROR, ret, "vmm_alloc failed last page\n");
/* While the last page is allocated, get an object corresponding to it */
ret = vmm_get_obj(aspace, (vaddr_t)ptr1, PAGE_SIZE, &slice);
EXPECT_EQ(NO_ERROR, ret, "vmm_get_obj failed to get last page object");
/* Check the slice we got back */
EXPECT_NE(NULL, slice.obj);
EXPECT_EQ(PAGE_SIZE, slice.size);
EXPECT_EQ(NO_ERROR, slice.offset);
vmm_obj_slice_release(&slice);
/* Allocate page anywhere, while the last page is allocated. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0, 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret, "vmm_alloc failed anywhere page\n");
/* Try to allocate last kernel aspace page again, should fail */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr1, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
EXPECT_EQ(ERR_NO_MEMORY, ret, "vmm_alloc last page\n");
/* Allocate 2nd last kernel aspace page, while last page is allocated. */
ptr3 = (void*)(aspace->base + (aspace->size - 2 * PAGE_SIZE));
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr3, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
/* TODO: allow this to fail as page could already be in use */
ASSERT_EQ(NO_ERROR, ret, "vmm_alloc failed 2nd last page\n");
/* Free allocated pages */
ret = vmm_free_region(aspace, (vaddr_t)ptr1);
EXPECT_EQ(NO_ERROR, ret, "vmm_free_region failed\n");
ret = vmm_free_region(aspace, (vaddr_t)ptr2);
EXPECT_EQ(NO_ERROR, ret, "vmm_free_region failed\n");
ret = vmm_free_region(aspace, (vaddr_t)ptr3);
EXPECT_EQ(NO_ERROR, ret, "vmm_free_region failed\n");
/* Try to allocate last page without VMM_FLAG_NO_END_GUARD flag */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr1, 0,
VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_OUT_OF_RANGE, ret, "vmm_alloc succeeded unexpectedly\n");
/* Allocate and free last page */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr1, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
/* TODO: allow this to fail as page could be in use */
ASSERT_EQ(NO_ERROR, ret, "vmm_alloc failed last page\n");
ret = vmm_free_region(aspace, (vaddr_t)ptr1);
EXPECT_EQ(NO_ERROR, ret, "vmm_free_region failed\n");
/* Allocate and free page anywhere, while last page is free */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0, 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret, "vmm_alloc failed anywhere page\n");
ret = vmm_free_region(aspace, (vaddr_t)ptr2);
EXPECT_EQ(NO_ERROR, ret, "vmm_free_region failed\n");
test_abort:;
}
typedef struct {
vmm_aspace_t* aspace;
} mmutestaspace_t;
TEST_F_SETUP(mmutestaspace) {
int ret;
const bool* is_kernel_aspace = GetParam();
if (*is_kernel_aspace) {
_state->aspace = vmm_get_kernel_aspace();
} else {
ret = vmm_create_aspace(&_state->aspace, "mmutestaspace", 0);
ASSERT_EQ(NO_ERROR, ret);
}
test_abort:;
}
TEST_F_TEARDOWN(mmutestaspace) {
if (!(_state->aspace->flags & VMM_ASPACE_FLAG_KERNEL)) {
vmm_free_aspace(_state->aspace);
}
}
TEST_P(mmutestaspace, guard_page) {
int ret;
bool retb;
vmm_aspace_t* aspace = _state->aspace;
size_t size = PAGE_SIZE * 6;
vaddr_t base;
void* ptr1 = NULL;
void* ptr2 = NULL;
void* ptr3 = NULL;
void* ptr4 = NULL;
void* ptr5 = NULL;
struct vmm_obj_slice slice;
vmm_obj_slice_init(&slice);
/* Allocate a page at a random spot with guard pages. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr1, 0, 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
/*
* We may get an allocation right at the beginning of the address space
* by chance or because ASLR is disabled. In that case, we make another
* allocation to ensure that ptr1 - PAGE_SIZE >= aspace->base holds.
*/
if (aspace->base > (vaddr_t)ptr1 - PAGE_SIZE) {
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr3, 0, 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ASSERT_GE((vaddr_t)ptr3 - PAGE_SIZE, aspace->base);
vmm_free_region(aspace, (vaddr_t)ptr1);
ptr1 = ptr3;
ptr3 = NULL;
}
/* Check that there are no existing adjacent allocations. */
ret = vmm_get_obj(aspace, (vaddr_t)ptr1 - PAGE_SIZE, PAGE_SIZE, &slice);
EXPECT_EQ(ERR_NOT_FOUND, ret);
vmm_obj_slice_release(&slice);
ret = vmm_get_obj(aspace, (vaddr_t)ptr1 + PAGE_SIZE, PAGE_SIZE, &slice);
EXPECT_EQ(ERR_NOT_FOUND, ret);
vmm_obj_slice_release(&slice);
/* Check that guard pages cannot be allocated. */
ptr2 = (void*)((vaddr_t)ptr1 - PAGE_SIZE);
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD |
VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
ptr2 = (void*)((vaddr_t)ptr1 + PAGE_SIZE);
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD |
VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
ptr2 = NULL;
vmm_free_region(aspace, (vaddr_t)ptr1);
ptr1 = NULL;
/* Check that we cannot allocate at a random spot without guard page */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr1, 0,
VMM_FLAG_NO_START_GUARD | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_INVALID_ARGS, ret);
/* Find a range to to more specific tests in. */
retb = vmm_find_spot(aspace, size, &base);
ASSERT_EQ(true, retb, "failed to find region for test\n");
/* Allocate first test page. */
ptr1 = (void*)base;
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr1, 0,
VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret) {
/*
* This allocation can fail if another thread allocated the page after
* vmm_find_spot returned as that call does not reserve the memory.
* Set ptr1 to NULL so we don't free memory belonging to someone else.
*/
ptr1 = NULL;
}
ASSERT_EQ(NO_ERROR, ret);
/* Test adjacent page. Should all fail as ptr1 has guard on both sides. */
ptr2 = (void*)(base + PAGE_SIZE);
/* No flags. Should fail as both regions have a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0,
VMM_FLAG_VALLOC_SPECIFIC, 0);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No start guard. Should fail as first region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No end guard. Should fail as both regions have a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No guard pages. Should fail as first region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD |
VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* Allocate page after guard page with no end guard */
ptr2 = (void*)(base + PAGE_SIZE * 2);
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr2, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret) {
ptr2 = NULL;
}
ASSERT_EQ(NO_ERROR, ret);
/* Test page directly after ptr2 */
ptr3 = (void*)(base + PAGE_SIZE * 3);
/* No flags. Should fail as second region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr3, 0,
VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No end guard. Should fail as second region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr3, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No guard pages. Should succeed as neither region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr3, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD |
VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret) {
ptr3 = NULL;
}
ASSERT_EQ(NO_ERROR, ret);
/* Test page directly after ptr3 */
ptr4 = (void*)(base + PAGE_SIZE * 4);
/* No flags. Should fail as second region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr4, 0,
VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No end guard. Should fail as second region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr4, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No start guard. Should succeed as neither region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr4, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret) {
ptr4 = NULL;
}
ASSERT_EQ(NO_ERROR, ret);
/*
* Test page directly after ptr4. Should all fail as ptr4 has end guard.
* Similar the test after ptr1, but checks that disabling start guard does
* not affect end guard.
*/
ptr5 = (void*)(base + PAGE_SIZE * 5);
/* No flags. Should fail as both regions have a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr5, 0,
VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No start guard. Should fail as first region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr5, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No end guard. Should fail as both regions have a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr5, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* No guard pages. Should fail as first region has a guard page. */
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr5, 0,
VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD |
VMM_FLAG_NO_END_GUARD,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/*
* Clear ptr5 so we don't try to free it. Not strictly needed as the guard
* page around ptr4 will prevent anyone else from allocating memory at this
* location, and ptr5 is freed first below, but useful if vmm tracing is
* enabled as failing vmm_free_region calls should all be for vaddr 0.
*/
ptr5 = NULL;
test_abort:
vmm_free_region(aspace, (vaddr_t)ptr5);
vmm_free_region(aspace, (vaddr_t)ptr4);
vmm_free_region(aspace, (vaddr_t)ptr3);
vmm_free_region(aspace, (vaddr_t)ptr2);
vmm_free_region(aspace, (vaddr_t)ptr1);
}
TEST_P(mmutestaspace, find_slice_no_guard) {
int ret;
bool retb;
vmm_aspace_t* aspace = _state->aspace;
void* ptr[8];
size_t num_regions = countof(ptr);
size_t size = PAGE_SIZE * num_regions;
vaddr_t base;
uint vmm_flags = VMM_FLAG_VALLOC_SPECIFIC | VMM_FLAG_NO_START_GUARD |
VMM_FLAG_NO_END_GUARD;
struct vmm_obj_slice slice;
vmm_obj_slice_init(&slice);
for (size_t i = 0; i < num_regions; i++) {
ptr[i] = NULL;
}
retb = vmm_find_spot(aspace, size, &base);
ASSERT_EQ(true, retb, "failed to find region for test\n");
for (int i = num_regions - 1; i >= 0; --i) {
ptr[i] = (void*)(base + PAGE_SIZE * i);
ret = vmm_alloc(aspace, "mmutest", PAGE_SIZE, &ptr[i], 0, vmm_flags,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret) {
ptr[i] = NULL;
}
if (ptr[i]) {
/* Test that we can find slice corresponding to allocated page. */
ret = vmm_get_obj(aspace, (vaddr_t)ptr[i], PAGE_SIZE, &slice);
ASSERT_EQ(NO_ERROR, ret);
vmm_obj_slice_release(&slice);
}
}
test_abort:
for (size_t i = 0; i < num_regions; i++) {
vmm_free_region(aspace, (vaddr_t)ptr[i]);
}
}
INSTANTIATE_TEST_SUITE_P(aspacetype,
mmutestaspace,
/* user(false) and kernel(true) aspaces */
testing_Bool());
TEST(mmutest, check_stack_guard_page_bad_ptr)
__attribute__((no_sanitize("bounds"))) {
char data[4];
void* ptr1 = data;
void* ptr2 = data - DEFAULT_STACK_SIZE;
EXPECT_EQ(NO_ERROR, mmutest_arch_store_uint32(ptr1, false));
EXPECT_EQ(ERR_GENERIC, mmutest_arch_store_uint32(ptr2, false));
}
static int mmutest_stack_overflow_thread_func(void* arg) {
char data[DEFAULT_STACK_SIZE] __attribute((uninitialized));
void* ptr = data;
mmutest_arch_store_uint32(ptr, false);
return 0;
}
TEST(mmutest, check_stack_guard_page_stack_overflow) {
EXPECT_EQ(ERR_FAULT,
mmutest_run_in_thread("stack-overflow",
mmutest_stack_overflow_thread_func, NULL));
}
static int mmutest_recursive_stack_overflow_thread_func(void* arg) {
char b;
if ((vaddr_t)arg == 1) {
return 0;
}
return mmutest_recursive_stack_overflow_thread_func(&b) + 1;
}
TEST(mmutest, check_stack_guard_page_recursive_stack_overflow) {
EXPECT_EQ(ERR_FAULT,
mmutest_run_in_thread(
"stack-overflow",
mmutest_recursive_stack_overflow_thread_func, 0));
}
TEST(mmutest, DISABLED_ON_ARM_NAME(rodata_pnx)) {
EXPECT_EQ(ERR_FAULT, mmutest_arch_rodata_pnx());
}
TEST(mmutest, DISABLED_ON_ARM_NAME(data_pnx)) {
EXPECT_EQ(ERR_FAULT, mmutest_arch_data_pnx());
}
TEST(mmutest, DISABLED_ON_ARM_NAME(rodata_ro)) {
EXPECT_EQ(ERR_FAULT, mmutest_arch_rodata_ro());
}
TEST(mmutest, pan) {
if (!mmutest_arch_pan_supported()) {
trusty_unittest_printf("[ INFO ] PAN is not supported\n");
GTEST_SKIP();
}
EXPECT_EQ(true, mmutest_arch_pan_enabled());
test_abort:;
}
TEST(mmutest, store_kernel) {
int expected_user_rw_access;
int expected_user_ro_access;
if (mmutest_arch_pan_enabled()) {
expected_user_rw_access = ERR_GENERIC;
expected_user_ro_access = ERR_GENERIC;
} else {
expected_user_rw_access = 0;
expected_user_ro_access = ERR_FAULT;
}
EXPECT_EQ(NO_ERROR,
mmutest_vmm_store_uint32_kernel(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE));
EXPECT_EQ(expected_user_rw_access,
mmutest_vmm_store_uint32_kernel(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE |
ARCH_MMU_FLAG_PERM_USER));
EXPECT_EQ(NO_ERROR,
mmutest_vmm_store_uint32_kernel(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE));
EXPECT_EQ(expected_user_rw_access,
mmutest_vmm_store_uint32_kernel(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE |
ARCH_MMU_FLAG_PERM_USER));
EXPECT_EQ(ERR_FAULT, mmutest_vmm_store_uint32_kernel(
ARCH_MMU_FLAG_CACHED | ARCH_MMU_FLAG_PERM_RO));
EXPECT_EQ(expected_user_ro_access,
mmutest_vmm_store_uint32_kernel(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_RO |
ARCH_MMU_FLAG_PERM_USER));
}
TEST(mmutest, store_user) {
EXPECT_EQ(ERR_GENERIC,
mmutest_vmm_store_uint32_user(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE));
EXPECT_EQ(NO_ERROR,
mmutest_vmm_store_uint32_user(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE |
ARCH_MMU_FLAG_PERM_USER));
EXPECT_EQ(ERR_GENERIC,
mmutest_vmm_store_uint32_user(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE));
EXPECT_EQ(NO_ERROR,
mmutest_vmm_store_uint32_user(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_NO_EXECUTE |
ARCH_MMU_FLAG_PERM_USER));
EXPECT_EQ(ERR_GENERIC,
mmutest_vmm_store_uint32_user(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_PERM_RO));
EXPECT_EQ(ERR_FAULT, mmutest_vmm_store_uint32_user(
ARCH_MMU_FLAG_CACHED | ARCH_MMU_FLAG_PERM_RO |
ARCH_MMU_FLAG_PERM_USER));
}
/*
* The current implementation of this test checks checks that the data is lost
* when reading back from memory, but allows the store to reach the cache. This
* is not the only allowed behavior and the emulator does not emulate this
* behavior, so disable this test for now.
*/
TEST(mmutest, DISABLED_store_ns) {
EXPECT_EQ(2, mmutest_vmm_store_uint32_kernel(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_NS));
EXPECT_EQ(2, mmutest_vmm_store_uint32_kernel(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_NS |
ARCH_MMU_FLAG_PERM_USER));
EXPECT_EQ(ERR_GENERIC, mmutest_vmm_store_uint32_user(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_NS));
EXPECT_EQ(2, mmutest_vmm_store_uint32_user(ARCH_MMU_FLAG_CACHED |
ARCH_MMU_FLAG_NS |
ARCH_MMU_FLAG_PERM_USER));
}
TEST(mmutest, run_x) {
EXPECT_EQ(NO_ERROR, mmu_test_execute(ARCH_MMU_FLAG_PERM_RO));
}
#if ARCH_ARM64
#include <arch/arm64/sregs.h>
TEST(mmutest, run_wx) {
vmm_aspace_t* aspace = vmm_get_kernel_aspace();
struct obj_ref vmm_obj_ref = OBJ_REF_INITIAL_VALUE(vmm_obj_ref);
struct vmm_obj* vmm_obj = NULL;
void* ptr = NULL;
int ret;
/* Allocate a single page */
ret = pmm_alloc(&vmm_obj, &vmm_obj_ref, 1, PMM_ALLOC_FLAG_CONTIGUOUS, 0);
ASSERT_EQ(NO_ERROR, ret, "pmm_alloc failed\n");
/* Try to map as w+x and check it fails */
ret = vmm_alloc_obj(aspace, "mmutest_wx", vmm_obj, 0, PAGE_SIZE, &ptr, 0, 0,
0);
EXPECT_EQ(ERR_INVALID_ARGS, ret);
/*
* ARM64 should have WXN enabled.
* This means that any writable page is NX irrespective of the PTE entry.
*/
EXPECT_EQ(SCTLR_EL1_WXN, ARM64_READ_SYSREG(SCTLR_EL1) & SCTLR_EL1_WXN);
test_abort:
if (vmm_obj) {
vmm_obj_del_ref(vmm_obj, &vmm_obj_ref);
}
}
#else
TEST(mmutest, run_wx) {
EXPECT_EQ(NO_ERROR, mmu_test_execute(0));
}
#endif
TEST(mmutest, run_nx) {
EXPECT_EQ(ERR_FAULT, mmu_test_execute(ARCH_MMU_FLAG_PERM_NO_EXECUTE));
}
/*
* Tests that allocations with conflicting NS bits are not allowed
* near each other
*/
TEST(mmutest, ns_conflict) {
int ret;
void* ptr_ns = NULL;
void* ptr_s = NULL;
uint arch_mmu_flags_query, ns_flag;
vmm_aspace_t* aspace = vmm_get_kernel_aspace();
/*
* Allocate a NS page with a 16K alignment to ensure that there
* is enough room after it in the 1MB section for both the guard page
* and the S page below.
*/
ret = vmm_alloc(aspace, "ns_conflict_ns", PAGE_SIZE, &ptr_ns,
PAGE_SIZE_SHIFT + 2, 0,
ARCH_MMU_FLAG_NS | ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret == ERR_NOT_SUPPORTED) {
GTEST_SKIP();
}
EXPECT_EQ(NO_ERROR, ret);
ret = arch_mmu_query(&aspace->arch_aspace, (vaddr_t)ptr_ns, NULL,
&arch_mmu_flags_query);
EXPECT_EQ(NO_ERROR, ret);
ns_flag = arch_mmu_flags_query & ARCH_MMU_FLAG_NS;
EXPECT_EQ(ARCH_MMU_FLAG_NS, ns_flag);
/*
* Allocate an S page just after the previous one (plus the guard page).
* This should fail on arm32 because the kernel shouldn't let us mix the
* two kinds.
*/
ptr_s = (uint8_t*)ptr_ns + 2 * PAGE_SIZE;
ret = vmm_alloc(aspace, "ns_conflict_s", PAGE_SIZE, &ptr_s, PAGE_SIZE_SHIFT,
VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
if (ret) {
ptr_s = NULL;
} else {
ret = arch_mmu_query(&aspace->arch_aspace, (vaddr_t)ptr_s, NULL,
&arch_mmu_flags_query);
if (!ret) {
ns_flag = arch_mmu_flags_query & ARCH_MMU_FLAG_NS;
EXPECT_EQ(NO_ERROR, ns_flag);
}
}
test_abort:
if (ptr_ns) {
vmm_free_region(aspace, (vaddr_t)ptr_ns);
}
if (ptr_s) {
vmm_free_region(aspace, (vaddr_t)ptr_s);
}
}
/* Test suite for vmm_obj_slice and vmm_get_obj */
typedef struct {
vmm_aspace_t* aspace;
vaddr_t spot_a_2_page;
vaddr_t spot_b_1_page;
struct vmm_obj_slice slice;
} mmutest_slice_t;
TEST_F_SETUP(mmutest_slice) {
_state->aspace = vmm_get_kernel_aspace();
_state->spot_a_2_page = 0;
_state->spot_b_1_page = 0;
vmm_obj_slice_init(&_state->slice);
ASSERT_EQ(vmm_alloc(_state->aspace, "mmutest_slice", 2 * PAGE_SIZE,
(void**)&_state->spot_a_2_page, 0, 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE),
NO_ERROR);
ASSERT_EQ(vmm_alloc(_state->aspace, "mmutest_slice", PAGE_SIZE,
(void**)&_state->spot_b_1_page, 0, 0,
ARCH_MMU_FLAG_PERM_NO_EXECUTE),
NO_ERROR);
test_abort:;
}
TEST_F_TEARDOWN(mmutest_slice) {
vmm_obj_slice_release(&_state->slice);
if (_state->spot_a_2_page) {
vmm_free_region(_state->aspace, (vaddr_t)_state->spot_a_2_page);
}
if (_state->spot_b_1_page) {
vmm_free_region(_state->aspace, (vaddr_t)_state->spot_b_1_page);
}
}
/*
* Simplest use of interface - get the slice for a mapped region,
* of the whole size
*/
TEST_F(mmutest_slice, simple) {
ASSERT_EQ(vmm_get_obj(_state->aspace, _state->spot_b_1_page, PAGE_SIZE,
&_state->slice),
NO_ERROR);
EXPECT_EQ(_state->slice.offset, 0);
EXPECT_EQ(_state->slice.size, PAGE_SIZE);
test_abort:;
}
/* Validate that we will reject an attempt to span two slices */
TEST_F(mmutest_slice, two_objs) {
vaddr_t base;
size_t size;
vaddr_t spot_a = _state->spot_a_2_page;
vaddr_t spot_b = _state->spot_b_1_page;
base = MIN(spot_a, spot_b);
size = MAX(spot_a, spot_b) - base + PAGE_SIZE;
/* We should not be able to create a slice spanning both objects */
EXPECT_EQ(vmm_get_obj(_state->aspace, base, size, &_state->slice),
ERR_OUT_OF_RANGE);
test_abort:;
}
/* Check we can acquire a subslice of a mapped object */
TEST_F(mmutest_slice, subobj) {
ASSERT_EQ(vmm_get_obj(_state->aspace, _state->spot_a_2_page + PAGE_SIZE,
PAGE_SIZE, &_state->slice),
NO_ERROR);
EXPECT_EQ(_state->slice.offset, PAGE_SIZE);
EXPECT_EQ(_state->slice.size, PAGE_SIZE);
test_abort:;
}
/* Check for rejection of the requested range overflows */
TEST_F(mmutest_slice, overflow) {
EXPECT_EQ(vmm_get_obj(_state->aspace, _state->spot_a_2_page, SIZE_MAX,
&_state->slice),
ERR_INVALID_ARGS);
}
/* Test suite for PMM */
#define RESERVE_PAGES 500
typedef struct {
vmm_aspace_t* aspace;
} mmutest_pmm_t;
TEST_F_SETUP(mmutest_pmm) {
_state->aspace = NULL;
status_t ret = vmm_create_aspace_with_quota(&_state->aspace, "mmutestpmm",
PAGE_SIZE * 2, 0);
ASSERT_EQ(NO_ERROR, ret);
test_abort:;
}
TEST_F_TEARDOWN(mmutest_pmm) {
if (_state->aspace) {
ASSERT_EQ(NO_ERROR, vmm_free_aspace(_state->aspace));
}
test_abort:;
}
static uint probe_max_aspace_quota_pages(void) {
struct vmm_aspace* probe_aspace = NULL;
uint alloc_pages = 4096;
uint alloc_step = 4096;
status_t ret;
do {
ret = vmm_create_aspace_with_quota(&probe_aspace, "probe_aspace",
PAGE_SIZE * alloc_pages, 0);
if (probe_aspace) {
vmm_free_aspace(probe_aspace);
probe_aspace = NULL;
}
if (ret == NO_ERROR) {
alloc_pages += alloc_step;
} else if (alloc_step) {
alloc_pages -= alloc_step;
alloc_step = alloc_step / 2;
} else {
alloc_pages--;
}
} while (alloc_step > 0 || ret != NO_ERROR);
return alloc_pages;
}
/*
* Reserve physical pages and allocate from reserved memory.
*/
TEST_F(mmutest_pmm, reserve) {
void* ptr = NULL;
void* ptr_unused = NULL;
status_t ret;
struct vmm_aspace* temp_aspace = NULL;
uint max_pages, temp_aspace_pages;
/* Allocate virtual space without quota or pmm, which should pass */
ret = vmm_alloc(_state->aspace, "test_reserve",
PAGE_SIZE * (RESERVE_PAGES + 2), &ptr, 0,
VMM_FLAG_NO_PHYSICAL, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
/* Allocate all quota pages at previous virtual address */
ret = vmm_alloc(_state->aspace, "test_from_reserved", PAGE_SIZE * 2, &ptr,
0, VMM_FLAG_QUOTA | VMM_FLAG_VALLOC_SPECIFIC,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
/* Check the maximum quota that can be allocated to an aspace */
max_pages = probe_max_aspace_quota_pages();
ASSERT_GT(max_pages, RESERVE_PAGES);
/* Reserve most pages for temp_aspace, leaving RESERVE_PAGES / 2 free */
temp_aspace_pages = max_pages - (RESERVE_PAGES / 2);
ret = vmm_create_aspace_with_quota(&temp_aspace, "temp_aspace",
PAGE_SIZE * temp_aspace_pages, 0);
ASSERT_EQ(NO_ERROR, ret);
/* Almost all pages are reserved for temp_aspace quota; this should fail */
ret = vmm_alloc(_state->aspace, "test_failure", PAGE_SIZE * RESERVE_PAGES,
&ptr_unused, 0, 0, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
/* Allocate from the temp_aspace quota reservation; should succeed */
ptr += PAGE_SIZE * 2;
ret = vmm_alloc(temp_aspace, "test_from_reserved_success",
PAGE_SIZE * MIN(temp_aspace_pages, RESERVE_PAGES), &ptr, 0,
VMM_FLAG_QUOTA | VMM_FLAG_VALLOC_SPECIFIC,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
test_abort:
if (temp_aspace)
vmm_free_aspace(temp_aspace);
}
TEST_F(mmutest_pmm, reserve_contiguous) {
void* ptr = NULL;
status_t ret;
ret = vmm_alloc(_state->aspace, "test_reserve", PAGE_SIZE * 2, &ptr, 0,
VMM_FLAG_NO_PHYSICAL, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ret = vmm_alloc_contiguous(_state->aspace, "test_from_reserved_continuous",
PAGE_SIZE * 2, &ptr, 0,
VMM_FLAG_QUOTA | VMM_FLAG_VALLOC_SPECIFIC,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
test_abort:;
}
TEST_F(mmutest_pmm, reserve_too_small) {
void* ptr = NULL;
status_t ret;
ret = vmm_alloc(_state->aspace, "test_reserve", PAGE_SIZE * 2, &ptr, 0,
VMM_FLAG_NO_PHYSICAL, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ret = vmm_alloc(_state->aspace, "test_from_reserved_too_small",
PAGE_SIZE * 3, &ptr, 0,
VMM_FLAG_QUOTA | VMM_FLAG_VALLOC_SPECIFIC,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
test_abort:;
}
TEST_F(mmutest_pmm, reserve_outside_region) {
void* ptr = NULL;
status_t ret;
ret = vmm_alloc(_state->aspace, "test_reserve", PAGE_SIZE * 2, &ptr, 0,
VMM_FLAG_NO_PHYSICAL, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ptr += PAGE_SIZE;
ret = vmm_alloc(_state->aspace, "test_from_reserved_outside_region",
PAGE_SIZE * 2, &ptr, 0,
VMM_FLAG_QUOTA | VMM_FLAG_VALLOC_SPECIFIC,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_INVALID_ARGS, ret);
test_abort:;
}
/* Test suite for PMM */
typedef struct {
vmm_aspace_t* aspace;
} mmutest_res_group_t;
TEST_F_SETUP(mmutest_res_group) {
_state->aspace = NULL;
status_t ret = vmm_create_aspace_with_quota(&_state->aspace, "mmutestrg",
PAGE_SIZE, 0);
ASSERT_EQ(NO_ERROR, ret);
test_abort:;
}
TEST_F_TEARDOWN(mmutest_res_group) {
if (_state->aspace) {
ASSERT_EQ(NO_ERROR, vmm_free_aspace(_state->aspace));
}
test_abort:;
}
TEST_F(mmutest_res_group, reserve_group_too_big) {
void* ptr;
status_t ret = vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE + 1, &ptr,
0, VMM_FLAG_QUOTA, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
test_abort:;
}
TEST_F(mmutest_res_group, reserve_group_release_ref) {
/* Destroying an aspace releases refs on its vmm_objs. */
status_t slice_init = ERR_INVALID_ARGS;
void* ptr;
struct vmm_obj_slice slice;
vmm_obj_slice_init(&slice);
status_t alloc_ret =
vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE, &ptr, 0,
VMM_FLAG_QUOTA, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, alloc_ret);
slice_init = vmm_get_obj(_state->aspace, (vaddr_t)ptr, PAGE_SIZE, &slice);
ASSERT_EQ(NO_ERROR, slice_init);
ASSERT_EQ(NO_ERROR, vmm_free_aspace(_state->aspace));
_state->aspace = NULL;
ASSERT_EQ(true, obj_has_only_ref(&slice.obj->obj, &slice.obj_ref));
vmm_obj_slice_release(&slice);
test_abort:;
}
TEST_F(mmutest_res_group, no_physical_inner_obj) {
void* ptr;
struct vmm_obj_slice slice;
vmm_obj_slice_init(&slice);
status_t ret = vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE * 2, &ptr,
0, VMM_FLAG_QUOTA | VMM_FLAG_NO_PHYSICAL,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ret = vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE, &ptr, 0,
VMM_FLAG_QUOTA | VMM_FLAG_VALLOC_SPECIFIC,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
/* vmm_get_obj should look inside NO_PHYSICAL regions and return nested
* vmm_objs from inside. */
ret = vmm_get_obj(_state->aspace, (vaddr_t)ptr, PAGE_SIZE, &slice);
ASSERT_EQ(NO_ERROR, ret);
ASSERT_EQ(PAGE_SIZE, slice.size);
ASSERT_EQ(NO_ERROR, vmm_free_region(_state->aspace, (vaddr_t)ptr));
ASSERT_EQ(true, obj_has_only_ref(&slice.obj->obj, &slice.obj_ref));
vmm_obj_slice_release(&slice);
test_abort:;
}
TEST_F(mmutest_res_group, reserve_group_no_physical) {
/* NO_PHYSICAL allocations don't count towards memory usage. */
void* ptr;
status_t ret =
vmm_alloc(_state->aspace, "test_reserved_alloc", PAGE_SIZE * 10,
&ptr, 0, VMM_FLAG_QUOTA | VMM_FLAG_NO_PHYSICAL,
ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ret = vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE, &ptr, 0,
VMM_FLAG_QUOTA, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ret = vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE, &ptr, 0,
VMM_FLAG_QUOTA, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
test_abort:;
}
TEST_F(mmutest_res_group, reserve_group_disable_quota) {
/* Allocations without VMM_FLAG_QUOTA set don't count towards memory usage.
*/
void* ptr;
status_t ret =
vmm_alloc(_state->aspace, "test_reserved_alloc", PAGE_SIZE * 10,
&ptr, 0, 0, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ret = vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE, &ptr, 0,
VMM_FLAG_QUOTA, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(NO_ERROR, ret);
ret = vmm_alloc(_state->aspace, "test_alloc", PAGE_SIZE, &ptr, 0,
VMM_FLAG_QUOTA, ARCH_MMU_FLAG_PERM_NO_EXECUTE);
ASSERT_EQ(ERR_NO_MEMORY, ret);
test_abort:;
}
PORT_TEST(mmutest, "com.android.kernel.mmutest");