The IA32 MMU has no concept of a "no execute" flag, this is unfortunately only implemented in x86_64. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
185 lines
4.5 KiB
C
185 lines
4.5 KiB
C
/*
|
|
* Parts derived from tests/kernel/fatal/src/main.c, which has the
|
|
* following copyright and license:
|
|
*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr.h>
|
|
#include <ztest.h>
|
|
#include <kernel_structs.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "targets.h"
|
|
|
|
#define INFO(fmt, ...) printk(fmt, ##__VA_ARGS__)
|
|
|
|
/* ARM is a special case, in that k_thread_abort() does indeed return
|
|
* instead of calling _Swap() directly. The PendSV exception is queued
|
|
* and immediately fires upon completing the exception path; the faulting
|
|
* thread is never run again.
|
|
*/
|
|
#ifndef CONFIG_ARM
|
|
FUNC_NORETURN
|
|
#endif
|
|
void _SysFatalErrorHandler(unsigned int reason, const NANO_ESF *pEsf)
|
|
{
|
|
INFO("Caught system error -- reason %d\n", reason);
|
|
ztest_test_pass();
|
|
#ifndef CONFIG_ARM
|
|
CODE_UNREACHABLE;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_CPU_CORTEX_M
|
|
#include <arch/arm/cortex_m/cmsis.h>
|
|
/* Must clear LSB of function address to access as data. */
|
|
#define FUNC_TO_PTR(x) (void *)((uintptr_t)(x) & ~0x1)
|
|
/* Must set LSB of function address to call in Thumb mode. */
|
|
#define PTR_TO_FUNC(x) (int (*)(int))((uintptr_t)(x) | 0x1)
|
|
/* Flush preceding data writes and instruction fetches. */
|
|
#define DO_BARRIERS() do { __DSB(); __ISB(); } while (0)
|
|
#else
|
|
#define FUNC_TO_PTR(x) (void *)(x)
|
|
#define PTR_TO_FUNC(x) (int (*)(int))(x)
|
|
#define DO_BARRIERS() do { } while (0)
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARM)
|
|
#define NO_EXECUTE_SUPPORT 1
|
|
#elif defined(CONFIG_X86)
|
|
/* i386 MMU doesn't control execute capabilities, only on x86_64 */
|
|
#else
|
|
#error "Architecture not supported"
|
|
#endif
|
|
|
|
static int __attribute__((noinline)) add_one(int i)
|
|
{
|
|
return (i + 1);
|
|
}
|
|
|
|
#ifdef NO_EXECUTE_SUPPORT
|
|
static void execute_from_buffer(u8_t *dst)
|
|
{
|
|
void *src = FUNC_TO_PTR(add_one);
|
|
int (*func)(int i) = PTR_TO_FUNC(dst);
|
|
int i = 1;
|
|
|
|
/* Copy add_one() code to destination buffer. */
|
|
memcpy(dst, src, BUF_SIZE);
|
|
DO_BARRIERS();
|
|
|
|
/*
|
|
* Try executing from buffer we just filled.
|
|
* Optimally, this triggers a fault.
|
|
* If not, we check to see if the function
|
|
* returned the expected result as confirmation
|
|
* that we truly executed the code we wrote.
|
|
*/
|
|
INFO("trying to call code written to %p\n", func);
|
|
i = func(i);
|
|
INFO("returned from code at %p\n", func);
|
|
if (i == 2) {
|
|
INFO("Execute from target buffer succeeded!\n");
|
|
} else {
|
|
INFO("Did not get expected return value!\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void write_ro(void)
|
|
{
|
|
u32_t *ptr = (u32_t *)&rodata_var;
|
|
|
|
/*
|
|
* Try writing to rodata. Optimally, this triggers a fault.
|
|
* If not, we check to see if the rodata value actually changed.
|
|
*/
|
|
INFO("trying to write to rodata at %p\n", ptr);
|
|
*ptr = ~RODATA_VALUE;
|
|
|
|
DO_BARRIERS();
|
|
|
|
if (*ptr == RODATA_VALUE) {
|
|
INFO("rodata value still the same\n");
|
|
} else if (*ptr == ~RODATA_VALUE) {
|
|
INFO("rodata modified!\n");
|
|
} else {
|
|
INFO("something went wrong!\n");
|
|
}
|
|
|
|
zassert_unreachable("Write to rodata did not fault");
|
|
}
|
|
|
|
static void write_text(void)
|
|
{
|
|
void *src = FUNC_TO_PTR(add_one);
|
|
void *dst = FUNC_TO_PTR(overwrite_target);
|
|
int i = 1;
|
|
|
|
/*
|
|
* Try writing to a function in the text section.
|
|
* Optimally, this triggers a fault.
|
|
* If not, we try calling the function after overwriting
|
|
* to see if it returns the expected result as
|
|
* confirmation that we truly executed the code we wrote.
|
|
*/
|
|
INFO("trying to write to text at %p\n", dst);
|
|
memcpy(dst, src, BUF_SIZE);
|
|
DO_BARRIERS();
|
|
i = overwrite_target(i);
|
|
if (i == 2) {
|
|
INFO("Overwrite of text succeeded!\n");
|
|
} else {
|
|
INFO("Did not get expected return value!\n");
|
|
}
|
|
|
|
zassert_unreachable("Write to text did not fault");
|
|
}
|
|
|
|
#ifdef NO_EXECUTE_SUPPORT
|
|
static void exec_data(void)
|
|
{
|
|
execute_from_buffer(data_buf);
|
|
zassert_unreachable("Execute from data did not fault");
|
|
}
|
|
|
|
static void exec_stack(void)
|
|
{
|
|
u8_t stack_buf[BUF_SIZE] __aligned(sizeof(int));
|
|
|
|
execute_from_buffer(stack_buf);
|
|
zassert_unreachable("Execute from stack did not fault");
|
|
}
|
|
|
|
#if (CONFIG_HEAP_MEM_POOL_SIZE > 0)
|
|
static void exec_heap(void)
|
|
{
|
|
u8_t *heap_buf = k_malloc(BUF_SIZE);
|
|
|
|
execute_from_buffer(heap_buf);
|
|
k_free(heap_buf);
|
|
zassert_unreachable("Execute from heap did not fault");
|
|
}
|
|
#endif
|
|
#endif /* NO_EXECUTE_SUPPORT */
|
|
|
|
void test_main(void *unused1, void *unused2, void *unused3)
|
|
{
|
|
ztest_test_suite(test_protection,
|
|
#ifdef NO_EXECUTE_SUPPORT
|
|
ztest_unit_test(exec_data),
|
|
ztest_unit_test(exec_stack),
|
|
#if (CONFIG_HEAP_MEM_POOL_SIZE > 0)
|
|
ztest_unit_test(exec_heap),
|
|
#endif
|
|
#endif /* NO_EXECUTE_SUPPORT */
|
|
ztest_unit_test(write_ro),
|
|
ztest_unit_test(write_text)
|
|
);
|
|
ztest_run_test_suite(test_protection);
|
|
}
|