9 min read

Integer Overflow 是什麼?從里程表繞一圈到火箭自毀

Integer Overflow 是什麼?從里程表繞一圈到火箭自毀

這是「白話資安」系列的第二篇。上一篇聊了 Buffer Overflow(資料寫太多,超出原本準備的空間),這篇要講它的表親——Integer Overflow(數字超出範圍)。有趣的是,這兩個漏洞常常手牽手:數字先出事,害程式把空間準備錯,接著就引發上一篇那種溢位。一樣不需要任何基礎,看完你會發現,一個超出可表示範圍的數字,怎麼讓程式準備錯空間、讓火箭失控,還催生了「核彈甘地」這則經典都市傳說。


先講一句話:電腦的數字,是有「格數上限」的

你手機的計算機可以算到很大很大的數,所以你可能以為電腦裡的數字沒有上限。但在許多底層程式裡,整數會放在一個「固定寬度的格子」裡——就像舊車的里程表只有六格,最多顯示到 999999。

那如果 999999 再跑一公里呢?這台里程表沒有第七格,於是翻回 000000。當一個計算結果超出格子能表示的範圍,接下來會怎樣,其實取決於語言與型別:可能像里程表一樣繞回、可能直接報錯、也可能產生「未定義行為」(連程式設計師都無法保證會發生什麼)。這篇我們先從最直觀的「繞回」講起。

圖解一:里程表繞一圈

整數溢位:里程表繞回數字加到格子上限後,可能不會繼續變大,而是繞回最小值,就像里程表從 999999 跳回 000000。 加到上限 再加 1 999999 六格全滿 000000 翻回最小值 +1

舊車的里程表只有六格,跑破 999999 之後沒有第七格可以進位,只好從頭數起、翻回 000000。當程式裡的數字型別發生這種繞回時,就叫 Integer Overflow(整數溢位)。光是「數字繞回去」聽起來只是個計算錯誤,但接下來你會看到,當程式信任了這個出錯的數字去做決定時,事情就會失控。

圖解二:算錯的數字,怎麼變成安全漏洞

這就是 Integer Overflow 和上一篇 Buffer Overflow 手牽手的地方。很多程式在放東西進記憶體之前,會先算一下「要準備多大的空間」。如果這個計算被溢位動了手腳、算出一個小到離譜的數字,程式就會準備一個太小的空間——然後你把真正的(很大的)東西塞進去,就滿出來了。

整數溢位引發緩衝區溢位的攻擊鏈使用者輸入巨大的數,讓空間計算溢位成很小的值,程式準備了太小的空間,最後被塞爆。 ① 輸入超大的數 攻擊者故意給 ② 計算溢位 空間算成小值 ③ 空間準備太小 程式信任那個數 ④ 塞爆 溢出去

用個生活比喻:程式問「要訂幾個便當?我照數量準備桌子」,你輸入一個天文數字,害它算「總共要幾張桌子」時溢位、繞成 0,於是它只擺了一張小桌——但便當還是照原本的天文數字送來,桌子擺不下,全部滿出來。這就接回了上一篇的 Buffer Overflow。

所以 Integer Overflow 本身往往不是最終目的,而是引信:它負責讓程式「把空間準備錯」,真正造成破壞的溢位,是它點燃的下一步。不過要補一句——這是最經典的一條攻擊鏈,但不是唯一結果。整數溢位也可能直接扭曲金額計算、陣列索引、權限判斷或迴圈次數,各自都能單獨釀成問題。

另一個方向:Integer Underflow

繞回不只往上發生,也會往下。有一種格子只存 0 和正數(叫「無號整數」)。當無號整數是 0 再減 1,結果會低於它能表示的範圍。若語言或運算模式採用回繞規則,它會變成能表示的最大值;例如 8 位元無號整數會從 0 繞到 255。這個方向叫 Integer Underflow。

整數 underflow 與核彈甘地傳說以採回繞規則的 8 位元無號整數為例,從 0 再減 1 會往下繞回成最大值 255。 無號整數的 0 再減 1 0 255 −1 經典都市傳說:核彈甘地 遊戲《文明帝國》裡甘地的侵略性被減到繞回成最大值,和平大師秒變核彈狂人。

這個 underflow 有個超有名的都市傳說:遊戲《文明帝國》裡,甘地的「侵略性」數值本來設成最低的 1,當系統把它「減 2」時變成 −1——但那個無號格子存不了負數,於是往下繞回成 255,讓和平的甘地瞬間變成全場最好戰、最愛射核彈的狂人。要強調的是,這故事後來被開發者本人闢謠、其實是虛構的,但它太完美地演示了 underflow,早已成為工程圈的經典教材。

(技術上還有第三種情況:在明確採用二補數回繞規則的環境中,能存負數的「有號整數」從最大正值再加 1,可能翻成最小負值——「加一個正數,結果反而變負」;但不同語言與設定的處理方式並不相同。)

真實事故:Ariane 5 火箭

這不是只出現在教科書裡的理論。1996 年 Ariane 5 的事故鏈中,慣性參考系統嘗試把一個 64 位元浮點值轉成 16 位元有號整數。數值超出可表示範圍,未處理的例外使兩套慣性參考系統先後停止。火箭在主引擎點火序列開始約 37 秒、也就是離地約 30 秒後失去導引,隨即急劇偏航並解體,之後自毀機制才被觸發。

注意這個案例的細節——它不是里程表式的默默繞回,而是數字超出範圍後觸發例外、導致整個系統停擺的另一種結果(呼應前面說的「接下來會怎樣取決於語言與設計」)。這次事故讓火箭與四枚 Cluster 科學衛星全毀,成了史上最常被引用的軟體 bug 之一。

現代程式怎麼防住它?

跟上一篇一樣,現代的工具與語言早就準備了防線。Integer Overflow 的防禦重點是「在算錯之前就攔下來」:

三種防止整數溢位的方式設定合理的輸入範圍、使用有檢查的計算、以及轉換型別前先檢查。 限制輸入範圍 先擋掉不合理的 超大/超小值 用有檢查的計算 checked arithmetic: 溢位時回報、不硬算 轉換前先檢查 縮小型別或轉換時 確認值在範圍內

關於「換個語言就沒事」要講精確一點,別過度樂觀。Rust 開發模式下遇到溢位預設會直接 panic 中止,但 release 模式預設是回繞,除非另外開檢查;它同時提供 checked_*wrapping_*saturating_* 等不同策略讓你明確選擇要哪種行為。Python 的整數確實是任意精度、可以長到很大,但仍受記憶體限制,而且一旦你把它轉進固定寬度的型別(例如寫進某個二進位格式),一樣可能出事。所以「用更大的格子」只能延後溢位,不能保證根本繞不回去——真正的關鍵,始終是「別讓算錯的數字被下游默默信任」。

收尾

把兩篇串起來,記憶體漏洞的一條經典劇本就完整了:

攻擊者餵一個超出範圍的數 → 程式算「要準備多少空間」時溢位、繞成一個小數 → 準備了太小的空間 → 真正的資料塞進來、滿出去(Buffer Overflow)→ 蓋掉關鍵資料、接管程式。

Integer Overflow 教會我們一件反直覺的事:許多底層程式使用的固定寬度整數都有表示上限,它們住在有大小的格子裡,超出範圍就會出事——可能繞回、可能報錯、可能引發更嚴重的連鎖。而只要有任何一段程式「毫不懷疑地相信」了這個出錯的數字,缺口就開了。

下一篇,我們會走進記憶體漏洞的另一個家族——不是「空間寫太多」,也不是「數字超出範圍」,而是「東西還了、卻又偷偷拿回來用」的 Use After Free。一樣用生活比喻、一樣配圖,敬請期待。