- C和C++安全編碼(原書第2版)
- (美)Robert C.Seacord
- 2095字
- 2020-10-30 17:56:42
2.4.2 C11附錄K邊界檢查接口
第一個內存管理模型(調用者分配,調用者釋放)由在<string.h>文件中定義的C字符串處理函數實現,也由OpenBSD的函數strlcpy()和strlcat()和C11附錄K邊界檢查接口實現。調用這些函數之前,可靜態或動態分配內存,這種模式使得這個模型的效率最佳。C11附錄K提供替代庫函數,它們促進了更安全的編程。替代函數驗證輸出緩沖區對于預期的結果是否足夠大,如果不夠大,則返回一個故障指示。數據永遠不會越過數組末尾寫入。所有字符串結果都是以空字符結尾的。
C11附錄K邊界檢查接口的設計目的主要是實現現有函數的更安全的替代品。例如,C11附錄K定義了strcpy_s()、strcat_s()、strncpy_s()和strncat_s()函數,分別作為strcpy()、strcat()、strncpy()和strncat()的替代品,適用于源字符串長度未知的或保證小于已知目標緩沖區大小的情況。
C11附錄K函數是微軟為響應許多眾所周知的安全事故而創建的,以幫助改造其現有的遺留代碼庫。這些函數,隨后被提交給ISO/IEC JTC1/SC 22/WG 14國際標準化組織的C編程語言標準化工作組。這些函數已作為ISO/IEC TR 24731-1公布,后來又合并入C11中,以規范性附錄中一組可選的擴展形式進行了規定。由于C11附錄K函數往往可以用作原來遺留代碼中的庫函數的簡單替換,《C安全編碼標準》[Seacord 2008],“STR07-C.使用TR 24731整治現有的字符串操作代碼”建議在實現了該附錄要求的實現中使用它們用于此目的。(預期這樣的實現定義了__STDC_LIB_EXT1__宏。)
附錄K還解決了另一個使編寫健壯代碼復雜化的問題:因為函數返回函數所擁有的靜態對象的指針,所以它們是不可重入的。這些函數可能會很麻煩,因為如果該函數被再次調用,也許是被另一個線程調用,以前返回的結果就可能改變。
C11附錄K是一個規范性的,但可選的附錄,你應該確保它在自己所有的目標平臺上可用。盡管這些函數最初由微軟開發,但隨微軟Visual C++2012和早期版本附帶的邊界檢查庫的實現并不完全符合附錄K,因為在標準化過程中,這些函數發生了變化,而微軟的Visual C++并沒有相應做出改變。
2.2.1一節的例2.1可以使用C11附錄K函數重新實現,如例2.5所示。除了增加了數組邊界檢查之外,這個程序類似于最初的例子。如果輸入8個或更多字符,它的行為是實現定義的行為(通常情況下,該程序將終止)。
例2.5 使用gets_s()從stdin中讀取
01 #define __STDC_WANT_LIB_EXT1__ 1 02 #include <stdio.h> 03 #include <stdlib.h> 04 05 void get_y_or_n(void) { 06 char response[8]; 07 size_t len = sizeof(response); 08 puts("Continue? [y] n: "); 09 gets_s(response, len); 10 if (response[0] == 'n') 11 exit(0); 12 }
大多數邊界檢查函數,在檢測到錯誤,如參數無效或輸出緩沖區沒有足夠的可用字節時,會調用一種特殊的運行時約束處理程序(runtime-constraint-handler)函數。此函數可能會打印一個錯誤信息和中止程序。程序員可以通過set_constraint_handler_s()函數控制哪個處理函數被調用,并可以使處理程序簡單地返回,如果需要返回的話。如果處理簡單地返回,調用處理程序的函數,用它的返回值提示它的調用者處理失敗。安裝一個處理程序的程序,退出時必須檢查每次調用任何邊界檢查函數的返回值,并適當地處理錯誤。《C安全編碼標準》[Seacord 2008],“ERR 03-C.當調用TR 24731-1定義的函數時,使用運行約束處理程序”,建議安裝運行時約束處理程序,以消除實現定義的行為。
通過一些額外的復雜性成本來消除實現定義的行為,使用C11附錄K邊界檢查函數從標準輸入讀取的例2.1可以得到改善,如例2.6所示。
例2.6 使用gets_s()從stdin中讀取(改進版)
01 #define __STDC_WANT_LIB_EXT1__ 1 02 #include <stdio.h> 03 #include <stdlib.h> 04 05 void get_y_or_n(void) { 06 char response[8]; 07 size_t len = sizeof(response); 08 09 puts("Continue? [y] n: "); 10 if ((gets_s(response, len) == NULL) || (response[0] == 'n')) { 11 exit(0); 12 } 13 } 14 15 int main(void) { 16 constraint_handler_t oconstraint = 17 set_constraint_handler_s(ignore_handler_s); 18 get_y_or_n(); 19 }
這個例子通過增加一個對set_constraint_handler_s()的調用來安裝ignore_handler_s()函數作為運行時約束處理程序。如果把運行時約束處理程序設置為ignore_handler_s()函數,那么任何庫函數違反運行時約束時,都將返回到它的調用者。調用者可以在庫函數的規格說明的基礎上確定是否發生運行約束違反。大多數邊界檢查函數返回一個非零errno_t。但是,get_s()函數返回一個空指針,以便它可以作為gets()的近似直接替代。
依據《C安全編碼標準》[Seacord 2008]“ERR00-C.采用并實施一個一致和全面的錯誤處理策略”,約束處理程序設置在main(),以便在整個應用程序中保持一致的錯誤處理策略。自定義的庫函數可能希望避免設置特定的約束處理程序的策略,因為它可能與應用程序執行的整體策略產生沖突。在這種情況下,庫函數應假定邊界檢查函數的調用會返回,并檢查相應的返回狀態。在庫函數不設置約束處理程序的情況下,在返回或退出(以防止存在向atexit()注冊的函數)之前,函數必須恢復原來的約束處理程序(由set_constraint_handler_s()函數返回)。
C字符串處理函數和C11附錄K邊界檢查函數都需要預先分配存儲。一旦目標內存被填滿,就不可能增加新的數據。因此,這些函數或者拋棄多余的數據或運行失敗。重要的是程序員要確保目標的大小足夠容納要復制的字符數據和空終止字符,如《C安全編碼標準》[Seacord 2008]“STR 31-C.保證字符串的存儲有足夠的空間存放字符數據和空終結符”所述。
C11附錄K中定義的邊界檢查函數并非萬無一失。如果把一個無效的大小傳遞給這些函數之一,它仍然會遭受緩沖區溢出的問題,雖然號稱這樣的問題都已解決。由于這些函數通常比其傳統的與之對應的函數具有更多的參數,所以使用它們之前需要對每個參數的目的有一個深刻的理解。為遺留代碼庫引入邊界檢查函數作為與它們對應的傳統函數的替代也需要非常謹慎,以免在這個過程中無意中注入了新的缺陷。另外值得一提的是,把每一個C字符串處理函數更換成相應的邊界檢查函數并不總是恰當的。
- Deploying Node.js
- Flask Blueprints
- 兩周自制腳本語言
- Unity 2020 Mobile Game Development
- 區塊鏈架構與實現:Cosmos詳解
- INSTANT Weka How-to
- QGIS By Example
- Elasticsearch Server(Third Edition)
- INSTANT Silverlight 5 Animation
- 人工智能算法(卷1):基礎算法
- Mastering Apache Camel
- Mastering ASP.NET Core 2.0
- WCF技術剖析(卷1)
- Learning Apache Thrift
- Java程序性能優化實戰