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 bacbea6e21275bf5 ("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: Stephen Smalley <sds@tycho.nsa.gov>
diff --git a/tests/kernel/protection/Makefile b/tests/kernel/protection/Makefile
new file mode 100644
index 0000000..783c401
--- /dev/null
+++ b/tests/kernel/protection/Makefile
@@ -0,0 +1,4 @@
+BOARD ?= frdm_k64f
+CONF_FILE = prj.conf
+
+include ${ZEPHYR_BASE}/Makefile.test
diff --git a/tests/kernel/protection/README.rst b/tests/kernel/protection/README.rst
new file mode 100644
index 0000000..06f46f6
--- /dev/null
+++ b/tests/kernel/protection/README.rst
@@ -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
diff --git a/tests/kernel/protection/prj.conf b/tests/kernel/protection/prj.conf
new file mode 100644
index 0000000..a3b3b01
--- /dev/null
+++ b/tests/kernel/protection/prj.conf
@@ -0,0 +1,2 @@
+CONFIG_HEAP_MEM_POOL_SIZE=256
+CONFIG_ZTEST=y
diff --git a/tests/kernel/protection/src/Makefile b/tests/kernel/protection/src/Makefile
new file mode 100644
index 0000000..e1f454f
--- /dev/null
+++ b/tests/kernel/protection/src/Makefile
@@ -0,0 +1,5 @@
+include $(ZEPHYR_BASE)/tests/Makefile.test
+
+ccflags-y += -I${ZEPHYR_BASE}/tests/include
+
+obj-y = targets.o main.o
diff --git a/tests/kernel/protection/src/main.c b/tests/kernel/protection/src/main.c
new file mode 100644
index 0000000..258e0b0
--- /dev/null
+++ b/tests/kernel/protection/src/main.c
@@ -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);
+}
diff --git a/tests/kernel/protection/src/targets.c b/tests/kernel/protection/src/targets.c
new file mode 100644
index 0000000..8aad7d7
--- /dev/null
+++ b/tests/kernel/protection/src/targets.c
@@ -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);
+}
diff --git a/tests/kernel/protection/src/targets.h b/tests/kernel/protection/src/targets.h
new file mode 100644
index 0000000..71b71c2
--- /dev/null
+++ b/tests/kernel/protection/src/targets.h
@@ -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
diff --git a/tests/kernel/protection/testcase.ini b/tests/kernel/protection/testcase.ini
new file mode 100644
index 0000000..b9edbaa
--- /dev/null
+++ b/tests/kernel/protection/testcase.ini
@@ -0,0 +1,3 @@
+[test]
+tags = core security ignore_faults
+filter = CONFIG_CPU_HAS_MPU or CONFIG_X86_MMU