zephyr/kernel/mem_domain.c
Leandro Pereira b007b64d30 kernel: Add option to ensure writable pages are not executable
This adds CONFIG_EXECUTE_XOR_WRITE, which is enabled by default on
systems that support controlling whether a page can contain executable
code.  This is also known as W^X[1].

Trying to add a memory domain with a page that is both executable and
writable, either for supervisor mode threads, or for user mode threads,
will result in a kernel panic.

There are few cases where a writable page should also be executable
(JIT compilers, which are most likely out of scope for Zephyr), so an
option is provided to disable the check.

Since the memory domain APIs are executed in supervisor mode, a
determined person could bypass these checks with ease.  This is seen
more as a way to avoid people shooting themselves in the foot.

[1] https://en.wikipedia.org/wiki/W%5EX

Signed-off-by: Leandro Pereira <leandro.pereira@intel.com>
2017-11-02 13:40:50 -07:00

185 lines
3.9 KiB
C

/*
* Copyright (c) 2017 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <init.h>
#include <kernel.h>
#include <kernel_structs.h>
#include <nano_internal.h>
#include <misc/__assert.h>
static u8_t max_partitions;
static void ensure_w_xor_x(u32_t attrs)
{
#if defined(CONFIG_EXECUTE_XOR_WRITE) && __ASSERT_ON
bool writable = K_MEM_PARTITION_IS_WRITABLE(attrs);
bool executable = K_MEM_PARTITION_IS_EXECUTABLE(attrs);
__ASSERT(writable != executable, "writable page not executable");
#else
ARG_UNUSED(attrs);
#endif
}
void k_mem_domain_init(struct k_mem_domain *domain, u32_t num_parts,
struct k_mem_partition *parts[])
{
unsigned int key;
__ASSERT(domain && (!num_parts || parts), "");
__ASSERT(num_parts <= max_partitions, "");
key = irq_lock();
domain->num_partitions = num_parts;
memset(domain->partitions, 0, sizeof(domain->partitions));
if (num_parts) {
u32_t i;
for (i = 0; i < num_parts; i++) {
__ASSERT(parts[i], "");
ensure_w_xor_x(parts[i]->attr);
domain->partitions[i] = *parts[i];
}
}
sys_dlist_init(&domain->mem_domain_q);
irq_unlock(key);
}
void k_mem_domain_destroy(struct k_mem_domain *domain)
{
unsigned int key;
sys_dnode_t *node, *next_node;
__ASSERT(domain, "");
key = irq_lock();
SYS_DLIST_FOR_EACH_NODE_SAFE(&domain->mem_domain_q, node, next_node) {
struct k_thread *thread =
CONTAINER_OF(node, struct k_thread, mem_domain_info);
sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node);
thread->mem_domain_info.mem_domain = NULL;
}
irq_unlock(key);
}
void k_mem_domain_add_partition(struct k_mem_domain *domain,
struct k_mem_partition *part)
{
int p_idx;
unsigned int key;
__ASSERT(domain && part, "");
__ASSERT(part->start + part->size > part->start, "");
ensure_w_xor_x(part->attr);
key = irq_lock();
for (p_idx = 0; p_idx < max_partitions; p_idx++) {
/* A zero-sized partition denotes it's a free partition */
if (domain->partitions[p_idx].size == 0) {
break;
}
}
/* Assert if there is no free partition */
__ASSERT(p_idx < max_partitions, "");
domain->partitions[p_idx].start = part->start;
domain->partitions[p_idx].size = part->size;
domain->partitions[p_idx].attr = part->attr;
domain->num_partitions++;
irq_unlock(key);
}
void k_mem_domain_remove_partition(struct k_mem_domain *domain,
struct k_mem_partition *part)
{
int p_idx;
unsigned int key;
__ASSERT(domain && part, "");
key = irq_lock();
/* find a partition that matches the given start and size */
for (p_idx = 0; p_idx < max_partitions; p_idx++) {
if (domain->partitions[p_idx].start == part->start &&
domain->partitions[p_idx].size == part->size) {
break;
}
}
/* Assert if not found */
__ASSERT(p_idx < max_partitions, "");
domain->partitions[p_idx].start = 0;
domain->partitions[p_idx].size = 0;
domain->partitions[p_idx].attr = 0;
domain->num_partitions--;
irq_unlock(key);
}
void k_mem_domain_add_thread(struct k_mem_domain *domain, k_tid_t thread)
{
unsigned int key;
__ASSERT(domain && thread && !thread->mem_domain_info.mem_domain, "");
key = irq_lock();
sys_dlist_append(&domain->mem_domain_q,
&thread->mem_domain_info.mem_domain_q_node);
thread->mem_domain_info.mem_domain = domain;
irq_unlock(key);
}
void k_mem_domain_remove_thread(k_tid_t thread)
{
unsigned int key;
__ASSERT(thread && thread->mem_domain_info.mem_domain, "");
key = irq_lock();
sys_dlist_remove(&thread->mem_domain_info.mem_domain_q_node);
thread->mem_domain_info.mem_domain = NULL;
irq_unlock(key);
}
static int init_mem_domain_module(struct device *arg)
{
ARG_UNUSED(arg);
max_partitions = _arch_mem_domain_max_partitions_get();
/*
* max_partitions must be less than or equal to
* CONFIG_MAX_DOMAIN_PARTITIONS, or would encounter array index
* out of bounds error.
*/
__ASSERT(max_partitions <= CONFIG_MAX_DOMAIN_PARTITIONS, "");
return 0;
}
SYS_INIT(init_mem_domain_module, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);