就像咱們看到的那樣,有符號數到無符號數的隱式強制類型轉換致使了某些非直觀的行爲。而這些非直觀的特性常常致使程序錯誤,而且這種包含隱式強制類型轉換細微差異的錯誤很難被發現。 由於這種強制類型轉換是在代碼中沒有明確指示的狀況下發生的,程序員常常忽視了它的影響。下面3個例子說明了某些因爲隱式強制類型轉換和無符號數據類型形成的細微錯誤。程序員
考慮下列代碼,這段代碼試圖計算數組 a 中全部元素的和,其中元素的數量由參數 length 給出。數組
/* WARNING: This is buggy code */ float sum_elements(float a[], unsigned length) { int i; float result = 0; for (i = 0; i <= length-1; i++) result += a[i]; return result; }
當參數 length 等於 0 時,運行這段代碼應該返回 0.0。但實際上,運行時會遇到一個存儲器錯誤。安全
修改代碼以下:數據結構
/* WARNING: This is right code */ float sum_elements(float a[], unsigned length) { int i; float result = 0; for (i = 0; i < length; i++) result += a[i]; return result; }
寫一個函數用來斷定一個字符串是否比另外一個更長。最開始你寫的函數是這樣的 :函數
/* Determine whether string s is longer than string t */ /* WARNING: This function is buggy */ int strlonger(char *s, char *t) { return strlen(s) - strlen(t) > 0; }
注意在頭文件 stdio.h 中數據類型 size_t 是定義成 unsigned int 的。spa
/* Prototype for library function strlen */ size_t strlen(const char *s);
實際上當 s 比 t 短時,strlonger 函數的返回值也是 1,爲何會出現這種狀況呢?原來 strlen 的返回值類型爲 size_t,C語言中將 size_t 定義爲 unsigned int,當 s 比 t 短時,strlen(s) - strlen(t) 爲負數,但無符號數的運算結果隱式轉換爲無符號數就變成了很大的無符號數。爲了讓函數正確工做,代碼應該修改以下:操作系統
int strlonger(char *s1, char *s2) { return strlen(s1) > strlen(s2); }
2002 年,從事 FreeBSD 開源操做系統項目的程序員意識到,他們對 getpeername 函數的實現存在安全漏洞。代碼的簡化版本以下 :code
/* * Illustration of code vulnerability similar to that found in * FreeBSD’s implementation of getpeername() */ /* Declaration of library function memcpy */ void *memcpy(void *dest, void *src, size_t n); /* Kernel memory region holding user-accessible data */ #define KSIZE 1024 char kbuf[KSIZE]; /* Copy at most maxlen bytes from kernel region to user buffer */ int copy_from_kernel(void *user_dest, int maxlen) { /* Byte count len is minimum of buffer size and maxlen */ int len = KSIZE < maxlen ? KSIZE : maxlen; memcpy(user_dest, kbuf, len); return len; }
在這段代碼裏,第 7 行給出的是庫函數 memcpy 的原型,這個函數是要將一段指定長度爲 n 的字節從存儲器的一個區域複製到另外一個區域。進程
從第 14 行開始的函數 copy_from_kernel 是要將一些操做系統內核維護的數據複製到指定的用戶能夠訪問的存儲器區域。對用戶來講,大多數內核維護的數據結構應該是不可讀的,由於這些數據結構可能包含其餘用戶和系統上運行的其餘做業的敏感信息,可是顯示爲 kbuf 的區域是用戶能夠讀的。參數 maxlen 給出的是分配給用戶的緩衝區的長度,這個緩衝區是用參數 user_dest 指示的。而後,第 16 行的計算確保複製的字節數據不會超出源或者目標緩衝區可用的範圍。不過,假設有些懷有惡意的程序員在調用 copy_from_kernel 的代碼中對 maxlen 使用了負數值,那麼,第 16 行的最小值計算會把這個值賦給 len,而後 len 會做爲參數 n 被傳遞給 memcpy。element
不過,請注意參數n是被聲明爲數據類型size_t的。這個數據類型是在庫文件 stdio.h中(經過typedef)被聲明的。典型地,在 32 位機器上被定義爲unsigned int。 既然參數n是無符號的,那麼memcpy會把它看成一個很是大的正整數,而且試圖將這樣多字 節的數據從內核區域複製到用戶的緩衝區。雖然複製這麼多字節(至少 231 個)實際上不會完成, 由於程序會遇到進程中非法地址的錯誤,可是程序仍是能讀到沒有被受權的內核存儲器區域。
咱們能夠看到,這個問題是因爲數據類型的不匹配形成的 :在一個地方,長度參數是有符號數 ;而另外一個地方,它又是無符號數。正如這個例子代表的那樣,這樣的不匹配會成爲缺陷的緣由,甚至會致使安全漏洞。幸運的是,尚未案例報告有程序員在 FreeBSD 上利用了這個漏洞。他們發佈了一個安全建議,「FreeBSD-SA-02:38.signed-error」,建議系統管理員如何應用補丁消除這個 漏洞。要修正這個缺陷,只要將 copy_from_kernel 的參數 maxlen 聲明爲類型 size_t,也就是與 memcpy 的參數 n 一致。同時,咱們也應該將本地變量 len 和返回值聲明爲 size_t。