From a9bd72de21ab5554dba77f3c61f9756af7b6070c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Mon, 28 Sep 2026 11:31:41 -0400 Subject: [PATCH] Merge commit from fork * Fixed the Cortex-A7 module data check to validate whole ranges The Cortex-A7 module port received a module instance, a start address and a byte size from the common Module Manager, then discarded the instance and the size and asked the MMU to translate the start address alone, for reading only. A range was therefore accepted whenever its first byte happened to be readable by the module, so privileged dispatch code could read or write past the end of the module's mapping, or use read-only module code as a write destination. The check now takes the size and an access intent. The module's own data region is answered from the manager's records, which name it exactly and cost no translations; everything else is answered by translating every page the range touches with the requested unprivileged access. Empty ranges, ranges whose last byte would wrap, and ranges that leave the recorded data region partway through are all refused, and the walk is bounded by TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES so one module request cannot impose unbounded work on the kernel. Translation is only believed while the requesting module's own context is loaded. The outside direction gets its own check rather than negating the inside one: a range that reaches into the module only partway is inside neither answer, and negating a whole-range check would have let it pass as a kernel object. Other ports keep their single data check through backward-compatible fallbacks in the common header; their preprocessed dispatch output is byte-identical. Regression coverage runs on the host by standing a simulated page map behind the one architecture primitive, and reaches 100% line, branch and call coverage of the new logic. Twenty-four of its expectations fail against the previous implementation. Assisted-by: Claude Code (Opus 5) * Drove the Cortex-A7 range check through a live MMU The host test for this check stands a simulated page map behind the port's CP15 primitive, so it never executes an address translation. That leaves the part most easily got wrong unexercised: an encoding naming the wrong operation, or a PAR fault bit read the wrong way round, passes it without complaint. This adds a bare-metal image that builds a short-descriptor translation table, enables the MMU, and drives the real check through the real translations on a real Cortex-A7 translation regime, plus a script that builds and runs it under an emulator. Sections are mapped for unprivileged read/write, unprivileged read only, and privileged only, with an unmapped section behind the read-only one so that a range can be made to leave its mapping partway through. That last case is the one the replaced check accepted, and the test asserts both answers against the same live MMU: the current check rejects the range, and translating only its first address accepts it. It also confirms what the simulated map could only assume, that ATS1CUW denies a write to a read-only mapping while ATS1CUR allows the read. The write intent is the reason the port asks for two translations rather than one. The script skips with a notice when the cross toolchain or the emulator is absent, so a machine without them does not fail the build. Assisted-by: Claude Code (Opus 5) --- .../inc/txm_module_manager_util.h | 32 +- .../ac5/example_build/build_threadx.bat | 3 +- .../cortex_a7/ac5/inc/txm_module_port.h | 44 +- .../txm_module_manager_inside_data_check.c | 538 ++++++++++++++++++ .../txm_module_manager_mm_register_setup.c | 115 +++- .../gnu/example_build/build_threadx.bat | 3 +- .../cortex_a7/gnu/inc/txm_module_port.h | 44 +- .../txm_module_manager_inside_data_check.c | 538 ++++++++++++++++++ .../txm_module_manager_mm_register_setup.c | 115 +++- .../cortex_a7/iar/example_build/tx.ewp | 3 + .../cortex_a7/iar/inc/txm_module_port.h | 44 +- .../txm_module_manager_inside_data_check.c | 538 ++++++++++++++++++ .../txm_module_manager_mm_register_setup.c | 115 +++- scripts/check_module_mmu_a7.sh | 120 ++++ test/tx/cmake/module_manager/CMakeLists.txt | 16 + ...hreadx_module_manager_cortex_a7_mmu_test.c | 205 +++++++ ...odule_manager_cortex_a7_range_check_test.c | 443 ++++++++++++++ ...adx_module_manager_delete_ownership_test.c | 43 +- ...le_manager_live_object_deallocation_test.c | 43 +- ...le_manager_object_allocate_overflow_test.c | 18 +- ...odule_manager_object_authentication_test.c | 43 +- ..._module_manager_thread_kernel_stack_test.c | 19 +- 22 files changed, 3023 insertions(+), 59 deletions(-) create mode 100644 ports_module/cortex_a7/ac5/module_manager/src/txm_module_manager_inside_data_check.c create mode 100644 ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_inside_data_check.c create mode 100644 ports_module/cortex_a7/iar/module_manager/src/txm_module_manager_inside_data_check.c create mode 100755 scripts/check_module_mmu_a7.sh create mode 100644 test/tx/module_manager/threadx_module_manager_cortex_a7_mmu_test.c create mode 100644 test/tx/module_manager/threadx_module_manager_cortex_a7_range_check_test.c 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,