未定義行為與安全

陣列與指標越界存取

想像一排編號 0 到 4 的五個信箱。如果有人叫你「把這封信放進 7 號信箱」,根本沒有 7 號信箱——你會伸到這排信箱的盡頭之外,碰到那裡碰巧有的東西:別住戶的信箱、牆、街道。C 的陣列正是這樣一排信箱,在它有效範圍之外讀或寫,就叫越界存取。它是未定義行為最常見的來源之一,也是許多安全漏洞背後的引擎。

在 C 裡,大小為 n 的陣列有效索引是 0 到 n-1;沒有任何自動檢查確保你的索引留在範圍內。語言完全信任你。寫 a[n](剛好越過尾端一格)、a[-1] 或 a[1000],會讀到或覆寫屬於其他變數、記帳資料、甚至什麼都不屬於的記憶體。指標也一樣:你可以做出指向陣列尾端後一格的指標(那個精確位置可以持有,但不可解參考),但再往外、或解參考那個尾後指標,都是未定義行為。越界「讀取」可能洩漏機密資料(著名的 Heartbleed 漏洞就是這樣洩漏了私鑰);越界「寫入」可能毀掉相鄰變數、砸毀堆疊上的返回位址、或覆寫堆積上的配置器記帳資料,把程式的控制權交給攻擊者。

為什麼重要:因為沒有內建的邊界檢查,越界存取常常不會立刻當掉——它悄悄毀掉某個東西,程式跛行一陣子,直到很久之後出現令人困惑的失敗。就算當掉,那個當掉也是一個可能離真正臭蟲很遠的症狀。防禦之道既實際又值得養成習慣:把長度和陣列放在一起、用那個長度去推導迴圈邊界而不要寫死、驗證任何來自外部的索引,並用像 AddressSanitizer 這類工具執行程式,補上語言省略掉的邊界檢查。

int a[5]; /* 有效索引:0..4 */ for (int i = 0; i <= 5; i++) a[i] = i; /* a[5] 越界:未定義行為 */ /* 修正:用 < 而非 <=,並推導元素數量 */ int n = sizeof a / sizeof a[0]; /* n == 5 */ for (int i = 0; i < n; i++) a[i] = i;

用 <= 會在尾端之外多寫一格。修正版用 sizeof 推導元素數量,並以 < 停在 n-1。

sizeof 只有在陣列本身上才會給出真正長度;陣列退化為指標後(例如在函式內部),sizeof 回報的是指標大小,長度資訊已遺失。請把長度當作另一個引數傳入。

又称
out-of-bounds accessbuffer over-readbuffer over-write越界存取緩衝區越界