Skip to content

Commit

Permalink
tests: Add a self-protection test suite
Browse files Browse the repository at this point in the history
Add a self-protection test suite with a set of tests
to check whether one can overwrite read-only data
and text, and whether one can execute from data,
stack, or heap buffers.  These tests are modeled after
a subset of the lkdtm tests in the Linux kernel.

These tests have twice caught bugs in the Zephyr NXP MPU
driver, once during initial testing/review of the code
(in its earliest forms on gerrit, reported to the original
author there) and most recently the regression introduced
by commit bacbea6 ("arm: nxp: mpu: Rework handling
of region descriptor 0"), which was fixed by
commit a8aa9d4 ("arm: nxp: mpu: Fix region descriptor
0 attributes") after being reported.

This is intended to be a testsuite of self-protection features
rather than just a test of MPU functionality.  It is envisioned
that these tests will be expanded to cover a wider range of
protection features beyond just memory protection, and the
current tests are independent of any particular enforcement
mechanism (e.g. MPU, MMU, or other).

The tests are intended to be cross-platform, and have been
built and run on both x86- and ARM-based boards.  The tests
currently fail on x86-based boards, but this is an accurate
reflection of current protections and should change as MMU
support arrives.

The tests leverage the ztest framework, making them suitable
for incorporation into automated regression testing for Zephyr.

Signed-off-by: Stephen Smalley <[email protected]>
  • Loading branch information
stephensmalley authored and Anas Nashif committed Jun 22, 2017
1 parent 083fdf3 commit c997577
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 0 deletions.
4 changes: 4 additions & 0 deletions tests/kernel/protection/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BOARD ?= frdm_k64f
CONF_FILE = prj.conf

include ${ZEPHYR_BASE}/Makefile.test
84 changes: 84 additions & 0 deletions tests/kernel/protection/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.. _protection_tests:

Protection tests
#################################

Overview
********
This test case verifies that protection is provided
against the following security issues:

* Write to read-only data.
* Write to text.
* Execute from data.
* Execute from stack.
* Execute from heap.

Building and Running
********************

This project can be built and executed as follows:

.. code-block:: console
$ cd tests/protection
$ make BOARD=<insert your board here>
Connect the board to your host computer using the USB port.
Flash the generated zephyr.bin on the board.
Reset the board.

Sample Output
=============

.. code-block:: console
***** BOOTING ZEPHYR OS v1.8.99 - BUILD: Jun 19 2017 12:44:27 *****
Running test suite test_protection
tc_start() - write_ro
trying to write to rodata at 0x00003124
***** BUS FAULT *****
Executing thread ID (thread): 0x200001bc
Faulting instruction address: 0x88c
Imprecise data bus error
Caught system error -- reason 0
===================================================================
PASS - write_ro.
tc_start() - write_text
trying to write to text at 0x000006c0
***** BUS FAULT *****
Executing thread ID (thread): 0x200001bc
Faulting instruction address: 0xd60
Imprecise data bus error
Caught system error -- reason 0
===================================================================
PASS - write_text.
tc_start() - exec_data
trying to call code written to 0x2000041d
***** BUS FAULT *****
Executing thread ID (thread): 0x200001bc
Faulting instruction address: 0x2000041c
Imprecise data bus error
Caught system error -- reason 0
===================================================================
PASS - exec_data.
tc_start() - exec_stack
trying to call code written to 0x20000929
***** BUS FAULT *****
Executing thread ID (thread): 0x200001bc
Faulting instruction address: 0x20000928
Imprecise data bus error
Caught system error -- reason 0
===================================================================
PASS - exec_stack.
tc_start() - exec_heap
trying to call code written to 0x20000455
***** BUS FAULT *****
Executing thread ID (thread): 0x200001bc
Faulting instruction address: 0x20000454
Imprecise data bus error
Caught system error -- reason 0
===================================================================
PASS - exec_heap.
===================================================================
PROJECT EXECUTION SUCCESSFUL
2 changes: 2 additions & 0 deletions tests/kernel/protection/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CONFIG_HEAP_MEM_POOL_SIZE=256
CONFIG_ZTEST=y
5 changes: 5 additions & 0 deletions tests/kernel/protection/src/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include $(ZEPHYR_BASE)/tests/Makefile.test

ccflags-y += -I${ZEPHYR_BASE}/tests/include

obj-y = targets.o main.o
170 changes: 170 additions & 0 deletions tests/kernel/protection/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* 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

static int __attribute__((noinline)) add_one(int i)
{
return (i + 1);
}

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");
}
}

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");
}

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

void test_main(void *unused1, void *unused2, void *unused3)
{
ztest_test_suite(test_protection,
ztest_unit_test(write_ro),
ztest_unit_test(write_text),
ztest_unit_test(exec_data),
ztest_unit_test(exec_stack)
#if (CONFIG_HEAP_MEM_POOL_SIZE > 0)
, ztest_unit_test(exec_heap)
#endif
);
ztest_run_test_suite(test_protection);
}
14 changes: 14 additions & 0 deletions tests/kernel/protection/src/targets.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <zephyr.h>
#include <misc/printk.h>

#include "targets.h"

const u32_t rodata_var = RODATA_VALUE;

u8_t data_buf[BUF_SIZE] __aligned(sizeof(int));

int overwrite_target(int i)
{
printk("text not modified\n");
return (i - 1);
}
12 changes: 12 additions & 0 deletions tests/kernel/protection/src/targets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef _PROT_TEST_TARGETS_H_
#define _PROT_TEST_TARGETS_H_

#define RODATA_VALUE 0xF00FF00F
extern const u32_t rodata_var;

#define BUF_SIZE 16
extern u8_t data_buf[BUF_SIZE];

extern int overwrite_target(int i);

#endif
3 changes: 3 additions & 0 deletions tests/kernel/protection/testcase.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[test]
tags = core security ignore_faults
filter = CONFIG_CPU_HAS_MPU or CONFIG_X86_MMU

0 comments on commit c997577

Please sign in to comment.