緩衝區溢出詳解

 

1 緩衝區溢出原理

     緩衝區是一塊連續的計算機內存區域,可保存相同數據類型的多個實例。緩衝區能夠是堆棧(自動變量)、堆(動態內存)和靜態數據區(全局或靜態)。在C/C++語言中,一般使用字符數組和malloc/new之類內存分配函數實現緩衝區。溢出指數據被添加到分配給該緩衝區的內存塊以外。緩衝區溢出是最多見的程序缺陷。程序員

     棧幀結構的引入爲高級語言中實現函數或過程調用提供直接的硬件支持,但因爲將函數返回地址這樣的重要數據保存在程序員可見的堆棧中,所以也給系統安全帶來隱患。若將函數返回地址修改成指向一段精心安排的惡意代碼,則可達到危害系統安全的目的。此外,堆棧的正確恢復依賴於壓棧的EBP值的正確性,但EBP域鄰近局部變量,若編程中有意無心地經過局部變量的地址偏移竄改EBP值,則程序的行爲將變得很是危險。shell

     因爲C/C++語言沒有數組越界檢查機制,當向局部數組緩衝區裏寫入的數據超過爲其分配的大小時,就會發生緩衝區溢出。攻擊者可利用緩衝區溢出來竄改進程運行時棧,從而改變程序正常流向,輕則致使程序崩潰,重則系統特權被竊取。編程

     例如,對於下圖的棧結構:數組

 

     若將長度爲16字節的字符串賦給acArrBuf數組,則系統會從acArrBuf[0]開始向高地址填充棧空間,致使覆蓋EBP值和函數返回地址。若攻擊者用一個有意義的地址(不然會出現段錯誤)覆蓋返回地址的內容,函數返回時就會去執行該地址處事先安排好的攻擊代碼。最多見的手段是經過製造緩衝區溢出使程序運行一個用戶shell,再經過shell執行其它命令。若該程序有root或suid執行權限,則攻擊者就得到一個有root權限的shell,進而可對系統進行任意操做。安全

     除經過使堆棧緩衝區溢出而更改返回地址外,還可改寫局部變量(尤爲函數指針)以利用緩衝區溢出缺陷。app

     注意,本文描述的堆棧緩衝區溢出不一樣於廣義的「堆棧溢出(Stack OverFlow)」,後者除局部數組越界和內存覆蓋外,還可能因爲調用層次太多(尤爲應注意遞歸函數)或過大的局部變量所致使。less

 

2 緩衝區溢出實例

     本節給出若干緩衝區溢出相關的示例性程序。前三個示例爲手工修改返回地址或實參,後兩個示例爲局部數組越界訪問和緩衝區溢出。更加深刻的緩衝區溢出攻擊參見相關資料。ide

     示例函數必須包含stdio.h頭文件,並按需包含string.h頭文件(如strcpy函數)。函數

    【示例1】改變函數的返回地址,使其返回後跳轉到某個指定的指令位置,而不是函數調用後緊跟的位置。實現原理是在函數體中修改返回地址,即找到返回地址的位置並修改它。代碼以下:工具

 1 //foo.c
 2 void foo(void){
 3     int a, *p;
 4     p = (int*)((char *)&a + 12);  //讓p指向main函數調用foo時入棧的返回地址,等效於p = (int*)(&a + 3);
 5     *p += 12;    //修改該地址的值,使其指向一條指令的起始地址
 6 }
 7 int main(void){
 8     foo();
 9     printf("First printf call\n");
10     printf("Second printf call\n");
11     return 0;
12 }
View Code

     編譯運行,結果輸出Second printf call,未輸出First printf call。

     下面詳細介紹代碼中兩個12的由來。

     編譯(gcc main.c –g)和反彙編(objdump a.out –d)後,獲得彙編代碼片斷以下:

 

     從上述彙編代碼可知,foo後面的指令地址(即調用foo時壓入的返回地址)是0x80483b8,而進入調用printf("Second printf call「)的指令地址是0x80483c4。二者相差12,故將返回地址的值加12便可(*p += 12)。

     指令<804838a>將-8(%ebp)的地址賦值給%eax寄存器(p = &a)。可知foo()函數中的變量a存儲在-8(%ebp)地址上,該地址向上8+4=12個單位就是返回地址((char *)&a + 12)。修改該地址內容(*p += 12)便可實現函數調用結束後跳轉到第二個printf函數調用的位置。

     用gdb查看彙編指令剛進入foo時棧頂的值(%esp),以下所示:

 

     可見%esp值的確是調用foo後main中下條待執行指令的地址,而代碼所修改的也正是該值。%eip則指向當前程序(foo)的指令地址。

    【示例2】暫存RunAway函數的返回地址後修改其值,使函數返回後跳轉到Detour函數的地址;Detour函數內嘗試經過以前保存的返回地址重回main函數內。代碼以下:

 1 //RunAway.c
 2 int gPrevRet = 0; //保存函數的返回地址
 3 void Detour(void){
 4     int *p = (int*)&p + 2;  //p指向函數的返回地址
 5     *p = gPrevRet;
 6     printf("Run Away!\n"); //須要回車,或打印後fflush(stdout);刷新緩衝區,不然可能在段錯誤時沒法輸出
 7 }
 8 int RunAway(void){
 9     int *p = (int*)&p + 2;
10     gPrevRet = *p;
11     *p = (int)Detour;
12     return 0;
13 }
14 int main(void){
15     RunAway();
16     printf("Come Home!\n");
17     return 0;
18 }
View Code

     編譯運行後輸出:

Run Away!

Come Home!

Run Away!

Come Home!

Segmentation fault

     運行後出現段錯誤?There must be something wrong!錯誤緣由留待讀者思考,下面給出上述代碼的另外一版本,藉助彙編獲取返回地址(而不是根據棧幀結構估算)。

 1 register void *gEbp __asm__ ("%ebp");
 2 void Detour(void){
 3     *((int *)gEbp + 1) = gPrevRet;
 4     printf("Run Away!\n");
 5 }
 6 int RunAway(void){
 7     gPrevRet = *((int *)gEbp + 1);
 8     *((int *)gEbp + 1) = Detour;
 9     return 0;
10 }
View Code

    【示例3】在被調函數內修改主調函數指針變量,形成後續訪問該指針時程序崩潰。代碼以下:

 1 //Crasher.c
 2 typedef struct{
 3     int member1;
 4     int member2;
 5 }T_STRT;
 6 T_STRT gtTestStrt = {0};
 7 register void *gEbp __asm__ ("%ebp");
 8 
 9 void Crasher(T_STRT *ptStrt){
10     printf("[%s]: ebp    = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp));
11     printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);
12     printf("[%s]: (1)    = %p(0x%08x)\n", __FUNCTION__, ((int*)&ptStrt-2), *((int*)&ptStrt-2));
13     printf("[%s]: (2)    = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-4), *(int*)(*((int*)&ptStrt-2)-4));
14     printf("[%s]: (3)    = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-8), *(int*)(*((int*)&ptStrt-2)-8));
15     *(int*)( *( (int*)&ptStrt - 2 ) - 8 ) = 0;  //A:此句將致使代碼B處發生段錯誤
16 }
17 
18 int main(void){
19     printf("[%s]: ebp    = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp));
20     T_STRT *ptStrt = &gtTestStrt;
21     printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);
22 
23     Crasher(ptStrt);
24     printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);
25     ptStrt->member1 = 5;  //B:須要在此處崩潰
26     printf("Try to come here!\n");
27     return 0;
28 }
View Code

     運行結果以下所示:

 

     根據打印出的地址及其存儲內容,可獲得如下堆棧佈局:

 

     &ptStrt爲形參地址0xbff8f090,該地址處在main函數棧幀中。(int*)&ptStrt - 2地址存儲主調函數的EBP值,根據該值可直接定位到main函數棧幀底部。(*((int*)&ptStrt - 2) - 8)爲主調函數中實參ptStrt的地址,而*(int*) (*((int*)&ptStrt - 2) - 4) = 0將該地址內容置零,即實參指針ptStrt設置爲NULL(再也不指向全局結構gtTestStrt)。這樣,訪問ptStrt->member1時就會發生段錯誤。

     注意,雖然本例代碼結構簡單,但不能輕率地推斷main函數中局部變量ptStrt位於幀基指針EBP-4處(實際上本例爲EBP-8處)。如下改進版本用於自動計算該偏移量:

 1 static int gOffset = 0;
 2 void Crasher(T_STRT *ptStrt){
 3    *(int*)( *(int*)gEbp - gOffset ) = 0;
 4 }
 5 
 6 int main(void){
 7     T_STRT *ptStrt = &gtTestStrt;
 8     gOffset = (char*)gEbp - (char*)(&ptStrt);
 9     Crasher(ptStrt);
10     ptStrt->member1 = 5;  //在此處崩潰
11     printf("Try to come here!\n");
12     return 0;
13 }
View Code

     固然,該版本已失去原有意義(不借助寄存器層面手段),純爲示例。

    【示例4】越界訪問形成死循環。代碼以下:

1 //InfinteLoop.c
2 void InfinteLoop(void){ 
3     unsigned char ucIdx, aucArr[10]; 
4     for(ucIdx = 0; ucIdx <= 10; ucIdx++)
5         aucArr[ucIdx] = 1;
6 }
View Code

     在循環內部,當訪問不存在的數組元素aucArr[10]時,實際上在訪問數組aucArr所在地址以後的那個位置,而該位置存放着變量ucIdx。所以aucArr[10] = 1將ucIdx重置爲1,而後繼續循環的條件仍然成立,最終將致使死循環。

    【示例5】緩衝區溢出。代碼以下:

 1 //CarelessPapa.c
 2 register int *gEbp __asm__ ("%ebp");
 3 void NaughtyBoy(void){
 4     printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1));
 5     printf("Catch Me!\n");
 6 }
 7 void CarelessPapa(const char *pszStr){
 8     printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp);
 9     printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1));
10     char szBuf[8];
11     strcpy(szBuf, pszStr);
12 }
13 int main(void){
14     printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp);
15     printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy);
16     char szArr[]="0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8";
17     CarelessPapa(szArr);
18     printf("Come Home!\n");
19     printf("[3]EBP=%p\n", gEbp);
20     return 0;
21 }
View Code

     編譯運行結果以下:

 

     可見,當CarelessPapa函數調用結束後,並未直接執行Come Home的輸出,而是轉而執行NaughtyBoy函數(輸出Catch Me),而後回頭輸出Come Home。該過程重複一次後發生段錯誤(具體緣由留待讀者思考)。

     結合下圖所示的棧幀佈局,詳細分析本示例緩衝區溢出過程。注意,本示例中地址及其內容由內嵌彙編和打印輸出得到,正常狀況下應經過gdb調試器得到。

     首先,main函數將字符數組szArr的地址做爲參數(即pszStr)傳遞給函數CarelessPapa。該數組內容爲"0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8",其中轉義字符串"\xe4\x83\x4\x8"對應NaughtyBoy函數入口地址0x080483e4(小字節序),而"\x23\x85\x4\x8"對應調用CarelessPapa函數時的返回地址0x8048523(小字節序)。CarelessPapa函數內部調用strcpy庫函數,將pszStr所指字符串內容拷貝至szBuf數組。由於strcpy函數不進行越界檢查,會逐字節拷貝直到碰見'\0'結束符。故pszStr字符串將從szBuf數組起始地址開始向高地址覆蓋,原返回地址0x8048523被覆蓋爲NaughtyBoy函數地址0x080483e4。

     這樣,當CarelessPapa函數返回時,修改後的返回地址從棧中彈出到EIP寄存器中,此時棧頂指針ESP指向返回地址上方的空間(esp+4),程序跳轉到EIP所指地址(NaughtyBoy函數入口)開始執行,首先就是EBP入棧——並未像正常調用那樣先壓入返回地址,故NaughtyBoy函數棧幀中EBP位置相對CarelessPapa函數上移4個字節!此時,"\x23\x85\x4\x8"可將EBP上方的EIP修改成CarelessPapa函數的返回地址(0x8048523),從而保證正確返回main函數內。

     注意,返回main函數並輸出Come Home後,main函數棧幀的EBP地址被改成0x42413938("89AB"),該地址已非堆棧空間,最終產生段錯誤。EBP地址會隨每次程序執行而改變,故試圖在szArr字符串中恢復EBP是很是困難的。

     從main函數return時將返回到調用它的啓動例程(_start函數)中,返回值被啓動例程得到並用其做爲參數調用exit函數。exit函數首先作一些清理工做,而後調用_exit系統調用終止進程。main函數的返回值最終傳給_exit系統調用,成爲進程的退出狀態。如下代碼在main函數中直接調用exit函數終止進程而不返回到啓動例程:

 1 //CarelessPapa.c
 2 register int *gEbp __asm__ ("%ebp");
 3 void NaughtyBoy(void){
 4     printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1));
 5     printf("Catch Me!\n");
 6 }
 7 void CarelessPapa(const char *pszStr){
 8     printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp);
 9     printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1));
10     char szBuf[8];
11     strcpy(szBuf, pszStr);
12 }
13 int main(void){
14     printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp);
15     printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy);
16     char szArr[]="0123456789AB\x14\x84\x4\x8\x33\x85\x4\x8"; //轉義字符串稍有變化
17     CarelessPapa(szArr);
18     printf("Come Home!\n");
19     printf("[3]EBP=%p\n", gEbp);
20     exit(0); //#include <stdlib.h>
21 }
View Code

     編譯運行結果以下:

 

     此次沒有重複執行,也未出現段錯誤。

 

3 緩衝區溢出防範

     防範緩衝區溢出問題的準則是:確保作邊界檢查(一般沒必要擔憂影響程序效率)。不要爲接收數據預留相對太小的緩衝區,大的數組應經過malloc/new分配堆空間來解決;在將數據讀入或複製到目標緩衝區前,檢查數據長度是否超過緩衝區空間。一樣,檢查以確保不會將過大的數據傳遞給別的程序,尤爲是第三方COTS(Commercial-off-the-shelf)商用軟件庫——不要設想關於其餘人軟件行爲的任何事情。

     如有可能,改用具有防止緩衝區溢出內置機制的高級語言(Java、C#等)。但許多語言依賴於C庫,或具備關閉該保護特性的機制(爲速度而犧牲安全性)。其次,能夠藉助某些底層系統機制或檢測工具(如對C數組進行邊界檢查的編譯器)。許多操做系統(包括Linux和Solaris)提供非可執行堆棧補丁,但該方式不適於這種狀況:攻擊者利用堆棧溢出使程序跳轉到放置在堆上的執行代碼。此外,存在一些偵測和去除緩衝區溢出漏洞的靜態工具(檢查代碼但並不運行)和動態工具(執行代碼以肯定行爲),甚至採用grep命令自動搜索源代碼中每一個有問題函數的實例。

     但即便採用這些保護手段,程序員自身也可能犯其餘許多錯誤,從而引入缺陷。例如,當使用有符號數存儲緩衝區長度或某個待讀取內容長度時,攻擊者可將其變爲負值,從而使該長度被解釋爲很大的正值。經驗豐富的程序員還容易過於自信地"把玩"某些危險的庫函數,如對其添加本身總結編寫的檢查,或錯誤地推論出使用潛在危險的函數在某些特殊狀況下是"安全"的。

     本節將主要討論一些已被證實危險的C庫函數。經過在C/C++程序中禁用或慎用危險的函數,可有效下降在代碼中引入安全漏洞的可能性。在考慮性能和可移植性的前提下,強烈建議在開發過程當中使用相應的安全函數來替代危險的庫函數調用。

     如下分析某些危險的庫函數,較完整的列表參見表3-1。

     1. gets

     該函數從標準輸入讀入用戶輸入的一行文本,在遇到EOF字符或換行字符前,不會中止讀入文本。即該函數不執行越界檢查,故幾乎總有可能使任何緩衝區溢出(應禁用)。

     gcc編譯器下會對gets調用發出警告(the `gets' function is dangerous and should not be used)。

     2. strcpy

     該函數將源字符串複製到目標緩衝區,但並未指定要複製字符的數目。若源字符串來自用戶輸入且未限制其長度,則可能引起危險。規避的方法以下:

     1) 若知道目標緩衝區大小,則可添加明確的檢查(不建議該法):

1 if(strlen(szSrc) >= dwDstSize){
2     /* Do something appropriate, such as throw an error. */
3 }
4 else{
5     strcpy(szDst, szSrc);
6 }
View Code

     2) 改用strncpy函數:

1 strncpy(szDst, szSrc, dwDstSize-1);
2 szDst[dwDstSize-1] = '\0';  //Always do this to be safe!
View Code

     若szSrc比szDst大,則該函數不會返回錯誤;當達到指定長度(dwDstSize-1)時,中止複製字符。第二句將字符串結束符放在szDst數組的末尾。

     3) 在源字符串上調用strlen()來爲其分配足夠的堆空間:

1 pszDst = (char *)malloc(strlen(szSrc));
2 strcpy(pszDst, szSrc);
View Code

     4) 某些狀況下使用strcpy不會帶來潛在的安全性問題:

1 strcpy(szDst, "Hello!");  //Usually by initialization, such as char szDst[] = 「Hello!」;
View Code

     即便該操做形成szDst溢出,但這幾個字符顯然不會形成危害——除非用其它方式覆蓋字符串「Hello」所在的靜態存儲區。

     安全的字符串處理函數一般體如今以下幾個方面:

     •顯式指明目標緩衝區大小

     •動態校驗

     •返回碼(以指明成功或失敗緣由)

     與strcpy函數具備相同問題的還有strcat函數。

     3. strncpy/strncat

     該對函數是strcpy/strcat調用的「安全」版本,但仍存在一些問題:

     1) strncpy和strncat要求程序員給出剩餘的空間,而不是給出緩衝區的總大小。緩衝區大小一經分配就再也不變化,但緩衝區中剩餘的空間量會在每次添加或刪除數據時發生變化。這意味着程序員需始終跟蹤或從新計算剩餘的空間,而這種跟蹤或從新計算很容易出錯。

     2) 在發生溢出(和數據丟失)時,strncpy和strncat返回結果字符串的起始地址(而不是其長度)。雖然這有利於鏈式表達,但卻沒法報告緩衝區溢出。

     3) 若源字符串長度至少和目標緩衝區相同,則strncpy不會使用NUL來結束字符串;這可能會在之後致使嚴重破壞。所以,在執行strncpy後一般須要手工終止目標字符串。

     4) strncpy還可複製源字符串的一部分到目標緩衝區,要複製的字符數目一般基於源字符串的相關信息來計算。這種操做也會產生未終止字符串。

     5) strncpy會在源字符串結束時使用NUL來填充整個目標緩衝區,這在源字符串較短時存在性能問題。

     4. sprintf

     該函數使用控制字符串來指定輸出格式,該字符串一般包括"%s"(字符串輸出)。若指定字符串輸出的精確指定符,則可經過指定輸出的最大長度來防止緩衝區溢出(如%.10s將複製不超過10個字符)。也可使用"*"做爲精確指定符(如"%.*s"),這樣就可傳入一個最大長度值。精確字段僅指定一個參數的最大長度,但緩衝區須要針對組合起來的數據的最大尺寸調整大小。

     注意,"字段寬度"(如"%10s",無點號)僅指定最小長度——而非最大長度,從而留下緩衝區溢出隱患。

     5. scanf

     scanf系列函數具備一個最大寬度值,函數不能讀取超過最大寬度的數據。但並不是全部規範都規定了這點,也不肯定是否全部實現都能正確執行這些限制。若要使用這一特性,建議在安裝或初始化期間運行小測試來確保它能正確工做。

     6. streadd/strecpy

     這對函數可將含有不可讀字符的字符串轉換成可打印的表示。其原型包含在libgen.h頭文件內,編譯時需加-lgen [library ...]選項。

     char *strecpy(char *pszOut, const char *pszIn, const char *pszExcept);

     char *streadd(char *pszOut, const char *pszIn, const char *pszExcept);

     strecpy將輸入字符串pszIn(連同結束符)拷貝到輸出字符串pszOut中,並將非圖形字符展開爲C語言中相應的轉義字符序列(如Control-A轉爲「\001」)。參數pszOut指向的緩衝區大小必須足夠容納結果字符串;輸出緩衝區大小應爲輸入緩衝區大小的四倍(單個字符可能轉換爲\abc共四個字符)。出如今參數pszExcept字符串內的字符不被展開。該參數可設爲空串,表示擴展全部非圖形字符。strecpy函數返回指向pszOut字符串的指針。

     streadd函數與strecpy相同,只不過返回指向pszOut字符串結束符的指針。

     考慮如下代碼:

1 #include <libgen.h>
2 int main(void){
3     char szBuf[20] = {0};
4     streadd(szBuf, "\t\n", "");
5     printf(%s\n", szBuf);
6     return 0;
7 }
View Code

     打印輸出\t\n,而不是全部空白。

7. strtrns

     該函數將pszStr字符串中的字符轉換後複製到結果緩衝區pszResult。其原型包含在libgen.h頭文件內:

     char * strtrns(const char *pszStr, const char *pszOld, const char *pszNew, char *pszResult);

     出如今pszOld字符串中的字符被pszNew字符串中相同位置的字符替換。函數返回新的結果字符串。

     以下示例將小寫字符轉換成大寫字符:

 1 #include <libgen.h>
 2 int main(int argc,char *argv[]){
 3     char szLower[] = "abcdefghijklmnopqrstuvwxyz";
 4     char szUpper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 5     if(argc < 2){
 6         printf("USAGE: %s arg\n", argv[0]);
 7         exit(0);
 8     }
 9     char *pszBuf = (char *)malloc(strlen(argv[1]));
10     strtrns(argv[1], szLower, szUpper, pszBuf);
11     printf("%s\n", pszBuf);
12     return 0;
13 }
View Code

     以上代碼使用malloc分配足夠空間來複制argv[1],所以不會引發緩衝區溢出。

     8. realpath

     該函數在libc 4.5.21及之後版本中提供,使用時須要limits.h和stdlib.h頭文件。其原型爲:

     char *realpath(const char *pszPath, char *pszResolvedPath);

     該函數展開pszPath字符串中的全部符號連接,並解析pszPath中所引用的/./、/../和'/'字符(相對路徑),最終生成規範化的絕對路徑名。該路徑名做爲帶結束符的字符串存入pszResolvedPath指向的緩衝區,長度最大爲PATH_MAX字節。結果路徑中不含符號連接、/./或/../。

     若pszResolvedPath爲空指針,則realpath函數使用malloc來分配PATH_MAX字節的緩衝區以存儲解析後的路徑名,並返回指向該緩衝區的指針。調用者應使用free函數去釋放該該緩衝區。

     若執行成功,realpath函數返回指向pszResolvedPath(規範化絕對路徑)的指針;不然返回空指針並設置errno以指示該錯誤,此時pszResolvedPath的內容未定義。

     調用者須要確保結果緩衝區足夠大(但不該超過PATH_MAX),以處理任何大小的路徑。此外,不可能爲輸出緩衝區肯定合適的長度,所以POSIX.1-2001規定,PATH_MAX字節的緩衝區足夠,但PATH_MAX沒必要定義爲常量,且能夠經過pathconf函數得到。然而,pathconf輸出的結果可能超大,以至不適合動態分配內存;另外一方面,pathconf函數可返回-1代表結果路徑名超出PATH_MAX限制。pszResolvedPath爲空指針的特性被POSIX.1-2008標準化,以免輸出緩衝區長度難以靜態肯定的缺陷。

 

     應禁用或慎用的庫函數以下表所示:

表3-1

函數

危險性

解決方案

gets

最高

禁用gets(buf),改用fgets(buf, size, stdin)

strcpy

檢查目標緩衝區大小,或改用strncpy,或動態分配目標緩衝區

strcat

改用strncat

sprintf

改用snprintf,或使用精度說明符

scanf

使用精度說明符,或本身進行解析

sscanf

使用精度說明符,或本身進行解析

fscanf

使用精度說明符,或本身進行解析

vfscanf

使用精度說明符,或本身進行解析

vsprintf

改成使用vsnprintf,或使用精度說明符

vscanf

使用精度說明符,或本身進行解析

vsscanf

使用精度說明符,或本身進行解析

streadd

確保分配的目標參數緩衝區大小是源參數大小的四倍

strecpy

確保分配的目標參數緩衝區大小是源參數大小的四倍

strtrns

手工檢查目標緩衝區大小是否至少與源字符串相等

getenv

不可假定特殊環境變量的長度

realpath

高(或稍低,實現依賴)

分配緩衝區大小爲PATH_MAX字節,並手工檢查參數以確保輸入參數和輸出參數均不超過PATH_MAX

syslog

高(或稍低,實現依賴)

將字符串輸入傳遞給該函數以前,將全部字符串輸入截成合理大小

getopt

高(或稍低,實現依賴)

將字符串輸入傳遞給該函數以前,將全部字符串輸入截成合理大小

getopt_long

高(或稍低,實現依賴)

將字符串輸入傳遞給該函數以前,將全部字符串輸入截成合理大小

getpass

高(或稍低,實現依賴)

將字符串輸入傳遞給該函數以前,將全部字符串輸入截成合理大小

getchar

若在循環中使用該函數,確保檢查緩衝區邊界

fgetc

若在循環中使用該函數,確保檢查緩衝區邊界

getc

若在循環中使用該函數,確保檢查緩衝區邊界

read

若在循環中使用該函數,確保檢查緩衝區邊界

bcopy

確保目標緩衝區不小於指定長度

fgets

確保目標緩衝區不小於指定長度

memcpy

確保目標緩衝區不小於指定長度

snprintf

確保目標緩衝區不小於指定長度

strccpy

確保目標緩衝區不小於指定長度

strcadd

確保目標緩衝區不小於指定長度

strncpy

確保目標緩衝區不小於指定長度

vsnprintf

確保目標緩衝區不小於指定長度

相關文章
相關標籤/搜索