本節書摘來自華章計算機《編寫高品質代碼:改善c程式代碼的125個建議》一書中的第1章,建議2-7,作者:馬 偉 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
整數溢出是一種常見、難預測且嚴重的軟體漏洞,由它引發的程式bug可能比格式化字元串與緩沖區溢出等缺陷更難于發現。c99标準中規定,當兩個操作數都是有符号整數時,就有可能發生整數溢出,它将會導緻“不能确定的行為”。也就是說整數溢出是一種未定義的行為,這也就意味着編譯器在處理有符号整數的溢出時具有很多的選擇,遵循标準的編譯器可以做它們想做的任何事,比如完全忽略該溢出或終止程序。大多數編譯器都會忽略這種溢出,這可能會導緻不确定的值或錯誤的值儲存在整數變量中。
整數溢出有時候是很難發現的,一般情況下在整數溢出發生之前,你都無法知道它是否會發生溢出,即使你的代碼經過仔細審查,有時候溢出也是不可避免的。是以,程式很難區分先前計算出的結果是否正确,而且如果計算結果将作為一個緩沖區的大小、數組的下标、循環計數器與記憶體配置設定函數的實參等時将會非常危險。當然,因為無法直接改寫記憶體單元,是以大多數整數溢出是沒有辦法利用的。但是,有時候整數溢出将會導緻其他類型的缺陷發生,比如很容易發生的緩沖區溢出等。代碼清單1-13是一個簡單的整數溢出示例。
在32位作業系統中,類型int 的取值範圍為“-2147483647~2147483647”,限制是由int_min與int_max宏指定的,如下面的代碼所示:
而在代碼清單1-13中,當程式執行語句“s1+s4 、s2-s3 與s2*s5”時,其結果都超過類型int 的取值範圍,是以發生溢出行為,運作結果如圖1-15所示。

當然,面對這些簡單的有符号整數運算溢出,簡單地通過對操作數進行預測的方法就能夠避免發生有符号整數運算溢出。比如,代碼清單1-14就采用了補碼的表示形式來對操作數進行預測。
如上面的代碼所示,這種方式可以有效地避免發生簡單的有符号整數運算溢出,有興趣的朋友可以自己測試。其實,不隻算術運算可能造成溢出,任何企圖改變該有符号整型變量值的操作都可能造成溢出。示例如代碼清單1-15所示。
代碼清單1-15的運作結果如圖1-16所示。
與無符号整數的回繞相似,并不是每種運算符号都會令有符号操作數運算産生溢出,
與前面所講的無符号整數回繞一樣,有符号整數的這種溢出也很容易導緻緩沖區溢出,同時也很容易讓攻擊者可執行任意代碼,示範示例如代碼清單1-16所示。
在代碼清單1-16中,程式需要将c1與c2的内容複制到buf中,并分别由len1與len2來指定複制的位元組數。這裡需要特别注意的語句是“if((len1 + len2) > 100)”,我們利用該語句進行了相對嚴格的大小檢查:如果len1 + len2的值大于buf數組的大小(100),則不進行複制。
運作代碼清單1-16,當我們執行指令“1-16 hello! 6 c 2”時,程式運作正常,并成功地将字元串複制到buf中,運作結果如圖1-17所示。
https://yqfile.alicdn.com/8c08fa3021127403f0c03083b05544ce95285b22.png" >
當我們執行指令“1-16 hello! 50 c 51”時,程式同樣運作正常,運作結果如圖1-18所示。
可當我們執行指令“1-16 hello! 2147483647 c 2”時,程式卻意外地繞過大小檢查語句“if((len1 + len2) > 100)”來執行相關的操作。是什麼原因導緻這種情況發生的呢?
其實很簡單,就是由于整數溢出而導緻的。從執行的指令“1-16 hello! 2147483647 c 2”可以得出,len1的值為2147483647(即十六進制為0x7fffffff),len2值為2(即十六進制為0x00000002),當執行語句“len1 + len2(即0x7fffffff+0x00000002)”時會發生溢出,所得結果為-2147483647(即十六進制為0x80000001)。因為-2147483647遠遠小于100,進而使程式繞過大小檢查語句“if((len1 + len2) > 100)”來執行餘下的操作。也正因為如此,在執行語句“memcpy(buf, c1, len1)”時便導緻異常“unhandled exception at 0x65726f66 in 1-16.exe: 0xc0000005: access violation reading location 0x65726f66”的發生。