堆疊緩衝區溢位(stack buffer overflow)
想像旅館的一排信箱,每位客人的格子剛好只能放十封信。如果櫃台一直往某個格子塞,超過第十封,多出來的就會溢進「隔壁」客人的格子,把他的信件弄亂。堆疊緩衝區溢位就是記憶體裡同樣的意外:程式往一個固定大小的區域陣列寫入超過它能容納的位元組,多出來的位元組就蓋掉了同一個堆疊框裡緊鄰它的東西。
具體的畫面是這樣。當一個函式執行時,它的區域變數位於一塊稱為堆疊的行程記憶體中(這裡的堆疊是隨呼叫成長收縮的「記憶體區段」,不是後進先出的資料結構)。編譯器替輸入保留了例如 char buf[16]。如果程式做了像 strcpy(buf, attacker_input) 這樣的事,而輸入有 40 位元組,第 17 到第 40 個位元組就會被寫到 buf 的尾端「之後」。因為堆疊在區域變數附近還存放了被儲存的暫存器與函式的返回位址,這些溢出的位元組可能蓋掉返回位址——也就是函式結束時 CPU 要跳去的地方。能控制輸入的攻擊者,因此能控制接下來執行流程往哪裡走。
它之所以重要,是因為這是 C 與 C++ 中最古老、研究最透徹的記憶體損毀漏洞,而這兩種語言不做自動邊界檢查。根本原因幾乎都是某次複製缺少或寫錯了長度檢查:strcpy()、gets()、sprintf(),或一個信任輸入大小的手寫迴圈。誠實的說法:溢位本身是未定義行為,C 標準對接下來會發生什麼不加任何要求——可見的崩潰、無聲的損毀、或被劫持,都是被允許的後果,而非被保證的結果。
void greet(char *name) { char buf[16]; strcpy(buf, name); // 沒有長度檢查:name 超過 15 就溢出 buf printf("hi %s\n", buf); } // 對一個 64 位元組字串呼叫 greet 會蓋掉被儲存的暫存器與返回位址
strcpy 一路複製到字串結尾字元為止,完全不顧 buf 的大小——典型的堆疊溢位原語。
這個漏洞的堆疊溢位,和堆疊「耗盡」(遞迴太深用光堆疊)是不同的東西——兩者都被隨口叫做「stack overflow」,但只有前者會蓋掉相鄰資料。