diff --git a/common_modules/module_manager/inc/txm_module_manager_util.h b/common_modules/module_manager/inc/txm_module_manager_util.h index 92bb55dd..7feb0f3e 100644 --- a/common_modules/module_manager/inc/txm_module_manager_util.h +++ b/common_modules/module_manager/inc/txm_module_manager_util.h @@ -45,8 +45,32 @@ /* Define check macros for modules. */ +/* A port supplies TXM_MODULE_MANAGER_CHECK_INSIDE_DATA to decide whether a caller-supplied + range lies inside the module's writable data or shared memory. Ports whose check can tell + a read-only region from a writable one also supply the two intent-specific variants below. + Ports that cannot make the distinction inherit their single check for both intents, so + their behaviour is unchanged. */ + +#ifndef TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ(module_instance, obj_ptr, obj_size) \ + TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size) +#endif + +#ifndef TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) \ + TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size) +#endif + +/* A range is outside the module's data only when no part of it is reachable by the module. + The read intent is used here because it describes the widest set of reachable addresses. + A port whose inside check cannot prove the whole range at once must supply its own + definition, because negating a partial-range check would report a partly reachable range + as being outside the module. */ + +#ifndef TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA #define TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA(module_instance, obj_ptr, obj_size) \ - (!(TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size))) + (!(TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ(module_instance, obj_ptr, obj_size))) +#endif #define TXM_MODULE_MANAGER_CHECK_INSIDE_CODE(module_instance, obj_ptr, obj_size) \ (((obj_ptr) < ((obj_ptr) + (obj_size))) && \ @@ -65,12 +89,14 @@ /* Define macros for module. */ +/* The module reads from these ranges, so a read-only region is acceptable. */ #define TXM_MODULE_MANAGER_ENSURE_INSIDE_MODULE(module_instance, obj_ptr, obj_size) \ - (TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size) || \ + (TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ(module_instance, obj_ptr, obj_size) || \ TXM_MODULE_MANAGER_CHECK_INSIDE_CODE(module_instance, obj_ptr, obj_size)) +/* The kernel writes to these ranges, so the module must be able to write them too. */ #define TXM_MODULE_MANAGER_ENSURE_INSIDE_MODULE_DATA(module_instance, obj_ptr, obj_size) \ - TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size) + TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) #define TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, obj_ptr, obj_size) \ (TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA(module_instance, obj_ptr, obj_size) && \ diff --git a/ports_module/cortex_a7/ac5/example_build/build_threadx.bat b/ports_module/cortex_a7/ac5/example_build/build_threadx.bat index ea349e37..764992f0 100644 --- a/ports_module/cortex_a7/ac5/example_build/build_threadx.bat +++ b/ports_module/cortex_a7/ac5/example_build/build_threadx.bat @@ -223,6 +223,7 @@ armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../com armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_properties_get.c armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_queue_notify_trampoline.c armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_semaphore_notify_trampoline.c +armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../module_manager/src/txm_module_manager_inside_data_check.c armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../module_manager/src/txm_module_manager_mm_register_setup.c armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_start.c armcc -g -O0 --cpu=cortex-a7.no_neon --fpu=softvfp -c -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_stop.c @@ -278,7 +279,7 @@ armar -r tx.a txm_module_manager_in_place_load.o txm_module_manager_initialize.o armar -r tx.a txm_module_manager_kernel_dispatch.o txm_module_manager_maximum_module_priority_set.o txm_module_manager_memory_fault_handler.o armar -r tx.a txm_module_manager_memory_fault_notify.o txm_module_manager_memory_load.o txm_module_manager_object_pointer_get.o armar -r tx.a txm_module_manager_object_pool_create.o txm_module_manager_queue_notify_trampoline.o txm_module_manager_semaphore_notify_trampoline.o -armar -r tx.a txm_module_manager_mm_register_setup.o txm_module_manager_start.o txm_module_manager_stop.o +armar -r tx.a txm_module_manager_inside_data_check.o txm_module_manager_mm_register_setup.o txm_module_manager_start.o txm_module_manager_stop.o armar -r tx.a txm_module_manager_thread_create.o txm_module_manager_thread_notify_trampoline.o txm_module_manager_thread_reset.o armar -r tx.a txm_module_manager_timer_notify_trampoline.o txm_module_manager_unload.o txm_module_manager_thread_stack_build.o armar -r tx.a txm_module_manager_user_mode_entry.o diff --git a/ports_module/cortex_a7/ac5/inc/txm_module_port.h b/ports_module/cortex_a7/ac5/inc/txm_module_port.h index 1102a377..126be1e8 100644 --- a/ports_module/cortex_a7/ac5/inc/txm_module_port.h +++ b/ports_module/cortex_a7/ac5/inc/txm_module_port.h @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -240,6 +242,22 @@ The following extensions must also be defined in tx_port.h: #define TXM_ADDRESS_TRANSLATION_FAULT_BIT 1 +/* Mask selecting the ASID field of CONTEXTIDR in the short-descriptor format. */ +#define TXM_CONTEXTIDR_ASID_MASK 0x000000FF + +/* Access intents understood by the module data range check. */ +#define TXM_MODULE_MANAGER_ACCESS_READ 0 +#define TXM_MODULE_MANAGER_ACCESS_WRITE 1 + +/* Define the largest number of MMU pages a single caller-supplied range may span before + the data range check rejects it. This bounds the worst-case, module-controlled cost of + validating one kernel request, which keeps the check deterministic. The default permits + a 4 MB range. Applications that hand larger shared-memory ranges to kernel services can + define a larger value. */ +#ifndef TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES +#define TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES 1024 +#endif + #define TXM_ASID_RESERVED 0xFFFFFFFF #define TXM_MODULE_ASID_ERROR 0xF6 @@ -375,9 +393,26 @@ typedef struct TXM_MODULE_MANAGER_MEMORY_FAULT_INFO_STRUCT /* Define the macros to perform port-specific checks when passing pointers to the kernel. */ -/* Define macro to make sure object is inside the module's data or shared memory. */ +/* Define macros to make sure a whole object is inside the module's data or shared memory. + The MMU is the authority for shared and external memory, so the check translates every + page the range touches with the requested unprivileged access. Passing the size and the + access intent through is what keeps the check honest: translating only the first address, + and only for reading, would accept a range that leaves the module's mapping partway + through, or a read-only range used as a kernel write destination. */ +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size), TXM_MODULE_MANAGER_ACCESS_READ) + +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size), TXM_MODULE_MANAGER_ACCESS_WRITE) + +/* The unqualified check keeps its historical name and takes the stricter write intent. */ #define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size) \ - _txm_module_manager_inside_data_check((ULONG) obj_ptr) + TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) + +/* Negating the inside check would report a range that only partly reaches into the module + as being outside it, so the outside direction gets its own check. */ +#define TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_outside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size)) /* Define some internal prototypes to this module port. */ @@ -397,7 +432,10 @@ UINT _txm_module_manager_mm_initialize(VOID); VOID _txm_module_manager_mm_register_setup(TXM_MODULE_INSTANCE *module_instance); \ VOID _txm_level2_page_clear(TXM_MODULE_INSTANCE *module_instance); \ VOID _txm_module_manager_remove_asid(TXM_MODULE_INSTANCE *module_instance); \ -UINT _txm_module_manager_inside_data_check(ULONG pointer); +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size, UINT write_request); \ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size); \ +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request); \ +ULONG _txm_module_manager_current_asid_get(VOID); #define TXM_MODULE_MANAGER_VERSION_ID \ CHAR _txm_module_manager_version_id[] = \ diff --git a/ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_inside_data_check.c b/ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_inside_data_check.c new file mode 100644 index 00000000..e7ebf262 --- /dev/null +++ b/ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_inside_data_check.c @@ -0,0 +1,538 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Module Manager */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +#define TX_SOURCE_CODE + +#include "tx_api.h" +#include "txm_module.h" + + +/* Define the ways a caller-supplied range can sit relative to the module's data region. */ + +#define TXM_MODULE_DATA_WINDOW_OUTSIDE 0 /* No byte touches the data mapping. */ +#define TXM_MODULE_DATA_WINDOW_CONTAINED 1 /* Every byte is recorded module data. */ +#define TXM_MODULE_DATA_WINDOW_PARTIAL 2 /* Some, but not all, bytes are. */ + + +/* Define the page mask derived from the smallest MMU page this port maps. */ + +#define TXM_MODULE_PAGE_OFFSET_MASK ((ALIGN_TYPE) (TXM_MODULE_MEMORY_ALIGNMENT - 1)) +#define TXM_MODULE_PAGE_BASE_MASK (~TXM_MODULE_PAGE_OFFSET_MASK) + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_range_end_get Cortex-A7/MMU/AC5 */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function derives the inclusive address of the last byte of a */ +/* caller-supplied range. An empty range is rejected, matching the */ +/* portable check macros, and so is a range whose last byte is not */ +/* representable, so that no caller ever works from a wrapped address. */ +/* */ +/* INPUT */ +/* */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* last_byte Destination for the last address */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if the range is non-empty and does not wrap */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_range_end_get(ALIGN_TYPE obj_ptr, ULONG obj_size, ALIGN_TYPE *last_byte) +{ + +ALIGN_TYPE span; +ALIGN_TYPE highest; +UINT status; + + + /* Assume the range cannot be used until proven otherwise. */ + status = TX_FALSE; + + /* An empty range has no last byte and is rejected, just as the portable macros do. */ + if (obj_size != ((ULONG) 0)) + { + + /* Work with the inclusive span so that a range reaching the top of the address + space stays representable. */ + span = ((ALIGN_TYPE) obj_size) - ((ALIGN_TYPE) 1); + + /* Determine the highest first byte this span can start from. */ + highest = (~((ALIGN_TYPE) 0)) - span; + + /* Determine if the range would run past the end of the address space. */ + if (obj_ptr <= highest) + { + + /* Record the last byte of the range. */ + *last_byte = obj_ptr + span; + + /* The range is usable. */ + status = TX_TRUE; + } + } + + /* Return the completion status. */ + return(status); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_data_window_classify Cortex-A7/MMU/AC5 */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function reports how a range sits relative to the module's own */ +/* data region. The module manager records that region exactly, while */ +/* the MMU can only map it a page at a time, so the mapping may reach */ +/* past the recorded end. The recorded region therefore stays the */ +/* authority for the whole mapped window: a range that leaves the */ +/* recorded region but still lands in the window is reported as */ +/* partial, and its caller rejects it rather than asking the MMU, */ +/* which would accept the rounding slack. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* first_byte Address of the first byte */ +/* last_byte Address of the last byte */ +/* */ +/* OUTPUT */ +/* */ +/* One of the TXM_MODULE_DATA_WINDOW_* classifications */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_data_window_classify(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE first_byte, ALIGN_TYPE last_byte) +{ + +ALIGN_TYPE data_start; +ALIGN_TYPE data_end; +ALIGN_TYPE window_start; +ALIGN_TYPE window_end; +UINT window; + + + /* Pick up the recorded data region. The end address is inclusive. */ + data_start = (ALIGN_TYPE) module_instance -> txm_module_instance_data_start; + data_end = (ALIGN_TYPE) module_instance -> txm_module_instance_data_end; + + /* Determine if the module has a usable data region at all. */ + if (data_start > data_end) + { + + /* An unset or inverted region owns nothing. */ + window = TXM_MODULE_DATA_WINDOW_OUTSIDE; + } + else + { + + /* Widen the region to the pages the MMU actually maps for it. */ + window_start = data_start & TXM_MODULE_PAGE_BASE_MASK; + window_end = data_end | TXM_MODULE_PAGE_OFFSET_MASK; + + /* Determine if every byte is recorded module data. */ + if ((first_byte >= data_start) && (last_byte <= data_end)) + { + + /* The whole range belongs to the module. */ + window = TXM_MODULE_DATA_WINDOW_CONTAINED; + } + else if ((first_byte <= window_end) && (last_byte >= window_start)) + { + + /* The range overlaps the mapped window without being contained by the + recorded region, so it leaves the module's data partway through. */ + window = TXM_MODULE_DATA_WINDOW_PARTIAL; + } + else + { + + /* The range lies elsewhere, so only the MMU can speak for it. */ + window = TXM_MODULE_DATA_WINDOW_OUTSIDE; + } + } + + /* Return the classification. */ + return(window); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_page_walk Cortex-A7/MMU/AC5 */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function translates every page a range touches and reports */ +/* whether all of them answered as expected. The walk stops at the */ +/* first page that disagrees, and a range spanning more pages than */ +/* the configured maximum is refused so that one kernel request can */ +/* never impose unbounded work. */ +/* */ +/* INPUT */ +/* */ +/* first_byte Address of the first byte */ +/* last_byte Address of the last byte */ +/* write_request Access intent to translate for */ +/* expected Result every page must return */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if every page answered as expected */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_address_probe */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_page_walk(ALIGN_TYPE first_byte, ALIGN_TYPE last_byte, UINT write_request, UINT expected) +{ + +ALIGN_TYPE first_page; +ALIGN_TYPE last_page; +ALIGN_TYPE page; +ULONG pages; +ULONG index; +UINT matched; + + + /* Reduce the range to the pages it touches. */ + first_page = first_byte & TXM_MODULE_PAGE_BASE_MASK; + last_page = last_byte & TXM_MODULE_PAGE_BASE_MASK; + + /* Count them. Both addresses are page-aligned, so the division is exact. */ + pages = ((ULONG) ((last_page - first_page) / ((ALIGN_TYPE) TXM_MODULE_MEMORY_ALIGNMENT))) + ((ULONG) 1); + + /* Determine if the range is longer than this port is willing to translate. */ + if (pages > ((ULONG) TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES)) + { + + /* Refuse rather than spend unbounded time in a kernel request. Refusing is safe + for both callers: neither can then claim the range is inside or outside. */ + matched = TX_FALSE; + } + else + { + + /* Assume every page agrees until one does not. */ + matched = TX_TRUE; + index = (ULONG) 0; + + /* Translate each page in turn, stopping as soon as the answer is settled. The + address is rebuilt from the index so that the walk cannot wrap past the top + page of the address space. */ + while ((index < pages) && (matched == TX_TRUE)) + { + + /* Build the address of this page. */ + page = first_page + (((ALIGN_TYPE) index) * ((ALIGN_TYPE) TXM_MODULE_MEMORY_ALIGNMENT)); + + /* Ask the MMU whether the module may reach this page with the requested access. */ + if (_txm_module_manager_address_probe((ULONG) page, write_request) != expected) + { + + /* This page disagrees, so the range as a whole does. */ + matched = TX_FALSE; + } + + /* Move to the next page. */ + index++; + } + } + + /* Return whether the whole range answered as expected. */ + return(matched); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_inside_data_check Cortex-A7/MMU/AC5 */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function determines whether every byte of a caller-supplied */ +/* range lies inside the module's data or shared memory and is */ +/* reachable by the module with the requested access. The module's own */ +/* data region is answered from the manager's own records, which are */ +/* exact. Anything else is answered by translating every page the */ +/* range touches, because this port keeps no record of the shared and */ +/* external regions the application maps into the module. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* write_request TXM_MODULE_MANAGER_ACCESS_READ or */ +/* TXM_MODULE_MANAGER_ACCESS_WRITE */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if the whole range is reachable with that access */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_range_end_get */ +/* _txm_module_manager_current_asid_get */ +/* _txm_module_manager_data_window_classify */ +/* _txm_module_manager_page_walk */ +/* */ +/* CALLED BY */ +/* */ +/* TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ */ +/* TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size, UINT write_request) +{ + +ALIGN_TYPE last_byte; +UINT window; +UINT status; + + + /* Assume the range is not usable until every condition is met. */ + status = TX_FALSE; + + /* A range can only belong to a module that exists. */ + if (module_instance != TX_NULL) + { + + /* Determine the last byte, rejecting empty and wrapping ranges. */ + if (_txm_module_manager_range_end_get(obj_ptr, obj_size, &last_byte) == TX_TRUE) + { + + /* Address translation answers for whichever context is loaded, so it can only + be trusted while the requesting module's context is the loaded one. */ + if (_txm_module_manager_current_asid_get() == (module_instance -> txm_module_instance_asid)) + { + + /* Place the range relative to the module's own data region. */ + window = _txm_module_manager_data_window_classify(module_instance, obj_ptr, last_byte); + + if (window == ((UINT) TXM_MODULE_DATA_WINDOW_CONTAINED)) + { + + /* The module's data region is mapped for unprivileged reading and + writing, so both intents are satisfied without translating. */ + status = TX_TRUE; + } + else if (window == ((UINT) TXM_MODULE_DATA_WINDOW_OUTSIDE)) + { + + /* Shared or external memory: every page must grant the access. */ + status = _txm_module_manager_page_walk(obj_ptr, last_byte, write_request, TX_TRUE); + } + else + { + + /* The range leaves the module's data partway through. */ + status = TX_FALSE; + } + } + } + } + + /* Return the completion status. */ + return(status); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_outside_data_check Cortex-A7/MMU/AC5 */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function determines whether no byte of a caller-supplied range */ +/* is reachable by the module. It is deliberately not the negation of */ +/* the inside check: a range that reaches into the module's memory */ +/* only partway is inside neither answer, and reporting it as outside */ +/* would let it pass as a kernel object. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if no byte of the range is reachable by the module */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_range_end_get */ +/* _txm_module_manager_current_asid_get */ +/* _txm_module_manager_data_window_classify */ +/* _txm_module_manager_page_walk */ +/* */ +/* CALLED BY */ +/* */ +/* TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size) +{ + +ALIGN_TYPE last_byte; +UINT window; +UINT status; + + + /* Assume the range is not provably outside the module. */ + status = TX_FALSE; + + /* A range can only be placed against a module that exists. */ + if (module_instance != TX_NULL) + { + + /* Determine the last byte, rejecting empty and wrapping ranges. */ + if (_txm_module_manager_range_end_get(obj_ptr, obj_size, &last_byte) == TX_TRUE) + { + + /* Address translation answers for whichever context is loaded, so it can only + be trusted while the requesting module's context is the loaded one. */ + if (_txm_module_manager_current_asid_get() == (module_instance -> txm_module_instance_asid)) + { + + /* Place the range relative to the module's own data region. */ + window = _txm_module_manager_data_window_classify(module_instance, obj_ptr, last_byte); + + if (window == ((UINT) TXM_MODULE_DATA_WINDOW_OUTSIDE)) + { + + /* Clear of the module's data region, so the remaining question is + whether any page of it is shared or external memory. */ + status = _txm_module_manager_page_walk(obj_ptr, last_byte, TXM_MODULE_MANAGER_ACCESS_READ, TX_FALSE); + } + else + { + + /* The range touches the module's data region. */ + status = TX_FALSE; + } + } + } + } + + /* Return the completion status. */ + return(status); +} diff --git a/ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_mm_register_setup.c b/ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_mm_register_setup.c index bdaa548d..a86cfbe4 100644 --- a/ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_mm_register_setup.c +++ b/ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_mm_register_setup.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -35,24 +37,34 @@ extern ULONG _txm_ttbr1_page_table[TXM_MAXIMUM_MODULES][TXM_MASTER_PAGE_TABLE_EN /* */ /* FUNCTION RELEASE */ /* */ -/* _txm_module_manager_inside_data_check Cortex-A7/MMU/AC5 */ -/* 6.1 */ +/* _txm_module_manager_address_probe Cortex-A7/MMU/AC5 */ +/* 6.5.0 */ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Eclipse ThreadX contributors */ /* */ /* DESCRIPTION */ /* */ -/* This function determines if pointer is within the module's data or */ -/* shared memory. */ +/* This function asks the MMU whether unprivileged code running in the */ +/* currently loaded translation context may reach one address with the */ +/* requested access. Reading and writing are asked separately, because */ +/* a module's code and its read-only shared memory translate for */ +/* reading while a kernel service writing to them would fault or, */ +/* worse, succeed from privileged mode. */ +/* */ +/* This answers for one address only. Callers that hold a range must */ +/* ask about every page the range touches. */ /* */ /* INPUT */ /* */ -/* pointer Data pointer */ +/* address Address to translate */ +/* write_request TXM_MODULE_MANAGER_ACCESS_READ or */ +/* TXM_MODULE_MANAGER_ACCESS_WRITE */ /* */ /* OUTPUT */ /* */ -/* Completion Status */ +/* TX_TRUE if the translation granted the requested access */ /* */ /* CALLS */ /* */ @@ -60,17 +72,40 @@ extern ULONG _txm_ttbr1_page_table[TXM_MAXIMUM_MODULES][TXM_MASTER_PAGE_TABLE_EN /* */ /* CALLED BY */ /* */ -/* TXM_MODULE_MANAGER_DATA_POINTER_CHECK */ +/* _txm_module_manager_page_walk */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 03-08-2023 Scott Larson Initial Version 6.2.1 */ +/* 08-24-2026 Eclipse ThreadX Added the write intent and */ +/* contributors moved range handling to */ +/* the caller, resulting in */ +/* version 6.5.0 */ /* */ /**************************************************************************/ -UINT _txm_module_manager_inside_data_check(ULONG pointer) +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request) { -ULONG translation; +ULONG translation; - /* ATS1CUR operation on address supplied in pointer, Stage 1 unprivileged read. */ - __asm("MCR p15, 0, pointer, c7, c8, 2"); - __asm("ISB"); /* Ensure completion of the MCR write to CP15. */ + + /* Determine which unprivileged access the caller needs. */ + if (write_request == ((UINT) TXM_MODULE_MANAGER_ACCESS_WRITE)) + { + + /* ATS1CUW operation on address supplied in address, Stage 1 unprivileged write. */ + __asm("MCR p15, 0, address, c7, c8, 3"); + } + else + { + + /* ATS1CUR operation on address supplied in address, Stage 1 unprivileged read. */ + __asm("MCR p15, 0, address, c7, c8, 2"); + } + + __asm("ISB"); /* Ensure completion of the MCR write to CP15. */ __asm("MRC p15, 0, translation, c7, c4, 0"); /* Read result from 32-bit PAR into translation. */ if (translation & TXM_ADDRESS_TRANSLATION_FAULT_BIT) @@ -82,6 +117,62 @@ ULONG translation; } +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_current_asid_get Cortex-A7/MMU/AC5 */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function returns the ASID of the translation context that is */ +/* currently loaded. Address translation always answers for that */ +/* context, so a caller validating a pointer on behalf of a module */ +/* must confirm that the loaded context is that module's before it */ +/* believes the answer. */ +/* */ +/* INPUT */ +/* */ +/* None */ +/* */ +/* OUTPUT */ +/* */ +/* ASID of the currently loaded translation context */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +ULONG _txm_module_manager_current_asid_get(VOID) +{ + +ULONG contextidr; + + + /* Read CONTEXTIDR, whose low byte holds the ASID in the short-descriptor format. */ + __asm("MRC p15, 0, contextidr, c13, c0, 1"); + + return(contextidr & TXM_CONTEXTIDR_ASID_MASK); +} + + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ diff --git a/ports_module/cortex_a7/gnu/example_build/build_threadx.bat b/ports_module/cortex_a7/gnu/example_build/build_threadx.bat index 0e094b38..3e687587 100644 --- a/ports_module/cortex_a7/gnu/example_build/build_threadx.bat +++ b/ports_module/cortex_a7/gnu/example_build/build_threadx.bat @@ -223,6 +223,7 @@ arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -m arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -marm -mthumb-interwork -DTX_ENABLE_VFP_SUPPORT -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_properties_get.c arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -marm -mthumb-interwork -DTX_ENABLE_VFP_SUPPORT -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_queue_notify_trampoline.c arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -marm -mthumb-interwork -DTX_ENABLE_VFP_SUPPORT -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_semaphore_notify_trampoline.c +arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -marm -mthumb-interwork -DTX_ENABLE_VFP_SUPPORT -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../module_manager/src/txm_module_manager_inside_data_check.c arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -marm -mthumb-interwork -DTX_ENABLE_VFP_SUPPORT -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../module_manager/src/txm_module_manager_mm_register_setup.c arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -marm -mthumb-interwork -DTX_ENABLE_VFP_SUPPORT -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_start.c arm-none-eabi-gcc -c -g -O0 -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4 -marm -mthumb-interwork -DTX_ENABLE_VFP_SUPPORT -I../inc -I../../../../common/inc -I../../../../common_modules/inc -I../../../../common_modules/module_manager/inc ../../../../common_modules/module_manager/src/txm_module_manager_stop.c @@ -278,7 +279,7 @@ arm-none-eabi-ar -r tx.a txm_module_manager_in_place_load.o txm_module_manager_i arm-none-eabi-ar -r tx.a txm_module_manager_kernel_dispatch.o txm_module_manager_maximum_module_priority_set.o txm_module_manager_memory_fault_handler.o arm-none-eabi-ar -r tx.a txm_module_manager_memory_fault_notify.o txm_module_manager_memory_load.o txm_module_manager_object_pointer_get.o arm-none-eabi-ar -r tx.a txm_module_manager_object_pool_create.o txm_module_manager_queue_notify_trampoline.o txm_module_manager_semaphore_notify_trampoline.o -arm-none-eabi-ar -r tx.a txm_module_manager_mm_register_setup.o txm_module_manager_start.o txm_module_manager_stop.o +arm-none-eabi-ar -r tx.a txm_module_manager_inside_data_check.o txm_module_manager_mm_register_setup.o txm_module_manager_start.o txm_module_manager_stop.o arm-none-eabi-ar -r tx.a txm_module_manager_thread_create.o txm_module_manager_thread_notify_trampoline.o txm_module_manager_thread_reset.o arm-none-eabi-ar -r tx.a txm_module_manager_timer_notify_trampoline.o txm_module_manager_unload.o txm_module_manager_thread_stack_build.o arm-none-eabi-ar -r tx.a txm_module_manager_user_mode_entry.o diff --git a/ports_module/cortex_a7/gnu/inc/txm_module_port.h b/ports_module/cortex_a7/gnu/inc/txm_module_port.h index 9cd5a280..11947992 100644 --- a/ports_module/cortex_a7/gnu/inc/txm_module_port.h +++ b/ports_module/cortex_a7/gnu/inc/txm_module_port.h @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -240,6 +242,22 @@ The following extensions must also be defined in tx_port.h: #define TXM_ADDRESS_TRANSLATION_FAULT_BIT 1 +/* Mask selecting the ASID field of CONTEXTIDR in the short-descriptor format. */ +#define TXM_CONTEXTIDR_ASID_MASK 0x000000FF + +/* Access intents understood by the module data range check. */ +#define TXM_MODULE_MANAGER_ACCESS_READ 0 +#define TXM_MODULE_MANAGER_ACCESS_WRITE 1 + +/* Define the largest number of MMU pages a single caller-supplied range may span before + the data range check rejects it. This bounds the worst-case, module-controlled cost of + validating one kernel request, which keeps the check deterministic. The default permits + a 4 MB range. Applications that hand larger shared-memory ranges to kernel services can + define a larger value. */ +#ifndef TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES +#define TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES 1024 +#endif + #define TXM_ASID_RESERVED 0xFFFFFFFF #define TXM_MODULE_ASID_ERROR 0xF6 @@ -375,9 +393,26 @@ typedef struct TXM_MODULE_MANAGER_MEMORY_FAULT_INFO_STRUCT /* Define the macros to perform port-specific checks when passing pointers to the kernel. */ -/* Define macro to make sure object is inside the module's data or shared memory. */ +/* Define macros to make sure a whole object is inside the module's data or shared memory. + The MMU is the authority for shared and external memory, so the check translates every + page the range touches with the requested unprivileged access. Passing the size and the + access intent through is what keeps the check honest: translating only the first address, + and only for reading, would accept a range that leaves the module's mapping partway + through, or a read-only range used as a kernel write destination. */ +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size), TXM_MODULE_MANAGER_ACCESS_READ) + +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size), TXM_MODULE_MANAGER_ACCESS_WRITE) + +/* The unqualified check keeps its historical name and takes the stricter write intent. */ #define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size) \ - _txm_module_manager_inside_data_check((ULONG) obj_ptr) + TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) + +/* Negating the inside check would report a range that only partly reaches into the module + as being outside it, so the outside direction gets its own check. */ +#define TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_outside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size)) /* Define some internal prototypes to this module port. */ @@ -397,7 +432,10 @@ UINT _txm_module_manager_mm_initialize(VOID); VOID _txm_module_manager_mm_register_setup(TXM_MODULE_INSTANCE *module_instance); \ VOID _txm_level2_page_clear(TXM_MODULE_INSTANCE *module_instance); \ VOID _txm_module_manager_remove_asid(TXM_MODULE_INSTANCE *module_instance); \ -UINT _txm_module_manager_inside_data_check(ULONG pointer); +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size, UINT write_request); \ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size); \ +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request); \ +ULONG _txm_module_manager_current_asid_get(VOID); #define TXM_MODULE_MANAGER_VERSION_ID \ CHAR _txm_module_manager_version_id[] = \ diff --git a/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_inside_data_check.c b/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_inside_data_check.c new file mode 100644 index 00000000..942fd9f6 --- /dev/null +++ b/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_inside_data_check.c @@ -0,0 +1,538 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Module Manager */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +#define TX_SOURCE_CODE + +#include "tx_api.h" +#include "txm_module.h" + + +/* Define the ways a caller-supplied range can sit relative to the module's data region. */ + +#define TXM_MODULE_DATA_WINDOW_OUTSIDE 0 /* No byte touches the data mapping. */ +#define TXM_MODULE_DATA_WINDOW_CONTAINED 1 /* Every byte is recorded module data. */ +#define TXM_MODULE_DATA_WINDOW_PARTIAL 2 /* Some, but not all, bytes are. */ + + +/* Define the page mask derived from the smallest MMU page this port maps. */ + +#define TXM_MODULE_PAGE_OFFSET_MASK ((ALIGN_TYPE) (TXM_MODULE_MEMORY_ALIGNMENT - 1)) +#define TXM_MODULE_PAGE_BASE_MASK (~TXM_MODULE_PAGE_OFFSET_MASK) + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_range_end_get Cortex-A7/MMU/GNU */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function derives the inclusive address of the last byte of a */ +/* caller-supplied range. An empty range is rejected, matching the */ +/* portable check macros, and so is a range whose last byte is not */ +/* representable, so that no caller ever works from a wrapped address. */ +/* */ +/* INPUT */ +/* */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* last_byte Destination for the last address */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if the range is non-empty and does not wrap */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_range_end_get(ALIGN_TYPE obj_ptr, ULONG obj_size, ALIGN_TYPE *last_byte) +{ + +ALIGN_TYPE span; +ALIGN_TYPE highest; +UINT status; + + + /* Assume the range cannot be used until proven otherwise. */ + status = TX_FALSE; + + /* An empty range has no last byte and is rejected, just as the portable macros do. */ + if (obj_size != ((ULONG) 0)) + { + + /* Work with the inclusive span so that a range reaching the top of the address + space stays representable. */ + span = ((ALIGN_TYPE) obj_size) - ((ALIGN_TYPE) 1); + + /* Determine the highest first byte this span can start from. */ + highest = (~((ALIGN_TYPE) 0)) - span; + + /* Determine if the range would run past the end of the address space. */ + if (obj_ptr <= highest) + { + + /* Record the last byte of the range. */ + *last_byte = obj_ptr + span; + + /* The range is usable. */ + status = TX_TRUE; + } + } + + /* Return the completion status. */ + return(status); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_data_window_classify Cortex-A7/MMU/GNU */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function reports how a range sits relative to the module's own */ +/* data region. The module manager records that region exactly, while */ +/* the MMU can only map it a page at a time, so the mapping may reach */ +/* past the recorded end. The recorded region therefore stays the */ +/* authority for the whole mapped window: a range that leaves the */ +/* recorded region but still lands in the window is reported as */ +/* partial, and its caller rejects it rather than asking the MMU, */ +/* which would accept the rounding slack. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* first_byte Address of the first byte */ +/* last_byte Address of the last byte */ +/* */ +/* OUTPUT */ +/* */ +/* One of the TXM_MODULE_DATA_WINDOW_* classifications */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_data_window_classify(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE first_byte, ALIGN_TYPE last_byte) +{ + +ALIGN_TYPE data_start; +ALIGN_TYPE data_end; +ALIGN_TYPE window_start; +ALIGN_TYPE window_end; +UINT window; + + + /* Pick up the recorded data region. The end address is inclusive. */ + data_start = (ALIGN_TYPE) module_instance -> txm_module_instance_data_start; + data_end = (ALIGN_TYPE) module_instance -> txm_module_instance_data_end; + + /* Determine if the module has a usable data region at all. */ + if (data_start > data_end) + { + + /* An unset or inverted region owns nothing. */ + window = TXM_MODULE_DATA_WINDOW_OUTSIDE; + } + else + { + + /* Widen the region to the pages the MMU actually maps for it. */ + window_start = data_start & TXM_MODULE_PAGE_BASE_MASK; + window_end = data_end | TXM_MODULE_PAGE_OFFSET_MASK; + + /* Determine if every byte is recorded module data. */ + if ((first_byte >= data_start) && (last_byte <= data_end)) + { + + /* The whole range belongs to the module. */ + window = TXM_MODULE_DATA_WINDOW_CONTAINED; + } + else if ((first_byte <= window_end) && (last_byte >= window_start)) + { + + /* The range overlaps the mapped window without being contained by the + recorded region, so it leaves the module's data partway through. */ + window = TXM_MODULE_DATA_WINDOW_PARTIAL; + } + else + { + + /* The range lies elsewhere, so only the MMU can speak for it. */ + window = TXM_MODULE_DATA_WINDOW_OUTSIDE; + } + } + + /* Return the classification. */ + return(window); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_page_walk Cortex-A7/MMU/GNU */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function translates every page a range touches and reports */ +/* whether all of them answered as expected. The walk stops at the */ +/* first page that disagrees, and a range spanning more pages than */ +/* the configured maximum is refused so that one kernel request can */ +/* never impose unbounded work. */ +/* */ +/* INPUT */ +/* */ +/* first_byte Address of the first byte */ +/* last_byte Address of the last byte */ +/* write_request Access intent to translate for */ +/* expected Result every page must return */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if every page answered as expected */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_address_probe */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_page_walk(ALIGN_TYPE first_byte, ALIGN_TYPE last_byte, UINT write_request, UINT expected) +{ + +ALIGN_TYPE first_page; +ALIGN_TYPE last_page; +ALIGN_TYPE page; +ULONG pages; +ULONG index; +UINT matched; + + + /* Reduce the range to the pages it touches. */ + first_page = first_byte & TXM_MODULE_PAGE_BASE_MASK; + last_page = last_byte & TXM_MODULE_PAGE_BASE_MASK; + + /* Count them. Both addresses are page-aligned, so the division is exact. */ + pages = ((ULONG) ((last_page - first_page) / ((ALIGN_TYPE) TXM_MODULE_MEMORY_ALIGNMENT))) + ((ULONG) 1); + + /* Determine if the range is longer than this port is willing to translate. */ + if (pages > ((ULONG) TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES)) + { + + /* Refuse rather than spend unbounded time in a kernel request. Refusing is safe + for both callers: neither can then claim the range is inside or outside. */ + matched = TX_FALSE; + } + else + { + + /* Assume every page agrees until one does not. */ + matched = TX_TRUE; + index = (ULONG) 0; + + /* Translate each page in turn, stopping as soon as the answer is settled. The + address is rebuilt from the index so that the walk cannot wrap past the top + page of the address space. */ + while ((index < pages) && (matched == TX_TRUE)) + { + + /* Build the address of this page. */ + page = first_page + (((ALIGN_TYPE) index) * ((ALIGN_TYPE) TXM_MODULE_MEMORY_ALIGNMENT)); + + /* Ask the MMU whether the module may reach this page with the requested access. */ + if (_txm_module_manager_address_probe((ULONG) page, write_request) != expected) + { + + /* This page disagrees, so the range as a whole does. */ + matched = TX_FALSE; + } + + /* Move to the next page. */ + index++; + } + } + + /* Return whether the whole range answered as expected. */ + return(matched); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_inside_data_check Cortex-A7/MMU/GNU */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function determines whether every byte of a caller-supplied */ +/* range lies inside the module's data or shared memory and is */ +/* reachable by the module with the requested access. The module's own */ +/* data region is answered from the manager's own records, which are */ +/* exact. Anything else is answered by translating every page the */ +/* range touches, because this port keeps no record of the shared and */ +/* external regions the application maps into the module. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* write_request TXM_MODULE_MANAGER_ACCESS_READ or */ +/* TXM_MODULE_MANAGER_ACCESS_WRITE */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if the whole range is reachable with that access */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_range_end_get */ +/* _txm_module_manager_current_asid_get */ +/* _txm_module_manager_data_window_classify */ +/* _txm_module_manager_page_walk */ +/* */ +/* CALLED BY */ +/* */ +/* TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ */ +/* TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size, UINT write_request) +{ + +ALIGN_TYPE last_byte; +UINT window; +UINT status; + + + /* Assume the range is not usable until every condition is met. */ + status = TX_FALSE; + + /* A range can only belong to a module that exists. */ + if (module_instance != TX_NULL) + { + + /* Determine the last byte, rejecting empty and wrapping ranges. */ + if (_txm_module_manager_range_end_get(obj_ptr, obj_size, &last_byte) == TX_TRUE) + { + + /* Address translation answers for whichever context is loaded, so it can only + be trusted while the requesting module's context is the loaded one. */ + if (_txm_module_manager_current_asid_get() == (module_instance -> txm_module_instance_asid)) + { + + /* Place the range relative to the module's own data region. */ + window = _txm_module_manager_data_window_classify(module_instance, obj_ptr, last_byte); + + if (window == ((UINT) TXM_MODULE_DATA_WINDOW_CONTAINED)) + { + + /* The module's data region is mapped for unprivileged reading and + writing, so both intents are satisfied without translating. */ + status = TX_TRUE; + } + else if (window == ((UINT) TXM_MODULE_DATA_WINDOW_OUTSIDE)) + { + + /* Shared or external memory: every page must grant the access. */ + status = _txm_module_manager_page_walk(obj_ptr, last_byte, write_request, TX_TRUE); + } + else + { + + /* The range leaves the module's data partway through. */ + status = TX_FALSE; + } + } + } + } + + /* Return the completion status. */ + return(status); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_outside_data_check Cortex-A7/MMU/GNU */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function determines whether no byte of a caller-supplied range */ +/* is reachable by the module. It is deliberately not the negation of */ +/* the inside check: a range that reaches into the module's memory */ +/* only partway is inside neither answer, and reporting it as outside */ +/* would let it pass as a kernel object. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if no byte of the range is reachable by the module */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_range_end_get */ +/* _txm_module_manager_current_asid_get */ +/* _txm_module_manager_data_window_classify */ +/* _txm_module_manager_page_walk */ +/* */ +/* CALLED BY */ +/* */ +/* TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size) +{ + +ALIGN_TYPE last_byte; +UINT window; +UINT status; + + + /* Assume the range is not provably outside the module. */ + status = TX_FALSE; + + /* A range can only be placed against a module that exists. */ + if (module_instance != TX_NULL) + { + + /* Determine the last byte, rejecting empty and wrapping ranges. */ + if (_txm_module_manager_range_end_get(obj_ptr, obj_size, &last_byte) == TX_TRUE) + { + + /* Address translation answers for whichever context is loaded, so it can only + be trusted while the requesting module's context is the loaded one. */ + if (_txm_module_manager_current_asid_get() == (module_instance -> txm_module_instance_asid)) + { + + /* Place the range relative to the module's own data region. */ + window = _txm_module_manager_data_window_classify(module_instance, obj_ptr, last_byte); + + if (window == ((UINT) TXM_MODULE_DATA_WINDOW_OUTSIDE)) + { + + /* Clear of the module's data region, so the remaining question is + whether any page of it is shared or external memory. */ + status = _txm_module_manager_page_walk(obj_ptr, last_byte, TXM_MODULE_MANAGER_ACCESS_READ, TX_FALSE); + } + else + { + + /* The range touches the module's data region. */ + status = TX_FALSE; + } + } + } + } + + /* Return the completion status. */ + return(status); +} diff --git a/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_mm_register_setup.c b/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_mm_register_setup.c index 60202970..b2161fde 100644 --- a/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_mm_register_setup.c +++ b/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_mm_register_setup.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -35,24 +37,34 @@ extern ULONG _txm_ttbr1_page_table[TXM_MAXIMUM_MODULES][TXM_MASTER_PAGE_TABLE_EN /* */ /* FUNCTION RELEASE */ /* */ -/* _txm_module_manager_inside_data_check Cortex-A7/MMU/GNU */ -/* 6.2.1 */ +/* _txm_module_manager_address_probe Cortex-A7/MMU/GNU */ +/* 6.5.0 */ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Eclipse ThreadX contributors */ /* */ /* DESCRIPTION */ /* */ -/* This function determines if pointer is within the module's data or */ -/* shared memory. */ +/* This function asks the MMU whether unprivileged code running in the */ +/* currently loaded translation context may reach one address with the */ +/* requested access. Reading and writing are asked separately, because */ +/* a module's code and its read-only shared memory translate for */ +/* reading while a kernel service writing to them would fault or, */ +/* worse, succeed from privileged mode. */ +/* */ +/* This answers for one address only. Callers that hold a range must */ +/* ask about every page the range touches. */ /* */ /* INPUT */ /* */ -/* pointer Data pointer */ +/* address Address to translate */ +/* write_request TXM_MODULE_MANAGER_ACCESS_READ or */ +/* TXM_MODULE_MANAGER_ACCESS_WRITE */ /* */ /* OUTPUT */ /* */ -/* Completion Status */ +/* TX_TRUE if the translation granted the requested access */ /* */ /* CALLS */ /* */ @@ -60,18 +72,41 @@ extern ULONG _txm_ttbr1_page_table[TXM_MAXIMUM_MODULES][TXM_MASTER_PAGE_TABLE_EN /* */ /* CALLED BY */ /* */ -/* TXM_MODULE_MANAGER_DATA_POINTER_CHECK */ +/* _txm_module_manager_page_walk */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 03-08-2023 Scott Larson Initial Version 6.2.1 */ +/* 08-24-2026 Eclipse ThreadX Added the write intent and */ +/* contributors moved range handling to */ +/* the caller, resulting in */ +/* version 6.5.0 */ /* */ /**************************************************************************/ -UINT _txm_module_manager_inside_data_check(ULONG pointer) +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request) { -ULONG translation; +ULONG translation; + + + /* Determine which unprivileged access the caller needs. */ + if (write_request == ((UINT) TXM_MODULE_MANAGER_ACCESS_WRITE)) + { + + /* ATS1CUW operation on address supplied in address, Stage 1 unprivileged write. */ + __asm volatile ("MCR p15, 0, %0, c7, c8, 3" : : "r"(address) : ); + } + else + { + + /* ATS1CUR operation on address supplied in address, Stage 1 unprivileged read. */ + __asm volatile ("MCR p15, 0, %0, c7, c8, 2" : : "r"(address) : ); + } - /* ATS1CUR operation on address supplied in pointer, Stage 1 unprivileged read. */ - __asm volatile ("MCR p15, 0, %0, c7, c8, 2" : : "r"(pointer) : ); __asm volatile ("ISB"); /* Ensure completion of the MCR write to CP15. */ - __asm volatile ("MRC p15, 0, %0, c7, c4, 0" : "=r"(translation) : : ); /* Read result from 32-bit PAR into translation. */ + __asm volatile ("MRC p15, 0, %0, c7, c4, 0" : "=r"(translation) : : ); /* Read result from 32-bit PAR into translation. */ if (translation & TXM_ADDRESS_TRANSLATION_FAULT_BIT) { @@ -82,6 +117,62 @@ ULONG translation; } +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_current_asid_get Cortex-A7/MMU/GNU */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function returns the ASID of the translation context that is */ +/* currently loaded. Address translation always answers for that */ +/* context, so a caller validating a pointer on behalf of a module */ +/* must confirm that the loaded context is that module's before it */ +/* believes the answer. */ +/* */ +/* INPUT */ +/* */ +/* None */ +/* */ +/* OUTPUT */ +/* */ +/* ASID of the currently loaded translation context */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +ULONG _txm_module_manager_current_asid_get(VOID) +{ + +ULONG contextidr; + + + /* Read CONTEXTIDR, whose low byte holds the ASID in the short-descriptor format. */ + __asm volatile ("MRC p15, 0, %0, c13, c0, 1" : "=r"(contextidr) : : ); + + return(contextidr & TXM_CONTEXTIDR_ASID_MASK); +} + + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ diff --git a/ports_module/cortex_a7/iar/example_build/tx.ewp b/ports_module/cortex_a7/iar/example_build/tx.ewp index 9d486218..97f9ffe7 100644 --- a/ports_module/cortex_a7/iar/example_build/tx.ewp +++ b/ports_module/cortex_a7/iar/example_build/tx.ewp @@ -2832,6 +2832,9 @@ $PROJ_DIR$\..\module_manager\src\txm_module_manager_mm_initialize.c + + $PROJ_DIR$\..\module_manager\src\txm_module_manager_inside_data_check.c + $PROJ_DIR$\..\module_manager\src\txm_module_manager_mm_register_setup.c diff --git a/ports_module/cortex_a7/iar/inc/txm_module_port.h b/ports_module/cortex_a7/iar/inc/txm_module_port.h index 247868ea..dbaf7e17 100644 --- a/ports_module/cortex_a7/iar/inc/txm_module_port.h +++ b/ports_module/cortex_a7/iar/inc/txm_module_port.h @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -251,6 +253,22 @@ The following extensions must also be defined in tx_port.h: #define TXM_ADDRESS_TRANSLATION_FAULT_BIT 1 +/* Mask selecting the ASID field of CONTEXTIDR in the short-descriptor format. */ +#define TXM_CONTEXTIDR_ASID_MASK 0x000000FF + +/* Access intents understood by the module data range check. */ +#define TXM_MODULE_MANAGER_ACCESS_READ 0 +#define TXM_MODULE_MANAGER_ACCESS_WRITE 1 + +/* Define the largest number of MMU pages a single caller-supplied range may span before + the data range check rejects it. This bounds the worst-case, module-controlled cost of + validating one kernel request, which keeps the check deterministic. The default permits + a 4 MB range. Applications that hand larger shared-memory ranges to kernel services can + define a larger value. */ +#ifndef TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES +#define TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES 1024 +#endif + #define TXM_ASID_RESERVED 0xFFFFFFFF #define TXM_MODULE_ASID_ERROR 0xF6 @@ -386,9 +404,26 @@ typedef struct TXM_MODULE_MANAGER_MEMORY_FAULT_INFO_STRUCT /* Define the macros to perform port-specific checks when passing pointers to the kernel. */ -/* Define macro to make sure object is inside the module's data or shared memory. */ +/* Define macros to make sure a whole object is inside the module's data or shared memory. + The MMU is the authority for shared and external memory, so the check translates every + page the range touches with the requested unprivileged access. Passing the size and the + access intent through is what keeps the check honest: translating only the first address, + and only for reading, would accept a range that leaves the module's mapping partway + through, or a read-only range used as a kernel write destination. */ +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size), TXM_MODULE_MANAGER_ACCESS_READ) + +#define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size), TXM_MODULE_MANAGER_ACCESS_WRITE) + +/* The unqualified check keeps its historical name and takes the stricter write intent. */ #define TXM_MODULE_MANAGER_CHECK_INSIDE_DATA(module_instance, obj_ptr, obj_size) \ - _txm_module_manager_inside_data_check((ULONG) obj_ptr) + TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE(module_instance, obj_ptr, obj_size) + +/* Negating the inside check would report a range that only partly reaches into the module + as being outside it, so the outside direction gets its own check. */ +#define TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA(module_instance, obj_ptr, obj_size) \ + _txm_module_manager_outside_data_check(module_instance, (ALIGN_TYPE) (obj_ptr), (ULONG) (obj_size)) /* Define some internal prototypes to this module port. */ @@ -408,7 +443,10 @@ UINT _txm_module_manager_mm_initialize(VOID); VOID _txm_module_manager_mm_register_setup(TXM_MODULE_INSTANCE *module_instance); \ VOID _txm_level2_page_clear(TXM_MODULE_INSTANCE *module_instance); \ VOID _txm_module_manager_remove_asid(TXM_MODULE_INSTANCE *module_instance); \ -UINT _txm_module_manager_inside_data_check(ULONG pointer); +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size, UINT write_request); \ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size); \ +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request); \ +ULONG _txm_module_manager_current_asid_get(VOID); #define TXM_MODULE_MANAGER_VERSION_ID \ CHAR _txm_module_manager_version_id[] = \ diff --git a/ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_inside_data_check.c b/ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_inside_data_check.c new file mode 100644 index 00000000..972cae84 --- /dev/null +++ b/ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_inside_data_check.c @@ -0,0 +1,538 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Module Manager */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +#define TX_SOURCE_CODE + +#include "tx_api.h" +#include "txm_module.h" + + +/* Define the ways a caller-supplied range can sit relative to the module's data region. */ + +#define TXM_MODULE_DATA_WINDOW_OUTSIDE 0 /* No byte touches the data mapping. */ +#define TXM_MODULE_DATA_WINDOW_CONTAINED 1 /* Every byte is recorded module data. */ +#define TXM_MODULE_DATA_WINDOW_PARTIAL 2 /* Some, but not all, bytes are. */ + + +/* Define the page mask derived from the smallest MMU page this port maps. */ + +#define TXM_MODULE_PAGE_OFFSET_MASK ((ALIGN_TYPE) (TXM_MODULE_MEMORY_ALIGNMENT - 1)) +#define TXM_MODULE_PAGE_BASE_MASK (~TXM_MODULE_PAGE_OFFSET_MASK) + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_range_end_get Cortex-A7/MMU/IAR */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function derives the inclusive address of the last byte of a */ +/* caller-supplied range. An empty range is rejected, matching the */ +/* portable check macros, and so is a range whose last byte is not */ +/* representable, so that no caller ever works from a wrapped address. */ +/* */ +/* INPUT */ +/* */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* last_byte Destination for the last address */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if the range is non-empty and does not wrap */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_range_end_get(ALIGN_TYPE obj_ptr, ULONG obj_size, ALIGN_TYPE *last_byte) +{ + +ALIGN_TYPE span; +ALIGN_TYPE highest; +UINT status; + + + /* Assume the range cannot be used until proven otherwise. */ + status = TX_FALSE; + + /* An empty range has no last byte and is rejected, just as the portable macros do. */ + if (obj_size != ((ULONG) 0)) + { + + /* Work with the inclusive span so that a range reaching the top of the address + space stays representable. */ + span = ((ALIGN_TYPE) obj_size) - ((ALIGN_TYPE) 1); + + /* Determine the highest first byte this span can start from. */ + highest = (~((ALIGN_TYPE) 0)) - span; + + /* Determine if the range would run past the end of the address space. */ + if (obj_ptr <= highest) + { + + /* Record the last byte of the range. */ + *last_byte = obj_ptr + span; + + /* The range is usable. */ + status = TX_TRUE; + } + } + + /* Return the completion status. */ + return(status); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_data_window_classify Cortex-A7/MMU/IAR */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function reports how a range sits relative to the module's own */ +/* data region. The module manager records that region exactly, while */ +/* the MMU can only map it a page at a time, so the mapping may reach */ +/* past the recorded end. The recorded region therefore stays the */ +/* authority for the whole mapped window: a range that leaves the */ +/* recorded region but still lands in the window is reported as */ +/* partial, and its caller rejects it rather than asking the MMU, */ +/* which would accept the rounding slack. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* first_byte Address of the first byte */ +/* last_byte Address of the last byte */ +/* */ +/* OUTPUT */ +/* */ +/* One of the TXM_MODULE_DATA_WINDOW_* classifications */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_data_window_classify(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE first_byte, ALIGN_TYPE last_byte) +{ + +ALIGN_TYPE data_start; +ALIGN_TYPE data_end; +ALIGN_TYPE window_start; +ALIGN_TYPE window_end; +UINT window; + + + /* Pick up the recorded data region. The end address is inclusive. */ + data_start = (ALIGN_TYPE) module_instance -> txm_module_instance_data_start; + data_end = (ALIGN_TYPE) module_instance -> txm_module_instance_data_end; + + /* Determine if the module has a usable data region at all. */ + if (data_start > data_end) + { + + /* An unset or inverted region owns nothing. */ + window = TXM_MODULE_DATA_WINDOW_OUTSIDE; + } + else + { + + /* Widen the region to the pages the MMU actually maps for it. */ + window_start = data_start & TXM_MODULE_PAGE_BASE_MASK; + window_end = data_end | TXM_MODULE_PAGE_OFFSET_MASK; + + /* Determine if every byte is recorded module data. */ + if ((first_byte >= data_start) && (last_byte <= data_end)) + { + + /* The whole range belongs to the module. */ + window = TXM_MODULE_DATA_WINDOW_CONTAINED; + } + else if ((first_byte <= window_end) && (last_byte >= window_start)) + { + + /* The range overlaps the mapped window without being contained by the + recorded region, so it leaves the module's data partway through. */ + window = TXM_MODULE_DATA_WINDOW_PARTIAL; + } + else + { + + /* The range lies elsewhere, so only the MMU can speak for it. */ + window = TXM_MODULE_DATA_WINDOW_OUTSIDE; + } + } + + /* Return the classification. */ + return(window); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_page_walk Cortex-A7/MMU/IAR */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function translates every page a range touches and reports */ +/* whether all of them answered as expected. The walk stops at the */ +/* first page that disagrees, and a range spanning more pages than */ +/* the configured maximum is refused so that one kernel request can */ +/* never impose unbounded work. */ +/* */ +/* INPUT */ +/* */ +/* first_byte Address of the first byte */ +/* last_byte Address of the last byte */ +/* write_request Access intent to translate for */ +/* expected Result every page must return */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if every page answered as expected */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_address_probe */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +static UINT _txm_module_manager_page_walk(ALIGN_TYPE first_byte, ALIGN_TYPE last_byte, UINT write_request, UINT expected) +{ + +ALIGN_TYPE first_page; +ALIGN_TYPE last_page; +ALIGN_TYPE page; +ULONG pages; +ULONG index; +UINT matched; + + + /* Reduce the range to the pages it touches. */ + first_page = first_byte & TXM_MODULE_PAGE_BASE_MASK; + last_page = last_byte & TXM_MODULE_PAGE_BASE_MASK; + + /* Count them. Both addresses are page-aligned, so the division is exact. */ + pages = ((ULONG) ((last_page - first_page) / ((ALIGN_TYPE) TXM_MODULE_MEMORY_ALIGNMENT))) + ((ULONG) 1); + + /* Determine if the range is longer than this port is willing to translate. */ + if (pages > ((ULONG) TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES)) + { + + /* Refuse rather than spend unbounded time in a kernel request. Refusing is safe + for both callers: neither can then claim the range is inside or outside. */ + matched = TX_FALSE; + } + else + { + + /* Assume every page agrees until one does not. */ + matched = TX_TRUE; + index = (ULONG) 0; + + /* Translate each page in turn, stopping as soon as the answer is settled. The + address is rebuilt from the index so that the walk cannot wrap past the top + page of the address space. */ + while ((index < pages) && (matched == TX_TRUE)) + { + + /* Build the address of this page. */ + page = first_page + (((ALIGN_TYPE) index) * ((ALIGN_TYPE) TXM_MODULE_MEMORY_ALIGNMENT)); + + /* Ask the MMU whether the module may reach this page with the requested access. */ + if (_txm_module_manager_address_probe((ULONG) page, write_request) != expected) + { + + /* This page disagrees, so the range as a whole does. */ + matched = TX_FALSE; + } + + /* Move to the next page. */ + index++; + } + } + + /* Return whether the whole range answered as expected. */ + return(matched); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_inside_data_check Cortex-A7/MMU/IAR */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function determines whether every byte of a caller-supplied */ +/* range lies inside the module's data or shared memory and is */ +/* reachable by the module with the requested access. The module's own */ +/* data region is answered from the manager's own records, which are */ +/* exact. Anything else is answered by translating every page the */ +/* range touches, because this port keeps no record of the shared and */ +/* external regions the application maps into the module. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* write_request TXM_MODULE_MANAGER_ACCESS_READ or */ +/* TXM_MODULE_MANAGER_ACCESS_WRITE */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if the whole range is reachable with that access */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_range_end_get */ +/* _txm_module_manager_current_asid_get */ +/* _txm_module_manager_data_window_classify */ +/* _txm_module_manager_page_walk */ +/* */ +/* CALLED BY */ +/* */ +/* TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_READ */ +/* TXM_MODULE_MANAGER_CHECK_INSIDE_DATA_WRITE */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size, UINT write_request) +{ + +ALIGN_TYPE last_byte; +UINT window; +UINT status; + + + /* Assume the range is not usable until every condition is met. */ + status = TX_FALSE; + + /* A range can only belong to a module that exists. */ + if (module_instance != TX_NULL) + { + + /* Determine the last byte, rejecting empty and wrapping ranges. */ + if (_txm_module_manager_range_end_get(obj_ptr, obj_size, &last_byte) == TX_TRUE) + { + + /* Address translation answers for whichever context is loaded, so it can only + be trusted while the requesting module's context is the loaded one. */ + if (_txm_module_manager_current_asid_get() == (module_instance -> txm_module_instance_asid)) + { + + /* Place the range relative to the module's own data region. */ + window = _txm_module_manager_data_window_classify(module_instance, obj_ptr, last_byte); + + if (window == ((UINT) TXM_MODULE_DATA_WINDOW_CONTAINED)) + { + + /* The module's data region is mapped for unprivileged reading and + writing, so both intents are satisfied without translating. */ + status = TX_TRUE; + } + else if (window == ((UINT) TXM_MODULE_DATA_WINDOW_OUTSIDE)) + { + + /* Shared or external memory: every page must grant the access. */ + status = _txm_module_manager_page_walk(obj_ptr, last_byte, write_request, TX_TRUE); + } + else + { + + /* The range leaves the module's data partway through. */ + status = TX_FALSE; + } + } + } + } + + /* Return the completion status. */ + return(status); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_outside_data_check Cortex-A7/MMU/IAR */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function determines whether no byte of a caller-supplied range */ +/* is reachable by the module. It is deliberately not the negation of */ +/* the inside check: a range that reaches into the module's memory */ +/* only partway is inside neither answer, and reporting it as outside */ +/* would let it pass as a kernel object. */ +/* */ +/* INPUT */ +/* */ +/* module_instance Pointer to module instance */ +/* obj_ptr Address of the first byte */ +/* obj_size Size of the range in bytes */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE if no byte of the range is reachable by the module */ +/* */ +/* CALLS */ +/* */ +/* _txm_module_manager_range_end_get */ +/* _txm_module_manager_current_asid_get */ +/* _txm_module_manager_data_window_classify */ +/* _txm_module_manager_page_walk */ +/* */ +/* CALLED BY */ +/* */ +/* TXM_MODULE_MANAGER_CHECK_OUTSIDE_DATA */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, ULONG obj_size) +{ + +ALIGN_TYPE last_byte; +UINT window; +UINT status; + + + /* Assume the range is not provably outside the module. */ + status = TX_FALSE; + + /* A range can only be placed against a module that exists. */ + if (module_instance != TX_NULL) + { + + /* Determine the last byte, rejecting empty and wrapping ranges. */ + if (_txm_module_manager_range_end_get(obj_ptr, obj_size, &last_byte) == TX_TRUE) + { + + /* Address translation answers for whichever context is loaded, so it can only + be trusted while the requesting module's context is the loaded one. */ + if (_txm_module_manager_current_asid_get() == (module_instance -> txm_module_instance_asid)) + { + + /* Place the range relative to the module's own data region. */ + window = _txm_module_manager_data_window_classify(module_instance, obj_ptr, last_byte); + + if (window == ((UINT) TXM_MODULE_DATA_WINDOW_OUTSIDE)) + { + + /* Clear of the module's data region, so the remaining question is + whether any page of it is shared or external memory. */ + status = _txm_module_manager_page_walk(obj_ptr, last_byte, TXM_MODULE_MANAGER_ACCESS_READ, TX_FALSE); + } + else + { + + /* The range touches the module's data region. */ + status = TX_FALSE; + } + } + } + } + + /* Return the completion status. */ + return(status); +} diff --git a/ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_mm_register_setup.c b/ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_mm_register_setup.c index 3d477a4a..3adc61c3 100644 --- a/ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_mm_register_setup.c +++ b/ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_mm_register_setup.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -35,24 +37,34 @@ extern ULONG _txm_ttbr1_page_table[TXM_MAXIMUM_MODULES][TXM_MASTER_PAGE_TABLE_EN /* */ /* FUNCTION RELEASE */ /* */ -/* _txm_module_manager_inside_data_check Cortex-A7/MMU/IAR */ -/* 6.1 */ +/* _txm_module_manager_address_probe Cortex-A7/MMU/IAR */ +/* 6.5.0 */ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Eclipse ThreadX contributors */ /* */ /* DESCRIPTION */ /* */ -/* This function determines if pointer is within the module's data or */ -/* shared memory. */ +/* This function asks the MMU whether unprivileged code running in the */ +/* currently loaded translation context may reach one address with the */ +/* requested access. Reading and writing are asked separately, because */ +/* a module's code and its read-only shared memory translate for */ +/* reading while a kernel service writing to them would fault or, */ +/* worse, succeed from privileged mode. */ +/* */ +/* This answers for one address only. Callers that hold a range must */ +/* ask about every page the range touches. */ /* */ /* INPUT */ /* */ -/* pointer Data pointer */ +/* address Address to translate */ +/* write_request TXM_MODULE_MANAGER_ACCESS_READ or */ +/* TXM_MODULE_MANAGER_ACCESS_WRITE */ /* */ /* OUTPUT */ /* */ -/* Completion Status */ +/* TX_TRUE if the translation granted the requested access */ /* */ /* CALLS */ /* */ @@ -60,17 +72,40 @@ extern ULONG _txm_ttbr1_page_table[TXM_MAXIMUM_MODULES][TXM_MASTER_PAGE_TABLE_EN /* */ /* CALLED BY */ /* */ -/* TXM_MODULE_MANAGER_DATA_POINTER_CHECK */ +/* _txm_module_manager_page_walk */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 03-08-2023 Scott Larson Initial Version 6.2.1 */ +/* 08-24-2026 Eclipse ThreadX Added the write intent and */ +/* contributors moved range handling to */ +/* the caller, resulting in */ +/* version 6.5.0 */ /* */ /**************************************************************************/ -UINT _txm_module_manager_inside_data_check(ULONG pointer) +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request) { -ULONG translation; +ULONG translation; - /* ATS1CUR operation on address supplied in pointer, Stage 1 unprivileged read. */ - asm("MCR p15, 0, %0, c7, c8, 2" : : "r"(pointer) : ); - asm("ISB"); /* Ensure completion of the MCR write to CP15. */ + + /* Determine which unprivileged access the caller needs. */ + if (write_request == ((UINT) TXM_MODULE_MANAGER_ACCESS_WRITE)) + { + + /* ATS1CUW operation on address supplied in address, Stage 1 unprivileged write. */ + asm("MCR p15, 0, %0, c7, c8, 3" : : "r"(address) : ); + } + else + { + + /* ATS1CUR operation on address supplied in address, Stage 1 unprivileged read. */ + asm("MCR p15, 0, %0, c7, c8, 2" : : "r"(address) : ); + } + + asm("ISB"); /* Ensure completion of the MCR write to CP15. */ asm("MRC p15, 0, %0, c7, c4, 0" : "=r"(translation) : : ); /* Read result from 32-bit PAR into translation. */ if (translation & TXM_ADDRESS_TRANSLATION_FAULT_BIT) @@ -82,6 +117,62 @@ ULONG translation; } +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _txm_module_manager_current_asid_get Cortex-A7/MMU/IAR */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function returns the ASID of the translation context that is */ +/* currently loaded. Address translation always answers for that */ +/* context, so a caller validating a pointer on behalf of a module */ +/* must confirm that the loaded context is that module's before it */ +/* believes the answer. */ +/* */ +/* INPUT */ +/* */ +/* None */ +/* */ +/* OUTPUT */ +/* */ +/* ASID of the currently loaded translation context */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* _txm_module_manager_inside_data_check */ +/* _txm_module_manager_outside_data_check */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 08-24-2026 Eclipse ThreadX Initial Version 6.5.0 */ +/* contributors */ +/* */ +/**************************************************************************/ +ULONG _txm_module_manager_current_asid_get(VOID) +{ + +ULONG contextidr; + + + /* Read CONTEXTIDR, whose low byte holds the ASID in the short-descriptor format. */ + asm("MRC p15, 0, %0, c13, c0, 1" : "=r"(contextidr) : : ); + + return(contextidr & TXM_CONTEXTIDR_ASID_MASK); +} + + /**************************************************************************/ /* */ /* FUNCTION RELEASE */ diff --git a/scripts/check_module_mmu_a7.sh b/scripts/check_module_mmu_a7.sh new file mode 100755 index 00000000..593edb31 --- /dev/null +++ b/scripts/check_module_mmu_a7.sh @@ -0,0 +1,120 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################## + +# Runs the Cortex-A7 module range check against a live MMU. +# +# The host test for that check stands a simulated page map behind the port's +# CP15 primitive, so it never executes an address translation. An encoding that +# named the wrong operation, or a PAR fault bit read the wrong way round, would +# pass it. This check builds a bare-metal image that turns the MMU on and drives +# the real check through the real translations, then runs it under an emulator. +# +# scripts/check_module_mmu_a7.sh +# +# Environment: +# CROSS_CC arm-none-eabi C compiler; defaults to arm-none-eabi-gcc. +# QEMU Arm system emulator; defaults to qemu-system-arm. +# +# Exit status is 0 when the image runs and every expectation holds, 1 when an +# expectation fails, and 0 with a notice when the tools are absent, so that a +# machine without a cross toolchain does not fail the build. + +set -u + +CC="${CROSS_CC:-arm-none-eabi-gcc}" +QEMU_BIN="${QEMU:-qemu-system-arm}" + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SRC="$ROOT/test/tx/module_manager/threadx_module_manager_cortex_a7_mmu_test.c" +PORT="$ROOT/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_inside_data_check.c" + +for tool in "$CC" "$QEMU_BIN"; do + if ! command -v "$tool" >/dev/null 2>&1 && [ ! -x "$tool" ]; then + echo "Skipping: $tool not found." + echo " Needs an arm-none-eabi toolchain and qemu-system-arm." + exit 0 + fi +done + +# The port supplies the two architecture primitives from its register setup +# file, which cannot be compiled here because it also builds the module page +# tables. They are provided by the image itself, copied from that file, so what +# runs is the same instruction sequence the port issues. +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +cat > "$work/primitives.c" <<'PRIMITIVES' +#define TX_SOURCE_CODE +#include "tx_api.h" +#include "txm_module.h" + +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request) +{ +ULONG translation; + + if (write_request == ((UINT) TXM_MODULE_MANAGER_ACCESS_WRITE)) + { + __asm volatile ("MCR p15, 0, %0, c7, c8, 3" : : "r"(address) : ); + } + else + { + __asm volatile ("MCR p15, 0, %0, c7, c8, 2" : : "r"(address) : ); + } + + __asm volatile ("ISB"); + __asm volatile ("MRC p15, 0, %0, c7, c4, 0" : "=r"(translation) : : ); + + if (translation & TXM_ADDRESS_TRANSLATION_FAULT_BIT) + { + return(TX_FALSE); + } + + return(TX_TRUE); +} + +ULONG _txm_module_manager_current_asid_get(VOID) +{ +ULONG contextidr; + + __asm volatile ("MRC p15, 0, %0, c13, c0, 1" : "=r"(contextidr) : : ); + + return(contextidr & TXM_CONTEXTIDR_ASID_MASK); +} +PRIMITIVES + +"$CC" -mcpu=cortex-a7 -marm -O1 -std=gnu99 -Wall -Wextra -Werror \ + --specs=rdimon.specs \ + -I"$ROOT/ports_module/cortex_a7/gnu/inc" \ + -I"$ROOT/common/inc" \ + -I"$ROOT/common_modules/inc" \ + -I"$ROOT/common_modules/module_manager/inc" \ + -o "$work/mmu_test.elf" "$SRC" "$PORT" "$work/primitives.c" || { + echo "FAIL: the Cortex-A7 MMU test image did not build." + exit 1 +} + +output="$("$QEMU_BIN" -M realview-pb-a8 -cpu cortex-a7 -m 128 -nographic \ + -semihosting -kernel "$work/mmu_test.elf" 2>/dev/null)" +status=$? + +echo "$output" + +if [ $status -ne 0 ] || ! echo "$output" | grep -q "SUCCESS!"; then + echo "FAIL: the Cortex-A7 MMU test did not report success." + exit 1 +fi + +exit 0 diff --git a/test/tx/cmake/module_manager/CMakeLists.txt b/test/tx/cmake/module_manager/CMakeLists.txt index 2ebd9684..17511fd5 100644 --- a/test/tx/cmake/module_manager/CMakeLists.txt +++ b/test/tx/cmake/module_manager/CMakeLists.txt @@ -236,3 +236,19 @@ target_compile_options( add_test(${CMAKE_BUILD_TYPE}::threadx_module_manager_queue_message_range_test threadx_module_manager_queue_message_range_test) + +add_executable( + threadx_module_manager_cortex_a7_range_check_test + ${SOURCE_DIR}/threadx_module_manager_cortex_a7_range_check_test.c + ${cortex_a7_module_dir}/module_manager/src/txm_module_manager_inside_data_check.c) + +target_include_directories( + threadx_module_manager_cortex_a7_range_check_test + PRIVATE ${REPO_ROOT}/common/inc + ${REPO_ROOT}/ports/cortex_a7/gnu/inc + ${REPO_ROOT}/common_modules/inc + ${REPO_ROOT}/common_modules/module_manager/inc + ${cortex_a7_module_dir}/inc) + +add_test(${CMAKE_BUILD_TYPE}::threadx_module_manager_cortex_a7_range_check_test + threadx_module_manager_cortex_a7_range_check_test) diff --git a/test/tx/module_manager/threadx_module_manager_cortex_a7_mmu_test.c b/test/tx/module_manager/threadx_module_manager_cortex_a7_mmu_test.c new file mode 100644 index 00000000..8cc08108 --- /dev/null +++ b/test/tx/module_manager/threadx_module_manager_cortex_a7_mmu_test.c @@ -0,0 +1,205 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Test */ +/** */ +/** Module Manager Cortex-A7 range validation, against a live MMU */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +/* The host test for this check stands a simulated page map behind the port's + one architecture primitive, which lets it run everywhere but leaves the CP15 + address translation itself unexercised: an encoding that named the wrong + operation, or a PAR fault bit read the wrong way round, would pass it. + + This test closes that gap. It builds a short-descriptor translation table, + turns the MMU on, and drives the real check through the real translation + operations on a real Cortex-A7 translation regime. It is a bare-metal image, + built and run by scripts/check_module_mmu_a7.sh under an emulator. + + The sections below are mapped for their access permissions only; nothing is + dereferenced through them, and none of the addresses name a real target. */ + +#include + +#define TX_SOURCE_CODE + +#include "tx_api.h" +#include "txm_module.h" + + +/* Short-descriptor section entry fields. AP[2:0] selects the access + permissions: 011 is unprivileged read/write, 010 is unprivileged read only, + 001 leaves the section privileged-only. */ + +#define TEST_SECTION_TYPE 0x00000002 +#define TEST_AP_LOW(value) (((ULONG) (value)) << 10) +#define TEST_AP_USER_RW TEST_AP_LOW(3) +#define TEST_AP_USER_RO TEST_AP_LOW(2) +#define TEST_AP_PRIVILEGED_ONLY TEST_AP_LOW(1) + +#define TEST_LEVEL1_ENTRIES 4096 +#define TEST_SECTION_SIZE 0x00100000 + +/* One section of each kind the check has to tell apart, plus an unmapped + section immediately after the read-only one so a range can be made to leave + its mapping partway through. */ + +#define TEST_SHARED_RW 0x40000000 +#define TEST_SHARED_RO 0x40100000 +#define TEST_GUARD 0x40200000 +#define TEST_MODULE_DATA 0x41000000 +#define TEST_MODULE_DATA_SIZE 0x00001000 +#define TEST_KERNEL_ONLY 0x30000000 + +static ULONG test_level1_table[TEST_LEVEL1_ENTRIES] __attribute__((aligned(16384))); +static UINT test_failures = 0; + + +/* Reproduce the behaviour this check replaced: one translation of the first + address, for reading, with the size discarded. Kept so the boundary cases + can show both answers against the same live MMU. */ +static UINT test_first_address_only_check(ALIGN_TYPE obj_ptr) +{ + return(_txm_module_manager_address_probe((ULONG) obj_ptr, TXM_MODULE_MANAGER_ACCESS_READ)); +} + + +/* Build a flat translation table, then enable the MMU. */ +static VOID test_mmu_enable(VOID) +{ + +ULONG index; +ULONG sctlr; +ULONG zero = 0; +ULONG dacr = 1; /* Domain 0 as a client, so AP is checked. */ + + + /* Everything is privileged-only until a section below says otherwise. */ + for (index = 0; index < TEST_LEVEL1_ENTRIES; index++) + { + test_level1_table[index] = (index * TEST_SECTION_SIZE) | TEST_SECTION_TYPE | TEST_AP_PRIVILEGED_ONLY; + } + + test_level1_table[TEST_SHARED_RW / TEST_SECTION_SIZE] = + TEST_SHARED_RW | TEST_SECTION_TYPE | TEST_AP_USER_RW; + test_level1_table[TEST_SHARED_RO / TEST_SECTION_SIZE] = + TEST_SHARED_RO | TEST_SECTION_TYPE | TEST_AP_USER_RO; + test_level1_table[TEST_MODULE_DATA / TEST_SECTION_SIZE] = + TEST_MODULE_DATA | TEST_SECTION_TYPE | TEST_AP_USER_RW; + + /* Leave the guard section faulting. */ + test_level1_table[TEST_GUARD / TEST_SECTION_SIZE] = 0; + + __asm volatile ("MCR p15, 0, %0, c2, c0, 2" : : "r"(zero)); /* TTBCR */ + __asm volatile ("MCR p15, 0, %0, c2, c0, 0" : : "r"((ULONG) test_level1_table)); /* TTBR0 */ + __asm volatile ("MCR p15, 0, %0, c3, c0, 0" : : "r"(dacr)); /* DACR */ + __asm volatile ("MCR p15, 0, %0, c13, c0, 1" : : "r"(zero)); /* CONTEXTIDR */ + __asm volatile ("MCR p15, 0, %0, c8, c7, 0" : : "r"(zero)); /* TLBIALL */ + __asm volatile ("DSB"); + __asm volatile ("ISB"); + + __asm volatile ("MRC p15, 0, %0, c1, c0, 0" : "=r"(sctlr)); + sctlr = sctlr | ((ULONG) 1); /* SCTLR.M */ + __asm volatile ("MCR p15, 0, %0, c1, c0, 0" : : "r"(sctlr)); + __asm volatile ("ISB"); +} + + +static VOID test_expect(const char *description, UINT actual, UINT expected) +{ + if (actual != expected) + { + printf(" FAIL: %s (expected %u, got %u)\n", description, expected, actual); + test_failures++; + } +} + + +int main(VOID) +{ + +TXM_MODULE_INSTANCE module; +TXM_MODULE_INSTANCE *module_instance; +ALIGN_TYPE straddle; + + + printf("Cortex-A7 module range validation against a live MMU................... "); + + test_mmu_enable(); + + module_instance = &module; + module_instance -> txm_module_instance_id = TXM_MODULE_ID; + module_instance -> txm_module_instance_data_start = (VOID *) TEST_MODULE_DATA; + module_instance -> txm_module_instance_data_end = (VOID *) (TEST_MODULE_DATA + TEST_MODULE_DATA_SIZE - 1); + module_instance -> txm_module_instance_code_start = (VOID *) 0; + module_instance -> txm_module_instance_code_end = (VOID *) 0; + module_instance -> txm_module_instance_asid = 0; + + /* The module's own data is answered from the manager's records. */ + test_expect("recorded module data is writable", + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) TEST_MODULE_DATA, 4, + TXM_MODULE_MANAGER_ACCESS_WRITE), TX_TRUE); + test_expect("a range crossing the recorded data end is rejected", + _txm_module_manager_inside_data_check(module_instance, + (ALIGN_TYPE) (TEST_MODULE_DATA + TEST_MODULE_DATA_SIZE - 2), 4, + TXM_MODULE_MANAGER_ACCESS_WRITE), TX_FALSE); + + /* Shared memory is answered by the MMU, one page at a time. */ + test_expect("shared read/write memory is writable", + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) TEST_SHARED_RW, 4, + TXM_MODULE_MANAGER_ACCESS_WRITE), TX_TRUE); + test_expect("shared read-only memory is readable", + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) TEST_SHARED_RO, 4, + TXM_MODULE_MANAGER_ACCESS_READ), TX_TRUE); + + /* The write intent is the reason the port asks for two translations rather + than one; a read-only mapping must not serve as a kernel destination. */ + test_expect("shared read-only memory is not writable", + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) TEST_SHARED_RO, 4, + TXM_MODULE_MANAGER_ACCESS_WRITE), TX_FALSE); + + /* A range that leaves its mapping partway through. This is the case the + replaced check accepted, and the reason this test exists. */ + straddle = (ALIGN_TYPE) (TEST_GUARD - 2); + test_expect("a range crossing into the guard section is rejected", + _txm_module_manager_inside_data_check(module_instance, straddle, 4, + TXM_MODULE_MANAGER_ACCESS_READ), TX_FALSE); + test_expect("...and translating only its first address would have accepted it", + test_first_address_only_check(straddle), TX_TRUE); + + /* Privileged memory is reachable by neither intent, and is outside the module. */ + test_expect("privileged-only memory is not readable", + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) TEST_KERNEL_ONLY, 4, + TXM_MODULE_MANAGER_ACCESS_READ), TX_FALSE); + test_expect("privileged-only memory is outside the module", + _txm_module_manager_outside_data_check(module_instance, (ALIGN_TYPE) TEST_KERNEL_ONLY, 64), TX_TRUE); + test_expect("shared memory is not outside the module", + _txm_module_manager_outside_data_check(module_instance, (ALIGN_TYPE) TEST_SHARED_RW, 64), TX_FALSE); + + if (test_failures == 0) + { + printf("SUCCESS!\n"); + return(0); + } + + printf("ERROR: %u expectation(s) failed\n", test_failures); + return(1); +} diff --git a/test/tx/module_manager/threadx_module_manager_cortex_a7_range_check_test.c b/test/tx/module_manager/threadx_module_manager_cortex_a7_range_check_test.c new file mode 100644 index 00000000..a0cca868 --- /dev/null +++ b/test/tx/module_manager/threadx_module_manager_cortex_a7_range_check_test.c @@ -0,0 +1,443 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Test */ +/** */ +/** Module Manager Cortex-A7 data range validation */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +/* This test exercises the Cortex-A7 module data range check through the same + macros the module dispatchers use. The check normally asks the MMU about one + page at a time, so the test stands a simulated page map behind that one + architecture primitive and drives every outcome from the host build. + + No address in this test is dereferenced and none of them name a real target. + They are labels in a table that the stubbed primitive answers questions about, + which is what lets the test describe a range that leaves a module's mapping + without ever building something that could read past one. */ + +#include + +#define TX_SOURCE_CODE + +#include "tx_api.h" +#include "txm_module.h" +#include "txm_module_manager_util.h" + + +/* Define the simulated address space. + + The module's recorded data region deliberately ends partway through its last + page, because that is what the module manager's page-granular mapping does in + practice: the mapping reaches to the end of the page while the recorded region + does not. The bytes in between are the rounding slack, and the check must not + hand them out. */ + +#define TEST_PAGE_SIZE 4096 + +#define TEST_DATA_START 0x20001000UL +#define TEST_DATA_END 0x200027FFUL /* Ends mid-page: slack follows. */ +#define TEST_DATA_WINDOW_END 0x20002FFFUL /* Last byte the MMU maps for it. */ + +#define TEST_CODE_START 0x10001000UL +#define TEST_CODE_END 0x10001FFFUL + +#define TEST_SHARED_RW_START 0x20010000UL /* Two contiguous read/write pages. */ +#define TEST_SHARED_RW_END 0x20011FFFUL +#define TEST_GUARD_PAGE 0x20012000UL /* Unmapped, directly after them. */ + +#define TEST_SPLIT_FIRST_PAGE 0x20030000UL /* Mapped. */ +#define TEST_SPLIT_MIDDLE_PAGE 0x20031000UL /* Unmapped. */ +#define TEST_SPLIT_LAST_PAGE 0x20032000UL /* Mapped. */ + +#define TEST_SHARED_RO_START 0x20020000UL /* Readable, not writable. */ +#define TEST_SHARED_RO_END 0x20020FFFUL + +#define TEST_KERNEL_OBJECT 0x30000000UL /* Unmapped for the module. */ + +#define TEST_MODULE_ASID 5UL +#define TEST_OTHER_MODULE_ASID 6UL + + +/* Define one entry of the simulated page map. */ + +typedef struct TEST_PAGE_STRUCT +{ + ULONG test_page_base; + UINT test_page_readable; + UINT test_page_writable; +} TEST_PAGE; + + +static TEST_PAGE test_page_map[] = +{ + /* The module's own data pages, mapped read/write to the end of the page. */ + {TEST_DATA_START, TX_TRUE, TX_TRUE}, + {TEST_DATA_START + TEST_PAGE_SIZE, TX_TRUE, TX_TRUE}, + + /* The module's code, readable but never writable from unprivileged mode. */ + {TEST_CODE_START, TX_TRUE, TX_FALSE}, + + /* Read/write shared memory, followed by an unmapped guard page. */ + {TEST_SHARED_RW_START, TX_TRUE, TX_TRUE}, + {TEST_SHARED_RW_START + TEST_PAGE_SIZE, TX_TRUE, TX_TRUE}, + + /* Read-only shared memory. */ + {TEST_SHARED_RO_START, TX_TRUE, TX_FALSE}, + + /* Two mapped pages with an unmapped page between them. */ + {TEST_SPLIT_FIRST_PAGE, TX_TRUE, TX_TRUE}, + {TEST_SPLIT_LAST_PAGE, TX_TRUE, TX_TRUE} +}; + +#define TEST_PAGE_MAP_ENTRIES (sizeof(test_page_map) / sizeof(test_page_map[0])) + + +/* Define the state the stubbed architecture primitives answer from. */ + +static ULONG test_current_asid = TEST_MODULE_ASID; +static ULONG test_probe_count = 0UL; +static UINT test_failures = 0U; + + +/* Stand in for the CP15 address translation the real port performs. */ +UINT _txm_module_manager_address_probe(ULONG address, UINT write_request) +{ + +ULONG page_base; +ULONG index; +UINT granted; + + + test_probe_count++; + + page_base = address & (~((ULONG) (TEST_PAGE_SIZE - 1))); + granted = TX_FALSE; + + for (index = 0UL; index < (ULONG) TEST_PAGE_MAP_ENTRIES; index++) + { + if (test_page_map[index].test_page_base == page_base) + { + if (write_request == TXM_MODULE_MANAGER_ACCESS_WRITE) + { + granted = test_page_map[index].test_page_writable; + } + else + { + granted = test_page_map[index].test_page_readable; + } + } + } + + return(granted); +} + + +/* Stand in for the CONTEXTIDR read the real port performs. */ +ULONG _txm_module_manager_current_asid_get(VOID) +{ + return(test_current_asid); +} + + +/* Model the implementation this test guards against. It translated the first + address of the range and nothing else, and it only ever asked about reading. + Keeping it here lets each boundary case show both answers side by side. */ +static UINT test_first_address_only_check(ALIGN_TYPE obj_ptr) +{ + return(_txm_module_manager_address_probe((ULONG) obj_ptr, TXM_MODULE_MANAGER_ACCESS_READ)); +} + + +/* Record the outcome of one expectation. */ +static VOID test_expect(const char *description, UINT actual, UINT expected) +{ + if (actual != expected) + { + printf("FAIL: %s (expected %u, got %u)\n", description, expected, actual); + test_failures++; + } +} + + +/* Build a module instance describing the simulated module. */ +static VOID test_module_build(TXM_MODULE_INSTANCE *module_instance) +{ + module_instance -> txm_module_instance_id = TXM_MODULE_ID; + module_instance -> txm_module_instance_data_start = (VOID *) TEST_DATA_START; + module_instance -> txm_module_instance_data_end = (VOID *) TEST_DATA_END; + module_instance -> txm_module_instance_code_start = (VOID *) TEST_CODE_START; + module_instance -> txm_module_instance_code_end = (VOID *) TEST_CODE_END; + module_instance -> txm_module_instance_asid = TEST_MODULE_ASID; +} + + +int main(void) +{ + +TXM_MODULE_INSTANCE module; +TXM_MODULE_INSTANCE *module_instance; +ALIGN_TYPE address; +ULONG probes_before; +ULONG huge_size; + + + printf("Cortex-A7 module data range validation test............................ "); + + module_instance = &module; + test_module_build(module_instance); + + /**********************************************************************/ + /* Normal valid behaviour. */ + /**********************************************************************/ + + test_expect("one byte inside module data is writable", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, 1UL), TX_TRUE); + + test_expect("one word inside module data is writable", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, (ULONG) sizeof(ULONG)), TX_TRUE); + + test_expect("one word inside module data is readable", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_READ(module_instance, (ALIGN_TYPE) TEST_DATA_START, (ULONG) sizeof(ULONG)), TX_TRUE); + + test_expect("a range spanning both module data pages is writable", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, 0x1800UL), TX_TRUE); + + /* The module's own data is answered from the manager's records, so it costs + no translations at all. This is what keeps the common case deterministic. */ + probes_before = test_probe_count; + (VOID) TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, 0x1800UL); + test_expect("module data is answered without translating", + (UINT) (test_probe_count == probes_before), TX_TRUE); + + /**********************************************************************/ + /* Null and invalid parameters. */ + /**********************************************************************/ + + /* A null buffer is how the dispatchers say "not supplied", and the macros + accept it. Only the range check below it must reject a null module. */ + test_expect("a null buffer pointer is accepted by the buffer macros", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) 0, (ULONG) sizeof(ULONG)), TX_TRUE); + + test_expect("a null module instance is rejected", + _txm_module_manager_inside_data_check(TX_NULL, (ALIGN_TYPE) TEST_DATA_START, (ULONG) sizeof(ULONG), TXM_MODULE_MANAGER_ACCESS_WRITE), TX_FALSE); + + test_expect("a null module instance is not outside anything either", + _txm_module_manager_outside_data_check(TX_NULL, (ALIGN_TYPE) TEST_KERNEL_OBJECT, (ULONG) sizeof(ULONG)), TX_FALSE); + + /* A module torn down to an empty data region owns nothing, and the address + range must then be settled by the MMU rather than by stale records. */ + module.txm_module_instance_data_start = (VOID *) TEST_DATA_END; + module.txm_module_instance_data_end = (VOID *) TEST_DATA_START; + test_expect("an inverted data region grants nothing from records", + _txm_module_manager_inside_data_check(module_instance, (ALIGN_TYPE) TEST_KERNEL_OBJECT, (ULONG) sizeof(ULONG), TXM_MODULE_MANAGER_ACCESS_WRITE), TX_FALSE); + test_module_build(module_instance); + + /**********************************************************************/ + /* Exact boundaries, and the one byte past each of them. */ + /**********************************************************************/ + + test_expect("a range starting exactly at data start is accepted", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, 4UL), TX_TRUE); + + test_expect("a range starting one byte before data start is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) (TEST_DATA_START - 1UL), 4UL), TX_FALSE); + + test_expect("a range ending exactly at data end is accepted", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) (TEST_DATA_END - 3UL), 4UL), TX_TRUE); + + test_expect("the last byte of data on its own is accepted", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_END, 1UL), TX_TRUE); + + /* One byte further is the rounding slack: mapped, but not the module's. */ + address = (ALIGN_TYPE) (TEST_DATA_END - 3UL); + test_expect("a range ending one byte past data end is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, address, 5UL), TX_FALSE); + test_expect("...and the first-address-only check would have accepted it", + test_first_address_only_check(address), TX_TRUE); + + test_expect("the one-past-the-end byte is rejected on its own", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) (TEST_DATA_END + 1UL), 1UL), TX_FALSE); + + test_expect("the whole rounding slack is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) (TEST_DATA_END + 1UL), (ULONG) (TEST_DATA_WINDOW_END - TEST_DATA_END)), TX_FALSE); + + /* A word that straddles the recorded end crosses it by one word. */ + address = (ALIGN_TYPE) (TEST_DATA_END - 1UL); + test_expect("a word straddling data end is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, address, (ULONG) sizeof(ULONG)), TX_FALSE); + test_expect("...and the first-address-only check would have accepted it", + test_first_address_only_check(address), TX_TRUE); + + /**********************************************************************/ + /* Integer overflow and underflow. */ + /**********************************************************************/ + + test_expect("a zero-length range is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, 0UL), TX_FALSE); + + /* A range whose end wraps must be refused before anything is computed from + the wrapped address. */ + probes_before = test_probe_count; + test_expect("a range whose end wraps past the top of memory is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, 0xFFFFFFFFUL), TX_FALSE); + test_expect("...without translating the wrapped address", + (UINT) (test_probe_count == probes_before), TX_TRUE); + + test_expect("a range starting at the very top of memory with size two is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) 0xFFFFFFFFUL, 2UL), TX_FALSE); + + test_expect("a one-byte range at the very top of memory does not wrap, and is simply unmapped", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) 0xFFFFFFFFUL, 1UL), TX_FALSE); + + test_expect("a zero-length range is not outside the module either", + _txm_module_manager_outside_data_check(module_instance, (ALIGN_TYPE) TEST_KERNEL_OBJECT, 0UL), TX_FALSE); + + /**********************************************************************/ + /* Shared memory, guard pages, and holes. */ + /**********************************************************************/ + + test_expect("a range inside read/write shared memory is writable", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_SHARED_RW_START, (ULONG) sizeof(ULONG)), TX_TRUE); + + test_expect("a range spanning two shared pages is writable", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_SHARED_RW_START, 0x1800UL), TX_TRUE); + + test_expect("a range ending exactly at the end of shared memory is accepted", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) (TEST_SHARED_RW_END - 3UL), 4UL), TX_TRUE); + + /* One byte more crosses into the guard page. */ + address = (ALIGN_TYPE) (TEST_SHARED_RW_END - 3UL); + test_expect("a range crossing one byte into the guard page is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, address, 5UL), TX_FALSE); + test_expect("...and the first-address-only check would have accepted it", + test_first_address_only_check(address), TX_TRUE); + + test_expect("a three-page range with an unmapped middle page is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_SPLIT_FIRST_PAGE, (ULONG) ((TEST_SPLIT_LAST_PAGE + 4UL) - TEST_SPLIT_FIRST_PAGE)), TX_FALSE); + + test_expect("the first page of that range on its own is still accepted", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_SPLIT_FIRST_PAGE, 4UL), TX_TRUE); + + /**********************************************************************/ + /* Read intent versus write intent. */ + /**********************************************************************/ + + test_expect("read-only shared memory can be read from", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_READ(module_instance, (ALIGN_TYPE) TEST_SHARED_RO_START, (ULONG) sizeof(ULONG)), TX_TRUE); + + test_expect("read-only shared memory cannot be written to", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_SHARED_RO_START, (ULONG) sizeof(ULONG)), TX_FALSE); + + test_expect("a read-only shared range crossing its own end is rejected for reading too", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_READ(module_instance, (ALIGN_TYPE) (TEST_SHARED_RO_END - 3UL), 5UL), TX_FALSE); + + test_expect("module code can be read from", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_READ(module_instance, (ALIGN_TYPE) TEST_CODE_START, (ULONG) sizeof(ULONG)), TX_TRUE); + + test_expect("module code cannot be used as a kernel write destination", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_CODE_START, (ULONG) sizeof(ULONG)), TX_FALSE); + + test_expect("a string may be dereferenced out of module code", + TXM_MODULE_MANAGER_PARAM_CHECK_DEREFERENCE_STRING(module_instance, (ALIGN_TYPE) TEST_CODE_START), TX_TRUE); + + test_expect("a range running off the end of module code is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_READ(module_instance, (ALIGN_TYPE) (TEST_CODE_END - 3UL), 5UL), TX_FALSE); + + /**********************************************************************/ + /* The outside direction. */ + /**********************************************************************/ + + test_expect("a kernel object clear of the module is outside it", + TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, (ALIGN_TYPE) TEST_KERNEL_OBJECT, 64UL), TX_TRUE); + + test_expect("the module's own data is not outside it", + TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, (ALIGN_TYPE) TEST_DATA_START, 64UL), TX_FALSE); + + test_expect("shared memory is not outside the module", + TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, (ALIGN_TYPE) TEST_SHARED_RW_START, 64UL), TX_FALSE); + + test_expect("module code is not outside the module", + TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, (ALIGN_TYPE) TEST_CODE_START, 64UL), TX_FALSE); + + /* A range that reaches into the module only partway is inside neither answer. + Reporting it as outside would let it pass as a kernel object, which is the + mirror image of the defect this test guards against. */ + test_expect("a range straddling the end of module data is not outside it", + TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, (ALIGN_TYPE) (TEST_DATA_END - 3UL), 16UL), TX_FALSE); + + test_expect("a range straddling the guard page after shared memory is not outside the module", + TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, (ALIGN_TYPE) (TEST_SHARED_RW_END - 3UL), 16UL), TX_FALSE); + + /**********************************************************************/ + /* Cross-module and application-owned memory. */ + /**********************************************************************/ + + /* Translation answers for whichever context is loaded. If another module's + context is loaded, this module's records and the MMU disagree, so neither + check may answer at all. */ + test_current_asid = TEST_OTHER_MODULE_ASID; + + test_expect("nothing is inside the module while another context is loaded", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, (ULONG) sizeof(ULONG)), TX_FALSE); + + test_expect("nothing is provably outside it either", + TXM_MODULE_MANAGER_ENSURE_OUTSIDE_MODULE(module_instance, (ALIGN_TYPE) TEST_KERNEL_OBJECT, 64UL), TX_FALSE); + + test_current_asid = TEST_MODULE_ASID; + + test_expect("the module is served again once its own context is loaded", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_DATA_START, (ULONG) sizeof(ULONG)), TX_TRUE); + + /**********************************************************************/ + /* The bound on how much work one request may ask for. */ + /**********************************************************************/ + + huge_size = ((ULONG) TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES) * ((ULONG) TEST_PAGE_SIZE); + + probes_before = test_probe_count; + test_expect("a range one page longer than the maximum is rejected", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_SHARED_RW_START, huge_size + 1UL), TX_FALSE); + test_expect("...without translating any of it", + (UINT) (test_probe_count == probes_before), TX_TRUE); + + test_expect("an over-long range is not outside the module either", + _txm_module_manager_outside_data_check(module_instance, (ALIGN_TYPE) TEST_KERNEL_OBJECT, huge_size + 1UL), TX_FALSE); + + /* A range at exactly the maximum is still walked, and the walk stops at the + first page that fails rather than translating all of it. */ + probes_before = test_probe_count; + test_expect("a range of exactly the maximum length is walked and rejected here", + TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE(module_instance, (ALIGN_TYPE) TEST_SHARED_RW_START, huge_size), TX_FALSE); + test_expect("...stopping at the first page that fails", + (UINT) ((test_probe_count - probes_before) == 3UL), TX_TRUE); + + /**********************************************************************/ + + if (test_failures == 0U) + { + printf("SUCCESS!\n"); + return(0); + } + + printf("ERROR: %u expectation(s) failed\n", test_failures); + return(1); +} diff --git a/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c b/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c index a8477253..95a85ddc 100644 --- a/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c +++ b/test/tx/module_manager/threadx_module_manager_delete_ownership_test.c @@ -347,17 +347,56 @@ UINT _txe_mutex_put(TX_MUTEX *mutex_ptr) /* Stand in for the module port's data range check. */ -UINT _txm_module_manager_inside_data_check(ULONG obj_ptr) +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size, UINT write_request) { ULONG data_start; ULONG data_end; + (VOID) module_instance; + (VOID) write_request; + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); - if ((obj_ptr >= data_start) && (obj_ptr < data_end)) + /* The whole range has to lie inside the module's data, not just its first + address, and the subtraction is done on the end rather than the start so that + a size which would carry the range past the top of the address space cannot + wrap into an accepting answer. */ + if (((ULONG) obj_ptr >= data_start) && (obj_size <= (data_end - (ULONG) obj_ptr))) + { + return(TX_TRUE); + } + + return(TX_FALSE); +} + + +/* Outside is not the negation of inside: a range that only partly reaches into the + module's data is neither. It has to end before that data begins or begin after it + ends, and a size that would carry the end past the top of the address space is + refused rather than allowed to compare as though it had not. */ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size) +{ + +ULONG data_start; +ULONG data_end; + + + (VOID) module_instance; + + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; + data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); + + if (obj_size > (~((ULONG) 0) - (ULONG) obj_ptr)) + { + return(TX_FALSE); + } + + if ((((ULONG) obj_ptr + obj_size) <= data_start) || ((ULONG) obj_ptr >= data_end)) { return(TX_TRUE); } diff --git a/test/tx/module_manager/threadx_module_manager_live_object_deallocation_test.c b/test/tx/module_manager/threadx_module_manager_live_object_deallocation_test.c index 3eaf95c6..a351ce3b 100644 --- a/test/tx/module_manager/threadx_module_manager_live_object_deallocation_test.c +++ b/test/tx/module_manager/threadx_module_manager_live_object_deallocation_test.c @@ -318,17 +318,56 @@ UINT _txe_mutex_put(TX_MUTEX *mutex_ptr) /* Stand in for the module port's data range check. */ -UINT _txm_module_manager_inside_data_check(ULONG obj_ptr) +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size, UINT write_request) { ULONG data_start; ULONG data_end; + (VOID) module_instance; + (VOID) write_request; + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); - if ((obj_ptr >= data_start) && (obj_ptr < data_end)) + /* The whole range has to lie inside the module's data, not just its first + address, and the subtraction is done on the end rather than the start so that + a size which would carry the range past the top of the address space cannot + wrap into an accepting answer. */ + if (((ULONG) obj_ptr >= data_start) && (obj_size <= (data_end - (ULONG) obj_ptr))) + { + return(TX_TRUE); + } + + return(TX_FALSE); +} + + +/* Outside is not the negation of inside: a range that only partly reaches into the + module's data is neither. It has to end before that data begins or begin after it + ends, and a size that would carry the end past the top of the address space is + refused rather than allowed to compare as though it had not. */ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size) +{ + +ULONG data_start; +ULONG data_end; + + + (VOID) module_instance; + + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; + data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); + + if (obj_size > (~((ULONG) 0) - (ULONG) obj_ptr)) + { + return(TX_FALSE); + } + + if ((((ULONG) obj_ptr + obj_size) <= data_start) || ((ULONG) obj_ptr >= data_end)) { return(TX_TRUE); } diff --git a/test/tx/module_manager/threadx_module_manager_object_allocate_overflow_test.c b/test/tx/module_manager/threadx_module_manager_object_allocate_overflow_test.c index 5989ca48..38314ba4 100644 --- a/test/tx/module_manager/threadx_module_manager_object_allocate_overflow_test.c +++ b/test/tx/module_manager/threadx_module_manager_object_allocate_overflow_test.c @@ -148,14 +148,30 @@ ULONG _tx_timer_created_count; /* This test models no module memory of its own, so no address is ever inside the calling module's data. */ -UINT _txm_module_manager_inside_data_check(ULONG obj_ptr) +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size, UINT write_request) { + (VOID) module_instance; (VOID) obj_ptr; + (VOID) obj_size; + (VOID) write_request; return(TX_FALSE); } +/* No module data is modelled here, so every range lies outside it. */ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size) +{ + (VOID) module_instance; + (VOID) obj_ptr; + (VOID) obj_size; + + return(TX_TRUE); +} + + /* Not reached from the allocation path under test. */ VOID _txm_module_manager_alignment_adjust(TXM_MODULE_PREAMBLE *module_preamble, ULONG *code_size, ULONG *code_alignment, ULONG *data_size, ULONG *data_alignment) diff --git a/test/tx/module_manager/threadx_module_manager_object_authentication_test.c b/test/tx/module_manager/threadx_module_manager_object_authentication_test.c index 6c79efae..396f57f2 100644 --- a/test/tx/module_manager/threadx_module_manager_object_authentication_test.c +++ b/test/tx/module_manager/threadx_module_manager_object_authentication_test.c @@ -330,17 +330,56 @@ UINT _txe_mutex_put(TX_MUTEX *mutex_ptr) /* Stand in for the module port's data range check, which the portable outside-the-module test is built on. */ -UINT _txm_module_manager_inside_data_check(ULONG obj_ptr) +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size, UINT write_request) { ULONG data_start; ULONG data_end; + (VOID) module_instance; + (VOID) write_request; + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); - if ((obj_ptr >= data_start) && (obj_ptr < data_end)) + /* The whole range has to lie inside the module's data, not just its first + address, and the subtraction is done on the end rather than the start so that + a size which would carry the range past the top of the address space cannot + wrap into an accepting answer. */ + if (((ULONG) obj_ptr >= data_start) && (obj_size <= (data_end - (ULONG) obj_ptr))) + { + return(TX_TRUE); + } + + return(TX_FALSE); +} + + +/* Outside is not the negation of inside: a range that only partly reaches into the + module's data is neither. It has to end before that data begins or begin after it + ends, and a size that would carry the end past the top of the address space is + refused rather than allowed to compare as though it had not. */ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size) +{ + +ULONG data_start; +ULONG data_end; + + + (VOID) module_instance; + + data_start = (ULONG) (ALIGN_TYPE) test_module_data.test_data_bytes; + data_end = data_start + ((ULONG) TEST_MODULE_MEMORY_BYTES); + + if (obj_size > (~((ULONG) 0) - (ULONG) obj_ptr)) + { + return(TX_FALSE); + } + + if ((((ULONG) obj_ptr + obj_size) <= data_start) || ((ULONG) obj_ptr >= data_end)) { return(TX_TRUE); } diff --git a/test/tx/module_manager/threadx_module_manager_thread_kernel_stack_test.c b/test/tx/module_manager/threadx_module_manager_thread_kernel_stack_test.c index e971b159..1ab91145 100644 --- a/test/tx/module_manager/threadx_module_manager_thread_kernel_stack_test.c +++ b/test/tx/module_manager/threadx_module_manager_thread_kernel_stack_test.c @@ -252,15 +252,30 @@ ULONG index; /* Stand in for the module port's data-range check. Nothing here is inside a module's own data, so every address the manager asks about is outside it. */ -UINT _txm_module_manager_inside_data_check(ULONG obj_ptr) +UINT _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size, UINT write_request) { - + (VOID) module_instance; (VOID) obj_ptr; + (VOID) obj_size; + (VOID) write_request; return(TX_FALSE); } +/* No module data is modelled here, so every range lies outside it. */ +UINT _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr, + ULONG obj_size) +{ + (VOID) module_instance; + (VOID) obj_ptr; + (VOID) obj_size; + + return(TX_TRUE); +} + + /* Stand in for the module port's alignment adjustment. It is referenced by a loading path this test does not drive, and is never reached from here. */ VOID _txm_module_manager_alignment_adjust(TXM_MODULE_PREAMBLE *module_preamble, ULONG *code_size,