詳解C語言之緩沖區(qū)溢出
一、緩沖區(qū)溢出原理
棧幀結構的引入為高級語言中實現函數或過程調用提供直接的硬件支持,但由于將函數返回地址這樣的重要數據保存在程序員可見的堆棧中,因此也給系統(tǒng)安全帶來隱患。若將函數返回地址修改為指向一段精心安排的惡意代碼,則可達到危害系統(tǒng)安全的目的。此外,堆棧的正確恢復依賴于壓棧的EBP值的正確性,但EBP域鄰近局部變量,若編程中有意無意地通過局部變量的地址偏移竄改EBP值,則程序的行為將變得非常危險。
由于C/C++語言沒有數組越界檢查機制,當向局部數組緩沖區(qū)里寫入的數據超過為其分配的大小時,就會發(fā)生緩沖區(qū)溢出。攻擊者可利用緩沖區(qū)溢出來竄改進程運行時棧,從而改變程序正常流向,輕則導致程序崩潰,重則系統(tǒng)特權被竊取。
例如,對于下圖的棧結構:

若將長度為16字節(jié)的字符串賦給acArrBuf數組,則系統(tǒng)會從acArrBuf[0]開始向高地址填充棧空間,導致覆蓋EBP值和函數返回地址。若攻擊者用一個有意義的地址(否則會出現段錯誤)覆蓋返回地址的內容,函數返回時就會去執(zhí)行該地址處事先安排好的攻擊代碼。最常見的手段是通過制造緩沖區(qū)溢出使程序運行一個用戶shell,再通過shell執(zhí)行其它命令。若該程序有root或suid執(zhí)行權限,則攻擊者就獲得一個有root權限的shell,進而可對系統(tǒng)進行任意操作。
除通過使堆棧緩沖區(qū)溢出而更改返回地址外,還可改寫局部變量(尤其函數指針)以利用緩沖區(qū)溢出缺陷。
注意,本文描述的堆棧緩沖區(qū)溢出不同于廣義的“堆棧溢出(Stack OverFlow)”,后者除局部數組越界和內存覆蓋外,還可能由于調用層次太多(尤其應注意遞歸函數)或過大的局部變量所導致。
二、緩沖區(qū)溢出實例
本節(jié)給出若干緩沖區(qū)溢出相關的示例性程序。前三個示例為手工修改返回地址或實參,后兩個示例為局部數組越界訪問和緩沖區(qū)溢出。更加深入的緩沖區(qū)溢出攻擊參見相關資料。
示例函數必須包含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中下條待執(zhí)行指令的地址,而代碼所修改的也正是該值。%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);刷新緩沖區(qū),否則可能在段錯誤時無法輸出
}
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】在被調函數內修改主調函數指針變量,造成后續(xù)訪問該指針時程序崩潰。代碼如下:
//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處發(fā)生段錯誤
}
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時就會發(fā)生段錯誤。
注意,雖然本例代碼結構簡單,但不能輕率地推斷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;
}
當然,該版本已失去原有意義(不借助寄存器層面手段),純?yōu)槭纠?/p>
【示例4】越界訪問造成死循環(huán)。代碼如下:
//InfinteLoop.c
void InfinteLoop(void){
unsigned char ucIdx, aucArr[10];
for(ucIdx = 0; ucIdx <= 10; ucIdx++)
aucArr[ucIdx] = 1;
}
在循環(huán)內部,當訪問不存在的數組元素aucArr[10]時,實際上在訪問數組aucArr所在地址之后的那個位置,而該位置存放著變量ucIdx。因此aucArr[10] = 1將ucIdx重置為1,然后繼續(xù)循環(huán)的條件仍然成立,最終將導致死循環(huán)。
【示例5】緩沖區(qū)溢出。代碼如下:
//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函數調用結束后,并未直接執(zhí)行Come Home的輸出,而是轉而執(zhí)行NaughtyBoy函數(輸出Catch Me),然后回頭輸出Come Home。該過程重復一次后發(fā)生段錯誤(具體原因留待讀者思考)。
結合下圖所示的棧幀布局,詳細分析本示例緩沖區(qū)溢出過程。注意,本示例中地址及其內容由內嵌匯編和打印輸出獲得,正常情況下應通過gdb調試器獲得。

首先,main函數將字符數組szArr的地址作為參數(即pszStr)傳遞給函數CarelessPapa。該數組內容為"0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8",其中轉義字符串"\xe4\x83\x4\x8"對應NaughtyBoy函數入口地址0x080483e4(小字節(jié)序),而"\x23\x85\x4\x8"對應調用CarelessPapa函數時的返回地址0x8048523(小字節(jié)序)。CarelessPapa函數內部調用strcpy庫函數,將pszStr所指字符串內容拷貝至szBuf數組。因為strcpy函數不進行越界檢查,會逐字節(jié)拷貝直到遇見'\0'結束符。故pszStr字符串將從szBuf數組起始地址開始向高地址覆蓋,原返回地址0x8048523被覆蓋為NaughtyBoy函數地址0x080483e4。
這樣,當CarelessPapa函數返回時,修改后的返回地址從棧中彈出到EIP寄存器中,此時棧頂指針ESP指向返回地址上方的空間(esp+4),程序跳轉到EIP所指地址(NaughtyBoy函數入口)開始執(zhí)行,首先就是EBP入棧——并未像正常調用那樣先壓入返回地址,故NaughtyBoy函數棧幀中EBP位置相對CarelessPapa函數上移4個字節(jié)!此時,"\x23\x85\x4\x8"可將EBP上方的EIP修改為CarelessPapa函數的返回地址(0x8048523),從而保證正確返回main函數內。
注意,返回main函數并輸出Come Home后,main函數棧幀的EBP地址被改為0x42413938("89AB"),該地址已非堆棧空間,最終產生段錯誤。EBP地址會隨每次程序執(zhí)行而改變,故試圖在szArr字符串中恢復EBP是非常困難的。
從main函數return時將返回到調用它的啟動例程(_start函數)中,返回值被啟動例程獲得并用其作為參數調用exit函數。exit函數首先做一些清理工作,然后調用_exit系統(tǒng)調用終止進程。main函數的返回值最終傳給_exit系統(tǒng)調用,成為進程的退出狀態(tài)。以下代碼在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>
}
編譯運行結果如下:

這次沒有重復執(zhí)行,也未出現段錯誤。
三、緩沖區(qū)溢出防范
防范緩沖區(qū)溢出問題的準則是:確保做邊界檢查(通常不必擔心影響程序效率)。不要為接收數據預留相對過小的緩沖區(qū),大的數組應通過malloc/new分配堆空間來解決;在將數據讀入或復制到目標緩沖區(qū)前,檢查數據長度是否超過緩沖區(qū)空間。同樣,檢查以確保不會將過大的數據傳遞給別的程序,尤其是第三方COTS(Commercial-off-the-shelf)商用軟件庫——不要設想關于其他人軟件行為的任何事情。
若有可能,改用具備防止緩沖區(qū)溢出內置機制的高級語言(Java、C#等)。但許多語言依賴于C庫,或具有關閉該保護特性的機制(為速度而犧牲安全性)。其次,可以借助某些底層系統(tǒng)機制或檢測工具(如對C數組進行邊界檢查的編譯器)。許多操作系統(tǒng)(包括Linux和Solaris)提供非可執(zhí)行堆棧補丁,但該方式不適于這種情況:攻擊者利用堆棧溢出使程序跳轉到放置在堆上的執(zhí)行代碼。此外,存在一些偵測和去除緩沖區(qū)溢出漏洞的靜態(tài)工具(檢查代碼但并不運行)和動態(tài)工具(執(zhí)行代碼以確定行為),甚至采用grep命令自動搜索源代碼中每個有問題函數的實例。
但即使采用這些保護手段,程序員自身也可能犯其他許多錯誤,從而引入缺陷。例如,當使用有符號數存儲緩沖區(qū)長度或某個待讀取內容長度時,攻擊者可將其變?yōu)樨撝担瑥亩乖撻L度被解釋為很大的正值。經驗豐富的程序員還容易過于自信地"把玩"某些危險的庫函數,如對其添加自己總結編寫的檢查,或錯誤地推論出使用潛在危險的函數在某些特殊情況下是"安全"的。
本節(jié)將主要討論一些已被證明危險的C庫函數。通過在C/C++程序中禁用或慎用危險的函數,可有效降低在代碼中引入安全漏洞的可能性。在考慮性能和可移植性的前提下,強烈建議在開發(fā)過程中使用相應的安全函數來替代危險的庫函數調用。
以下分析某些危險的庫函數,較完整的列表參見表3-1。
3.1、gets
該函數從標準輸入讀入用戶輸入的一行文本,在遇到EOF字符或換行字符前,不會停止讀入文本。即該函數不執(zhí)行越界檢查,故幾乎總有可能使任何緩沖區(qū)溢出(應禁用)。
gcc編譯器下會對gets調用發(fā)出警告(the `gets' function is dangerous and should not be used)。
3.2、strcpy
該函數將源字符串復制到目標緩沖區(qū),但并未指定要復制字符的數目。若源字符串來自用戶輸入且未限制其長度,則可能引發(fā)危險。規(guī)避的方法如下:
1) 若知道目標緩沖區(qū)大小,則可添加明確的檢查(不建議該法):
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”所在的靜態(tài)存儲區(qū)。
安全的字符串處理函數通常體現在如下幾個方面:
- 顯式指明目標緩沖區(qū)大小
- 動態(tài)校驗
- 返回碼(以指明成功或失敗原因)
與strcpy函數具有相同問題的還有strcat函數。
3.3、 strncpy/strncat
該對函數是strcpy/strcat調用的“安全”版本,但仍存在一些問題:
1) strncpy和strncat要求程序員給出剩余的空間,而不是給出緩沖區(qū)的總大小。緩沖區(qū)大小一經分配就不再變化,但緩沖區(qū)中剩余的空間量會在每次添加或刪除數據時發(fā)生變化。這意味著程序員需始終跟蹤或重新計算剩余的空間,而這種跟蹤或重新計算很容易出錯。
2) 在發(fā)生溢出(和數據丟失)時,strncpy和strncat返回結果字符串的起始地址(而不是其長度)。雖然這有利于鏈式表達,但卻無法報告緩沖區(qū)溢出。
3) 若源字符串長度至少和目標緩沖區(qū)相同,則strncpy不會使用NUL來結束字符串;這可能會在以后導致嚴重破壞。因此,在執(zhí)行strncpy后通常需要手工終止目標字符串。
4) strncpy還可復制源字符串的一部分到目標緩沖區(qū),要復制的字符數目通常基于源字符串的相關信息來計算。這種操作也會產生未終止字符串。
5) strncpy會在源字符串結束時使用NUL來填充整個目標緩沖區(qū),這在源字符串較短時存在性能問題。
3.4、sprintf
該函數使用控制字符串來指定輸出格式,該字符串通常包括"%s"(字符串輸出)。若指定字符串輸出的精確指定符,則可通過指定輸出的最大長度來防止緩沖區(qū)溢出(如%.10s將復制不超過10個字符)。也可以使用"*"作為精確指定符(如"%.*s"),這樣就可傳入一個最大長度值。精確字段僅指定一個參數的最大長度,但緩沖區(qū)需要針對組合起來的數據的最大尺寸調整大小。
注意,"字段寬度"(如"%10s",無點號)僅指定最小長度——而非最大長度,從而留下緩沖區(qū)溢出隱患。
3.5、scanf
scanf系列函數具有一個最大寬度值,函數不能讀取超過最大寬度的數據。但并非所有規(guī)范都規(guī)定了這點,也不確定是否所有實現都能正確執(zhí)行這些限制。若要使用這一特性,建議在安裝或初始化期間運行小測試來確保它能正確工作。
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指向的緩沖區(qū)大小必須足夠容納結果字符串;輸出緩沖區(qū)大小應為輸入緩沖區(qū)大小的四倍(單個字符可能轉換為\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字符串中的字符轉換后復制到結果緩沖區(qū)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],因此不會引起緩沖區(qū)溢出。
3.8、realpath
該函數在libc 4.5.21及以后版本中提供,使用時需要limits.h和stdlib.h頭文件。其原型為:
char *realpath(const char *pszPath, char *pszResolvedPath);
該函數展開pszPath字符串中的所有符號鏈接,并解析pszPath中所引用的/./、/../和'/'字符(相對路徑),最終生成規(guī)范化的絕對路徑名。該路徑名作為帶結束符的字符串存入pszResolvedPath指向的緩沖區(qū),長度最大為PATH_MAX字節(jié)。結果路徑中不含符號鏈接、/./或/../。
若pszResolvedPath為空指針,則realpath函數使用malloc來分配PATH_MAX字節(jié)的緩沖區(qū)以存儲解析后的路徑名,并返回指向該緩沖區(qū)的指針。調用者應使用free函數去釋放該該緩沖區(qū)。
若執(zhí)行成功,realpath函數返回指向pszResolvedPath(規(guī)范化絕對路徑)的指針;否則返回空指針并設置errno以指示該錯誤,此時pszResolvedPath的內容未定義。
調用者需要確保結果緩沖區(qū)足夠大(但不應超過PATH_MAX),以處理任何大小的路徑。此外,不可能為輸出緩沖區(qū)確定合適的長度,因此POSIX.1-2001規(guī)定,PATH_MAX字節(jié)的緩沖區(qū)足夠,但PATH_MAX不必定義為常量,且可以通過pathconf函數獲得。然而,pathconf輸出的結果可能超大,以致不適合動態(tài)分配內存;另一方面,pathconf函數可返回-1表明結果路徑名超出PATH_MAX限制。pszResolvedPath為空指針的特性被POSIX.1-2008標準化,以避免輸出緩沖區(qū)長度難以靜態(tài)確定的缺陷。
應禁用或慎用的庫函數如下表所示:
表3-1
|
函數 |
危險性 |
解決方案 |
|
gets |
最高 |
禁用gets(buf),改用fgets(buf, size, stdin) |
|
strcpy |
高 |
檢查目標緩沖區(qū)大小,或改用strncpy,或動態(tài)分配目標緩沖區(qū) |
|
strcat |
高 |
改用strncat |
|
sprintf |
高 |
改用snprintf,或使用精度說明符 |
|
scanf |
高 |
使用精度說明符,或自己進行解析 |
|
sscanf |
高 |
使用精度說明符,或自己進行解析 |
|
fscanf |
高 |
使用精度說明符,或自己進行解析 |
|
vfscanf |
高 |
使用精度說明符,或自己進行解析 |
|
vsprintf |
高 |
改為使用vsnprintf,或使用精度說明符 |
|
vscanf |
高 |
使用精度說明符,或自己進行解析 |
|
vsscanf |
高 |
使用精度說明符,或自己進行解析 |
|
streadd |
高 |
確保分配的目標參數緩沖區(qū)大小是源參數大小的四倍 |
|
strecpy |
高 |
確保分配的目標參數緩沖區(qū)大小是源參數大小的四倍 |
|
strtrns |
高 |
手工檢查目標緩沖區(qū)大小是否至少與源字符串相等 |
|
getenv |
高 |
不可假定特殊環(huán)境變量的長度 |
|
realpath |
高(或稍低,實現依賴) |
分配緩沖區(qū)大小為PATH_MAX字節(jié),并手工檢查參數以確保輸入參數和輸出參數均不超過PATH_MAX |
|
syslog |
高(或稍低,實現依賴) |
將字符串輸入傳遞給該函數之前,將所有字符串輸入截成合理大小 |
|
getopt |
高(或稍低,實現依賴) |
將字符串輸入傳遞給該函數之前,將所有字符串輸入截成合理大小 |
|
getopt_long |
高(或稍低,實現依賴) |
將字符串輸入傳遞給該函數之前,將所有字符串輸入截成合理大小 |
|
getpass |
高(或稍低,實現依賴) |
將字符串輸入傳遞給該函數之前,將所有字符串輸入截成合理大小 |
|
getchar |
中 |
若在循環(huán)中使用該函數,確保檢查緩沖區(qū)邊界 |
|
fgetc |
中 |
若在循環(huán)中使用該函數,確保檢查緩沖區(qū)邊界 |
|
getc |
中 |
若在循環(huán)中使用該函數,確保檢查緩沖區(qū)邊界 |
|
read |
中 |
若在循環(huán)中使用該函數,確保檢查緩沖區(qū)邊界 |
|
bcopy |
低 |
確保目標緩沖區(qū)不小于指定長度 |
|
fgets |
低 |
確保目標緩沖區(qū)不小于指定長度 |
|
memcpy |
低 |
確保目標緩沖區(qū)不小于指定長度 |
|
snprintf |
低 |
確保目標緩沖區(qū)不小于指定長度 |
|
strccpy |
低 |
確保目標緩沖區(qū)不小于指定長度 |
|
strcadd |
低 |
確保目標緩沖區(qū)不小于指定長度 |
|
strncpy |
低 |
確保目標緩沖區(qū)不小于指定長度 |
|
vsnprintf |
低 |
確保目標緩沖區(qū)不小于指定長度 |
以上就是詳解C語言之緩沖區(qū)溢出的詳細內容,更多關于C語言 緩沖區(qū)溢出的資料請關注腳本之家其它相關文章!
相關文章
C語言實現航班售票系統(tǒng) C語言實現航班管理系統(tǒng)
這篇文章主要為大家詳細介紹了C語言實現航班售票系統(tǒng),C語言實現航班管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-12-12
VsCode安裝和配置c/c++環(huán)境小白教程(圖文)
本文主要介紹了VsCode安裝和配置c/c++環(huán)境小白教程,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01

