From 84e883b611276e0717b54ce4673502938504f05b Mon Sep 17 00:00:00 2001 From: Tom Burdick Date: Fri, 19 Jan 2024 11:03:46 -0600 Subject: [PATCH] llext: Support memory protection 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 --- include/zephyr/llext/llext.h | 23 +++++ subsys/llext/llext.c | 99 ++++++++++++++++++- tests/subsys/llext/hello_world/prj.conf | 1 - .../hello_world/src/test/test_llext_simple.c | 37 ++++++- tests/subsys/llext/hello_world/testcase.yaml | 21 +++- 5 files changed, 174 insertions(+), 7 deletions(-) diff --git a/include/zephyr/llext/llext.h b/include/zephyr/llext/llext.h index 832aba25cac..0a006dc53b7 100644 --- a/include/zephyr/llext/llext.h +++ b/include/zephyr/llext/llext.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,8 @@ enum llext_mem { LLEXT_MEM_COUNT, }; +#define LLEXT_MEM_PARTITIONS (LLEXT_MEM_BSS+1) + struct llext_loader; /** @@ -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 */ @@ -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 * diff --git a/subsys/llext/llext.c b/subsys/llext/llext.c index cbafa134eaa..e36dbd86633 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -17,6 +17,13 @@ LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL); #include +#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'}; @@ -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) { @@ -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); @@ -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) { @@ -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 +} diff --git a/tests/subsys/llext/hello_world/prj.conf b/tests/subsys/llext/hello_world/prj.conf index ed104f56b08..ad4d6c5a0b7 100644 --- a/tests/subsys/llext/hello_world/prj.conf +++ b/tests/subsys/llext/hello_world/prj.conf @@ -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 diff --git a/tests/subsys/llext/hello_world/src/test/test_llext_simple.c b/tests/subsys/llext/hello_world/src/test/test_llext_simple.c index b78f2809009..ee68e72d73a 100644 --- a/tests/subsys/llext/hello_world/src/test/test_llext_simple.c +++ b/tests/subsys/llext/hello_world/src/test/test_llext_simple.c @@ -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 @@ -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; - zassert_ok(res, "calling hello world should succeed"); + 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); + +#else /* CONFIG_USERSPACE */ + zassert_ok(llext_call_fn(ext, "hello_world"), + "hello_world call should succeed"); +#endif /* CONFIG_USERSPACE */ llext_unload(&ext); } diff --git a/tests/subsys/llext/hello_world/testcase.yaml b/tests/subsys/llext/hello_world/testcase.yaml index 35370a65ece..c977dc5f94f 100644 --- a/tests/subsys/llext/hello_world/testcase.yaml +++ b/tests/subsys/llext/hello_world/testcase.yaml @@ -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 @@ -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 @@ -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