vlambda博客
学习文章列表

C语言安全编码(2)

安全编码的意义是建立编程人员的攻击者思维,养成安全编码的习惯,编写出安全可靠的代码。

这是安全编码的第2篇。

1.填充数据禁止比较

在结构体对象中可能存在未命名的填充字段,但其并不位于结构体起始处,在结构体或联合体的尾部可能存在未命名的填充字段。
结构体和联合体中的未命名成员不参与初始化,未命名成员即时在初始化后值也是不确定的。

例1.1

 1// 例1.1
2struct str {
3    int a;
4    char s;
5    char buf[8];
6};
7
8void compare(const struct str *left, const struct str *right)
9
{
10    if(memcmp(left, right, sizeof(struct str)) == 0) {
11    /*....*/
12    }
13}
在这段代码中,memcmp比较的两个结构体内容包括了填充字节。
对于 上面的情况,合适的处理方法如下:
 1// 比较结构体中的各个字段
2void compare(const struct str *left, const struct str *right)
3
{
4    if((left && right) &&
5       (left->a == right->a) &&
6       (left->s == right->s) &&
7       (memcmp(left->buf, right->buf, 8) == 0)) {
8    /*....*/
9    }
10}



2.指针和数组的边界不要超出

(1)例2.1:越界指针

  
    
    
  
 1#define SIZE 24
2static int tab[SIZE];
3
4int *func(int index)
5
{
6    if(index < SIZE) {
7        return tab + index;
8    }
9    return NULL;
10}
在这段代码中 ,参数index作为偏移量来访问静态数组,在使用之前验证index,然而,当index小于0时,函数func的返回值是不确定的,仅仅使用加法可能触发硬件陷阱。
对于 上面的情况,合适的处理方法如下:
 1#define SIZE 24
2static int tab[SIZE];
3
4int *func(int index)
5
{
6    if(index > 0 && index < SIZE) {  // 检测并拒绝无效值
7        return tab + index;
8    }
9    return NULL;
10}
或者:
 1#define SIZE 24
2static int tab[SIZE];
3
4int *func(unsigned int index)
5
{
6    if(index < SIZE) {       // 无符号类型,自动规避负值
7        return tab + index;
8    }
9    return NULL;
10}


(2)例2.2:越界索引

  
    
    
  
 1// 例2.1
2static int *tab = NULL;
3static size_t size = 0;
4
5int insert(size_t pos, int val)
6
{
7    if(size < pos) {
8        int *temp;
9        size = pos +1;
10        temp = (int *)realloc(tab, size * sizeof(*tab));
11        if(temp == NULL) {
12            return -1;
13        }
14        tab = temp;
15    }
16    tab[pos] = val;
17    return 0;
18}
在这段代码中 函数insert使用一个索引来将数值放入一个数组成员中,然而,索引pos的大小进行了不正确的验证,导致越界,其次在增大tab前修改了size的大小,若realloc失败,下次调用insert函数时pos可能大于等于size,导致缓冲区溢出。
对于 上面的情况,合适的处理方法如下:
  
    
    
  
 1static int *tab = NULL;
2static size_t size = 0;
3
4int insert(size_t pos, int val)
5
{
6    if(size < pos) {
7        if((pos > SIZE-1) || ((pos + 1) > SIZE_MAX / sizeof(*tab))) {
8            return -1;
9        }
10        int *temp;       
11        temp = (int *)realloc(tab, (pos+1) * sizeof(*tab));
12        if(temp == NULL) {
13            return -1;
14        }
15        size = pos +1;
16        tab = temp;
17    }
18    tab[pos] = val;
19    return 0;
20}


(3)例2.3:指针超过柔性数组成员范围

 1// 例2.3
2struct Str {
3    size_t len;
4    char buf[];
5};
6
7char *find(const struct Str *s, int c) {
8    char *first = s->buf;
9    char *last = s->buf + s->len;
10
11    while(first++ != last) {
12        if(*first == (unsigned char)c) {
13            return first;
14        }
15    }
16    return NULL;
17}
18
19void func() {
20    struct Str *s = (struct Str *)malloc(sizeof(struct Str));
21    if(s == NULL) {
22        /* error */
23    }
24    s->len = 0;
25    find(s,'a')
26}
在这段代码中 函数find试图遍历柔性元素buf的成员,但func函数没有为buf数组分配存储空间,因此函数find中的first++将访问未分配成员的buf数组的末尾,导致未定义行为。
对于 上面的情况,合适的处理方法如下:
 1struct Str {
2    size_t len;
3    char buf[];
4};
5
6char *find(const struct Str *s, int c) {
7    char *first = s->buf;
8    char *last = s->buf + s->len;
9
10    while(first != last) {
11        if(*++first == (unsigned char)c) {   // 确认指针不会越界后再增加
12            return first;
13        }
14    }
15    return NULL;
16}
17
18void func(){
19    struct Str *s = (struct Str *)malloc(sizeof(struct Str));
20    if(s == NULL) {
21        /* error */
22    }
23    s->len = 0;
24    find(s,'a')
25}



3.变长数组的大小要在有效范围内

变长数组本质上与普通数组相同,只是在声明时使用了一个长度变量而不是长度常量,其只能在块范围内声明。若长度参数不是正值,则该行为是未定义行为,若参数过大,可能导致不可预料的行为。

(1)例3.1:

1// 例3.1
2void func(size_t size)
3
{
4    int buf[size];
5    do_sth(buf, size);
6    /*....*/
7}
在这段代码中 ,size的值可能为0或者过大,这将导致不可预料的错误。
对于上面的情况,合适的处理方法如下:
 1// 例3.1
2void func(size_t size)
3
{
4    if(size == 0||SIZE_MAX / sizeof(int) < size) {
5        return;
6    }
7    if(size < MAX_ARRAY) {
8        int buf[size];
9        do_sth(buf, size);
10    } else {
11        int *arr = (int *)malloc(size * sizeof(int));
12        if(arr == NULL) {
13            /* error */
14        }
15        do_sth(arr, size);
16        free(arr);
17    }
18}


(2)例3.2:

 1// 例3.1
2#define N1 1024
3
4void *func(size_t n2)
5
{
6    typedef int a[n2][N1];
7
8    A *arr = malloc(sizeof(A));
9    if(arr == NULL) {
10        /* error */
11    }
12
13    for(size_t i=0; i!=n2; i++) {
14        memset(arr[i], 0, N1*sizeof(int));
15    }
16    return arr;
17}
在这段代码中 ,当n2的值大于SIZE_MAX/(N1*sizeof(int)),含有sizeof的表达式可能产生回绕,其结果比N1*N2*sizeof(int)小,malloc调用成功后会分配少于n2个元素的数组,导致循环最后一个个memset函数在调用for时越界。

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

 1// 例3.1
2#define N1 1024
3
4void *func(size_t n2)
5
{
6    if(n2 > SIZE_MAX / (N1 * sizeof(int))) { // 进行检查
7        return;
8    }
9
10    typedef int a[n2][N1];
11
12    A *arr = malloc(sizeof(A));
13    if(arr == NULL) {
14        /* error */
15    }
16
17    for(size_t i=0; i!=n2; i++) {
18        memset(arr[i], 0, N1*sizeof(int));
19    }
20    return arr;
21}