构建安全代码

本文将针对编码过程中的一些漏洞进行总结,并给出相关示例

内存操作漏洞

字符串与数组操作

缓冲区溢出

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string>
using namespace std;
int main(){
char name[] = "duanshiqi";
uint8_t id = 0;
printf("please input the student id: \n");
scanf("%d, &id");
printf("student is %d, name is %s\n", id, name);
return 0;
}

这是一个典型的缓冲区溢出漏洞,id的类型是unsigned char,但是在scanf中格式却是%d,这将导致当输入较大的数字时,会对id后面的空间进行覆盖,导致输出错误。

差一错误

1
2
3
4
5
6
constexpr char buf[] = "hello world";
int main(){
char name[] = "duanshiqi";
char str[11];
memcpy(str, buf, 11);
}

上面是典型的差一错误,HELLO_WORLD后面是有\0的,所以长度实际为12,比11大。

外部可控的format

1
2
3
4
void OutputLog(char* loginfo){
char password[] ="password";
printf(loginfo);
}

上面直接用printf()未经格式化输出loginfo,那么如果外部精心构造了一个format,那么可能导致password泄漏。

函数漏洞

使用安全的函数

一些老旧的函数不甚安全

先看一个例子:

1
2
3
4
void GetScore(int* scoreList){
int score[] = {0,1,2,3,4,5,6,7,8,89,123,111};
memcpy(scoreList, score, sizeof(score)/sizeof(score[0]));
}

上面scoreList并没有指定拷贝长度,如果scoreList长度小于score,那么会造成缓冲区溢出,所以在传入指针指向的空间时,要同时传入空间大小。在这里,memcpy就是一个典型的不安全函数。

C11标准对于不安全的函数指定了安全版本,请使用安全版本替代不安全函数。安全函数的安全特性如下:

  • 边界检查及入参检查
  • 字符串强制0结尾
  • 增加错误码
  • 内存重叠检查
  • 限定操作内存的最大长度

对于memcpy,其安全函数版本如下:

1
errno_t memcpy_s(void *dest, size_t dsetMax, const void* src, size_t count);

使用安全函数要谨慎

即使有了安全函数,也可能出现不正确使用的情况,例如将源长度直接作为目的长度,属于典型的掩耳盗铃的做法:

1
memcpy_s(dest, sizeof(src), src, sizeof(src));  // 禁止掩耳盗铃

同时,确保目的长度确实地等于传入的目的地址,有时候定义了一个宏作为目的长度,结果输入的目的内存空间长度和宏不相等,也是要禁止的。

必须检查安全函数返回值

安全函数为我们设置了返回值,我们不能忽略,使用时一定要检查安全函数的返回值。

整数

谨慎地进行整数间比较操作

char类型最大值比int类型最大值小,如果使用char的类型和int类型比较,很有可能出现无效比较的情况,需要特别注意。

注意整数越界问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char g_studentScore[100] = {0};
unsigned short g_curOffset = 12; // 已经保存的学生成绩个数
void AddStudentScore(char* score, unsigned short num){
if(score == nullptr){
return;
}
unsigned short int length = g_curOffset + num;
if(length > (sizeof(g_studentScore) - 1)){
err;
return;
}
for(unsigned short i = 0; i < num; i++){
g_studentScore[g_curOffset] = score[i];
g_curOffset++;
}
}

上面的代码看似会对length进行检查,保证length不超过100,但是问题就出现在相加的语句上:

1
length = g_curOffset + num;

这一句并没有进行整数越界检查,如果num + g_curOffset超过了65535,那么length会溢出,导致实际相加的结果很小,但是num又很大,后面的循环会导致缓冲区溢出。可以在循环的时候添加检查,如果越界,就退出。

内存

参考文献

0%