mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
Merge commit from fork
A memory-protected module names the kernel objects it wants operated on by address, and the Module Manager decided whether a privileged service could dereference that address by asking only whether it fell outside the module. The object pool is outside every module, so that test was satisfied by an address shifted into the interior of one of the module's own privileged allocations, which denotes no object at all. The bytes such an address presents as a control block are bytes the module put there through ordinary create and set services, so the control block ID at the front of them could be made to read as any type the module chose, and the _txe_ layer's ID test then agreed. A module could therefore have the kernel read and write fields of an object that does not exist, at an address it picked; the reported chain reaches a privileged memset across an attacker-chosen range that way. Authentication now answers the question the location test could not: is this the exact address of a live kernel object of the type this service expects. It is answered from the kernel's created list for the type, which the create and delete services maintain, so membership establishes at once that the address is an object start rather than an address inside an object, that the object is of this type, and that it has not been deleted. That list is also what makes an object the application created and shared with a module authenticable at all, since the manager allocated no such object and has no record of its own to consult, so sharing keeps working and needs no new registration API. Nothing a module can influence is used to establish a type. An earlier form of this fix accepted an address that was the exact start of one of the calling module's own allocations of the right size and then took the type from the control block ID, which is cheaper -- a module's allocation list is much shorter than the system's created list for a type. The tests here refused it: a byte pool, a mutex and a timer are all the same size on a 32-bit target, so an allocation created as one of them and presented as another passed both the address and the size test, leaving the ID as the only thing between the module and a type confusion. The ID is still checked, after the created list has settled the question, because it is the test the _txe_ services make and it is compiled away with them under TX_DISABLE_ERROR_CHECKING. An address a module named is not read at all until a kernel record says there is an object there. All 58 object-using dispatchers now pass the object type rather than a control block size, since a size cannot establish a type. Both scans are bounded by the counts the kernel and the manager maintain beside their lists, so the cost of one check is bounded and a list whose links have been damaged cannot make a scan run on, and both run with interrupts disabled, which is the protection those lists are maintained under and which adds no blocking point or priority inversion to a kernel request. The cost is a walk of the created list for the type, paid only by memory-protected modules; the size-based check is kept for dispatchers outside this repository and hardened to refuse object pool interiors, which is the part of the attack it can see without a type. Two further changes close paths the authentication alone would leave open. Thread reset now refuses a thread that carries no module instance: such a thread is authentic, can be found by name and satisfies reset's own state test, and reset reads the shell entry function out of that instance, so a null one was followed in privileged mode. Object deallocation now clears the control block ID as it gives memory back, so an object freed without being deleted first stops being vouched for at the moment the manager stops owning its memory, rather than returning to the pool still carrying a valid ID for the next allocation placed there to present. The 120 expectations in the new test offer every aligned interior offset of a legitimate object as a foreign type, with that type's own ID planted at the offset, and assert that none is accepted; they cover all eight object types against each other, uncreated allocations, deleted objects, freed addresses that have been handed out again, objects the application owns and memory that merely carries a plausible ID, objects another module allocated, and the bounds on both scans. Reverting the object checks to the permissive policy fails 89 of them; removing the thread reset guard makes the test die with SIGSEGV inside the reset path; removing the ID clearing in deallocation fails 2. All 99 tests pass in each of the five configurations the tree builds with GCC 14. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
**************************************************************************/
|
||||
|
||||
// Some portions generated by Claude Code (Opus 5).
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
@@ -125,6 +127,27 @@ UINT return_value;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the first word of the object being given back, which for every kernel
|
||||
object is its control block ID.
|
||||
|
||||
Object authentication reads that ID to establish the type of an object and
|
||||
that it is still created, having first established that the address is the
|
||||
exact start of an allocation. The kernel clears the ID when it deletes an
|
||||
object, so the ordinary sequence of delete and then deallocate has already
|
||||
cleared it. What has not is a deallocation of an object that was never
|
||||
deleted: the memory returns to the pool still carrying a valid ID, and the
|
||||
next allocation to be placed there is a raw allocation that presents one.
|
||||
Clearing it here means the manager stops vouching for a control block at
|
||||
the moment it stops owning the memory, whatever order the module chose.
|
||||
|
||||
An allocation too small to hold an ID cannot be presenting one, and is
|
||||
left alone rather than written past its end. */
|
||||
if (module_allocated_object_ptr -> txm_module_object_size >= ((ULONG) sizeof(ULONG)))
|
||||
{
|
||||
|
||||
*((ULONG *) object_ptr) = TX_CLEAR_ID;
|
||||
}
|
||||
|
||||
/* Release the object memory. */
|
||||
return_value = (ULONG) _txe_byte_release((VOID *) module_allocated_object_ptr);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
**************************************************************************/
|
||||
|
||||
// Some portions generated by Claude Code (Opus 5).
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
@@ -106,6 +108,20 @@ TXM_MODULE_THREAD_ENTRY_INFO *thread_entry_info;
|
||||
status = TX_NOT_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resetting a thread means rebuilding its stack around a module's shell entry
|
||||
function, so the thread has to be one a module manager created. A thread the
|
||||
application created carries no module instance, and the fields this function
|
||||
goes on to read from it -- the shell entry function it builds the new stack
|
||||
frame around -- would be read through a null pointer in privileged mode. A
|
||||
module can name such a thread: any thread the system created can be found by
|
||||
name, and one that has run to completion satisfies the state test above. */
|
||||
if (thread_ptr -> tx_thread_module_instance_ptr == TX_NULL)
|
||||
{
|
||||
|
||||
/* Not a module thread, so there is nothing here to reset. */
|
||||
status = TX_NOT_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Is the request valid? */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user