一、CERT C编码标准:安全编码的基石
大家好,我是老李。做嵌入式安全这块十几年了,今天咱们聊聊CERT C编码标准。说实话,这玩意儿刚出来那会儿,我还觉得不就是一堆条条框框嘛。直到有一次,我负责的一个车载项目因为一个整数溢出导致系统崩溃,差点酿成大祸。从那以后,我再也不敢小看这些规则了。
CERT C编码标准,说白了就是一套教你如何写出安全、可靠C代码的指南。它由卡内基梅隆大学的CERT(计算机应急响应小组)制定,专门针对C语言中那些容易出问题的“坑”。你想想看,C语言虽然灵活,但指针、内存管理、类型转换这些特性,稍不留神就会引入漏洞。
1.1 CERT C规则概览
CERT C标准把规则分成了几个大类。我个人习惯把它们记成“安全编码的六大护法”:
- 预处理器(PRE):宏定义、条件编译相关的坑。比如宏参数没加括号,这种低级错误我见过太多次了。
- 声明与初始化(DCL):变量声明、作用域、初始化问题。我记得有个项目,就因为全局变量没初始化,导致随机崩溃查了两周。
- 表达式(EXP):运算符优先级、副作用、未定义行为。嗯,这里要注意,C语言的运算符优先级有时候真的很反直觉。
- 整数(INT):整数溢出、符号错误、截断。这是安全漏洞的重灾区,我后面会详细讲。
- 浮点数(FLP):浮点比较、精度问题。做控制算法的朋友要特别留意。
- 数组、字符串与指针(ARR, STR, MEM):缓冲区溢出、空指针解引用、内存泄漏。经典中的经典。
每个大类下面又有若干条具体规则,每条规则都有唯一的ID,比如INT30-C表示整数类的第30条规则。这样在代码审查或者静态分析报告里,大家能快速定位问题。
核心观点:CERT C不是教你写“能跑”的代码,而是教你写“不会出事故”的代码。它关注的是未定义行为和可预测性。
1.2 安全编码实践:从规则到习惯
光知道规则还不够,得变成肌肉记忆。我给大家分享几个我在项目中反复踩过的坑,以及对应的CERT C规则。
实践一:整数溢出(INT30-C)
先看个例子:
// 错误示例
unsigned int size = 100;
unsigned int new_size = size + 200; // 如果size很大,这里可能溢出
// 正确做法
if (size > UINT_MAX - 200) {
// 处理溢出错误
} else {
unsigned int new_size = size + 200;
}
我曾经在一个网络协议栈里,就因为没做这个检查,导致一个精心构造的报文让设备内存越界,直接重启。从那以后,我写任何加法之前都会先问自己:“会不会溢出?”
实践二:空指针检查(EXP34-C)
这个太常见了:
// 错误示例
void process_data(int *ptr) {
*ptr = 10; // 如果ptr是NULL,直接崩溃
}
// 正确做法
void process_data(int *ptr) {
if (ptr == NULL) {
// 记录错误日志,返回错误码
return;
}
*ptr = 10;
}
你可能会说:“我保证不会传NULL进来。” 但现实是,代码经过多人维护,谁也不敢保证。防御性编程不是怂,是专业。
实践三:缓冲区边界检查(ARR30-C)
// 错误示例
char buf[10];
strcpy(buf, user_input); // 如果user_input超过9个字符,缓冲区溢出
// 正确做法
char buf[10];
strncpy(buf, user_input, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; // 确保字符串以null结尾
我的小技巧:写代码时,每当你用到数组下标或者指针偏移,就条件反射地检查边界。我习惯在代码里加一个断言(assert),调试阶段就能发现问题。
1.3 CERT C与MISRA C的对比
很多朋友会问:“CERT C和MISRA C到底选哪个?” 其实它们不是二选一的关系,而是互补的。我整理了一个表格,方便大家理解:
| 对比维度 | CERT C | MISRA C |
|---|---|---|
| 目标 | 消除安全漏洞,防止攻击 | 提高代码可靠性,减少缺陷 |
| 侧重点 | 未定义行为、整数问题、指针安全 | 语言子集、编码风格、可维护性 |
| 规则数量 | 约200条(含建议) | 约150条(不同版本有差异) |
| 适用领域 | 通用安全、网络设备、金融系统 | 汽车、航空、医疗等安全关键系统 |
| 严格程度 | 规则分“必须”和“建议” | 规则分“强制”和“建议”,更严格 |
| 工具支持 | Coverity、Clang Static Analyzer等 | PC-lint、QAC、LDRA等 |
说白了,CERT C更像一个“安全漏洞百科全书”,告诉你哪些写法会出问题;而MISRA C更像一个“编码风格圣经”,告诉你应该怎么写才规范。我个人的建议是:如果你的项目对安全性要求极高(比如汽车、航空),两个都上;如果只是通用软件,至少把CERT C的“必须”规则过一遍。
1.4 在安全关键系统中的应用
安全关键系统,比如汽车的刹车控制、飞机的飞控、医疗设备的生命支持系统,这些代码出不得半点差错。在这些领域,CERT C和MISRA C几乎是标配。
我记得参与过一个汽车ECU项目,客户要求所有代码必须通过CERT C的“必须”规则检查。我们当时用了静态分析工具,一开始跑出来上千条告警。说实话,心里是崩溃的。但一条条改下来,代码质量确实上了一个台阶。最明显的感觉是:以前那种“偶尔死机”的bug,再也没有出现过。
在安全关键系统中,我建议遵循以下步骤:
- 规则裁剪:不是所有CERT C规则都适用于你的项目。比如嵌入式系统可能没有动态内存分配,那相关的规则就可以忽略。但裁剪要有理有据,不能偷懒。
- 工具集成:把静态分析工具集成到CI/CD流水线中。每次提交代码自动扫描,发现问题立即修复。不要等到最后才跑工具,那时候改代码成本太高。
- 代码审查:静态分析工具不能发现所有问题,比如逻辑错误、设计缺陷。人工审查仍然是必要的。我习惯在审查时对照CERT C规则列表,逐条确认。
- 持续改进:把每次事故、每个bug都映射到对应的CERT C规则上。如果某个规则反复被违反,说明团队需要培训或者流程需要改进。
警告:不要迷信静态分析工具。工具只能发现它“知道”的问题。有些漏洞,比如逻辑竞争条件、设计层面的安全缺陷,工具是看不出来的。工具是助手,不是救世主。
1.5 知识体系总览
为了让大家对CERT C有一个整体的认识,我画了一张图。这张图展示了CERT C编码标准的核心知识结构:
这张图从中心向外展开:最内层是CERT C标准本身,中间层是六大规则类别,外层是应用场景和对比,最下面是落地实践。你可以把它当作一个学习路线图,也可以当作团队培训的参考。
好了,关于CERT C编码标准的概览就讲到这里。记住,安全编码不是一朝一夕的事,而是一种习惯。每次写代码时多问自己一句:“这样写安全吗?” 久而久之,你也能写出让人放心的代码。
公众号:蓝海资料掘金营,微信deep3321