- C和C++安全編碼(原書第2版)
- (美)Robert C.Seacord
- 1754字
- 2020-10-30 17:56:46
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()函數。
- 大學計算機基礎(第二版)
- 深度實踐OpenStack:基于Python的OpenStack組件開發
- Python科學計算(第2版)
- Java 開發從入門到精通(第2版)
- JavaScript+jQuery網頁特效設計任務驅動教程(第2版)
- Windows Presentation Foundation Development Cookbook
- Java編程技術與項目實戰(第2版)
- Spring+Spring MVC+MyBatis從零開始學
- Apache Camel Developer's Cookbook
- Illustrator CC平面設計實戰從入門到精通(視頻自學全彩版)
- Angular應用程序開發指南
- Android移動應用開發項目教程
- 石墨烯改性塑料
- SignalR:Real-time Application Development(Second Edition)
- Java RESTful Web Service實戰