書名: C和C++安全編碼(原書第2版)作者名: (美)Robert C.Seacord本章字?jǐn)?shù): 1754字更新時(shí)間: 2020-10-30 17:56:46
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ù)。
- 前端架構(gòu):從入門到微前端
- Python高效開發(fā)實(shí)戰(zhàn):Django、Tornado、Flask、Twisted(第2版)
- Learning ArcGIS Pro
- Microsoft System Center Orchestrator 2012 R2 Essentials
- 量化金融R語言高級(jí)教程
- 自然語言處理Python進(jìn)階
- C語言程序設(shè)計(jì)
- Create React App 2 Quick Start Guide
- C#程序設(shè)計(jì)教程(第3版)
- C++反匯編與逆向分析技術(shù)揭秘(第2版)
- Django 5企業(yè)級(jí)Web應(yīng)用開發(fā)實(shí)戰(zhàn)(視頻教學(xué)版)
- 從程序員角度學(xué)習(xí)數(shù)據(jù)庫技術(shù)(藍(lán)橋杯軟件大賽培訓(xùn)教材-Java方向)
- SAS編程演義
- C# 7 and .NET Core 2.0 Blueprints
- 微信小程序開發(fā)圖解案例教程:附精講視頻(第3版)