2、内存模型基础:栈区、堆区、全局区、代码段在嵌入式环境中的划分
做嵌入式安全,说白了就是跟内存打交道。你想想看,AES CCM 跑在资源受限的 MCU 上,栈溢出了、堆碎片化了、全局变量被意外改写了——任何一个环节出问题,加密就可能形同虚设。所以这一章,咱们先把嵌入式环境下的内存模型彻底捋清楚。
我个人习惯把嵌入式内存想象成一块地皮。代码段是盖好的厂房,全局区是仓库,栈是临时工位,堆是随时可以租用的储物间。每个区域怎么划分、怎么管理,直接决定了你的 AES CCM 实现能不能稳定跑起来。
2.1 四大内存区域的物理划分
先看一张总图,把四个区域的位置和关系搞清楚。
这张图我建议你存下来。每次写 AES CCM 代码前扫一眼,心里就有数了。
2.2 代码段:只读的指令仓库
代码段存放的是编译后的机器指令。在嵌入式环境里,它通常被映射到 Flash 或 ROM 上。为什么强调只读?因为任何试图修改代码段的行为,要么是自修改代码(SMC),要么就是缓冲区溢出攻击。
我记得有一次调试 AES CCM 的加速实现,发现程序莫名其妙跳转到错误地址。查了半天,原来是栈溢出把返回地址覆盖了,CPU 从代码段里取了一条「假指令」——嗯,那之后我就在所有关键函数入口加了栈保护。
2.3 全局区:静态数据的安身之所
全局区包含三个子段:.data、.bss 和 .rodata。它们的区别我列个表,一目了然。
| 段名 | 存储内容 | 初始化方式 | 生命周期 | AES CCM 典型用途 |
|---|---|---|---|---|
| .data | 已初始化的全局/静态变量 | 启动时从 Flash 拷贝到 RAM | 整个程序运行期 | 密钥缓冲区、计数器初始值 |
| .bss | 未初始化的全局/静态变量 | 启动时清零 | 整个程序运行期 | 临时认证标签、状态标志 |
| .rodata | 只读常量 | 直接放在 Flash 中 | 整个程序运行期 | S-box 查找表、固定 IV |
这里有个关键点:.data 段在启动时要从 Flash 拷贝到 RAM,这会占用额外的启动时间。如果你的 AES CCM 实现里有一张大 S-box 表(比如 256 字节),放在 .rodata 里直接读 Flash 就行,别放 .data 里浪费 RAM。
2.4 栈区:临时变量的战场
栈是嵌入式开发中最容易出问题的区域。它向下增长(高地址往低地址),每个函数调用都会压入栈帧。栈帧里包含:局部变量、返回地址、保存的寄存器、以及可能的参数。
我见过太多 AES CCM 实现因为栈溢出而崩溃。举个例子:你在一个函数里声明了 uint8_t buffer[1024],如果栈大小只有 512 字节,那这个函数一调用就炸。更隐蔽的是,递归调用或者多层嵌套函数调用,栈帧一层层叠上去,不知不觉就溢出了。
怎么算栈大小?我有个土办法:把所有可能同时调用的函数栈帧大小加起来,再乘以 1.5 的安全系数。对于 AES CCM,核心函数 aes_ccm_encrypt() 内部可能调用 aes_encrypt_block()、ctr_generate()、mac_update(),每个函数栈帧按 64-128 字节估算,加上局部缓冲区,总栈需求通常在 512 字节以内。但如果你用了递归或者大数组,那就另说了。
2.5 堆区:动态分配的利与弊
堆区用于动态内存分配,通过 malloc/free 管理。在嵌入式环境里,我建议你慎用堆。原因有三:
- 碎片化: 频繁分配释放会产生内存碎片,导致后续大块分配失败。AES CCM 的临时缓冲区如果反复 malloc/free,堆很快就不连续了。
- 不可预测性: malloc 的执行时间不确定,对于实时性要求高的加解密任务,这可能是个隐患。
- 安全风险: 堆溢出攻击是常见手法。攻击者通过溢出堆上的缓冲区,改写相邻堆块的管理信息,最终劫持控制流。
我个人习惯的做法是:在初始化阶段一次性分配好 AES CCM 需要的所有缓冲区,然后在整个生命周期内复用,绝不动态释放。这样既避免了碎片,也减少了安全攻击面。
推荐实践: 使用静态池分配代替堆分配。比如预定义 static uint8_t aes_ccm_buf[256];,然后通过指针传递使用。这样内存位置固定,调试方便,安全性也更高。
2.6 链接脚本与内存布局
说了这么多理论,最后落到实际工具上。链接脚本(Linker Script)是定义内存布局的关键文件。以 GCC 的 .ld 文件为例,核心部分长这样:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.text : {
*(.text*)
*(.rodata*)
} > FLASH
.data : {
_sdata = .;
*(.data*)
_edata = .;
} > RAM AT> FLASH
.bss : {
_sbss = .;
*(.bss*)
_ebss = .;
} > RAM
}
注意看 .data 段的 AT> FLASH,这表示 .data 的初始值存储在 Flash 中,启动时由启动代码拷贝到 RAM。如果你在 AES CCM 里用了大数组做全局变量,记得检查 .data 段的大小,别把 Flash 撑爆了。