詳解C語言之緩沖區溢出
一、緩沖區溢出原理
棧幀結構的引入為高級語言中實現函數或過程調用提供直接的硬件支持,但由於將函數返回地址這樣的重要數據保存在程序員可見的堆棧中,因此也給系統安全帶來隱患。若將函數返回地址修改為指向一段精心安排的惡意代碼,則可達到危害系統安全的目的。此外,堆棧的正確恢復依賴於壓棧的EBP值的正確性,但EBP域鄰近局部變量,若編程中有意無意地通過局部變量的地址偏移竄改EBP值,則程序的行為將變得非常危險。
由於C/C++語言沒有數組越界檢查機制,當向局部數組緩沖區裡寫入的數據超過為其分配的大小時,就會發生緩沖區溢出。攻擊者可利用緩沖區溢出來竄改進程運行時棧,從而改變程序正常流向,輕則導致程序崩潰,重則系統特權被竊取。
例如,對於下圖的棧結構:
若將長度為16字節的字符串賦給acArrBuf數組,則系統會從acArrBuf[0]開始向高地址填充棧空間,導致覆蓋EBP值和函數返回地址。若攻擊者用一個有意義的地址(否則會出現段錯誤)覆蓋返回地址的內容,函數返回時就會去執行該地址處事先安排好的攻擊代碼。最常見的手段是通過制造緩沖區溢出使程序運行一個用戶shell,再通過shell執行其它命令。若該程序有root或suid執行權限,則攻擊者就獲得一個有root權限的shell,進而可對系統進行任意操作。
除通過使堆棧緩沖區溢出而更改返回地址外,還可改寫局部變量(尤其函數指針)以利用緩沖區溢出缺陷。
註意,本文描述的堆棧緩沖區溢出不同於廣義的“堆棧溢出(Stack OverFlow)”,後者除局部數組越界和內存覆蓋外,還可能由於調用層次太多(尤其應註意遞歸函數)或過大的局部變量所導致。
二、緩沖區溢出實例
本節給出若幹緩沖區溢出相關的示例性程序。前三個示例為手工修改返回地址或實參,後兩個示例為局部數組越界訪問和緩沖區溢出。更加深入的緩沖區溢出攻擊參見相關資料。
示例函數必須包含stdio.h頭文件,並按需包含string.h頭文件(如strcpy函數)。
【示例1】改變函數的返回地址,使其返回後跳轉到某個指定的指令位置,而不是函數調用後緊跟的位置。實現原理是在函數體中修改返回地址,即找到返回地址的位置並修改它。代碼如下:
//foo.c void foo(void){ int a, *p; p = (int*)((char *)&a + 12); //讓p指向main函數調用foo時入棧的返回地址,等效於p = (int*)(&a + 3); *p += 12; //修改該地址的值,使其指向一條指令的起始地址 } int main(void){ foo(); printf("First printf call\n"); printf("Second printf call\n"); return 0; }
編譯運行,結果輸出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函數內。代碼如下:
//RunAway.c int gPrevRet = 0; //保存函數的返回地址 void Detour(void){ int *p = (int*)&p + 2; //p指向函數的返回地址 *p = gPrevRet; printf("Run Away!\n"); //需要回車,或打印後fflush(stdout);刷新緩沖區,否則可能在段錯誤時無法輸出 } int RunAway(void){ int *p = (int*)&p + 2; gPrevRet = *p; *p = (int)Detour; return 0; } int main(void){ RunAway(); printf("Come Home!\n"); return 0; }
編譯運行後輸出:
Run Away!
Come Home!
Run Away!
Come Home!
Segmentation fault
運行後出現段錯誤?There must be something wrong!錯誤原因留待讀者思考,下面給出上述代碼的另一版本,借助匯編獲取返回地址(而不是根據棧幀結構估算)。
register void *gEbp __asm__ ("%ebp"); void Detour(void){ *((int *)gEbp + 1) = gPrevRet; printf("Run Away!\n"); } int RunAway(void){ gPrevRet = *((int *)gEbp + 1); *((int *)gEbp + 1) = Detour; return 0; }
【示例3】在被調函數內修改主調函數指針變量,造成後續訪問該指針時程序崩潰。代碼如下:
//Crasher.c typedef struct{ int member1; int member2; }T_STRT; T_STRT gtTestStrt = {0}; register void *gEbp __asm__ ("%ebp"); void Crasher(T_STRT *ptStrt){ printf("[%s]: ebp = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp)); printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt); printf("[%s]: (1) = %p(0x%08x)\n", __FUNCTION__, ((int*)&ptStrt-2), *((int*)&ptStrt-2)); printf("[%s]: (2) = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-4), *(int*)(*((int*)&ptStrt-2)-4)); printf("[%s]: (3) = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-8), *(int*)(*((int*)&ptStrt-2)-8)); *(int*)( *( (int*)&ptStrt - 2 ) - 8 ) = 0; //A:此句將導致代碼B處發生段錯誤 } int main(void){ printf("[%s]: ebp = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp)); T_STRT *ptStrt = >TestStrt; printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt); Crasher(ptStrt); printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt); ptStrt->member1 = 5; //B:需要在此處崩潰 printf("Try to come here!\n"); return 0; }
運行結果如下所示:
根據打印出的地址及其存儲內容,可得到以下堆棧佈局:
&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處)。以下改進版本用於自動計算該偏移量:
static int gOffset = 0; void Crasher(T_STRT *ptStrt){ *(int*)( *(int*)gEbp - gOffset ) = 0; } int main(void){ T_STRT *ptStrt = >TestStrt; gOffset = (char*)gEbp - (char*)(&ptStrt); Crasher(ptStrt); ptStrt->member1 = 5; //在此處崩潰 printf("Try to come here!\n"); return 0; }
當然,該版本已失去原有意義(不借助寄存器層面手段),純為示例。
【示例4】越界訪問造成死循環。代碼如下:
//InfinteLoop.c void InfinteLoop(void){ unsigned char ucIdx, aucArr[10]; for(ucIdx = 0; ucIdx <= 10; ucIdx++) aucArr[ucIdx] = 1; }
在循環內部,當訪問不存在的數組元素aucArr[10]時,實際上在訪問數組aucArr所在地址之後的那個位置,而該位置存放著變量ucIdx。因此aucArr[10] = 1將ucIdx重置為1,然後繼續循環的條件仍然成立,最終將導致死循環。
【示例5】緩沖區溢出。代碼如下:
//CarelessPapa.c register int *gEbp __asm__ ("%ebp"); void NaughtyBoy(void){ printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1)); printf("Catch Me!\n"); } void CarelessPapa(const char *pszStr){ printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp); printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1)); char szBuf[8]; strcpy(szBuf, pszStr); } int main(void){ printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp); printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy); char szArr[]="0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8"; CarelessPapa(szArr); printf("Come Home!\n"); printf("[3]EBP=%p\n", gEbp); return 0; }
編譯運行結果如下:
可見,當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函數終止進程而不返回到啟動例程:
//CarelessPapa.c register int *gEbp __asm__ ("%ebp"); void NaughtyBoy(void){ printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1)); printf("Catch Me!\n"); } void CarelessPapa(const char *pszStr){ printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp); printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1)); char szBuf[8]; strcpy(szBuf, pszStr); } int main(void){ printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp); printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy); char szArr[]="0123456789AB\x14\x84\x4\x8\x33\x85\x4\x8"; //轉義字符串稍有變化 CarelessPapa(szArr); printf("Come Home!\n"); printf("[3]EBP=%p\n", gEbp); exit(0); //#include <stdlib.h> }
編譯運行結果如下:
這次沒有重復執行,也未出現段錯誤。
三、緩沖區溢出防范
防范緩沖區溢出問題的準則是:確保做邊界檢查(通常不必擔心影響程序效率)。不要為接收數據預留相對過小的緩沖區,大的數組應通過malloc/new分配堆空間來解決;在將數據讀入或復制到目標緩沖區前,檢查數據長度是否超過緩沖區空間。同樣,檢查以確保不會將過大的數據傳遞給別的程序,尤其是第三方COTS(Commercial-off-the-shelf)商用軟件庫——不要設想關於其他人軟件行為的任何事情。
若有可能,改用具備防止緩沖區溢出內置機制的高級語言(Java、C#等)。但許多語言依賴於C庫,或具有關閉該保護特性的機制(為速度而犧牲安全性)。其次,可以借助某些底層系統機制或檢測工具(如對C數組進行邊界檢查的編譯器)。許多操作系統(包括Linux和Solaris)提供非可執行堆棧補丁,但該方式不適於這種情況:攻擊者利用堆棧溢出使程序跳轉到放置在堆上的執行代碼。此外,存在一些偵測和去除緩沖區溢出漏洞的靜態工具(檢查代碼但並不運行)和動態工具(執行代碼以確定行為),甚至采用grep命令自動搜索源代碼中每個有問題函數的實例。
但即使采用這些保護手段,程序員自身也可能犯其他許多錯誤,從而引入缺陷。例如,當使用有符號數存儲緩沖區長度或某個待讀取內容長度時,攻擊者可將其變為負值,從而使該長度被解釋為很大的正值。經驗豐富的程序員還容易過於自信地”把玩”某些危險的庫函數,如對其添加自己總結編寫的檢查,或錯誤地推論出使用潛在危險的函數在某些特殊情況下是”安全”的。
本節將主要討論一些已被證明危險的C庫函數。通過在C/C++程序中禁用或慎用危險的函數,可有效降低在代碼中引入安全漏洞的可能性。在考慮性能和可移植性的前提下,強烈建議在開發過程中使用相應的安全函數來替代危險的庫函數調用。
以下分析某些危險的庫函數,較完整的列表參見表3-1。
3.1、gets
該函數從標準輸入讀入用戶輸入的一行文本,在遇到EOF字符或換行字符前,不會停止讀入文本。即該函數不執行越界檢查,故幾乎總有可能使任何緩沖區溢出(應禁用)。
gcc編譯器下會對gets調用發出警告(the `gets’ function is dangerous and should not be used)。
3.2、strcpy
該函數將源字符串復制到目標緩沖區,但並未指定要復制字符的數目。若源字符串來自用戶輸入且未限制其長度,則可能引發危險。規避的方法如下:
1) 若知道目標緩沖區大小,則可添加明確的檢查(不建議該法):
if(strlen(szSrc) >= dwDstSize){ /* Do something appropriate, such as throw an error. */ } else{ strcpy(szDst, szSrc); }
2) 改用strncpy函數:
strncpy(szDst, szSrc, dwDstSize-1); szDst[dwDstSize-1] = '\0'; //Always do this to be safe!
若szSrc比szDst大,則該函數不會返回錯誤;當達到指定長度(dwDstSize-1)時,停止復制字符。第二句將字符串結束符放在szDst數組的末尾。
3) 在源字符串上調用strlen()來為其分配足夠的堆空間:
pszDst = (char *)malloc(strlen(szSrc)); strcpy(pszDst, szSrc);
4) 某些情況下使用strcpy不會帶來潛在的安全性問題:
strcpy(szDst, "Hello!"); //Usually by initialization, such as char szDst[] = “Hello!”;
即使該操作造成szDst溢出,但這幾個字符顯然不會造成危害——除非用其它方式覆蓋字符串“Hello”所在的靜態存儲區。
安全的字符串處理函數通常體現在如下幾個方面:
- 顯式指明目標緩沖區大小
- 動態校驗
- 返回碼(以指明成功或失敗原因)
與strcpy函數具有相同問題的還有strcat函數。
3.3、 strncpy/strncat
該對函數是strcpy/strcat調用的“安全”版本,但仍存在一些問題:
1) strncpy和strncat要求程序員給出剩餘的空間,而不是給出緩沖區的總大小。緩沖區大小一經分配就不再變化,但緩沖區中剩餘的空間量會在每次添加或刪除數據時發生變化。這意味著程序員需始終跟蹤或重新計算剩餘的空間,而這種跟蹤或重新計算很容易出錯。
2) 在發生溢出(和數據丟失)時,strncpy和strncat返回結果字符串的起始地址(而不是其長度)。雖然這有利於鏈式表達,但卻無法報告緩沖區溢出。
3) 若源字符串長度至少和目標緩沖區相同,則strncpy不會使用NUL來結束字符串;這可能會在以後導致嚴重破壞。因此,在執行strncpy後通常需要手工終止目標字符串。
4) strncpy還可復制源字符串的一部分到目標緩沖區,要復制的字符數目通常基於源字符串的相關信息來計算。這種操作也會產生未終止字符串。
5) strncpy會在源字符串結束時使用NUL來填充整個目標緩沖區,這在源字符串較短時存在性能問題。
3.4、sprintf
該函數使用控制字符串來指定輸出格式,該字符串通常包括”%s”(字符串輸出)。若指定字符串輸出的精確指定符,則可通過指定輸出的最大長度來防止緩沖區溢出(如%.10s將復制不超過10個字符)。也可以使用”*”作為精確指定符(如”%.*s”),這樣就可傳入一個最大長度值。精確字段僅指定一個參數的最大長度,但緩沖區需要針對組合起來的數據的最大尺寸調整大小。
註意,”字段寬度”(如”%10s”,無點號)僅指定最小長度——而非最大長度,從而留下緩沖區溢出隱患。
3.5、scanf
scanf系列函數具有一個最大寬度值,函數不能讀取超過最大寬度的數據。但並非所有規范都規定瞭這點,也不確定是否所有實現都能正確執行這些限制。若要使用這一特性,建議在安裝或初始化期間運行小測試來確保它能正確工作。
3.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字符串結束符的指針。
考慮以下代碼:
#include <libgen.h> int main(void){ char szBuf[20] = {0}; streadd(szBuf, "\t\n", ""); printf(%s\n", szBuf); return 0; }
打印輸出\t\n,而不是所有空白。
3.7、strtrns
該函數將pszStr字符串中的字符轉換後復制到結果緩沖區pszResult。其原型包含在libgen.h頭文件內:
char * strtrns(const char *pszStr, const char *pszOld, const char *pszNew, char *pszResult);
出現在pszOld字符串中的字符被pszNew字符串中相同位置的字符替換。函數返回新的結果字符串。
如下示例將小寫字符轉換成大寫字符:
#include <libgen.h> int main(int argc,char *argv[]){ char szLower[] = "abcdefghijklmnopqrstuvwxyz"; char szUpper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; if(argc < 2){ printf("USAGE: %s arg\n", argv[0]); exit(0); } char *pszBuf = (char *)malloc(strlen(argv[1])); strtrns(argv[1], szLower, szUpper, pszBuf); printf("%s\n", pszBuf); return 0; }
以上代碼使用malloc分配足夠空間來復制argv[1],因此不會引起緩沖區溢出。
3.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 |
低 |
確保目標緩沖區不小於指定長度 |
以上就是詳解C語言之緩沖區溢出的詳細內容,更多關於C語言 緩沖區溢出的資料請關註WalkonNet其它相關文章!