Commit d278473d authored by Stephen Smalley's avatar Stephen Smalley Committed by Anas Nashif
Browse files

tests: Add a self-protection test suite



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 bacbea6e ("arm: nxp: mpu: Rework handling
of region descriptor 0"), which was fixed by
commit a8aa9d4f3dbbe8 ("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: default avatarStephen Smalley <sds@tycho.nsa.gov>
parent 01142147
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
BOARD ?= frdm_k64f
CONF_FILE = prj.conf

include ${ZEPHYR_BASE}/Makefile.test
+84 −0
Original line number Diff line number Diff line
.. _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 −0
Original line number Diff line number Diff line
CONFIG_HEAP_MEM_POOL_SIZE=256
CONFIG_ZTEST=y
+5 −0
Original line number Diff line number Diff line
include $(ZEPHYR_BASE)/tests/Makefile.test

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

obj-y = targets.o main.o
+170 −0
Original line number Diff line number Diff line
/*
 * 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);
}
Loading