Repository navigation
Prepared the 6.5.2.202603b security hotfix release - #827
Merged
Merged
Conversation
…821) No Cortex-M module port ran in CI. gcc_check compiles each port's module manager sources and links nothing that loads a module, and the host tests under test/tx/module_manager compile a few portable sources against stubs. A module's start-up, its kernel calls through the supervisor call, the MPU programming on context switches and the fault path that terminates a misbehaving module were exercised only on the Cortex-R52 FVP. test/tx/cmake/module_qemu is a stand-alone CMake project, like test/tx/cmake/riscv, that builds the kernel and Module Manager with a module port's headers, builds test modules position-independent with the port's module linker script, embeds their raw images in manager images, and registers each image with CTest. A Python runner starts it on QEMU's MPS2 board for the core, mps2-an385 for cortex_m3 and mps2-an386 for cortex_m4 built for its FPU with the hard-float ABI, and judges the result line the image prints over semihosting and the exit status it sets. Three images per port: a protected module loaded, started, exercising queues, semaphores, a worker thread and the timer, stopped and unloaded twice with its memory returned; the same module faulting on a read of kernel memory, reported to the fault notification with its module, thread and address and terminated, then stopped and unloaded; and the module built privileged and unprotected, whose identical read succeeds. The fault image is also the check that QEMU's MPU enforces at all. The new module_qemu workflow runs both ports on changes to common/, common_modules/, the two module ports and the harness, installing QEMU from the Ubuntu archive through the bounded apt helpers in scripts/tx_ci_common.sh. Both ports pass 3/3 on QEMU 8.2.2 with Arm GNU 14.3.rel1, using the workflow's commands, with zero compiler warnings; configure, build and run take 9 seconds for both. The AI disclosure check passes. The workflow itself has not run on GitHub yet. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
The Module Manager forwarded tx_trace_buffer_full_notify from any module straight to _tx_trace_buffer_full_notify. The kernel stores that pointer and calls it directly from privileged code when the trace buffer wraps, with no trampoline, so on a build with TX_ENABLE_EVENT_TRACE a user-mode module could run its own code with kernel privilege. The stack-error notify and the other trace services already refuse user-mode modules. The dispatcher now returns TXM_MODULE_INVALID_PROPERTIES to a user-mode module without reaching the kernel service, for a null callback as well as a function. Privileged modules register as before. The new threadx_module_manager_trace_buffer_full_notify_test passes 27/27 with the fix and fails 12 expectations without it. Module Manager tests pass 10/10 in all five non-MISRA host configurations, and the changed lines are covered 5/5, branches 2/2. The full suite passes 120/120 in trace_build and 119/120 in default_build_coverage, where threadx_timer_information_test fails intermittently on an unchanged binary that does not include the changed header. The kernel dispatch source cross-compiles for eight GNU module ports with no errors and one warning fewer than dev. Not run on hardware. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
Every started module has a callback request queue in its module instance. The Module Manager posts a callback message to it when one of the module's timers or notifications fires, and the module's callback thread calls the function the message names, on that thread and with that module's privilege. The queue is a live queue outside every module, so the queue dispatchers' object check accepted it: a memory-protected module could find another module's callback request queue through tx_queue_info_get and post a message naming its own function, which the other module's callback thread then ran with that module's privilege. It could also receive from or flush the queue, or attach a send notification to it. The Module Manager now names each callback request queue with a manager-owned string. A memory-protected module can only name a queue it creates with a string inside the module, so a queue carrying that pointer is one the manager created. The send, front-send, flush, prioritize and send-notify dispatchers refuse any such queue with TXM_MODULE_INVALID_MEMORY, and the receive dispatcher refuses any but the caller's own, which its callback thread receives from. Queues created by the application or by a module, and modules without memory protection, are unaffected. The check is one pointer comparison on each queue request. The new threadx_module_manager_callback_request_queue_test passes 93/93 and fails 24 expectations against the unfixed dispatchers; reverting any one dispatcher fails its own cases. Changed dispatcher lines and branches are covered 13/13 and 14/14; the name argument in _txm_module_manager_start is not reached by any host test. On QEMU's MPS2 AN385 model with the Cortex-M3 GNU module port, a user-mode module posted a forged callback to a privileged module's callback request queue: unfixed, its function ran on that module's callback thread with CONTROL 0x2 and read a kernel-only word; fixed, the send, receive and flush were refused with 0xF4 and nothing ran. The tx suite passes 600/600 across five configurations and 212/212 across the two MISRA configurations; threadx_timer_multiple_accuracy_test failed once in misra_build and passed 3/3 when rerun alone. scripts/check_gcc.sh (Arm GNU 14.3.rel1) and scripts/check_clang.sh (ATfE 22.1.0) pass every stage with zero warnings, including the module manager sources of all ten Arm module ports. The AI disclosure check passes. Not run on hardware. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
The secure stack services the non-secure world calls through non-secure-callable entry points, _tx_thread_secure_mode_stack_allocate, _tx_thread_secure_mode_stack_free, _tx_thread_secure_stack_context_save and _tx_thread_secure_stack_context_restore, read and wrote through their thread_ptr argument without checking where it pointed. Running in secure state, they accessed whatever address the non-secure caller supplied, including secure memory: allocate's result showed whether a chosen secure word was zero and wrote a context index into it, free could clear it again, and restore and save reprogrammed the secure stack pointer and limit from the word. In free, save and restore the first read was a variable initialiser, ahead of even the caller-context check. Each service now checks thread_ptr with cmse_check_address_range for a non-secure range the size of TX_THREAD that the caller may read and write, before any access. Allocate and free return TX_PTR_ERROR; save and restore return without acting, as they already do for an invalid caller; the initialiser reads follow the check. The check is compiled only for secure-state code built with the security extension (__ARM_FEATURE_CMSE bit 1, that is -mcmse); without it there is no non-secure caller and every pointer passes, so builds that do not use TrustZone, such as the port example builds, are unchanged. The change is made in ports_arch/ARMv8-M and regenerated into the Cortex-M33, M52, M55 and M85 ports with scripts/copy_armv8_m.sh, and applied identically to the Cortex-M23 ports and the Cortex-M23 and Cortex-M33 module ports, which have no generator: 24 files across GNU, AC6 and IAR. scripts/check_trustzone_secure_stack.sh builds test/tx/trustzone's secure image with the Cortex-M33 GNU port and runs it on QEMU's MPS2 AN505 with the SAU and an SSRAM memory protection controller set up so one region is non-secure. It passes 22/22. Against the original file it fails both allocate expectations and takes a secure bus fault on free; with each of the four functions reverted alone, that function's cases fail. Measured with gcov on the emulator, the changed lines are covered 27/27 and their branches 24/24; the two-line branch for builds without the security extension is compile-checked only. scripts/check_ports.sh passes. Every GNU variant compiles with Arm GNU 14.3.rel1 with and without -mcmse, with the same warnings as before; the Cortex-M33 and ports_arch AC6 variants compile with ATfE 22.1.0 and -mcmse with no warnings. IAR was not compiled. Not run on hardware. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
On the Cortex-M module ports, PendSV saved a preempted thread's r4-r11, EXC_RETURN and, with the FPU, s16-s31 below its PSP with privileged stores. A user-mode thread of a memory-protected module sets its own PSP, so it could have up to 100 bytes of register values it chose written to manager memory or to the Module Manager's pool. When the outgoing thread is in user mode and its module is memory-protected, PendSV now saves through PSP only if PSP is within the thread's stack and the stores fit above its start; otherwise it saves the context on the thread's kernel stack, which the module cannot reach, so the thread faults when it resumes and is terminated. This applies to the Cortex-M0+, M3, M4, M7, M23 and M33 schedulers for every toolchain. Kernel-mode threads skip the check after loading one flag. A new QEMU harness test moves two protected-module threads' PSP to just above the bottom of the shared area and of the module's data. Without the fix, 9 words below each region changed on Cortex-M3 and 25 on Cortex-M4F, and no thread faulted; with it, nothing changed and both threads were terminated. Harness: 4/4 on cortex_m3 and cortex_m4. check_gcc.sh and check_clang.sh pass; AC6 files assemble with ATfE 22.1.0; tx suite 807/807. IAR and AC5 not assembled; M0+, M7, M23 and M33 not run. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
The suspend, resume, terminate, reset, priority-change, preemption-change, time-slice and wait-abort dispatchers only checked that a memory-protected module named a live thread outside itself. Every application thread and every other module's thread qualifies, so a module could stop or reschedule any thread in the system. For memory-protected modules, these dispatchers now refuse a thread whose tx_thread_module_instance_ptr is not the calling module with TXM_MODULE_INVALID, as tx_thread_entry_exit_notify already does. A protected module can no longer control application threads or another module's threads; such designs need a shared synchronisation object. Modules without memory protection are unaffected. threadx_module_manager_thread_control_ownership_test passes 115/115; restoring any one dispatcher fails its four cases. The new branches are 16/16 covered. On QEMU mps2-an385 every cross-owner call now returns TXM_MODULE_INVALID. tx: 120/120 in five configurations, 106/106 in two; check_gcc and check_clang pass. Not run on hardware. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
The loaders trusted the module preamble. None of them knew the length of the image they were given, so a preamble whose code_size exceeded the image made the in-place and absolute loaders read and register memory past it, and a truncated image was read for its preamble before anything was checked. The shell, start, stop and callback entries were only checked for being non-zero offsets: an entry, or an entry moved by the port's compiler adjustment, could point outside the module's code, and a zero callback entry was accepted. The declared preamble size was never compared with the code size. Separately, each size addition in the shared loader and in the absolute loader returned TXM_MODULE_MATH_OVERFLOW with the manager mutex still held, and the absolute loader did the same when the code allocation size overflowed, leaving every later Module Manager call blocked. The file loader cleared the caller's instance before the already-loaded check, unlinking a loaded module, and the in-place loader released the mutex it did not hold when the image was misaligned. Added txm_module_manager_in_place_load_ext, txm_module_manager_memory_load_ext and txm_module_manager_absolute_load_ext, which take the image length and, before reading the preamble's sizes, return TXM_MODULE_INVALID for an image shorter than the preamble or a code_size larger than the image (TX_PTR_ERROR for a null image). absolute_load_ext takes an optional data area and replaces both absolute loaders. The four length-less loaders are deprecated in favour of these but keep working. Every loader now returns TXM_MODULE_INVALID when code_size is smaller than the preamble, when the preamble size exceeds code_size, when the callback entry is zero, or when any computed entry, after the compiler adjustment, lies outside [code_start, code_start + code_size); the bound uses overflow-safe arithmetic. The absolute loader applies the same bound to its raw entry addresses. Size arithmetic now uses a status-returning helper, so every overflow releases the mutex. The in-place alignment check runs under the mutex inside the shared loader, and the file loader leaves the instance to the shared loader's already-loaded check. The preamble checksum is still not verified, and these checks do not authenticate the module. Added threadx_module_manager_load_bounds_test, which places a guard page after each image and drives all eight loaders: 830/830 expectations pass. Changed lines reach 109/109 line and 86/86 branch coverage. Each check, reverted on its own, fails the test: the length checks and the file loader's instance handling fault on the guard page or loaded list, and the others fail between 1 and 153 expectations. The tx suite passes 120/120 in each of the five standard configurations and 106/106 in both MISRA ones; the GNU and LLVM port checks and the AI disclosure check pass. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
The PendSV fix for user-mode threads of memory-protected modules was made directly in the Cortex-M3, M4 and M7 module ports, which scripts/copy_module_armv7_m.sh generates from ports_arch/ARMv7-M/threadx_modules. Re-running the copy script would have removed the check from those 12 files, and scripts/check_ports.sh failed on it. The same change is now in the four ARMv7-M threadx_modules sources (AC5, AC6, GNU, IAR). The generated ports do not change. Running copy_module_armv7_m.sh leaves all 12 generated files unchanged, and check_ports.sh passes. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Master is at 6.5.2.202603 and lacks the fixes for eight vulnerabilities, merged to dev today from their advisories' private forks. Dev also carries non-security work that is not part of a hotfix.
This branch starts from master and takes only the six fix commits from dev, unchanged, plus the QEMU module harness from #821, which c38r's regression test runs on and which adds no product code. It also applies the PendSV fix to the ARMv7-M
ports_archsource the Cortex-M3, M4 and M7 module ports are generated from (also proposed to dev), and the version pass byscripts/prepare_release.sh:THREADX_HOTFIX_VERSIONbecomes'b'and 212 port headers advertise 6.5.2.202603b. Merging releases:Each cherry-picked commit has the same patch ID as its dev commit. The QEMU module harness passes 4/4 on Cortex-M3 and M4, the ThreadX suite 117/117 in five configurations and 100/100 in two, and
scripts/check_ports.shpasses. Not run on hardware.