整數溢位分為無符號整數溢位和 有符號整數溢位無符號整數溢位
對於unsigned 整型溢位,C的規範是有定義的 -- "溢位後的數會以2^(8*sizeof(type))作模運算",
也就是說,如果一個unsigned char(1byte, 8bit)溢位了,會把溢位的值與256求模
以32bit OS 為例
|Type |byte|模:2^(8*sizeof(type))|
|unsigned char |1 |2^8 = 256 |
|unsigned short |2 |2^16 = 65536 |
|unsigned int |4 |2^32 = 4294967296 |
Example 1#include <stdio.h>int main(int argc, const char * argv[]){ //insert code here... unsigned char x = 0xff; x++; printf("x = %d\n", x);}
上面的程式碼會輸出:0 (因為0xff + 1是256,與2^8求模後就是0.
有符號整型溢位對於signed 整型的溢位,C的規範定義是"undefined behavior",雖然沒有定義,各編譯器可自己實現,但是大部分的溢位機制是一樣的.
有符號整型溢位可分為*向上溢位*和*向下溢位*.假設用k個位元組表示一個整型變數,那麼這個變數可以表示的有符號整數的範圍是-2^(k-1) ~ 2^(k-1)-1,
那麼兩個正整數或者兩個負整數相加就有可能超過這個整型變數所能表示的範圍,向上超出 > 2^(k-1)-1稱之為向上溢位,向下超出 < -2^(k-1)稱之為向下溢位.
對於signed char,正整數最大值為127,負整數最小值為128,(-128 ~ 127) unsigned char 所能表示的最大值為255.
Example 1signed char x = 0x7f;x++;
上面的程式碼會輸出:-128,因為0x7f + 0x01得到0x80,也就是二進位制的1000 0000,符號位為1,負數,後面為全0,就是負的最小數,即-128。
Example 2signed char x;x = (-100) + (-100);
上面程式碼會輸出56,因為200的二進位制為11001000, -200根據補碼的演算法, 得出00111000即56.
上面的例子無論是向上溢位還是向下溢位, 絕對值都在相對於無符號整型能表示的範圍內.
Example 3signed char x;x = 200 + 200;
對於signed char如果為400, 超出了位數的表示範圍,取結果的低八位.
因此上面程式碼會輸出-112, 如果x的結果為負數且超出了255, 則取結果的低八位, 並進行補碼的反向操作,減一後取反.
整型溢位的危害 Example 1: 整型溢位導致死迴圈
......short len = 0;...while (len < MAX_LEN) { len += readFromInput(fd, buf); buf += len;}其中的MAX_LEN可能會是一個比較大的整型,比如32767, short在32位OS中是16bits, 取值範圍是-32768 ~ 32767之間.但是上面的while迴圈程式碼有可能會造成整型溢位,而len又是一個有符號的整型,所以可能會成負數,導致不斷的死迴圈.
Example 2: 整型轉型時的溢位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);}
......short len = 0;...while (len < MAX_LEN) { len += readFromInput(fd, buf); buf += len;}其中的MAX_LEN可能會是一個比較大的整型,比如32767, short在32位OS中是16bits, 取值範圍是-32768 ~ 32767之間.但是上面的while迴圈程式碼有可能會造成整型溢位,而len又是一個有符號的整型,所以可能會成負數,導致不斷的死迴圈.
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);}
上面的len是個signed int, 而memcpy則需要一個size_t的len, 也就是一個unsigned 型別.
於是,len會被提升為unsigned, 此時, 如果我們給len傳一個負數, 會通過了if的檢查,但在memcpy裡會被提升為一個正數,於是我們的mybuf就會overflow,
會導致mybuf緩衝區後面的資料被重寫.
Example 3: 分配記憶體nresp = packet_get_int();if (nresp > 0){ response = malloc(nresp*sizeof(char*)); for (i = 0; i < nresp; i++) response[i] = packet_get_string(NULL);}
上面這個程式碼中, nresp是size_t型別(size_t 一般就是unsigned int/long int), 這是
一個解析資料包的示例,一般來說,資料包中都會有一個len, 然後後面就是data.
比如準備一個len(在32位系統上,指標佔4個位元組,unsigned int 的最大值是0xffffffff, 我們只要提供0xffffffff/4的值 -- 0x400000000, 這裡我們設定0x400000000 + 1),
nresp 就會讀到這個值, 然後nresp*sizeof(char*)就成了1073741825 * 4,於是溢位,結果成了0x1000000004, 然後求模,得到4. 於是malloc(4),
後面的for 迴圈nresp 次, 使用者的資料就會覆蓋malloc分配的4位元組的空間以及後面的資料,包括程式程式碼,函式指標,於是就可以改寫程式邏輯.
Example 4: 緩衝區溢位導致安全問題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(mubuf); return 0;}
上面的例子是將buf1和buf2的內容copy到mybuf裡, 其中對len1+len2是否超過256做了判斷,
但是, 如果len1+len2溢位了,根據unsigned的特性,其會與2^32求模,所以, 基本上來說,上面的程式碼中的[1]處有可能為假的.
Addition Ex1void func(unsigned int ui_a, unsigned int ui_b) { unsigned int usum = ui_a + ui_b; /* ... */}
Compliant Solution(Precondition Test)
#include <limits.h> void func(unsigned int ui_a, unsigned int ui_b) { unsigned int usum; if (UINT_MAX - ui_a < ui_b) { /* Handle error */ } else { usum = ui_a + ui_b; } /* ... */}
void func(unsigned int ui_a, unsigned int ui_b) { unsigned int usum = ui_a + ui_b; if (usum < ui_a) { /* Handle error */ } /* ... */}
Ex2
pen->num_vertices = _cairo_pen_vertices_needed( gstate->tolerance, radius, &gstate->ctm);pen->vertices = malloc( pen->num_vertices * sizeof(cairo_pen_vertex_t));
Compliant Solutionpen->num_vertices = _cairo_pen_vertices_needed( gstate->tolerance, radius, &gstate->ctm); if (pen->num_vertices > SIZE_MAX / sizeof(cairo_pen_vertex_t)) { /* Handle error */}pen->vertices = malloc( pen->num_vertices * sizeof(cairo_pen_vertex_t));