国产成人精品三级麻豆,色综合天天综合高清网,亚洲精品夜夜夜,国产成人综合在线女婷五月99播放,色婷婷色综合激情国产日韩

當(dāng)前位置:首頁(yè) > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > C++整數(shù)溢出

C++整數(shù)溢出 時(shí)間:2019-03-21      來(lái)源:華清遠(yuǎn)見(jiàn)

1、什么是整數(shù)溢出

C語(yǔ)言的整型問(wèn)題相信大家并不陌生了。對(duì)于整型溢出,分為無(wú)符號(hào)整型溢出和有符號(hào)整型溢出。

對(duì)于unsigned整型溢出,C的規(guī)范是有定義的——“溢出后的數(shù)會(huì)以2^(8*sizeof(type))作模運(yùn)算”,也就是說(shuō),如果一個(gè)unsigned char(1字符,8bits)溢出了,會(huì)把溢出的值與256求模。例如:

unsigned char x = 0xff;

printf("%d\n", ++x);

上面的代碼會(huì)輸出:0 (因?yàn)?xff + 1是256,與2^8求模后就是0)

對(duì)于signed整型的溢出,C的規(guī)范定義是“undefined behavior”,也就是說(shuō),編譯器愛(ài)怎么實(shí)現(xiàn)就怎么實(shí)現(xiàn)。對(duì)于大多數(shù)編譯器來(lái)說(shuō),算得啥就是啥。比如:

signed char x =0x7f; //注:0xff就是-1了,因?yàn)樽罡呶皇?也就是負(fù)數(shù)了

printf("%d\n", ++x);

上面的代碼會(huì)輸出:-128,因?yàn)?x7f + 0x01得到0x80,也就是二進(jìn)制的1000 0000,符號(hào)位為1,負(fù)數(shù),后面為全0,就是負(fù)的最小數(shù),即-128。

另外,千萬(wàn)別以為signed整型溢出就是負(fù)數(shù),這個(gè)是不定的。比如:

signed char x = 0x7f;

signed char y = 0x05;

signed char r = x * y;

printf("%d\n", r);

上面的代碼會(huì)輸出:123

相信對(duì)于這些大家不會(huì)陌生了

2、整數(shù)溢出的危害

示例一:整形溢出導(dǎo)致死循環(huán)

... ...

... ...

short len = 0;

... ...

while(len< MAX_LEN) {

len += readFromInput(fd, buf);

buf += len;

}

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

示例二:整形轉(zhuǎn)型時(shí)的溢出

int copy_something(char *buf, int len)

{

#define MAX_LEN 256

char mybuf[MAX_LEN];

... ...

... ...

if(len > MAX_LEN){ // <---- [1]

return -1;

}

return memcpy(mybuf, buf, len);

}

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

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

關(guān)于整數(shù)溢出導(dǎo)致堆溢出的很典型的例子是,OpenSSH Challenge-Response SKEY/BSD_AUTH 遠(yuǎn)程緩沖區(qū)溢出漏洞。下面這段有問(wèn)題的代碼摘自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);

}

上面這個(gè)代碼中,nresp是size_t類型(size_t一般就是unsigned int/long int),這個(gè)示例是一個(gè)解數(shù)據(jù)包的示例,一般來(lái)說(shuō),數(shù)據(jù)包中都會(huì)有一個(gè)len,然后后面是data。如果我們精心準(zhǔn)備一個(gè)len,比如:1073741825(在32位系統(tǒng)上,指針占4個(gè)字節(jié),unsigned int的最大值是0xffffffff,我們只要提供0xffffffff/4 的值——0x40000000,這里我們?cè)O(shè)置了0x4000000 + 1), nresp就會(huì)讀到這個(gè)值,然后nresp*sizeof(char*)就成了 1073741825 * 4,于是溢出,結(jié)果成為了 0x100000004,然后求模,得到4。于是,malloc(4),于是后面的for循環(huán)1073741825 次,就可以干環(huán)事了(經(jīng)過(guò)0x40000001的循環(huán),用戶的數(shù)據(jù)早已覆蓋了xmalloc原先分配的4字節(jié)的空間以及后面的數(shù)據(jù),包括程序代碼,函數(shù)指針,于是就可以改寫程序邏輯。

示例四:緩沖區(qū)溢出導(dǎo)致安全問(wèn)題

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;

}

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

三、關(guān)于編譯器的行為

編譯器優(yōu)化

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

關(guān)于編譯器的優(yōu)化,在這里再舉個(gè)例子,假設(shè)我們有下面的代碼(又是一個(gè)相當(dāng)相當(dāng)常見(jiàn)的代碼):

int len;

char* data;

if (data + len < data){

printf("invalid len\n");

exit(-1);

}

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

你可以寫個(gè)小程序,在gcc下編譯(我的版本是4.4.7,記得加上-O2和-g參數(shù)),然后用gdb調(diào)試時(shí),用disass /m命信輸出匯編,你會(huì)看到下面的結(jié)果(你可以看到整個(gè)if語(yǔ)句塊沒(méi)有任何的匯編代碼——直接被編譯器和諧掉了):

7 int len = 10;

8 char* data = (char *)malloc(len);

0x00000000004004d4 <+4>: mov $0xa,%edi

0x00000000004004d9 <+9>: callq 0x4003b8 <malloc@plt>

9

10 if (data + len < data){

11 printf("invalid len\n");

12 exit(-1);

13 }

14

15 }

0x00000000004004de <+14>: add $0x8,%rsp

0x00000000004004e2 <+18>: retq

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

if ((uintptr_t)data + len < (uintptr_t)data){

... ...

}

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

C語(yǔ)言,C++整數(shù)溢出

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

四、正確檢測(cè)整數(shù)溢出

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

我們來(lái)看一段代碼:

void foo(int m, int n)

{

size_t s = m + n;

.......

}

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

比如,下面的代碼是錯(cuò)的:

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) 這個(gè)判斷,為什么不用m + n > SIZE_MAX呢?因?yàn)椋绻?m + n 溢出后,就被截?cái)嗔耍员磉_(dá)式恒真,也就檢測(cè)不出來(lái)了。另外,這個(gè)表達(dá)式中,m和n分別會(huì)被提升為unsigned。

但是上面的代碼是錯(cuò)的,因?yàn)椋?/p>

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

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

3)另外,SIZE_MAX是size_t的最大值,size_t在64位系統(tǒng)下是64位的,嚴(yán)謹(jǐn)點(diǎ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頁(yè)的代碼中:

C語(yǔ)言,C++整數(shù)溢出

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

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

二分取中搜索算法中的溢出

我們?cè)賮?lái)看一個(gè)二分取中搜索算法(binary search),大多數(shù)人都會(huì)寫成下面這個(gè)樣子:

int binary_search(int a[], int len, int key)

{

int low = 0;

int high = len - 1;

while ( low<=high ) {

int mid = (low + high)/2;

if (a[mid] == key) {

return mid;

}

if (key < a[mid]) {

high = mid - 1;

}else{

low = mid + 1;

}

}

return -1;

}

上面這個(gè)代碼中,你可能會(huì)有這樣的想法:

1) 我們應(yīng)該用size_t來(lái)做len, low, high, mid這些變量的類型。沒(méi)錯(cuò),應(yīng)該是這樣的。但是如果這樣,你要小心第四行 int high = len -1; 如果len為0,那么就“high大發(fā)了”。

2) 無(wú)論你用不用size_t。我們?cè)谟?jì)算mid = (low+high)/2; 的時(shí)候,(low + high) 都可以溢出。正確的寫法應(yīng)該是:

int mid = low + (high - low)/2;

上溢出和下溢出的檢查

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

對(duì)于加法,還好。

#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;

}

對(duì)于乘法,就會(huì)很復(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》

上一篇:Python中數(shù)據(jù)類型

下一篇:嵌入式開(kāi)發(fā):Uboot配置文件mkconfig

熱點(diǎn)文章推薦
華清學(xué)員就業(yè)榜單
高薪學(xué)員經(jīng)驗(yàn)分享
熱點(diǎn)新聞推薦
前臺(tái)專線:010-82525158 企業(yè)培訓(xùn)洽談專線:010-82525379 院校合作洽談專線:010-82525379 Copyright © 2004-2022 北京華清遠(yuǎn)見(jiàn)科技集團(tuán)有限公司 版權(quán)所有 ,京ICP備16055225號(hào)-5,京公海網(wǎng)安備11010802025203號(hào)

回到頂部