雖然“緩衝區溢位”對現代作業系統與編譯器來講已經不是什麼大問題,但是作為一個合格的 C 程式設計師,還是完全有必要了解它的整個細節。這裡需要特別說明的是,為了更好地演示緩衝區溢位,本節的所有程式碼範例僅限於在 Windows XP SP3+Visua l C++6.0 環境中演示執行。
簡單來說,緩衝區就是一塊連續的計算機記憶體區域,它可以儲存相同資料型別的多個範例,如字元陣列。而緩衝區溢位則是指當計算機向緩衝區內填充資料位數時超過了緩衝區本身的容量,溢位的資料覆蓋在合法資料上。
通常,在理想的情況下,程式檢查資料長度並不允許輸入超過緩衝區長度的字元。然而,由於 C 語言沒有任何內建的邊界檢查,在寫入一個字元陣列時,如果超越了陣列的結尾就會造成溢位。
與此同時,標準 C 語言函數庫提供了一些沒有邊界檢查的字串處理常式,其中:
-
strcat()、strcpy()、sprintf() 與 vsprintf() 函數對一個 null 結尾的字串進行操作,並不檢查溢位情況;
-
gets() 函數從標準輸入中讀取一行到緩衝區中,直到換行或 EOF,它也不檢查緩衝區溢位;
-
scanf() 函數在匹配一系列非空格字元(%s)或從指定集合(%[])中匹配非空系列字元時,使用字元指標指向陣列,並且沒有定義最大欄位寬度這個可選項,就可能出現問題。
然而,如果這些函數的目標地址是一個固定大小的緩衝區,而函數的另外引數是由使用者以某種形式輸入,則很有可能被人利用緩衝區溢位來破解。
另一種常見的程式設計結構是使用 while 迴圈從標準輸入或某個檔案中一次讀入一個字元到緩衝區中,直到行尾或檔案結尾,或者碰到其他什麼終止符。這種結構通常使用 getc()、fgetc() 或 getchar() 函數中的某一個,如果這時在 while 迴圈中沒有明確檢查溢位,這種程式就很容易被破解。
我們知道,任何一個源程式通常都包括程式碼段(或者稱為文字段)和資料段,這些程式碼和資料本身都是靜態的。為了執行程式,首先要由作業系統負責為其建立進程,並在進程的虛擬地址空間中為其程式碼段和資料段建立對映。但是只有靜態的程式碼段和資料段是不夠的,進程在執行過程中還要有其動態環境。
一般說來,預設的動態儲存環境通過堆疊機制建立。所有區域性變數及所有按值傳遞的函數引數都通過堆疊機制自動分配記憶體空間,分配同一資料型別相鄰塊的記憶體區域被稱為緩衝區。圖 1 展示了程式在記憶體中的對映。
圖 1 程式在記憶體中的對映