Files
threadx/common_modules/module_manager
Frédéric Desbiens ee4c2fb6c7 Merge commit from fork
A memory-protected module's object lookup arrives at the Module Manager's dispatch
layer, which decides how much of the module's name buffer the privileged comparison
may touch. It checked one byte.

_txm_module_manager_object_name_compare reads a character from each name at the top
of every iteration and tests the remaining search length at the bottom, so a declared
length N permits reads at offsets 0 through N: N+1 bytes, the extra one being the
terminator a length excludes. The extended service receives that length in its extra
parameter array, validates the array correctly, and then never uses the length to
bound the buffer it describes. The deprecated service carries no length at all; its
manager wrapper calls the same implementation with the largest value a UINT can hold,
so it removes the bound rather than lacking one.

The comparison stops at the first differing character, so a walk past the end of the
module's memory looks as though it needs the bytes there to match a name the module
chose. It does not. A create service stores the name pointer a module supplies in the
control block rather than copying the string, so a module can register an object whose
name is the very buffer it then searches for. Both sides of the comparison are then
the same address, every character matches by construction, and the walk continues
until it meets a terminator in memory the module does not own and cannot see. That
walk runs in privileged mode with _tx_thread_preempt_disable raised, and none of the
27 memory fault handlers lowers it again, so a fault on it costs more than the
requesting thread.

The fix validates the range the comparison may reach. The extended dispatcher now
checks the name over name_length + 1 bytes, refusing a length whose range cannot be
expressed, and the extra parameter array is checked first because the length comes out
of it. The deprecated dispatcher refuses a memory-protected module outright, because
no range can be derived from a pointer alone; a module without protection is unchanged,
as it is for every other check in this layer.

_txm_module_manager_object_name_compare is left as it is. Reading N+1 bytes for a
declared length of N is the documented contract, and the defect is that the contract
was never checked.

The regression test drives both lookup dispatchers and measures two extents rather than
sampling either side of a boundary, because the finding is the relationship between
them. It anchors the name buffer to the end of one of the module's regions and walks it
backwards to find the smallest room the dispatcher accepts, which is the extent
validated; and it fills memory with a filler character, plants the only terminator at a
chosen depth, and asks for an object named with exactly the bytes up to it, so that the
deepest depth the lookup can be made to return from is the extent read. Against dev's
copy of the two headers the test exits 1 with 408 failed expectations of 1130: a
31-character name with one byte of room is accepted and read 31 bytes past the region,
and the aliased walk reaches every depth offered. With the fix it exits 0 with 1490
expectations and none failed.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 10:58:58 -04:00
..
2026-09-28 10:58:58 -04:00
2026-09-28 10:37:13 -04:00