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

2.6.3 對(duì)象大小檢查

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

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

第二個(gè)參數(shù)表示被引用的對(duì)象的詳細(xì)信息。如果該參數(shù)為0或2,那么被引用的對(duì)象是包含指向的字節(jié)的最大對(duì)象,否則,所涉及的對(duì)象是包含指向的字節(jié)的最小對(duì)象。為了說明這一點(diǎn)區(qū)別,考慮下面的代碼:


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

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

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

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

使用對(duì)象大小檢查。當(dāng)_FORTIFY_SOURCE已定義時(shí),__builtin_object_size()函數(shù)用來為以下標(biāo)準(zhǔn)函數(shù)添加輕量級(jí)的緩沖區(qū)溢出保護(hù):


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

許多支持GCC的操作系統(tǒng)在默認(rèn)情況下打開檢查對(duì)象大小。其他操作系統(tǒng)則提供了宏(如_FORTIFY_SOURCE),作為一個(gè)選項(xiàng)來啟用該功能。例如,Red Hat Linux,默認(rèn)情況下沒有進(jìn)行保護(hù)。當(dāng)把_FORTIFY_SOURCE設(shè)置為優(yōu)化級(jí)別1(_FORTIFY_SOURCE=1)或更高時(shí),采取的安全措施不應(yīng)該改變合規(guī)程序的行為。_FORTIFY_SOURCE=2增加了更多的檢查,但一些合規(guī)程序可能會(huì)失敗。

例如,在定義了_FORTIFY_SOURCE時(shí),memcpy()函數(shù)的實(shí)現(xiàn)可能會(huì)如下所示。


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  }

當(dāng)使用memcpy()和strcpy()函數(shù)時(shí),下列行為是可能的:

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


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

不需要進(jìn)行運(yùn)行時(shí)檢查,因此調(diào)用memcpy()和strcpy()函數(shù)。

2.下面的案例,不知道是否正確,但可在運(yùn)行時(shí)檢查:


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

編譯器知道對(duì)象中剩余的字節(jié)數(shù),但不知道實(shí)際會(huì)復(fù)制的長(zhǎng)度。在這種情況下,使用替代函數(shù)__memcpy_chk()和__strcpy_chk()。這些函數(shù)檢查是否發(fā)生緩沖區(qū)溢出。如果檢測(cè)到緩沖區(qū)溢出,就調(diào)用__chk_fail(),且通常在寫一個(gè)診斷消息到stderr后中止應(yīng)用程序。

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


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

在編譯時(shí),編譯器可以檢測(cè)到緩沖區(qū)溢出。它發(fā)出警告,并在運(yùn)行時(shí)調(diào)用帶檢查的替代函數(shù)。

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


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

編譯器不知道緩沖區(qū)的大小,并且不進(jìn)行檢查。在這些情況下,溢出不能被檢測(cè)出。

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


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 
會(huì)使 dest
溢出 */
11  }

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

定義了_FORTIFY_SOURCE時(shí),GCC把strcpy()實(shí)現(xiàn)為調(diào)用__builtin___strcpy_chk()的內(nèi)聯(lián)函數(shù)。否則,strcpy()函數(shù)是一個(gè)普通的glibc函數(shù)。__builtin___strcpy_chk()函數(shù)具有如下簽名:


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

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

主站蜘蛛池模板: 凌云县| 花莲市| 大新县| 竹北市| 柞水县| 且末县| 新巴尔虎右旗| 九龙县| 桂东县| 钦州市| 婺源县| 阿拉善右旗| 东至县| 湟源县| 沾化县| 阜康市| 清徐县| 镇安县| 桂东县| 屏东县| 安福县| 巴南区| 望奎县| 宝兴县| 克拉玛依市| 贵溪市| 平山县| 阳新县| 镇原县| 苏州市| 马龙县| 镇宁| 平陆县| 合肥市| 莱州市| 济南市| 孙吴县| 永和县| 吉安市| 庐江县| 靖江市|