Security note · ByteScan Security Research Lab
BSR-2026-FREERTOS-001
FreeRTOS-Kernel doesn’t check the task stack size for overflow before calling malloc
- Project
- FreeRTOS-Kernel (github.com/FreeRTOS/FreeRTOS-Kernel)
- Where
tasks.c,prvCreateTask()- Tested on
mainat commit8be86d4- Type
- Integer overflow leading to an undersized heap allocation (CWE-190, CWE-122)
- CVE
- Requested, nothing assigned yet
- Status
- Reported to AWS Security on 2026-10-14, waiting for a reply
- Found by
- Mohammadreza Ashouri
The short version
When you create a task, FreeRTOS works out how many bytes its stack needs by
multiplying the stack depth by the size of one stack word, and passes the result
to the allocator. Nothing checks that the multiplication fits in a size_t.
With a large enough depth the result wraps around to something tiny, the allocator
gives back a tiny block, and the rest of the kernel carries on as if it had received
the stack it asked for.
Where it is
It’s in prvCreateTask(). There are two call sites, one for each
stack growth direction (lines 1663 and 1681 on main), and they look the same:
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack(
( ( size_t ) uxStackDepth ) * sizeof( StackType_t ) );
Other places in the kernel that do size arithmetic before an allocation do guard it.
xQueueGenericCreate() checks against SIZE_MAX,
pvPortCalloc() in heap_4.c uses a multiply-overflow macro, and
xStreamBufferGenericCreate() checks that the size plus the header doesn’t wrap.
The task stack is the one that doesn’t.
An example
Take a 32-bit target with 4-byte stack words, which is an ordinary Cortex-M setup.
The stack depth type defaults to the stack word type, so it’s 32 bits wide there,
and 1,073,741,825 (0x40000001) is a legal value.
1,073,741,825 words × 4 bytes = 4,294,967,300 bytes
size_t is 32 bits, so it wraps: 4,294,967,300 − 4,294,967,296 = 4 bytes
The allocator is asked for 4 bytes and hands them over. The kernel still believes the task has a stack of over a billion words. The port then writes the task’s initial register frame at the top of that block, which is far bigger than 4 bytes, so it lands on whatever sits next to it on the heap. Once the task actually runs, it uses far more stack than it owns.
What I ran
I built the real heap_4.c and the exact expressions from tasks.c
into a small test harness on a 64-bit host, using a minimal test port, so this is not a
run on a real device. With a depth of 4611686018427387905 (262 + 1) and 4-byte
stack words, the size wraps to 4 bytes and pvPortMalloc(4) succeeds. A simulated
64-byte initial frame, written down from the top of that block, starts 63 bytes before the
block does.
I also looked at the configASSERT that runs after the stack frame is set up.
It doesn’t catch this. It compares how much stack the frame used against the original
depth, not against what was actually allocated, and a real frame is always small next to an
overflowed depth.
How much does it matter?
Less than the mechanics might suggest. The stack depth is almost always a constant the firmware author writes, like 256 or 1024, so on a normal device nobody can choose it from outside. I’d call this a missing hardening check, not a remotely exploitable bug.
It starts to matter if an application computes the depth from some other value, or if a
less trusted caller can supply it. I haven’t tested whether that’s possible through
the MPU wrappers (MPU_xTaskCreate() passes the depth straight through), so I’m
not claiming it.
Suggested fix
Reject a depth that would overflow before calling the allocator, the same way the other sites do, and treat it as an allocation failure:
if( uxStackDepth > ( SIZE_MAX / sizeof( StackType_t ) ) )
{
/* too big, fail the task creation */
}
else
{
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack(
( ( size_t ) uxStackDepth ) * sizeof( StackType_t ) );
}
Timeline
-
2026-09-22
Confirmed with the test harness against commit
8be86d4. -
2026-10-14
Checked again against current
main(same commit). Reported to AWS Security at aws-security@amazon.com, and a CVE ID request filed with MITRE.
Credit
Found and reported by Mohammadreza Ashouri, ByteScan Security Research Lab.
audit@bytescan.net ·
bytescan.net ·
linkedin.com/in/drashouri