欧美极品高清xxxxhd,国产日产欧美最新,无码AV国产东京热AV无码,国产精品人与动性XXX,国产传媒亚洲综合一区二区,四库影院永久国产精品,毛片免费免费高清视频,福利所导航夜趣136

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2819|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

防止 整型 溢出-C語言

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:90014 發(fā)表于 2015-9-15 15:05 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式


C語言的整型溢出問題

整型溢出有點老生常談了,bla, bla, bla… 但似乎沒有引起多少人的重視。整型溢出會有可能導(dǎo)致緩沖區(qū)溢出,緩沖區(qū)溢出會導(dǎo)致各種黑客攻擊,比如最近OpenSSL的heartbleed事件,就是 一個buffer overread的事件。在這里寫下這篇文章,希望大家都了解一下整型溢出,編譯器的行為,以及如何防范,以寫出更安全的代碼。
什么是整型溢出

C語言的整型問題相信大家并不陌生了。對于整型溢出,分為無符號整型溢出和有符號整型溢出。

對于unsigned整型溢出,C的規(guī)范是有定義的——“溢出后的數(shù)會以2^(8*sizeof(type))作模運算”,也就是說,如果一個unsigned char(1字符,8bits)溢出了,會把溢出的值與256求模。例如:
unsigned char x = 0xff;printf("%d\n", ++x);

上面的代碼會輸出:0 (因為0xff + 1是256,與2^8求模后就是0)

對于signed整型的溢出,C的規(guī)范定義是“undefined behavior”,也就是說,編譯器愛怎么實現(xiàn)就怎么實現(xiàn)。對于大多數(shù)編譯器來說,算得啥就是啥。比如:
signed char x =0x7f; //注:0xff就是-1了,因為最高位是1也就是負數(shù)了printf("%d\n", ++x);

上面的代碼會輸出:-128,因為0x7f + 0×01得到0×80,也就是二進制的1000 0000,符號位為1,負數(shù),后面為全0,就是負的最小數(shù),即-128。

另外,千萬別以為signed整型溢出就是負數(shù),這個是不定的。比如:
signed char x = 0x7f;signed char y = 0x05;signed char r = x * y;printf("%d\n", r);

上面的代碼會輸出:123

相信對于這些大家不會陌生了。
整型溢出的危害

下面說一下,整型溢出的危害。

示例一:整形溢出導(dǎo)致死循環(huán)
... ...... ...short len = 0;... ...while(len< MAX_LEN) { len += readFromInput(fd, buf); buf += len;}

上面這段代碼可能是很多程序員都喜歡寫的代碼(我在很多代碼里看到過多次),其中的MAX_LEN 可能會是個比較大的整型,比如32767,我們知道short是16bits,取值范圍是-32768 到 32767 之間。但是,上面的while循環(huán)代碼有可能會造成整型溢出,而len又是個有符號的整型,所以可能會成負數(shù),導(dǎo)致不斷地死循環(huán)。

示例二:整形轉(zhuǎn)型時的溢出
int copy_something(char *buf, int len){ #define MAX_LEN 256 char mybuf[MAX_LEN];</pre><pre> ... ... ... ... if(len > MAX_LEN){ // <---- [1] return -1; } return memcpy(mybuf, buf, len);}

上面這個例子中,還是[1]處的if語句,看上去沒有會問題,但是len是個signed int,而memcpy則需一個size_t的len,也就是一個unsigned 類型。于是,len會被提升為unsigned,此時,如果我們給len傳一個負數(shù),會通過了if的檢查,但在memcpy里會被提升為一個正數(shù),于是我 們的mybuf就是overflow了。這個會導(dǎo)致mybuf緩沖區(qū)后面的數(shù)據(jù)被重寫。

示例三:分配內(nèi)存

關(guān)于整數(shù)溢出導(dǎo)致堆溢出的很典型的例子是,OpenSSH Challenge-Response SKEY/BSD_AUTH 遠程緩沖區(qū)溢出漏洞。下面這段有問題的代碼摘自O(shè)penSSH的代碼中的auth2-chall.c中的 input_userauth_info_response() 函數(shù):
nresp = packet_get_int();if (nresp > 0) { response = xmalloc(nresp*sizeof(char*)); for (i = 0; i < nresp; i++) response[ i] = packet_get_string(NULL);}

上面這個代碼中,nresp是size_t類型(size_t一般就是unsigned int/long int),這個示例是一個解數(shù)據(jù)包的示例,一般來說,數(shù)據(jù)包中都會有一個len,然后后面是data。如果我們精心準(zhǔn)備一個len,比 如:1073741825(在32位系統(tǒng)上,指針占4個字節(jié),unsigned int的最大值是0xffffffff,我們只要提供0xffffffff/4 的值——0×40000000,這里我們設(shè)置了0×4000000 + 1), nresp就會讀到這個值,然后nresp*sizeof(char*)就成了 1073741825 * 4,于是溢出,結(jié)果成為了 0×100000004,然后求模,得到4。于是,malloc(4),于是后面的for循環(huán)1073741825 次,就可以干環(huán)事了(經(jīng)過0×40000001的循環(huán),用戶的數(shù)據(jù)早已覆蓋了xmalloc原先分配的4字節(jié)的空間以及后面的數(shù)據(jù),包括程序代碼,函數(shù)指 針,于是就可以改寫程序邏輯。關(guān)于更多的東西,你可以看一下這篇文章《 Survey of Protections from Buffer-Overflow Attacks 》)。

示例四:緩沖區(qū)溢出導(dǎo)致安全問題
int func(char *buf1, unsigned int len1, char *buf2, unsigned int len2 ){ char mybuf[256]; if((len1 + len2) > 256){ //<--- [1] return -1; } memcpy(mybuf, buf1, len1); memcpy(mybuf + len1, buf2, len2); do_some_stuff(mybuf); return 0;}

上面這個例子本來是想把buf1和buf2的內(nèi)容copy到mybuf里,其中怕len1 + len2超過256 還做了判斷,但是,如果len1+len2溢出了,根據(jù)unsigned的特性,其會與2^32求模,所以,基本上來說,上面代碼中的[1]處有可能為假 的。(注:通常來說,在這種情況下,如果你開啟-O代碼優(yōu)化選項,那個if語句塊就全部被和諧掉了——被編譯器給刪除了)比如,你可以測試一下 len1=0×104, len2 = 0xfffffffc 的情況。

這樣的例子有很多很多,這些整型溢出的問題如果在關(guān)鍵的地方,尤其是在搭配有用戶輸入的地方,如果被黑客利用了,就會導(dǎo)致很嚴(yán)重的安全問題。
關(guān)于編譯器的行為

在談一下如何正確的檢查整型溢出之前,我們還要來學(xué)習(xí)一下編譯器的一些東西。請別怪我羅嗦。

編譯器優(yōu)化

如何檢查整型溢出或是整型變量是否合法有時候是一件很麻煩的事情,就像上面的第四個例子一樣,編譯的優(yōu)化參數(shù)-O/-O2/-O3基本上會假設(shè)你的程序不會有整形溢出。會把你的代碼中檢查溢出的代碼給優(yōu)化掉。

關(guān)于編譯器的優(yōu)化,在這里再舉個例子,假設(shè)我們有下面的代碼(又是一個相當(dāng)相當(dāng)常見的代碼):
int len;char* data; if (data + len < data){ printf("invalid len\n"); exit(-1);}

上面這段代碼中,len 和 data 配套使用,我們害怕len的值是非法的,或是len溢出了,于是我們寫下了if語句來檢查。這段代碼在-O的參數(shù)下正常。但是在-O2的編譯選項下,整個if語句塊被優(yōu)化掉了。

你可以寫個小程序,在gcc下編譯(我的版本是4.4.7,記得加上-O2和-g參數(shù)),然后用gdb調(diào)試時,用disass /m命信輸出匯編,你會看到下面的結(jié)果(你可以看到整個if語句塊沒有任何的匯編代碼——直接被編譯器和諧掉了):
7 int len = 10;8 char* data = (char *)malloc(len); 0x00000000004004d4 <+4>: mov $0xa,%edi 0x00000000004004d9 <+9>: callq 0x4003b8 <malloc@plt>910 if (data + len < data){11 printf("invalid len\n");12 exit(-1);13 }1415 } 0x00000000004004de <+14>: add $0x8,%rsp 0x00000000004004e2 <+18>: retq

對此,你需要把上面 char* 轉(zhuǎn)型成 uintptr_t 或是 size_t,說白了也就是把char*轉(zhuǎn)成unsigned的數(shù)據(jù)結(jié)構(gòu),if語句塊就無法被優(yōu)化了。如下所示:
if ((uintptr_t)data + len < (uintptr_t)data){ ... ...}

關(guān)于這個事,你可以看一下C99的規(guī)范說明《 ISO/IEC 9899:1999 C specification 》第 §6.5.6 頁,第8點,我截個圖如下:(這段話的意思是定義了指針+/-一個整型的行為,如果越界了,則行為是undefined)

注意上面標(biāo)紅線的地方,說如果指針指在數(shù)組范圍內(nèi)沒事,如果越界了就是undefined,也就是說這事交給編譯器實現(xiàn)了,編譯器想咋干咋干,那怕你想把其優(yōu)化掉也可以。在這里要重點說一下, C語言中的一個大惡魔—— Undefined! 這里都是“野獸出沒”的地方,你一定要小心小心再小心 。

花絮:編譯器的彩蛋

上面說了所謂的undefined行為就全權(quán)交給編譯器實現(xiàn),gcc在1.17版本下對于undefined的行為還玩了個彩蛋( 參看Wikipedia )。

下面gcc 1.17版本下的遭遇undefined行為時,gcc在unix發(fā)行版下玩的彩蛋的源代碼。我們可以看到,它會去嘗試去執(zhí)行一些游戲 NetHack , Rogue 或是Emacs的 Towers of Hanoi ,如果找不到,就輸出一條NB的報錯。
execl("/usr/games/hack", "#pragma", 0); // try to run the game NetHackexecl("/usr/games/rogue", "#pragma", 0); // try to run the game Rogue// try to run the Tower's of Hanoi simulation in Emacs.execl("/usr/new/emacs", "-f","hanoi","9","-kill",0);execl("/usr/local/emacs","-f","hanoi","9","-kill",0); // same as abovefatal("You are in a maze of twisty compiler features, all different");
正確檢測整型溢出

在看過編譯器的這些行為后,你應(yīng)該會明白——“ 在整型溢出之前,一定要做檢查,不然,就太晚了 ”。

我們來看一段代碼:
void foo(int m, int n){ size_t s = m + n; .......}

上面這段代碼有兩個風(fēng)險: 1)有符號轉(zhuǎn)無符號 , 2)整型溢出 。這兩個情況在前面的那些示例中你都應(yīng)該看到了。所以,你千萬不要把任何檢查的代碼寫在 s = m + n 這條語名后面,不然就太晚了 。undefined行為就會出現(xiàn)了——用句純正的英文表達就是——“Dragon is here”——你什么也控制不住了。(注意:有些初學(xué)者也許會以為size_t是無符號的,而根據(jù)優(yōu)先級 m 和 n 會被提升到unsigned int。其實不是這樣的,m 和 n 還是signed int,m + n 的結(jié)果也是signed int,然后再把這個結(jié)果轉(zhuǎn)成unsigned int 賦值給s)

比如,下面的代碼是錯的:
void foo(int m, int n){ size_t s = m + n; if ( m>0 && n>0 && (SIZE_MAX - m < n) ){ //error handling... }}

上面的代碼中,大家要注意 (SIZE_MAX - m < n) 這個判斷,為什么不用m + n > SIZE_MAX呢?因為,如果 m + n 溢出后,就被截斷了,所以表達式恒真,也就檢測不出來了。另外,這個表達式中,m和n分別會被提升為unsigned。

但是上面的代碼是錯的,因為:

1)檢查的太晚了,if之前編譯器的undefined行為就已經(jīng)出來了(你不知道什么會發(fā)生)。

2)就像前面說的一樣,(SIZE_MAX - m < n) 可能會被編譯器優(yōu)化掉。

3)另外,SIZE_MAX是size_t的最大值,size_t在64位系統(tǒng)下是64位的,嚴(yán)謹(jǐn)點應(yīng)該用INT_MAX或是UINT_MAX

所以,正確的代碼應(yīng)該是下面這樣:
void foo(int m, int n){ size_t s = 0; if ( m>0 && n>0 && ( UINT_MAX - m < n ) ){ //error handling... return; } s = (size_t)m + (size_t)n;}

在《 蘋果安全編碼規(guī)范 》(PDF)中,第28頁的代碼中:

如果n和m都是signed int,那么這段代碼是錯的。正確的應(yīng)該像上面的那個例子一樣,至少要在n*m時要把 n 和 m 給 cast 成 size_t。因為,n*m可能已經(jīng)溢出了,已經(jīng)undefined了,undefined的代碼轉(zhuǎn)成size_t已經(jīng)沒什么意義了。(如果m和n是 unsigned int,也會溢出),上面的代碼僅在m和n是size_t的時候才有效。

不管怎么說,《 蘋果安全編碼規(guī)范 》絕對值得你去讀一讀。

上溢出和下溢出的檢查

前面的代碼只判斷了正數(shù)的上溢出overflow,沒有判斷負數(shù)的下溢出underflow。讓們來看看怎么判斷:

對于加法,還好。
#include <limits.h>void f(signed int si_a, signed int si_b) { signed int sum; if (((si_b > 0) && (si_a > (INT_MAX - si_b))) || ((si_b < 0) && (si_a < (INT_MIN - si_b)))) { /* Handle error */ return; } sum = si_a + si_b;}

對于乘法,就會很復(fù)雜(下面的代碼太夸張了):
void func(signed int si_a, signed int si_b){ signed int result; if (si_a > 0) { /* si_a is positive */ if (si_b > 0) { /* si_a and si_b are positive */ if (si_a > (INT_MAX / si_b)) { /* Handle error */ } } else { /* si_a positive, si_b nonpositive */ if (si_b < (INT_MIN / si_a)) { /* Handle error */ } } /* si_a positive, si_b nonpositive */ } else { /* si_a is nonpositive */ if (si_b > 0) { /* si_a is nonpositive, si_b is positive */ if (si_a < (INT_MIN / si_b)) { /* Handle error */ } } else { /* si_a and si_b are nonpositive */ if ( (si_a != 0) && (si_b < (INT_MAX / si_a))) { /* Handle error */ } } /* End if si_a and si_b are nonpositive */ } /* End if si_a is nonpositive */ result = si_a * si_b;}

更多的防止在操作中整型溢出的安全代碼可以參看《 INT32-C. Ensure that operations on signed integers do not result in overflow 》
其它

對于C++來說,你應(yīng)該使用STL中的numeric_limits::max() 來檢查溢出。

另外,微軟的SafeInt類是一個可以幫你遠理上面這些很tricky的類,下載地址: http://safeint.codeplex.com/

對于Java 來說,一種是用JDK 1.7中Math庫下的safe打頭的函數(shù),如safeAdd()和safeMultiply(),另一種用更大尺寸的數(shù)據(jù)類型,最大可以到BigInteger。

可見,寫一個安全的代碼并不容易,尤其對于C/C++來說。對于黑客來說,他們只需要搜一下開源軟件中代碼有memcpy/strcpy之類的地方,然后看一看其周邊的代碼,是否可以通過用戶的輸入來影響,如果有的話,你就慘了。


分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復(fù) 返回頂部 返回列表