vlambda博客
学习文章列表

C语言安全编码(1)



编码规范的目的:

保证不同背景和经历的开发人员可以良好的协同开发

保证组织级的开发技能和经验得以共享和传承

后面我会逐步分享这段时间学习的安全编码规范。

1.对象的持续期

每个对象都有一个生命周期,如果一个对象在生命周期外被引用,则该行为是未定义的,当生命周期结束时,指针值就变的不确定了。

(1)例1.1:不同的生命周期

 1// 例1.1
2char *p;
3void func()
4
{
5  char str[] = "abcdefg";
6  p = str;    // error
7}
8
9int main()
10
{
11  func();
12  printf("%s\n",p)
13  return 0;
14}
在这段代码中,str的 生命周期仅在func函数执行期内,当func函数运行结束时,str就失效了,但p中还存储了str的地址。
对于上面的情况,合适的处理方法如下:
1// 例1
2// p和str的持续时间是一致的
3void func()
4
{
5  char str[] = "abcdefg";
6  char *p = str;    
7}
或者 :
1// 在str被销毁之前设置p为NULL,防止p取不确定值
2char *p;
3void func()
4
{
5  char str[] = "abcdefg";
6  p = str;    // error
7  /*...*/
8  p = NULL;
9}


(2)例1.2:返回值

1// 例1.2
2charfunc()
3
{
4  char arr[5];
5  /*.....*/
6  return arr;
7}
在这段代码中,arr的 生命周期仅在func函数执行期内,当函数返回时,arr变量已经释放,编译器会产生警告。
对于上面的情况,合适的处理方法如下:
1// 需要修改arr的值,应该以地址的方式传递给函数
2// 这样才能让arr在函数外得以保持
3void func(char* arr, size_t len)
4
{
5  /*......*/
6  return;
7}


(3)例1.3:输出参数

 1// 例1.3
2void fun1(char **ptr)
3
{
4  char arr[10];
5  /*...*/
6  *ptr = arr;
7}
8
9void fun2()
10
{
11  char *p;
12  func1(&p);
13}
在这段代码中,fun1存储了一个本地数组arr,并将函数参数指针ptr指向arr,当fun1返回后,ptr指向的变量已经释放。
对于上面的情况,合适的处理方法如下:
 1// 将变量arr定义为全局变量
2char arr[10];
3void fun1(char **ptr)
4
{
5  /*...*/
6  *ptr = arr;
7}
8
9void fun2()
10
{
11  char *p;
12  func1(&p);
13}

2.禁止访问未初始化的内存

局部变量在未初始化就使用,变量值是未定的,访问这个变量将产生不可预料的后果。某些分配动态内存的函数调用时将不会对分配的内存进行初始化,例如malloc()、aligned_alloc()、realloc()等。

(1)例2.1:Return by Reference

 1//例2.1
2void func(int n, int *p)
3
{
4  if(p == NULL) {
5    return;
6  }
7
8  if(n > 0) {
9    *p = 1;
10  }
11  else if(n < 0) {
12    *p = 2;
13  }
14}
15
16int call(int num)
17
{
18  int flag;
19  func(num, flag);
20  return num;
21}
这段代码中,原本的意图是根据num的值来改变flag的值,但忽略了num为0的情况,在实际运行时,若num=0,则flag未被初始化,可能导致不可预料的后果。
对于上面的情况,合适的处理方法如下:
 1void func(int n, int *p)
2
{
3  if(p == NULL) {
4    return;
5  }
6
7  if(n >= 0) {  // 增加等于0的情况
8    *p = 1;
9  }
10  else if(n < 0) {
11    *p = 2;
12  }
13}


(2)例2.2:uninitialized local

 1//例2.2
2#define BUF_SIZE 24
3void myprint(char *msg)
4
{
5  const char *log = msg;
6  char buf[BUF_SIZE];
7
8  sprintf(buf,"%s\n"log);
9  printf("%s\n", buf);  
10}
这段代码中,如果msg引用的字符串超过了17个字节,则会发生缓冲区溢出。
对于上面的情况,合适的处理方法如下:
 1#define BUF_SIZE 24
2void myprint(char *msg)
3
{
4  char buf[BUF_SIZE];
5
6  // 调用snprintf()消除缓存区溢出
7  if(0 < snprintf(buf, BUF_SIZE,"%s\n", msg)) {
8    printf("%s\n", buf);
9  }
10  else {
11    printf("ERROR\n");
12  }
13}

(3)例2.3:mbstate_t初始化
 1//例2.3
2#include <string.h>
3#include <wchar.h>
4
5void func(char *msg)
6
{
7  size_t len;
8  mbstste_t state;
9  len = mbrlen(msg, strlen(msg), &state);
10}

对于上面的情况,合适的处理方法如下:

1void func(char *msg)
2
{
3  size_t len;
4  mbstste_t state;
5  memset(&state, 0sizeof(state)); // 将mbstate_t对象初始化为0
6  len = mbrlen(msg, strlen(msg), &state);
7}

(4)例2.4:realloc()函数
 1#define OLDSIZE 10
2#define NEWSIZE 20
3int func_array(int *arr, int count)
4
{
5  if(count == 0) {
6    return 0;
7  }
8  int *res = (int *)realloc(arr, count * sizeof(int));
9  if(res == NULL) {
10    free(res);
11    return 0;
12  }
13  return res;
14}
15
16void func()
17
{
18  int *array = (int *)malloc(OLDSIZE * sizeof(int));
19  if(array == NULL) {
20    /* error */
21  }
22
23  for(int i = 0; i < OLDSIZE; i++){
24    array[i]=i + 1;
25  }
26
27  array = func_array(array, NEWSIZE);
28  if(array == NULL) {
29    /* error */
30  }
31
32  for(int i = 0; i < NEWSIZE; i++){
33    printf("%d ",array[i]);
34  }  
35}

这段代码中,realloc()函数改变动态分配的内存的大小,返回内存对象最初的size个字节没有变化,但新添加空间没有初始化,其值不确定。

对于上面的情况,合适的处理方法如下:

 1#define OLDSIZE 10
2#define NEWSIZE 20
3// 第二个参数作为旧的长度
4int func_array(int *arr, int oldcount, int newcount)
5
{
6  if(newcount == 0) {
7    return 0;
8  }
9  int *res = (int *)realloc(arr, newcount * sizeof(int));
10  if(res == NULL) {
11    free(res);
12    return 0;
13  }
14
15  if(newcount > oldcount) {
16    memset(res + oldcount, 0, (newcount - oldcount) * sizeof(int));
17  }
18
19  return res;
20}

3 .禁止对NULL指针解引用
对NULL指针解引用是未定义行为,可能导致程序异常终止。
(1)例3.1
1void func(char *str)
2
{
3  int len = strlen(str) + 1;
4  char *c_str = (char *)malloc(len);
5  memcpy(c_str, str, len);
6  /*...*/
7}

这段代码中,str拷贝给c_str指向的动态分配内存,但如果malloc()失败,c_str=NULL,此时运行memcpy会出现未定义行为。

对于上面的情况,合适的处理方法如下:

 1void func(char *str)
2
{
3  if(str == NULL) {  // 确保str指针不是NULL
4    /* error */
5  }
6
7  int len = strlen(str) + 1;
8  char *c_str = (char *)malloc(len);
9  if(c_str == NULL) {    // 确保malloc返回值不是NULL
10    /* error */
11  }
12
13  memcpy(c_str, str, len);
14  /*...*/
15}