Skip to content

Commit

Permalink
llext: Support memory protection
Browse files Browse the repository at this point in the history
Sets up memory partitions and allows for the partitions to be added to a
memory domain after loading an extension. This allows for applying
memory protection attributes to all of the needed memory regions an
extension requires to execute code correctly.

Currently only works when usermode is enabled as otherwise memory
protection APIs are unavailable.

Signed-off-by: Tom Burdick <[email protected]>
  • Loading branch information
teburd authored and nashif committed Feb 2, 2024
1 parent 172bc0c commit 84e883b
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 7 deletions.
23 changes: 23 additions & 0 deletions include/zephyr/llext/llext.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <zephyr/sys/slist.h>
#include <zephyr/llext/elf.h>
#include <zephyr/llext/symbol.h>
#include <zephyr/kernel.h>
#include <sys/types.h>
#include <stdbool.h>

Expand Down Expand Up @@ -40,6 +41,8 @@ enum llext_mem {
LLEXT_MEM_COUNT,
};

#define LLEXT_MEM_PARTITIONS (LLEXT_MEM_BSS+1)

struct llext_loader;

/**
Expand All @@ -48,6 +51,12 @@ struct llext_loader;
struct llext {
/** @cond ignore */
sys_snode_t _llext_list;

#ifdef CONFIG_USERSPACE
struct k_mem_partition mem_parts[LLEXT_MEM_PARTITIONS];
struct k_mem_domain mem_domain;
#endif

/** @endcond */

/** Name of the llext */
Expand Down Expand Up @@ -167,6 +176,20 @@ const void * const llext_find_sym(const struct llext_symtable *sym_table, const
*/
int llext_call_fn(struct llext *ext, const char *sym_name);

/**
* @brief Add the known memory partitions of the extension to a memory domain
*
* Allows an extension to be executed in supervisor or user mode threads when
* memory protection hardware is enabled.
*
* @param[in] ext Extension to add to a domain
* @param[in] domain Memory domain to add partitions to
*
* @retval 0 success
* @retval -errno error
*/
int llext_add_domain(struct llext *ext, struct k_mem_domain *domain);

/**
* @brief Architecture specific function for updating op codes given a relocation
*
Expand Down
99 changes: 96 additions & 3 deletions subsys/llext/llext.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL);

#include <string.h>

#ifdef CONFIG_MMU_PAGE_SIZE
#define LLEXT_PAGE_SIZE CONFIG_MMU_PAGE_SIZE
#else
/* Arm's MPU wants a 32 byte minimum mpu region */
#define LLEXT_PAGE_SIZE 32
#endif

K_HEAP_DEFINE(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024);

static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'};
Expand Down Expand Up @@ -251,6 +258,38 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext)
return 0;
}

/*
* Initialize the memory partition associated with the extension memory
*/
static void llext_init_mem_part(struct llext *ext, enum llext_mem mem_idx,
uintptr_t start, size_t len)
{
#ifdef CONFIG_USERSPACE
if (mem_idx < LLEXT_MEM_PARTITIONS) {
ext->mem_parts[mem_idx].start = start;
ext->mem_parts[mem_idx].size = len;

switch (mem_idx) {
case LLEXT_MEM_TEXT:
ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RX_U_RX;
break;
case LLEXT_MEM_DATA:
case LLEXT_MEM_BSS:
ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RW_U_RW;
break;
case LLEXT_MEM_RODATA:
ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RO_U_RO;
break;
default:
break;
}
LOG_DBG("mem partition %d start 0x%lx, size %d", mem_idx,
ext->mem_parts[mem_idx].start,
ext->mem_parts[mem_idx].size);
}
#endif
}

static int llext_copy_section(struct llext_loader *ldr, struct llext *ext,
enum llext_mem mem_idx)
{
Expand All @@ -265,18 +304,41 @@ static int llext_copy_section(struct llext_loader *ldr, struct llext *ext,
IS_ENABLED(CONFIG_LLEXT_STORAGE_WRITABLE)) {
ext->mem[mem_idx] = llext_peek(ldr, ldr->sects[mem_idx].sh_offset);
if (ext->mem[mem_idx]) {
llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx],
ldr->sects[mem_idx].sh_size);
ext->mem_on_heap[mem_idx] = false;
return 0;
}
}

ext->mem[mem_idx] = k_heap_aligned_alloc(&llext_heap, sizeof(uintptr_t),
ldr->sects[mem_idx].sh_size,
/* On ARM with an MPU a pow(2, N)*32 sized and aligned region is needed,
* otherwise its typically an mmu page (sized and aligned memory region)
* we are after that we can assign memory permission bits on.
*/
#ifndef CONFIG_ARM_MPU
const uintptr_t sect_alloc = ROUND_UP(ldr->sects[mem_idx].sh_size, LLEXT_PAGE_SIZE);
const uintptr_t sect_align = LLEXT_PAGE_SIZE;
#else
uintptr_t sect_alloc = LLEXT_PAGE_SIZE;

while (sect_alloc < ldr->sects[mem_idx].sh_size) {
sect_alloc *= 2;
}
uintptr_t sect_align = sect_alloc;
#endif

ext->mem[mem_idx] = k_heap_aligned_alloc(&llext_heap, sect_align,
sect_alloc,
K_NO_WAIT);

if (!ext->mem[mem_idx]) {
return -ENOMEM;
}
ext->alloc_size += ldr->sects[mem_idx].sh_size;

ext->alloc_size += sect_alloc;

llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx],
sect_alloc);

if (ldr->sects[mem_idx].sh_type == SHT_NOBITS) {
memset(ext->mem[mem_idx], 0, ldr->sects[mem_idx].sh_size);
Expand Down Expand Up @@ -769,6 +831,14 @@ static int do_llext_load(struct llext_loader *ldr, struct llext *ext,
ldr->sect_cnt = ldr->hdr.e_shnum;
ext->alloc_size += sect_map_sz;

#ifdef CONFIG_USERSPACE
ret = k_mem_domain_init(&ext->mem_domain, 0, NULL);
if (ret != 0) {
LOG_ERR("Failed to initialize extenion memory domain %d", ret);
goto out;
}
#endif

LOG_DBG("Finding ELF tables...");
ret = llext_find_tables(ldr);
if (ret != 0) {
Expand Down Expand Up @@ -974,3 +1044,26 @@ int llext_call_fn(struct llext *ext, const char *sym_name)

return 0;
}

int llext_add_domain(struct llext *ext, struct k_mem_domain *domain)
{
#ifdef CONFIG_USERSPACE
int ret = 0;

for (int i = 0; i < LLEXT_MEM_PARTITIONS; i++) {
if (ext->mem_size[i] == 0) {
continue;
}
ret = k_mem_domain_add_partition(domain, &ext->mem_parts[i]);
if (ret != 0) {
LOG_ERR("Failed adding memory partition %d to domain %p",
i, domain);
return ret;
}
}

return ret;
#else
return -ENOSYS;
#endif
}
1 change: 0 additions & 1 deletion tests/subsys/llext/hello_world/prj.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
CONFIG_ZTEST=y
CONFIG_ZTEST_STACK_SIZE=8192
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_LLEXT=y
CONFIG_LLEXT_HEAP_SIZE=32
CONFIG_LLEXT_LOG_LEVEL_DBG=y
37 changes: 35 additions & 2 deletions tests/subsys/llext/hello_world/src/test/test_llext_simple.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ static uint8_t hello_world_elf[] __aligned(4) = {
};
#endif

K_THREAD_STACK_DEFINE(llext_stack, 1024);
struct k_thread llext_thread;

#ifdef CONFIG_USERSPACE
void llext_entry(void *arg0, void *arg1, void *arg2)
{
struct llext *ext = arg0;

zassert_ok(llext_call_fn(ext, "hello_world"),
"hello_world call should succeed");
}
#endif /* CONFIG_USERSPACE */

/**
* Attempt to load, list, list symbols, call a fn, and unload a
* hello world extension for each supported architecture
Expand Down Expand Up @@ -45,9 +58,29 @@ ZTEST(llext, test_llext_simple)

zassert_not_null(hello_world_fn, "hello_world should be an exported symbol");

res = llext_call_fn(ext, "hello_world");
#ifdef CONFIG_USERSPACE
struct k_mem_domain domain;

k_mem_domain_init(&domain, 0, NULL);

res = llext_add_domain(ext, &domain);
zassert_ok(res, "adding partitions to domain should succeed");

/* Should be runnable from newly created thread */
k_thread_create(&llext_thread, llext_stack,
K_THREAD_STACK_SIZEOF(llext_stack),
&llext_entry, ext, NULL, NULL,
1, 0, K_FOREVER);

k_mem_domain_add_thread(&domain, &llext_thread);

k_thread_start(&llext_thread);
k_thread_join(&llext_thread, K_FOREVER);

zassert_ok(res, "calling hello world should succeed");
#else /* CONFIG_USERSPACE */
zassert_ok(llext_call_fn(ext, "hello_world"),
"hello_world call should succeed");
#endif /* CONFIG_USERSPACE */

llext_unload(&ext);
}
Expand Down
21 changes: 20 additions & 1 deletion tests/subsys/llext/hello_world/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@ common:
arch_allow:
- arm
- xtensa
filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE_R52
platform_exclude:
- nuvoton_pfm_m487 # See #63167
tests:
llext.simple.readonly:
arch_exclude: xtensa # for now
filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE_R52
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- CONFIG_LLEXT_STORAGE_WRITABLE=n
llext.simple.readonly_mpu:
arch_exclude: xtensa # for now
filter: CONFIG_CPU_HAS_MPU
extra_configs:
- CONFIG_USERSPACE=y
- CONFIG_LLEXT_STORAGE_WRITABLE=n
llext.simple.writable:
filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE_R52
extra_configs:
- arch:arm:CONFIG_ARM_MPU=n
- CONFIG_LLEXT_STORAGE_WRITABLE=y
llext.simple.modules_enabled_writable:
filter: not CONFIG_MPU and not CONFIG_MMU
platform_key:
- simulation
- arch
Expand All @@ -28,6 +36,7 @@ tests:
- CONFIG_LLEXT_STORAGE_WRITABLE=y
- CONFIG_LLEXT_TEST_HELLO=m
llext.simple.modules_enabled_readonly:
filter: not CONFIG_MPU and not CONFIG_MMU
arch_exclude: xtensa # for now
platform_key:
- simulation
Expand All @@ -38,3 +47,13 @@ tests:
- arch:arm:CONFIG_ARM_MPU=n
- CONFIG_MODULES=y
- CONFIG_LLEXT_TEST_HELLO=m
llext.simple.modules_enabled_readonly_mpu:
filter: CONFIG_CPU_HAS_MPU
arch_exclude: xtensa # for now
platform_key:
- simulation
- arch
extra_configs:
- CONFIG_USERSPACE=y
- CONFIG_MODULES=y
- CONFIG_LLEXT_TEST_HELLO=m

0 comments on commit 84e883b

Please sign in to comment.