vlambda博客
学习文章列表

C语言安全编码(4)

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

这是安全编码的第4篇。


1.非NULL结尾的字符序列禁止传递给库函数

很多的库函数接受一个以NULL结尾的字符串,如果传递一个非NULL结尾的字符序列,则可能导致库函数访问边界外的非法内存。

例1.1:

  
    
    
  
1// 例1.1
2void func()
3
{
4    char str[] = "xyz";
5    printf("%s\n", str);
6}
在这段代码中 字符序列str没有以NULL结尾,当传入printf时导致访问非法内存。
对于 上面的情况,合适的处理方法如下:
  
    
    
  
1void func()
2
{
3    char str[] = "xyz"// 不指定数组大小
4    printf("%s\n", str);
5}



例1.2:

  
    
    
  
    
      
      
    
      
        
        
      
 1// 例1.2
2wchar_t *msg = NULL;
3size_t msg_size = 1024;
4void lesson_memory()
5
{
6    wchar_t *temp;
7    size_t temp_size;
8    /* .... */
9    if(msg != NULL) {
10        temp_size = msg_size / 2 + 1;
11        temp = realloc(msg, temp_size * sizeof(wchar_t));
12        if(temp == NULL) {
13            /* error */
14        }
15        msg = temp;
16        msg_size = temp_size;
17    }
18}
在这段代码中 ,函数realloc()不存在NULL结尾的字节字符串,调用realloc()无法保证msg是否以NULL结尾。
对于 上面的情况,合适的处理方法如下:
 1wchar_t *msg = NULL;
2size_t msg_size = 1024;
3void lesson_memory()
4
{
5    wchar_t *temp;
6    size_t temp_size;
7    /* .... */
8    if(msg != NULL) {
9        temp_size = msg_size / 2 + 1;
10        temp = realloc(msg, temp_size * sizeof(wchar_t));
11        if(temp == NULL) {
12            /* error */
13        }
14        msg = temp;
15        msg[temp_size - 1] = L'\0'//保证字符串总是以NULL结尾
16        msg_size = temp_size;
17    }
18}



例1.3:

 1// 例1.3
2#define STR_SIZE 64
3size_t func(const char *src)
4{
5    char str[STR_SIZE];
6    size_t ret = 0;
7    if(src) {
8        str[sizeof(str) - 1] = '\0';
9        strncpy(str, src, sizeof(str));
10        ret = strlen(str);
11    }
12    return ret;
13}
在这段代码中 strncpy()并不保证结果字符串以NULL结尾,如果源字符串前n位没有NULL,则其结果可能不以null结尾。
对于 上面的情况,合适的处理方法如下:
 1#define STR_SIZE 64
2size_t func(const char *src)
3{
4    char str[STR_SIZE];
5    size_t ret = 0;
6    if(src) {  // 截断字符串
7        strncpy(str, src, sizeof(str) - 1);
8        str[sizeof(str) - 1] = '\0';
9        ret = strlen(str);
10    }
11    return ret;
12}

或者:

 1#define STR_SIZE 64
2size_t func(const char *src)
3{
4    char str[STR_SIZE];
5    size_t ret = 0;
6    if(src) {  // 截断字符串 strncpy_s()
7        // strncpy_s()最多拷贝n个字符,如果没有NULL结尾,则在第n位自动被设置为NULL
8        errno_t err = strncpy_s(str, sizeof(str), src, strlen(src));
9        if(err == 0) {
10            ret = strlen_a(str, sizeof(str));
11        } 
12    }
13    return ret;
14}

或者:

 1#define STR_SIZE 64
2size_t func(const char *src)
3{
4    char str[STR_SIZE];
5    size_t ret = 0;
6    if(src) {  // 不截断复制
7        if(strlen(src) < sizeof(str)) {
8            strcpy(str, src);
9            ret = strlen(str);
10        }
11    }
12    return ret;
13}


2.正确判断宽字符串的长度

当宽字符串被错误的表示为窄字符串时,它们的长度可能弄错,不正确的字符串长度在使用时导致缓冲区溢出。

例2.1:

1// 例2.1-1
2void func()
3
{
4    wchar_t wstr1[] = L"0123456789";
5    wchar_t wstr2[] = L"
0000000000";
6    strncpy(wstr1, wstr2, 10);
7}
在这段代码中 使用strncpy来拷贝10个宽字符,宽字符可能含有NULL字节,从而导致strncpy()出乎意料的提前拷贝结束。
1// 例2.1-2
2void func()
3
{
4    char nstr1[] = "0123456789";
5    char nstr2[] = "0000000000";
6    wcsncpy(nstr1, nstr2, 10);
7}
在这段代码中 使用wcsncpy来拷贝10个宽字符,因为nstr2是窄字符串,没有足够的空间存放10个宽字符,导致缓冲区溢出。
对于 上面的情况,合适的处理方法如下:
 1void func()
2
{
3    wchar_t wstr1[] = L"0123456789";
4    wchar_t wstr2[] = L"0000000000";
5    wcsncpy(wstr1, wstr2, 10);   // wcsncpy处理宽字符串
6
7    char nstr1[] = "0123456789";
8    char nstr2[] = "0000000000";
9    strncpy(nstr1, nstr2, 10);   // strncpy处理窄字符串
10}



例2.2:

 1// 例2.2-1
2void func()
3
{
4    wchar_t wstr1[] = L"0123456789";
5    wchar_t *wstr2[] = (wchar_t *)malloc(strlen(wstr1) + 1);
6    if(wstr2 == NULL) {
7        /* error */
8    }
9    free(wstr2);
10    wstr2 = NULL;
11}
在这段代码中 ,strlen()计算一个以NULL结尾的字符串中NULL之前的字符数,但宽字符可能包含NULL字符,因此strlen()将返回字符串中第一个NULL字节之前的字节数量。
 1// 例2.2-2
2void func()
3
{
4    wchar_t wstr1[] = L"0123456789";
5    wchar_t *wstr2[] = (wchar_t *)malloc(wcslen(wstr1) + 1);
6    if(wstr2 == NULL) {
7        /* error */
8    }
9    free(wstr2);
10    wstr2 = NULL;
11}
在这段代码中 ,wcslen()用于确定一个宽字符串长度,但长度并没有与sizeof(wchar_t)相乘。
对于 上面的情况,合适的处理方法如下:
 1void func()
2
{
3    wchar_t wstr1[] = L"0123456789";
4    wchar_t *wstr2[] = (wchar_t *)malloc((wcslen(wstr1) + 1) * sizeof(wchar_t));
5    if(wstr2 == NULL) {
6        /* error */
7    }
8    free(wstr2);
9    wstr2 = NULL;
10}

3.只能释放动态分配的内存

释放非动态分配的内存可能导致严重的错误,有些编译器甚至会使程序异常终止,因此避免对不是由动态内存分配函数(malloc()、calloc()、realloc()等)所返回的指针调用free()。free()传递空指针不会用任何动作发生。

例3.1:
 1// 例3.1
2#define MAX_SIZE 1024
3int main(int argc, char *argv[])
4
{
5    char *str = NULL;
6    unsigned int len;
7    if(argc == 2) {
8        len = strlen(argv[1]) + 1;
9        if(len > MAX_SIZE) {
10            /* error */
11        }
12        str = (char *)malloc(len);
13        if(str == NULL) {
14            /* error */
15        }
16        strcpy(str, argv[1]);
17    } else {
18        str = "string";
19        printf("%s", str);
20    }
21    free(str);
22    return 0;
23}

在这段代码中,若argc的值不等于2,则str是一个字符串常量,不是动态分配的内存,传入free()可能导致严重的错误。

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

 1#define MAX_SIZE 1024
2int main(int argc, char *argv[])
3
{
4    char *str = NULL;
5    unsigned int len;
6    if(argc == 2) {
7        len = strlen(argv[1]) + 1;
8        if(len > MAX_SIZE) {
9            /* error */
10        }
11        str = (char *)malloc(len);
12        if(str == NULL) {
13            /* error */
14        }
15        strcpy(str, argv[1]);
16    } else {
17        printf("string");
18        return EXIT_FALURE;
19    }
20    free(str);
21    return 0;
22}

交易担保 粉丝社 点击进入留言区