安全编码规范:CERT C编码标准、MISRA C简介、静态分析工具

说实话,我见过太多因为编码不规范导致的惨案了。有一次,一个同事写的代码在测试环境跑得好好的,一上线就崩。查了三天,最后发现是一个很简单的数组越界。你说冤不冤?

所以这一章,咱们聊聊安全编码规范。说白了,就是一套规则,告诉你哪些写法容易出问题,哪些写法是安全的。我这些年踩过的坑,大部分都能用这些规范提前规避掉。

CERT C编码标准

CERT C是卡内基梅隆大学搞的一套安全编码标准。它把问题分成了几个大类,每个大类下面又有具体的规则和建议。

我个人习惯把CERT C当成一本「避坑指南」。它不教你写代码,而是告诉你哪些写法是危险的。

核心分类:

  • 预处理器(PRE):宏定义、条件编译相关的安全问题
  • 声明和初始化(DCL):变量声明、初始化相关的陷阱
  • 表达式(EXP):运算符优先级、副作用等问题
  • 整数(INT):整数溢出、符号错误等
  • 浮点数(FLP):浮点精度、比较问题
  • 数组(ARR):越界访问、指针运算
  • 字符和字符串(STR):缓冲区溢出、空终止问题
  • 内存管理(MEM):泄漏、重复释放、野指针
  • 输入输出(FIO):文件操作、格式化字符串
  • 环境(ENV):系统调用、信号处理
  • 信号(SIG):信号处理函数中的限制
  • 错误处理(ERR):返回值检查、异常状态
  • 并发(CON):竞态条件、死锁
  • 杂项(MSC):其他安全问题

举个例子,INT30-C这条规则说:确保无符号整数运算不会回绕。你可能会想,无符号整数回绕不是定义好的行为吗?没错,标准确实定义了回绕行为,但很多安全漏洞恰恰是利用了这个特性。

/* 不安全的写法 */
unsigned int size = 100;
unsigned int total = size + 200;  /* 如果size很大,这里可能回绕 */

/* 安全的写法 */
unsigned int size = 100;
if (size > UINT_MAX - 200) {
    /* 处理溢出 */
} else {
    unsigned int total = size + 200;
}

我曾经在一个嵌入式项目中遇到过类似的问题。一个计数器用了无符号整数,结果在边界条件下回绕了,导致系统误判了某个状态。嗯,从那以后我再也不敢忽视整数溢出了。

MISRA C简介

MISRA C最早是汽车行业的标准,后来被航空航天、医疗设备等领域广泛采用。它比CERT C更严格,有些规则甚至有点「偏执」。

你想想看,汽车上的代码如果出问题,那可是要命的。所以MISRA C的核心理念就是:消除一切不确定性。

MISRA C的几个关键原则:

  • 禁止使用动态内存分配(malloc/free)
  • 禁止使用递归
  • 所有if/else必须有else分支
  • switch语句必须有default分支
  • 禁止使用goto
  • 函数必须有明确的返回值
  • 禁止使用变长数组

说实话,刚开始接触MISRA C的时候,我觉得有些规则太死板了。比如禁止使用动态内存分配,这在实际开发中多不方便啊。但后来我理解了:在安全关键系统中,确定性比灵活性重要得多。

/* MISRA C 合规写法 */
void process_data(uint32_t value) {
    uint32_t result;
    
    if (value > 100) {
        result = value * 2;
    } else {
        result = value;  /* 必须有else分支 */
    }
    
    /* 使用result */
}

MISRA C的规则分为「强制」和「建议」两类。强制规则必须遵守,建议规则尽量遵守。在实际项目中,我一般会先确保所有强制规则都通过,然后再逐步处理建议规则。

静态分析工具

光有规范还不够,你得有工具帮你检查。人工审查代码效率太低,而且容易遗漏。静态分析工具就是干这个的。

静态分析工具的工作原理很简单:它把你的代码当成文本,然后按照规则集去扫描。发现违规的地方就报出来。

常见的静态分析工具:

工具名称 支持的规则 适用场景
PC-lint MISRA C, CERT C 嵌入式、汽车电子
Coverity CERT C, 自定义规则 大型项目、企业级
Clang Static Analyzer 内置检查 开源项目、快速检查
Cppcheck MISRA C (部分) 轻量级、免费
SonarQube 多种规则集 持续集成、代码质量

我个人比较喜欢PC-lint,虽然它配置起来有点麻烦,但检查得非常细致。Coverity也不错,就是价格贵了点。如果是小项目,用Cppcheck就够了。

注意:静态分析工具不是万能的。它只能检查出「已知」的问题模式。逻辑错误、设计缺陷这类问题,工具是发现不了的。所以静态分析不能替代代码审查和测试。

我曾经在一个项目里过度依赖静态分析工具,觉得工具报的警告都处理了就安全了。结果上线后还是出了问题——是一个并发竞态条件,工具根本检查不出来。从那以后,我学会了把静态分析当成辅助手段,而不是救命稻草。

知识体系结构图

下面这张图展示了安全编码规范的核心知识体系。你可以看到,CERT C和MISRA C是两大规范体系,静态分析工具是落地手段,三者缺一不可。

安全编码规范知识体系 CERT C编码标准 MISRA C 静态分析工具 核心分类 • 整数(INT) · 数组(ARR) • 字符串(STR) · 内存(MEM) • 并发(CON) · 信号(SIG) • 输入输出(FIO) · 环境(ENV) 关键原则 • 禁止动态内存分配 • 禁止递归 • 强制else分支 • 禁止goto 常用工具 • PC-lint (MISRA/CERT) • Coverity (企业级) • Clang Static Analyzer • Cppcheck (轻量级) 安全编码实践 规范 + 工具 = 更安全的C代码 三者结合,才能有效降低安全风险

如何选择规范

很多初学者会问:到底该用CERT C还是MISRA C?我的建议是看行业。

  • 汽车、航空航天、医疗:必须用MISRA C,这是行业准入要求
  • 通用软件、网络设备:CERT C更合适,灵活度更高
  • 安全关键系统:两个都用,取交集
  • 学习阶段:先从CERT C开始,它更贴近实际开发

说白了,规范是死的,人是活的。关键是要理解每条规则背后的原理,而不是死记硬背。你想想看,如果你知道整数溢出的危害,自然就会去检查边界条件,而不是等着工具来提醒你。

我的工作流:

  1. 写代码时心里默念CERT C的关键规则
  2. 写完用静态分析工具扫描一遍
  3. 人工审查工具报出的每个警告(不要盲目忽略)
  4. 对于MISRA C项目,再加一轮专项检查
  5. 定期回顾,把常见问题整理成团队规范

嗯,这套流程帮我避免了不少麻烦。有一次工具报了一个「未初始化变量」的警告,我本来想忽略的,因为我觉得那个变量肯定会被赋值。但最后还是查了一下,结果发现确实有一条路径没覆盖到。要是上线了,那就是一个随机崩溃的bug。

所以我的建议是:尊重工具,但不要迷信工具。工具报的每个警告都值得你花30秒想一想:这里真的没问题吗?


公众号:蓝海资料掘金营,微信deep3321