Describe the bug
When both TX_ENABLE_STACK_CHECKING and TX_ENABLE_RANDOM_NUMBER_STACK_FILLING are enabled, the random stack fill value stored in the thread control block is cleared later during _tx_thread_create().
The function generates a random 8-bit value, replicates it into the four bytes of
thread_ptr->tx_thread_stack_fill_value, and uses it as the stack fill pattern.
However, later in the same function, the entire TX_THREAD control block is cleared with:
TX_MEMSET(thread_ptr, 0, (sizeof(TX_THREAD)));
This resets:
thread_ptr->tx_thread_stack_fill_value
to zero.
The stack memory still contains the previously generated random pattern, but the thread control block no longer contains the value used to fill the stack.
As a consequence, the stack checking and stack analysis code compares the stack against an incorrect fill value.
Affected configuration
The issue occurs when the following options are enabled:
#define TX_ENABLE_STACK_CHECKING
#define TX_ENABLE_RANDOM_NUMBER_STACK_FILLING
and stack filling is not disabled:
/* TX_DISABLE_STACK_FILLING must not be defined. */
Affected versions
I originally found the issue in Azure RTOS ThreadX 6.4.0.
The same initialization order also appears to be present in the current Eclipse ThreadX source code, including the latest 6.5.x source and the current master branch.
Relevant source file
common/src/tx_thread_create.c
Function:
Current initialization sequence
The current sequence is effectively:
thread_ptr->tx_thread_stack_fill_value =
((ULONG) TX_RAND()) & 0xFFUL;
thread_ptr->tx_thread_stack_fill_value =
thread_ptr->tx_thread_stack_fill_value |
(thread_ptr->tx_thread_stack_fill_value << 8) |
(thread_ptr->tx_thread_stack_fill_value << 16) |
(thread_ptr->tx_thread_stack_fill_value << 24);
TX_MEMSET(stack_start, ((UCHAR) TX_STACK_FILL), stack_size);
/* Later in the same function. */
TX_MEMSET(thread_ptr, 0, (sizeof(TX_THREAD)));
After the last operation:
thread_ptr->tx_thread_stack_fill_value == 0
while the stack contains the random fill pattern.
For example, if the generated byte is 0x37, the stack contains 0x37373737, but the value stored in the thread control block becomes 0x00000000.
Expected behavior
At the end of _tx_thread_create(), thread_ptr->tx_thread_stack_fill_value should contain the exact value used to initialize the unused stack memory.
For example:
thread_ptr->tx_thread_stack_fill_value == 0x37373737UL
when the stack was filled with byte 0x37.
Actual behavior
The random fill pattern is written to the stack, but the corresponding field in the thread control block is subsequently cleared to zero.
The stack analysis routine therefore searches for a pattern different from the one used to initialize the stack.
Suggested fix
A simple fix would be to move:
TX_MEMSET(thread_ptr, 0, (sizeof(TX_THREAD)));
to the beginning of _tx_thread_create(), immediately after the local variable declarations and before any field of thread_ptr is initialized.
The original TX_MEMSET() at its current location should then be removed.
The initialization sequence would become:
TX_MEMSET(thread_ptr, 0, (sizeof(TX_THREAD)));
thread_ptr->tx_thread_stack_fill_value =
((ULONG) TX_RAND()) & 0xFFUL;
thread_ptr->tx_thread_stack_fill_value =
thread_ptr->tx_thread_stack_fill_value |
(thread_ptr->tx_thread_stack_fill_value << 8) |
(thread_ptr->tx_thread_stack_fill_value << 16) |
(thread_ptr->tx_thread_stack_fill_value << 24);
TX_MEMSET(stack_start, ((UCHAR) TX_STACK_FILL), stack_size);
This preserves the current implementation and guarantees that the random fill value remains valid in the thread control block.
An alternative fix would be to preserve the random fill value in a local variable and copy it into the TCB after the TCB has been cleared, but moving the TCB initialization before any field assignment appears simpler.
Reproduction
-
Enable:
#define TX_ENABLE_STACK_CHECKING
#define TX_ENABLE_RANDOM_NUMBER_STACK_FILLING
-
Make TX_RAND() return a deterministic nonzero byte, for example
0x5A.
-
Create a thread using tx_thread_create().
-
Inspect the unused stack memory and:
thread.tx_thread_stack_fill_value
Expected result:
thread.tx_thread_stack_fill_value == 0x5A5A5A5AUL
Actual result:
thread.tx_thread_stack_fill_value == 0x00000000UL
while the stack remains filled with byte 0x5A.
Additional notes
If TX_RAND() returns zero, the problem may be accidentally hidden because both the stack fill pattern and the cleared TCB field are zero.
This does not make the implementation correct and occurs only for that specific random result.
I could not find an existing issue or release note documenting this problem.
Describe the bug
When both
TX_ENABLE_STACK_CHECKINGandTX_ENABLE_RANDOM_NUMBER_STACK_FILLINGare enabled, the random stack fill value stored in the thread control block is cleared later during_tx_thread_create().The function generates a random 8-bit value, replicates it into the four bytes of
thread_ptr->tx_thread_stack_fill_value, and uses it as the stack fill pattern.However, later in the same function, the entire
TX_THREADcontrol block is cleared with:This resets:
to zero.
The stack memory still contains the previously generated random pattern, but the thread control block no longer contains the value used to fill the stack.
As a consequence, the stack checking and stack analysis code compares the stack against an incorrect fill value.
Affected configuration
The issue occurs when the following options are enabled:
and stack filling is not disabled:
/* TX_DISABLE_STACK_FILLING must not be defined. */Affected versions
I originally found the issue in Azure RTOS ThreadX 6.4.0.
The same initialization order also appears to be present in the current Eclipse ThreadX source code, including the latest 6.5.x source and the current
masterbranch.Relevant source file
common/src/tx_thread_create.c
Function:
_tx_thread_create()Current initialization sequence
The current sequence is effectively:
After the last operation:
while the stack contains the random fill pattern.
For example, if the generated byte is
0x37, the stack contains0x37373737, but the value stored in the thread control block becomes0x00000000.Expected behavior
At the end of
_tx_thread_create(),thread_ptr->tx_thread_stack_fill_valueshould contain the exact value used to initialize the unused stack memory.For example:
when the stack was filled with byte
0x37.Actual behavior
The random fill pattern is written to the stack, but the corresponding field in the thread control block is subsequently cleared to zero.
The stack analysis routine therefore searches for a pattern different from the one used to initialize the stack.
Suggested fix
A simple fix would be to move:
to the beginning of
_tx_thread_create(), immediately after the local variable declarations and before any field ofthread_ptris initialized.The original
TX_MEMSET()at its current location should then be removed.The initialization sequence would become:
This preserves the current implementation and guarantees that the random fill value remains valid in the thread control block.
An alternative fix would be to preserve the random fill value in a local variable and copy it into the TCB after the TCB has been cleared, but moving the TCB initialization before any field assignment appears simpler.
Reproduction
Enable:
Make
TX_RAND()return a deterministic nonzero byte, for example0x5A.Create a thread using
tx_thread_create().Inspect the unused stack memory and:
Expected result:
Actual result:
while the stack remains filled with byte
0x5A.Additional notes
If
TX_RAND()returns zero, the problem may be accidentally hidden because both the stack fill pattern and the cleared TCB field are zero.This does not make the implementation correct and occurs only for that specific random result.
I could not find an existing issue or release note documenting this problem.