程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C安全編碼--整數理解

C安全編碼--整數理解

編輯:C++入門知識

建議和規則

建議:

  • 理解編譯器所使用的數據模型

  • 使用rsize_t或size_t類型表示所有表示對象長度的整數值

  • 理解整數轉換規則

  • 使用安全的整數庫

  • 對來自不信任來源的整數值實行限制

  • 如果輸入函數無法處理所有可能出現的錯誤就不要用它們轉換字符數據

  • 使用strtol()或相關函數把字符串標記換換為整數

  • 只使用顯式的有符號或無符號char類型表示數值

  • 驗證所有的整數值位於范圍內

  • 保證枚舉常量映射到唯一的值

  • 使用%操作符時不要假設余數總是正的

  • 把指針轉換為整數或者把整數轉換為指針時需要小心

  • 當普通的整數位段用於表達式時,不要對它的類型作出假設

  • 只對無符號操作數使用位操作符

  • 避免在同一個數據上執行位操作和算術運算

  • 在程序員定義的整數類型的格式化I/O中使用intmax_t或者unitmax_t

規則:

  • 保證無符號整數運算不產生回繞

  • 保證整型轉換不會丟失或錯誤解釋數據

  • 保證有符號整數運算不會產生溢出

  • 保證除法和求模運算不會導致除零錯誤

  • 移位的數量不能是負數或大於操作數的位數

  • 把整型表達式比較或賦值為一種較大類型之前用這種較大類型對它進行求值

本文地址:http://www.cnblogs.com/archimedes/p/c-security-int.html,轉載請注明源地址。

理解編譯器所使用的數據模型

<limits.h>除了提供特定類型的位數信息之外,還定義了一些宏,可以用於確定任何遵循標准的編譯器所使用的標准整數類型數的整型范圍

e.g.  UNIT_MAX是unsigned int可以出現的最大值,LONG_MIN是long int可以出現的最小值

<sdtint.h>頭文件在類型中引入了特定的長度限制,可用於避免對特定數據模型的依賴

e.g.  int_least32_t是編譯器所支持的最小有符號整數類型,它包含了至少32位

所有編譯器要求提供的類型:

<inttypes.h>聲明了用於操縱最大寬度整數以及把數值字符串轉換為最大寬度整數的函數

代碼:

unsigned int a, b;
unsigned long c;
//初始化a和b
c = (unsigned long)a * b;  //可能會溢出

解決方案:

#if UNIT_MAX > UNITMAX / UNIT_MAX
#error No safe type is available.
#endif
unsigned int a, b;
unsigned long c;
//初始化a和b
c = (unitmax_t)a * b;

使用rsize_t或size_t類型表示所有表示對象長度的整數值

size_t類型表示sizeof操作符執行結果的無符號整型類型。size_t類型的變量保證具有足夠的精度,能夠表示一個對象的長度。2007年引入了一種新的類型rsize_t,它被定義為size_t,但是明確用於保存單個對象的長度,任何表示對象長度的變量,包括作為大小、索引、循環計數器和長度的整數值,如果有可能,都應該聲明為rsize_t,否則就聲明為size_t

代碼:

char *copy(size_t n, const char *str) {
    int i;
    char *p;
    if(n == 0) {
        //error...
    }
    p = (char*)malloc(n);
    if(p == NULL) {
        //error...
    }
    for(i = 0; i < n; ++i) {
        p[i] = *str++;
    }
    return p;
}
char *p = copy(9, "hi there");

當n > INT_MAX,i超過INT_MAX時,i的值將是從(INT_MIN)開始的負值,p[i]所引用的內存位置是在p所引用的內存之前,導致寫入發生在數組邊界之外

解決方案:

char *copy(rsize_t n, const char *str) {
    int i;
    char *p;
    if(n == 0 || n > RSIZE_MAX) {
        //error...
    }
    p = (char*)malloc(n);
    if(p == NULL) {
        //error...
    }
    for(i = 0; i < n; ++i) {
        p[i] = *str++;
    }
    return p;
}
char *p = copy(9, "hi there");

理解整數轉換規則

轉換可以作為類型轉換的結果顯式或隱式地發生,C99的整數轉換規則定義了C編譯器如何處理轉換,這些規則包括:整型提升、整型轉換秩和尋常算術轉換。

目的是保證轉換結果是相同的值,並且這些值對計算所產生的影響是最小的

整型提升

在執行算術運算的時候,小於int的整數類型將會提升,較小的轉換為int類型,否則轉換為unsigned int類型

代碼:

#include<stdio.h>
int main(void){
    signed char cresult, c1, c2, c3;
    c1 = 100;
    c2 = 3;
    c3 = 4;
    cresult = c1 * c2 / c3;
    printf("cresult= %d\n", cresult);  //cresult= 75
    return 0;
}

假設signed char是用8位值表示,c1 * c2 = 300就無法表示,但是由於整數提升的緣故,c1,c2,c3都轉換為int類型

使用安全的整數庫

安全整數庫的一個例子是IntegerLib,可以免費使用,這個庫的目標是提供一些工具函數的集合,在編寫C程序的時候幫助軟件開發人員避免整數溢出、整數截斷和符號錯誤

對來自不信任來源的整數值實行限制

所有不信任來源的整數值都應該求值,以確定是否存在可確認的上界和下界

代碼:

int create_table(size_t length) {
    char **table;
    if(sizeof(char *) > SIZE_MAX / length) {
        return -1; //溢出
    }
    size_t table_length = length * sizeof(char*);
    table = (char**)malloc(table_length);
    if(table == NULL) {
        return -1; //error
    }
    /*...*/
    return 0;
}

length可接受的范圍定義為[1, MAX_TABLE_LENGTH]

解決方案:

enum {MAX_TABLE_LENGTH = 256};
int create_table(size_t length) {
    size_t table_length;
    char **table;
    if(length == 0 || length > MAX_TABLE_LENGTH) {  // 假設MAX_TABLE_LENGTH * sizeof(char *) < SIZE_MAX
        return -1; //error
    }
    assert(length <= SIZE_MAX / sizeof(char *));
    table_length = length * sizeof(char*);
    table = (char**)malloc(table_length);
    if(table == NULL) {
        return -1; //error
    }
    /*...*/
    return 0;
}

如果輸入函數無法處理所有可能出現的錯誤就不要用它們轉換字符數據

如果輸入函數無法處理所有可能出現的輸入,就不要使用它們把輸入值轉換為整數。比如:scanf()、fscanf()、vscanf()、vfscanf()這些函數可以處理合法的整數值,但是缺乏對非法值的健壯錯誤處理功能。另一種方法是把字符數據輸入以null字符結尾的字節字符串形式,並利用strtol()或者相關函數把它轉換為一個整數值。

代碼:

long sl;
if(scanf("%ld", &sl) != 1) { 
    /*處理錯誤*/
}

一般而言,不要使用scanf對輸入字符串的整數或浮點數進行解析,因為輸入中可能包含實參類型無法表示的數

解決方案:

char buff[25];
char *end_ptr;
long sl;
if(fgets(buf, sizeof(buff), stdin) == NULL) {
    if(puts("EOF or read error\n") == EOF) {
        //處理錯誤
    }
} else {
    errno = 0;
    sl = strtol(buff, &end_ptr, 10);
    if(ERANGE == errno) {
        if(puts("number out of range\n") == EOF) {
            //處理錯誤
        }
    } else if(end_ptr == buf) {
        if(puts("not valid numberic input\n") == EOF) {
            //處理錯誤
        }
    } else if('\n' != *end_ptr && '\0' != *end_ptr) {
        if(puts("extra characters on input line\n") == EOF) {
            //處理錯誤
        }
    }
}

只使用顯式的有符號或無符號char類型表示數值

代碼:

char c = 200;
int i = 1000;
printf("i/c = %d\n", i/c); //" i/c = -17 "

解決方案:

unsigned char c = 200;
int i = 1000;
printf("i/c = %d\n", i/c); //" i/c = 5 "

驗證所有的整數值位於范圍內

保證枚舉常量映射到唯一的值

代碼:

enum {red = 4, orange, yellow, green, blue, indigo = 6, violet}; //yellow=indigo=6

解決方案:

enum {red, orange, yellow, green, blue, indigo, violet}; //不提供顯式的整數賦值
enum {red, orange, yellow, green, blue, indigo, violet}; //只提供第一個成員賦值
enum { //為所有的成員賦值
    red = 4, 
    orange = 5, 
    yellow = 6, 
    green = 7, 
    blue = 8, 
    indigo = 6, 
    violet = 7
}; 

使用%操作符時不要假設余數總是正的

C99對%操作符的定義提示了下列行為:

17%3=2

17%(-3)=2

-17%3=-2

-17%(-3)=-2

結果與被除數具有相同的符號

代碼:

int insert(int index, int *list, int size, int value) {
    if(size != 0) {
        index = (index + 1) % size;
        list[index] = value;
        return index;
    } else {
        return -1;
    }
}

解決方案1(使用一個修正的取模內聯函數):

inline imod(int i, int j) {
    return (i % j) < 0 ? (i % j) + (j < 0 ? -j : j) : i % j;
}

解決方案2(使用size_t):

int insert(size_t index, int *list, size_t size, int value) {
    if(size != 0) {
        index = (index + 1) % size;
        list[index] = value;
        return index;
    } else {
        return -1;
    }
}

保證無符號整數運算不產生回繞

C99規定(無符號整數將會回繞):涉及無符號操作數的計算不會溢出,因為無法由最終的無符號整數類型表示的結果將會根據這種最終類型可以表示的最大值加1執行求模操作

整數運算:

來自不信任來源的整數值如果按下面方式之一使用,就不允許回繞:

  • 作為數組索引

  • 在任何指針運算中

  • 作為對象的長度或大小

  • 作為數組的上屆(例如循環計數器)

  • 作為內存分配函數的實參

  • 在安全關鍵代碼中

代碼:

unsigned int ui1, ui2, sum;
//初始化ui1, ui2
sum = ui1 +ui2;

解決方案:

unsigned int ui1, ui2, sum;
//初始化ui1, ui2
if(UINT_MAX - ui1 < ui2) {
    //處理錯誤
} else {
    sum = ui1 +ui2;
}

參考資料

《C安全編碼標准》

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved