减少编程错误

if(connection == nullptr)

​ goto fail;

​ goto fail;

安全编码与未定义行为

未定义行为

缓冲区溢出

查看下面的代码,其输出结果是什么?

1
2
3
4
5
6
7
8
int main(){
int i;
int a[4] = 0;
for(i = 0; i <= 4; i++){
...
}
return 0;
}

产生了越界访问,导致了未定义行为。建议:

  • 不要使用超出边界的指针或数组下标(这不是废话吗)
  • 缓冲区溢出问题在CWE的数据库中排名第二

访问已经销毁的局部空间(悬空指针)

这种最常见的就是在一个函数中定义一段空间,然后返回这段空间的首地址。由于这段空间在函数返回后就被销毁,因此这个地址也成为了悬空指针。

超出类型能容纳的范围

查看下面的代码:

1
2
3
int32_t Average(int32_t x, int32_t y){
return (x+y)/2;
}

两个整型相加,可能会溢出。建议:

  • 确保有符号整数运算不溢出,避免无符号整数运算产生的回绕

有符号和无符号混用

建议:

  • 用来表示数值时,不要用char
  • 避免混用有符号和无符号数,如果不得不一起用,显式指定转换类型
  • 类型转换导致的溢出问题在CWE中排11位,有超过800条漏洞

未定义行为后果

未定义行为的一个典型后果是,由于访问了非法内存,所以可以通过精心设计构造的输入,修改栈中保存的返回地址,从而跳转到黑客想要跳转的区域。

表达式与变量

全局变量初始化

假设我们在a.cpp中有一个全局变量

1
int g_x = 1;

而在b.cpp中,也有一个全局变量,且使用g_x进行定义

1
2
3
4
5
6
7
extern int g_x;
int g_y = 10 + g_x;

int main(){
cout << g_y << endl;
return 0;
}

这里就有了问题,两个不同文件定义的全局变量,其初始化顺序是不确定的。建议:

  • 避免全局变量出现初始化顺序依赖的情况

代码度量

代码的组织

  • 程序必须为阅读者编写,只是顺便用于机器执行
  • 短小的函数总是更简洁、更容易阅读
  • 功能单一的函数更容易复用,简洁明了的代码更容易维护

业界指南、规范与工具

业界指南

  • 谷歌C++风格指南1,以风格约定与习惯引导为主,不涉及消除安全问题
  • SEI CERT C++ Coding Standard2,以消除安全隐患为目的,不涉及风格约定,内容太多,建议作为字典
  • C++ Core Guideline3,重点为如何合理应用现代C++特性,这个非常值得一读

工具

编译器

gcc等编译器本身就能检查很多问题,且几乎不会误报,我们应当做到保持编译器警告清零

辅助检查工具

参考文献

0%