Summary
The Cortex-A7 MMU Modules port validates a caller-supplied buffer by translating one address — the first byte. The common Module Manager hands its range check the module instance, the start address and the complete byte size; the Cortex-A7 implementation discarded the instance and the size and asked the MMU a single question about the start.
A memory-protected module therefore passed any range whose first byte was user-readable, however far the rest of it reached. Privileged dispatch code then read or wrote the whole range. Because every protected-module dispatcher that consumes a buffer or an extra-parameter array goes through this one macro, the defect is not in any individual service: it is in the primitive all of them rely on.
Details
TXM_MODULE_MANAGER_CHECK_INSIDE_DATA is the port's answer to "may this module touch these bytes?". The Cortex-A7 version called _txm_module_manager_inside_data_check((ULONG)obj_ptr), which performs a single ATS1CUR translation.
That operation tests an unprivileged read at one address. It establishes nothing about the last byte, the 4 KiB pages in between, integer wraparound of obj_ptr + obj_size, ownership of the memory by the requesting module, or unprivileged write permission. The read intent is the wrong question for TXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE, whose contract requires writable data or shared memory rather than merely readable code or data.
Queue receive shows the write-oriented path. A protected module obtains a queue with a multi-word message size and passes a destination whose first byte sits in a readable mapped page but whose full range crosses the page or region end. The dispatcher computes the byte count and calls PARAM_CHECK_BUFFER_WRITE, the Cortex-A7 implementation translates the start address alone and returns true, and _txe_queue_receive then copies the complete message in privileged context — including the bytes past the end of what was checked.
Read-oriented dispatchers have the inverse problem: privileged code reads beyond the module-owned source and returns or enqueues those bytes. Where the neighbouring page is unmapped, the same condition yields a privileged data abort instead.
The reachable surface is wide. Queue send and receive, the information getters, event and pool services, object lookup, and the extra-parameter arrays all pass through the affected macros before privileged code dereferences a module-controlled range.
Impact
A module whose only privilege is the right to make ordinary system calls can drive privileged code into out-of-bounds reads and writes. Depending on the service and the virtual layout, that means disclosure of adjacent privileged data, modification of memory outside the module's writable mapping, modification of read-only module code through a write-output API, or a kernel-mode fault.
The exposure is only as large as what sits next to a module mapping. Where the layout places an unmapped guard after every module region, the reachable consequence is the privileged fault rather than disclosure or corruption.
No race, ISR context, disabled error checking or object corruption is required.
Affected configurations
Cortex-A7 MMU Modules builds — GNU, AC5 or IAR — with TXM_MODULE_MEMORY_PROTECTION. Every other module port carries its own range-complete implementation of the same macro and is unaffected.
The port and the single-address check arrived together in commit 1b5816a2, the 6.1 minor release. Every release from v6.1_rel through v6.5.1.202602a_rel carries it, including both 6.5.1 releases. The 6.0.x releases predate the Cortex-A7 module port entirely.
Severity
CVSS v4.0 base score 8.5 (High) CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
The actor is module code already running on the device, needing syscall access but not privileged CPU access, and no user interaction. A pointer placed near a mapped boundary and an ordinary kernel request are sufficient. The vulnerable condition is the supported memory-protected Modules configuration itself, not an exceptional runtime state. Exploit Maturity was not assessed, so EQ5 takes the v4.0 default of E:A.
Subsequent-system metrics are N: only the ThreadX instance is claimed, and product-level physical effects are deployment-specific.
The all-high vulnerable-system impact assumes a deployment in which a module mapping is adjacent to privileged memory or to another module's. An integrator whose page-table layout guarantees an unmapped guard after every module region should reduce confidentiality and integrity in their Environmental metrics; the availability impact remains either way.
Patches
Fixed in ThreadX 6.5.2.202603.
The check now takes the range size and an access intent. The module's own data region is answered from the Module Manager's records, which name it exactly and cost no address 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 refused. The page walk is bounded by TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES, so one module request cannot impose unbounded work on the kernel, and translation results are trusted only while the requesting module's own address-space context is loaded.
The outside direction has its own check rather than being the negation of the inside one. Negating a range-complete inside check would report a range reaching into the module only partway as being outside it, which _txm_module_manager_param_check_object_for_use would then accept as a kernel object.
Other ports are untouched. The backward-compatible fallbacks in txm_module_manager_util.h keep their single data check, and the preprocessed dispatch output for all 23 other module ports that GCC can preprocess is byte-identical to the unfixed tree.
Verification: host suite 99/99 across all five build configurations under GCC 13.3.0 and GCC 14.2.0; the Cortex-A7 module manager cross-compiles clean under GCC 14.3.1 at -Wall -Wextra -Werror against the port's own shipped headers; the bounded host regression test passes on an emulated Cortex-A7 at every optimisation level and fails 24 expectations against a shim reproducing the pre-fix behaviour; 100% line, branch and call coverage of the new logic.
The CP15 address translation is exercised rather than stubbed. A second test builds a short-descriptor translation table, enables the MMU, and drives the real check through the real translation operations on a real Cortex-A7 translation regime. It confirms what a simulated page map could only assume: ATS1CUW denies a write to an unprivileged read-only mapping while ATS1CUR allows the read, so the write intent does what the fix depends on; a range leaving its mapping partway through is rejected, while translating only its first address accepts it; and privileged-only memory is reachable by neither intent and is reported outside the module. Reverting the page walk to a single page makes exactly the boundary case fail, so the test is known to detect the regression it guards.
Still unverified: these checks run under an emulator, which implements the architecture rather than a particular silicon implementation, so errata, cache and TLB interactions and speculative effects remain outside their reach. The AC5 and IAR builds of the port also still need their native toolchains.
Workarounds
None that preserve the feature. Every path here is reachable only from a module the Module Manager has loaded, so a system that loads only modules it builds itself is not exposed — but that is precisely the assumption module memory protection exists so that you do not have to make.
Credits
Found and fixed by Frédéric Desbiens (@fdesbiens). The analysis, fix and regression tests were produced with AI assistance under human review, in accordance with the Eclipse Foundation's Generative AI Usage Guidelines.
Summary
The Cortex-A7 MMU Modules port validates a caller-supplied buffer by translating one address — the first byte. The common Module Manager hands its range check the module instance, the start address and the complete byte size; the Cortex-A7 implementation discarded the instance and the size and asked the MMU a single question about the start.
A memory-protected module therefore passed any range whose first byte was user-readable, however far the rest of it reached. Privileged dispatch code then read or wrote the whole range. Because every protected-module dispatcher that consumes a buffer or an extra-parameter array goes through this one macro, the defect is not in any individual service: it is in the primitive all of them rely on.
Details
TXM_MODULE_MANAGER_CHECK_INSIDE_DATAis the port's answer to "may this module touch these bytes?". The Cortex-A7 version called_txm_module_manager_inside_data_check((ULONG)obj_ptr), which performs a singleATS1CURtranslation.That operation tests an unprivileged read at one address. It establishes nothing about the last byte, the 4 KiB pages in between, integer wraparound of
obj_ptr + obj_size, ownership of the memory by the requesting module, or unprivileged write permission. The read intent is the wrong question forTXM_MODULE_MANAGER_PARAM_CHECK_BUFFER_WRITE, whose contract requires writable data or shared memory rather than merely readable code or data.Queue receive shows the write-oriented path. A protected module obtains a queue with a multi-word message size and passes a destination whose first byte sits in a readable mapped page but whose full range crosses the page or region end. The dispatcher computes the byte count and calls
PARAM_CHECK_BUFFER_WRITE, the Cortex-A7 implementation translates the start address alone and returns true, and_txe_queue_receivethen copies the complete message in privileged context — including the bytes past the end of what was checked.Read-oriented dispatchers have the inverse problem: privileged code reads beyond the module-owned source and returns or enqueues those bytes. Where the neighbouring page is unmapped, the same condition yields a privileged data abort instead.
The reachable surface is wide. Queue send and receive, the information getters, event and pool services, object lookup, and the extra-parameter arrays all pass through the affected macros before privileged code dereferences a module-controlled range.
Impact
A module whose only privilege is the right to make ordinary system calls can drive privileged code into out-of-bounds reads and writes. Depending on the service and the virtual layout, that means disclosure of adjacent privileged data, modification of memory outside the module's writable mapping, modification of read-only module code through a write-output API, or a kernel-mode fault.
The exposure is only as large as what sits next to a module mapping. Where the layout places an unmapped guard after every module region, the reachable consequence is the privileged fault rather than disclosure or corruption.
No race, ISR context, disabled error checking or object corruption is required.
Affected configurations
Cortex-A7 MMU Modules builds — GNU, AC5 or IAR — with
TXM_MODULE_MEMORY_PROTECTION. Every other module port carries its own range-complete implementation of the same macro and is unaffected.The port and the single-address check arrived together in commit
1b5816a2, the 6.1 minor release. Every release fromv6.1_relthroughv6.5.1.202602a_relcarries it, including both 6.5.1 releases. The 6.0.x releases predate the Cortex-A7 module port entirely.Severity
CVSS v4.0 base score 8.5 (High)
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NThe actor is module code already running on the device, needing syscall access but not privileged CPU access, and no user interaction. A pointer placed near a mapped boundary and an ordinary kernel request are sufficient. The vulnerable condition is the supported memory-protected Modules configuration itself, not an exceptional runtime state. Exploit Maturity was not assessed, so EQ5 takes the v4.0 default of
E:A.Subsequent-system metrics are
N: only the ThreadX instance is claimed, and product-level physical effects are deployment-specific.The all-high vulnerable-system impact assumes a deployment in which a module mapping is adjacent to privileged memory or to another module's. An integrator whose page-table layout guarantees an unmapped guard after every module region should reduce confidentiality and integrity in their Environmental metrics; the availability impact remains either way.
Patches
Fixed in ThreadX 6.5.2.202603.
The check now takes the range size and an access intent. The module's own data region is answered from the Module Manager's records, which name it exactly and cost no address 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 refused. The page walk is bounded by
TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES, so one module request cannot impose unbounded work on the kernel, and translation results are trusted only while the requesting module's own address-space context is loaded.The outside direction has its own check rather than being the negation of the inside one. Negating a range-complete inside check would report a range reaching into the module only partway as being outside it, which
_txm_module_manager_param_check_object_for_usewould then accept as a kernel object.Other ports are untouched. The backward-compatible fallbacks in
txm_module_manager_util.hkeep their single data check, and the preprocessed dispatch output for all 23 other module ports that GCC can preprocess is byte-identical to the unfixed tree.Verification: host suite 99/99 across all five build configurations under GCC 13.3.0 and GCC 14.2.0; the Cortex-A7 module manager cross-compiles clean under GCC 14.3.1 at
-Wall -Wextra -Werroragainst the port's own shipped headers; the bounded host regression test passes on an emulated Cortex-A7 at every optimisation level and fails 24 expectations against a shim reproducing the pre-fix behaviour; 100% line, branch and call coverage of the new logic.The CP15 address translation is exercised rather than stubbed. A second test builds a short-descriptor translation table, enables the MMU, and drives the real check through the real translation operations on a real Cortex-A7 translation regime. It confirms what a simulated page map could only assume:
ATS1CUWdenies a write to an unprivileged read-only mapping whileATS1CURallows the read, so the write intent does what the fix depends on; a range leaving its mapping partway through is rejected, while translating only its first address accepts it; and privileged-only memory is reachable by neither intent and is reported outside the module. Reverting the page walk to a single page makes exactly the boundary case fail, so the test is known to detect the regression it guards.Still unverified: these checks run under an emulator, which implements the architecture rather than a particular silicon implementation, so errata, cache and TLB interactions and speculative effects remain outside their reach. The AC5 and IAR builds of the port also still need their native toolchains.
Workarounds
None that preserve the feature. Every path here is reachable only from a module the Module Manager has loaded, so a system that loads only modules it builds itself is not exposed — but that is precisely the assumption module memory protection exists so that you do not have to make.
Credits
Found and fixed by Frédéric Desbiens (@fdesbiens). The analysis, fix and regression tests were produced with AI assistance under human review, in accordance with the Eclipse Foundation's Generative AI Usage Guidelines.