官术网_书友最值得收藏!

2.6.3 對象大小檢查

GNU C編譯器(GCC)對于訪問由指針指向的對象的大小,提供的功能有限。從4.1版本開始,GCC推出了__builtin_object_size()函數來提供這種能力。它的簽名是size_t __builtin_object_size(void *ptr.int type)。其第一個參數是一個指向任何對象的指針。此指針可以指向對象的開始,但這不是必需的。例如,如果該對象是一個字符串或字符數組,則該指針可以指向數組的第一個字符或其范圍中的任何字符。第二個參數提供關于被引用的對象的詳細信息,并可以取從0~3的任何值。該函數返回從被引用的字節到被引用的對象的最后一個字節的字節數。

此函數僅適用于可以在編譯時確定范圍的對象。如果GCC不能確定被引用的是哪個對象,或者如果它不能確定這個對象的大小,則該函數返回0或–1,它們都是無效的大小。對于能夠確定對象大小的編譯器,該程序必須在優化級別-01或更高級別下編譯。

第二個參數表示被引用的對象的詳細信息。如果該參數為0或2,那么被引用的對象是包含指向的字節的最大對象,否則,所涉及的對象是包含指向的字節的最小對象。為了說明這一點區別,考慮下面的代碼:


struct V { char buf1[10]; int b; char buf2[10]; } var; 
void *ptr = &var.b; 

如果把ptr傳給__builtin_object_size()并把type設置為0,那么返回值是從var.b到var的末尾所包括的字節數量。(此值將至少等于sizeof(int)加上buf2數組的大小10)然而,如果type為1,則返回值為從var.b到var.b的末尾(包括它)所包括的字節數(即,sizeof(int))。

如果__builtin_object_size()無法確定指向的對象的大小,如果第二個參數是0或1,那么它返回(size_t)-1。如果第二個參數是2或3,那么它返回(size_t)0。表2.9總結type參數是如何影響__builtin_object_size()的行為的。

表2.9 type對__builtin_object_size()行為的影響

使用對象大小檢查。當_FORTIFY_SOURCE已定義時,__builtin_object_size()函數用來為以下標準函數添加輕量級的緩沖區溢出保護:


memcpy()     strcpy()     strcat()      sprintf()     vsprintf()
memmove()    strncpy()    strncat()     snprintf()    vsnprintf()
memset()     fprintf()    vfprintf()    printf()      vprintf()

許多支持GCC的操作系統在默認情況下打開檢查對象大小。其他操作系統則提供了宏(如_FORTIFY_SOURCE),作為一個選項來啟用該功能。例如,Red Hat Linux,默認情況下沒有進行保護。當把_FORTIFY_SOURCE設置為優化級別1(_FORTIFY_SOURCE=1)或更高時,采取的安全措施不應該改變合規程序的行為。_FORTIFY_SOURCE=2增加了更多的檢查,但一些合規程序可能會失敗。

例如,在定義了_FORTIFY_SOURCE時,memcpy()函數的實現可能會如下所示。


1  __attribute__ ((__nothrow__)) memcpy(
2    void * __restrict __dest,
3    __const void * __restrict __src,
4    size_t __len
5  ) {
6    return ___memcpy_chk(
7             __dest, __src, __len, __builtin_object_size(__dest, 0)
8           ); 9  }

當使用memcpy()和strcpy()函數時,下列行為是可能的:

1.下面的案例已知是正確的:


1  char buf[5];
2  memcpy(buf, foo, 5);
3  strcpy(buf, "abcd");

不需要進行運行時檢查,因此調用memcpy()和strcpy()函數。

2.下面的案例,不知道是否正確,但可在運行時檢查:


1  memcpy(buf, foo, n);
2  strcpy(buf, bar);

編譯器知道對象中剩余的字節數,但不知道實際會復制的長度。在這種情況下,使用替代函數__memcpy_chk()和__strcpy_chk()。這些函數檢查是否發生緩沖區溢出。如果檢測到緩沖區溢出,就調用__chk_fail(),且通常在寫一個診斷消息到stderr后中止應用程序。

3.下面的案例已知是不正確的:


1  memcpy(buf, foo, 6);
2  strcpy(buf, "abcde");

在編譯時,編譯器可以檢測到緩沖區溢出。它發出警告,并在運行時調用帶檢查的替代函數。

4.最后一種情況是,不知道代碼是否正確,并且不能在在運行時檢查:


1  memcpy(p, q, n);
2  strcpy(p, q);

編譯器不知道緩沖區的大小,并且不進行檢查。在這些情況下,溢出不能被檢測出。

了解更多:使用__builtin_object_size()。此函數可以與復制操作結合使用。例如,通過檢查該數組的大小,可以安全地把一個字符串復制到一個大小固定的數組,如下所示。


01  char dest[BUFFER_SIZE];
02  char *src = /* 
合法指針 */;
03  size_t src_end = __builtin_object_size(src, 0);
04  if (src_end == (size_t) -1 && /* 
不知道 src 
是否太大 */
05      strlen(src) < BUFFER_SIZE) {
06    strcpy(dest, src);
07  } else if (src_end <= BUFFER_SIZE) {
08    strcpy(dest, src);
09  } else {
10    /* src 
會使 dest
溢出 */
11  }

使用__builtin_object_size()的優點是,如果它返回一個有效的大小(而不是0或–1),那么在運行時調用strlen()函數是不必要的,并且可以被旁路,從而提高運行時的性能。

定義了_FORTIFY_SOURCE時,GCC把strcpy()實現為調用__builtin___strcpy_chk()的內聯函數。否則,strcpy()函數是一個普通的glibc函數。__builtin___strcpy_chk()函數具有如下簽名:


char *__builtin___strcpy_chk(char *dest, const char *src,
                             size_t dest_end)

這個函數的行為與strcpy()類似,但它首先檢查dest緩沖區是否足夠大,以防止緩沖區溢出。這種檢查是通過dest_end參數提供的,通常是__builtin_object_size()調用的結果。這種檢查通常可以在編譯時執行。如果編譯器能夠確定不會發生緩沖區溢出,它就可以把運行時檢查優化掉。同樣,如果編譯器確定緩沖區溢出總是發生,它會發出警告,并在運行時中止調用。如果編譯器知道目標字符串的空間大小,但不知道源字符串的長度,它增加了一個運行時檢查。最后,如果編譯器不能保證目標字符串有足夠的空間,那么它會把調用移交給沒有增加檢查的標準的strcpy()函數。

主站蜘蛛池模板: 洪雅县| 霍山县| 囊谦县| 东乡| 孝义市| 平邑县| 九江市| 北京市| 南投县| 深泽县| 乌兰县| 泗水县| 翼城县| 和顺县| 房山区| 临江市| 社旗县| 沙田区| 睢宁县| 灵武市| 扶余县| 浮梁县| 鄢陵县| 公主岭市| 东阳市| 延安市| 长沙市| 威远县| 开平市| 沙田区| 五寨县| 开封市| 登封市| 永定县| 江永县| 巫溪县| 屏东县| 镇原县| 汽车| 龙口市| 田林县|