未定義行為與安全

信任邊界與不受信任的輸入

想像一棟大樓的櫃台。在裡面、同事之間,你假設大家出於善意;在大門口、陌生人抵達之處,你查證件、看識別證。「裡面,假定安全」與「外面,必須檢查」之間的那條線,就是信任邊界。每支程式都有一條,而很大一部分的安全性,歸結為精確知道它在哪裡、並對每一個跨越它的東西保持紀律。

信任邊界是把你掌控的程式碼與資料、和來自你不掌控之處的程式碼與資料分開的那條線——使用者選的檔案、在提示符輸入的文字、從網路抵達的位元組、命令列引數、環境變數、另一支程式傳來的請求內容。任何從外部「跨進」你程式的東西,都是不受信任的輸入:你必須假設它可能格式錯誤、過大、惡意、或被特意打造來破壞你。所有不受信任輸入能進入的地方合起來,就是你的攻擊面(attack surface)。防禦的動作是在邊界驗證——就在資料抵達之處檢查大小、範圍、格式與指標——並且只在資料通過那些檢查「之後」才把它當作受信任的。危險的錯誤是把未驗證的輸入一路帶進程式深處,直到它在離進入點很遠的地方溢出緩衝區或越界索引時,才發現它是壞的。

為什麼重要:幾乎每一場著名的安全事件,都是不受信任的輸入在沒有妥善檢查下跨越信任邊界的故事——一個過長的名字砸毀堆疊緩衝區、一個攻擊者控制的長度欄位驅動越界複製、一個使用者提供的格式字串。在 C 裡賭注最高,因為沒有自動保護:一個來自外部、未檢查的長度會變成緩衝區溢位,而緩衝區溢位會變成接管。實用的紀律是有意識地畫出邊界、為跨越它的輸入命名、並在跨越處驗證它們每一個——完整的安全強化還會走得更遠(那是第二卷的主題),但信任邊界上的正確性是它的起點,因為在這裡,安全漏洞不過是一個正確性臭蟲看得見的後果。

/* 不受信任:從網路讀來的長度欄位 */ uint32_t len = read_u32(socket); /* 攻擊者控制這個 */ if (len > MAX_PAYLOAD) return -1; /* 在邊界「就」驗證 */ char *buf = malloc(len); if (buf == NULL) return -1; read_exact(socket, buf, len); /* 到此 len 才算受信任 */

由遠端對等方提供的長度是不受信任的;就在邊界拿它和上限比對驗證,能阻止攻擊者選定的大小驅動巨大或溢位的配置。

在邊界「就」驗證輸入,趁它還沒往深處跑——一旦未驗證的輸入流進程式,之後的溢位會出現在離資料進入點很遠的地方,更難追查。完整的安全強化是第二卷主題;邊界上的正確性是它的起點。

又称
trust boundaryuntrusted inputattack surface信任邊界不受信任的輸入