Skip to content

Prepared the 6.5.2.202603b security hotfix release - #827

Merged
fdesbiens merged 10 commits into
masterfrom
release-6.5.2.202603b-preparation
Nov 1, 2026
Merged

fdesbiens merged 10 commits into
masterfrom
release-6.5.2.202603b-preparation

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

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_arch source the Cortex-M3, M4 and M7 module ports are generated from (also proposed to dev), and the version pass by scripts/prepare_release.sh: THREADX_HOTFIX_VERSION becomes 'b' and 212 port headers advertise 6.5.2.202603b. Merging releases:

CVE CVSS-B Component
CVE-2026-115231 8.5 High Module Manager callback request queues
CVE-2026-102710 8.5 High Module Manager trace-full notification
CVE-2026-102709 8.4 High TrustZone-M secure stack services
CVE-2026-113421 7.3 High Module Manager trace-full notification
CVE-2026-115653 7.0 High Cortex-M module ports, PendSV
CVE-2026-102711 6.9 Medium Module Manager loaders
CVE-2026-115654 6.9 Medium Module Manager thread control
CVE-2026-118052 6.8 Medium Module Manager loaders

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.sh passes. Not run on hardware.

…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>
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Test Results FreeRTOS

3 tests  ±0   3 ✅ ±0   0s ⏱️ ±0s
1 suites ±0   0 💤 ±0 
1 files   ±0   0 ❌ ±0 

Results for commit c2950ee. ± Comparison against base commit 56c8695.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Test Results RISC-V

1 158 tests  ±0   1 158 ✅ ±0   6m 9s ⏱️ +20s
   12 suites ±0       0 💤 ±0 
   12 files   ±0       0 ❌ ±0 

Results for commit c2950ee. ± Comparison against base commit 56c8695.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Test Results ThreadX

785 tests  +20   785 ✅ +20   3m 26s ⏱️ +10s
  7 suites ± 0     0 💤 ± 0 
  7 files   ± 0     0 ❌ ± 0 

Results for commit c2950ee. ± Comparison against base commit 56c8695.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Health
common.src 99% 77% ➖
Summary 99% (4658 / 4687) 77% (2607 / 3374) ➖

Minimum allowed line rate is 99%

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Test Results SMP

590 tests  ±0   590 ✅ ±0   7m 22s ⏱️ + 1m 29s
  5 suites ±0     0 💤 ±0 
  5 files   ±0     0 ❌ ±0 

Results for commit c2950ee. ± Comparison against base commit 56c8695.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Health
common_smp.src 100% 81% ➖
Summary 100% (5455 / 5468) 81% (3052 / 3788) ➖

Minimum allowed line rate is 99%

@fdesbiens
fdesbiens merged commit b2868c3 into master Nov 1, 2026
27 checks passed
@fdesbiens
fdesbiens deleted the release-6.5.2.202603b-preparation branch November 1, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant