Skip to content

Commit 26a6acb

Browse files
authored
Merge pull request #169 from f9micro/overflow-protection
Implement stack overflow protection with canary checks
2 parents e36194f + e7371aa commit 26a6acb

File tree

8 files changed

+138
-0
lines changed

8 files changed

+138
-0
lines changed

include/platform/cortex_m.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ inline void __ISB(void) {
104104
#define SCB_VTOR (volatile uint32_t *) (SCB_BASE + 0x008) /* Vector Table Offset Register */
105105
#define SCB_AIRCR (volatile uint32_t *) (SCB_BASE + 0x00c) /* Application Interrupt/Reset Control Register */
106106
#define SCB_SCR (volatile uint32_t *) (SCB_BASE + 0x010) /* System Control Register */
107+
#define SCB_CCR (volatile uint32_t *) (SCB_BASE + 0x014) /* Configuration and Control Register */
107108
#define SCB_SHPR (volatile uint8_t *) (SCB_BASE + 0x018) /* System Handler Priority Register */
108109
#define SCB_SHCSR (volatile uint32_t *) (SCB_BASE + 0x024) /* System Handler Control and State Register */
109110
#define SCB_CFSR (volatile uint32_t *) (SCB_BASE + 0x028) /* Configurable fault status register - Describes Usage, Bus, and Memory faults */
@@ -143,6 +144,8 @@ inline void __ISB(void) {
143144
#define SCB_SCR_SLEEPDEEP (uint32_t) (1 << 2) /* Use deep sleep as low power mode */
144145
#define SCB_SCR_SEVONPEND (uint32_t) (1 << 4) /* Send event on pending exception */
145146

147+
#define SCB_CCR_STKALIGN (uint32_t) (1 << 9) /* Stack 8-byte alignment on exception entry */
148+
146149
#define SCB_SHCSR_MEMFAULTENA (uint32_t) (1 << 16) /* Enables Memory Management Fault */
147150
#define SCB_SHCSR_BUSFAULTENA (uint32_t) (1 << 17) /* Enables Bus Fault */
148151
#define SCB_SHCSR_USEFAULTENA (uint32_t) (1 << 18) /* Enables Usage Fault */

include/platform/irq.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <softirq.h>
1010
#include <thread.h>
1111
#include <sched.h>
12+
#include <error.h>
1213
#include <platform/link.h>
1314
#include <platform/cortex_m.h>
1415

@@ -168,7 +169,24 @@ extern volatile uint32_t __irq_saved_regs[8];
168169
{ \
169170
register tcb_t *sel; \
170171
sel = schedule_select(); \
172+
/* Check current thread canary before any return path. \
173+
* Catches overflow that occurred while thread ran. */ \
174+
if (!thread_check_canary((tcb_t *)current)) { \
175+
panic("Stack overflow (current): tid=%t, " \
176+
"stack_base=%p, canary=%p\n", \
177+
current->t_globalid, current->stack_base, \
178+
current->stack_base ? \
179+
*((uint32_t *)current->stack_base) : 0); \
180+
} \
171181
if (sel != current) { \
182+
/* Check next thread before switching to it */ \
183+
if (!thread_check_canary(sel)) { \
184+
panic("Stack overflow (next): tid=%t, " \
185+
"stack_base=%p, canary=%p\n", \
186+
sel->t_globalid, sel->stack_base, \
187+
sel->stack_base ? \
188+
*((uint32_t *)sel->stack_base) : 0); \
189+
} \
172190
context_switch(current, sel); \
173191
} else { \
174192
/* No context switch - restore saved registers \

include/thread.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
* @file thread.h
1818
* @brief Thread dispatcher definitions
1919
*
20+
* Stack Overflow Protection:
21+
* A canary value is placed at the bottom of each thread's stack (lowest
22+
* address, since ARM stacks grow downward). The canary is checked on
23+
* every context switch - if corrupted, the stack has overflowed.
24+
*
2025
* Thread ID type is declared in @file types.h and called l4_thread_t
2126
*
2227
* For Global Thread ID only high 18 bits are used and lower are reserved,
@@ -38,6 +43,11 @@
3843

3944
#define THREAD_BY_TID(id) thread_by_globalid(TID_TO_GLOBALID(id))
4045

46+
/* Stack canary for overflow detection.
47+
* Placed at stack_base (lowest address). Checked on context switch.
48+
*/
49+
#define STACK_CANARY 0xDEADBEEF
50+
4151
typedef enum {
4252
THREAD_IDLE,
4353
THREAD_KERNEL,
@@ -98,6 +108,23 @@ struct tcb {
98108
};
99109
typedef struct tcb tcb_t;
100110

111+
/* Initialize stack canary at bottom of stack.
112+
* Must be called after stack_base is set.
113+
*/
114+
static inline void thread_init_canary(tcb_t *thr)
115+
{
116+
if (thr->stack_base)
117+
*((uint32_t *) thr->stack_base) = STACK_CANARY;
118+
}
119+
120+
/* Check stack canary. Returns 1 if valid, 0 if corrupted. */
121+
static inline int thread_check_canary(tcb_t *thr)
122+
{
123+
if (!thr->stack_base)
124+
return 1; /* No stack tracking, skip check */
125+
return *((uint32_t *) thr->stack_base) == STACK_CANARY;
126+
}
127+
101128
void thread_init_subsys(void);
102129

103130
tcb_t *thread_by_globalid(l4_thread_t globalid);

kernel/ipc.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,23 @@ void sys_ipc(uint32_t *param1)
287287
"IPC: %t thread start sp:%p stack_size:%p\n",
288288
to_tid, sp, stack_size);
289289

290+
/* Security check: Ensure stack is in user-writable memory */
291+
int pid = mempool_search(sp - stack_size, stack_size);
292+
mempool_t *mp = mempool_getbyid(pid);
293+
294+
if (!mp || !(mp->flags & MP_UW)) {
295+
dbg_printf(DL_IPC,
296+
"IPC: REJECT invalid stack for %t: %p (pool %s)\n",
297+
to_tid, sp - stack_size, mp ? mp->name : "N/A");
298+
user_ipc_error(caller,
299+
UE_IPC_ABORTED | UE_IPC_PHASE_SEND);
300+
caller->state = T_RUNNABLE;
301+
return;
302+
}
303+
290304
to_thr->stack_base = sp - stack_size;
291305
to_thr->stack_size = stack_size;
306+
thread_init_canary(to_thr);
292307

293308
dbg_printf(DL_IPC,
294309
"IPC: %t stack_base:%p stack_size:%p\n",

kernel/kdb.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ extern void kdb_dump_ktable(void);
2323
extern void kdb_show_ktimer(void);
2424
extern void kdb_dump_softirq(void);
2525
extern void kdb_dump_threads(void);
26+
extern void kdb_dump_stacks(void);
2627
extern void kdb_dump_mpu(void);
2728
extern void kdb_dump_mempool(void);
2829
extern void kdb_dump_as(void);
@@ -60,6 +61,12 @@ struct kdb_t kdb_functions[] = {
6061
.menuentry = "dump threads",
6162
.function = kdb_dump_threads
6263
},
64+
{
65+
.option = 'S',
66+
.name = "STACKS",
67+
.menuentry = "dump stack usage",
68+
.function = kdb_dump_stacks
69+
},
6370
{
6471
.option = 'M',
6572
.name = "MPU",

kernel/memory.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <fpage_impl.h>
1212
#include <init_hook.h>
1313
#include <kip.h>
14+
#include <platform/cortex_m.h>
1415

1516
/**
1617
* @file memory.c
@@ -181,6 +182,15 @@ void memory_init()
181182
int j = 0;
182183
uint32_t *shcsr = (uint32_t *) 0xE000ED24;
183184

185+
/* Verify and set STKALIGN for 8-byte stack alignment on exceptions.
186+
* This is critical for Cortex-M: misaligned stacks cause HardFault.
187+
* STKALIGN (bit 9 of CCR) ensures automatic 8-byte alignment.
188+
*/
189+
if (!(*SCB_CCR & SCB_CCR_STKALIGN)) {
190+
*SCB_CCR |= SCB_CCR_STKALIGN;
191+
__ISB();
192+
}
193+
184194
fpages_init();
185195

186196
ktable_init(&as_table);

kernel/systhread.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ void create_root_thread(void)
4141
root->stack_base = (memptr_t) &root_stack_start;
4242
root->stack_size = (uint32_t) &root_stack_end -
4343
(uint32_t) &root_stack_start;
44+
thread_init_canary(root);
4445

4546
sched_slot_dispatch(SSI_ROOT_THREAD, root);
4647
root->state = T_RUNNABLE;
@@ -52,6 +53,14 @@ void create_kernel_thread(void)
5253

5354
thread_init_kernel_ctx(&kernel_stack_end, kernel);
5455

56+
/* Initialize stack tracking for kernel thread.
57+
* Kernel stack follows idle stack in memory layout.
58+
*/
59+
kernel->stack_base = (memptr_t) &idle_stack_end;
60+
kernel->stack_size = (uint32_t) &kernel_stack_end -
61+
(uint32_t) &idle_stack_end;
62+
thread_init_canary(kernel);
63+
5564
/* This will prevent running other threads
5665
* than kernel until it will be initialized
5766
*/
@@ -64,6 +73,11 @@ void create_idle_thread(void)
6473
idle = thread_init(TID_TO_GLOBALID(THREAD_IDLE), NULL);
6574
thread_init_ctx((void *) &idle_stack_end, idle_thread, NULL, idle);
6675

76+
idle->stack_base = (memptr_t) &idle_stack_start;
77+
idle->stack_size = (uint32_t) &idle_stack_end -
78+
(uint32_t) &idle_stack_start;
79+
thread_init_canary(idle);
80+
6781
sched_slot_dispatch(SSI_IDLE, idle);
6882
idle->state = T_RUNNABLE;
6983
}

kernel/thread.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,15 @@ void thread_switch(tcb_t *thr)
441441
assert((intptr_t) thr);
442442
assert(thread_isrunnable(thr));
443443

444+
/* Check stack canary before switching to this thread.
445+
* If canary is corrupted, the thread's stack has overflowed.
446+
*/
447+
if (!thread_check_canary(thr)) {
448+
panic("Stack overflow: tid=%t, stack_base=%p, canary=%p\n",
449+
thr->t_globalid, thr->stack_base,
450+
thr->stack_base ? *((uint32_t *) thr->stack_base) : 0);
451+
}
452+
444453
current = thr;
445454
current_utcb = thr->utcb;
446455
if (current->as)
@@ -528,4 +537,39 @@ void kdb_dump_threads(void)
528537
}
529538
}
530539

540+
void kdb_dump_stacks(void)
541+
{
542+
tcb_t *thr;
543+
int idx;
544+
545+
dbg_printf(DL_KDB, "%5s %8s %10s %10s %10s %6s\n",
546+
"type", "global", "stack_base", "stack_size", "sp", "canary");
547+
548+
for_each_in_ktable(thr, idx, (&thread_table)) {
549+
char *canary_status;
550+
uint32_t canary_val = 0;
551+
552+
if (!thr->stack_base) {
553+
canary_status = "N/A";
554+
} else {
555+
canary_val = *((uint32_t *) thr->stack_base);
556+
canary_status = (canary_val == STACK_CANARY) ? "OK" : "FAIL";
557+
}
558+
559+
dbg_printf(DL_KDB, "%5s %t %p %10d %p %6s\n",
560+
kdb_get_thread_type(thr),
561+
thr->t_globalid,
562+
thr->stack_base,
563+
thr->stack_size,
564+
thr->ctx.sp,
565+
canary_status);
566+
567+
/* If canary failed, show what was found */
568+
if (thr->stack_base && canary_val != STACK_CANARY) {
569+
dbg_printf(DL_KDB, " expected: %p, found: %p\n",
570+
STACK_CANARY, canary_val);
571+
}
572+
}
573+
}
574+
531575
#endif /* CONFIG_KDB */

0 commit comments

Comments
 (0)