嵌入式场景下的内存越界canary检测是在裸机固件中手动部署的栈保护机制:在startup.s或crt0.c中初始化canary值(如0xdeadbeef)到保留RAM,定义弱符号__stack_chk_guard指向该地址,并实现不依赖libc的__stack_chk_fail函数;需确保GCC插桩生效(-fstack-protector-strong)、反汇编验证指令存在,且中断服务函数需手工加汇编级保护。

什么是嵌入式场景下的内存越界 canary 检测
它不是标准 C++ 的功能,也不是 std::vector::at() 那种运行时检查——而是编译器/链接器/启动代码协同在栈帧末尾插入一个固定值(canary),函数返回前校验该值是否被修改。关键在于:嵌入式环境通常禁用 libc、无 MMU、无 OS 支持的 stack protector,必须手动植入。
如何在裸机固件中手动部署栈 canary
核心是三件事:初始化 canary 值、修改函数 prologue/epilogue、确保所有函数都参与校验。GCC 的 -fstack-protector-strong 在多数 Cortex-M 工具链(如 arm-none-eabi-gcc)中默认失效,因为依赖 __stack_chk_fail 和全局 __stack_chk_guard,而裸机启动代码不会初始化后者。
- 在
startup.s或crt0.c中,于main()调用前,用真随机或编译期常量(如0xdeadbeef)写入一块保留 RAM 区域,例如*(volatile uint32_t*)0x2000FFFC = 0xdeadbeef; - 定义弱符号
__stack_chk_guard指向该地址:extern uint32_t __stack_chk_guard_location; uint32_t *__stack_chk_guard = &__stack_chk_guard_location;
- 必须实现
__stack_chk_fail:它不能调用printf或abort()(无 libc),应直接触发 BKPT、进入死循环或写看门狗寄存器复位
为什么 -fstack-protector 在裸机上常静默失效
现象是:编译通过、生成了 mov r0, #0 类似指令去读 __stack_chk_guard,但实际运行时该地址未初始化,导致 canary 值恒为 0,所有校验都“误通过”。根本原因是 GCC 不检查 __stack_chk_guard 是否已赋值,只管插桩。
- 检查是否真的启用了保护:反汇编目标函数,确认存在类似
ldr r0, [r11, #-4](读 canary)和cmp r0, [r11, #-8](比对)的指令 - 若没出现,可能是你用了
-ffreestanding或-nostdlib导致 GCC 认为“无运行时支持”,自动禁用 stack protector - 强制启用需加
-fstack-protector-strong -mthumb -mcpu=cortex-m4,并确保链接脚本把.bss和.data正确清零/拷贝
canary 值该用真随机还是固定值
固定值(如 0x0000a5a5)便于调试,但攻击者一旦逆向固件就完全失效;真随机需硬件 TRNG 支持,且必须在 canary 使用前完成初始化——这意味着不能放在 main() 里,得塞进 reset handler 后、SystemInit() 前的窗口。
立即学习“C++免费学习笔记(深入)”;
- 若 MCU 无 TRNG(如 STM32F0/F1),可用 ADC 噪声 + SysTick 计数器低比特拼凑熵,但注意不要引入启动延迟
- 避免用
__builtin_frame_address(0)或栈地址做 canary:它们本身可被预测,且在内联函数中不可靠 - canary 必须对齐到 4 字节边界,并确保不与局部变量重叠——检查编译后 map 文件中该变量的 section 和 size
真正麻烦的是中断服务函数(ISR):默认不被 -fstack-protector 覆盖,除非显式加 __attribute__((optimize("stack-protector"))),但多数编译器不支持该属性。更稳妥的做法是:所有 ISR 用 __attribute__((naked)) 手写汇编,在入口保存 canary、出口校验,否则栈溢出可能绕过防护。


















