eBPF 驗證器(verifier)
讓一支外來程式在核心裡執行聽起來很可怕:核心上下文中一個壞掉的指標、或一個無窮迴圈,就可能凍結或弄垮整台機器。eBPF 驗證器就是讓這件事變安全的守門人。在核心願意接受任何 eBPF 程式之前,驗證器會逐條指令檢查它,並且「證明」——在程式真正執行之前、用數學的方式——它不可能做出任何危險的事。若無法證明安全,程式就被直接拒絕。
用淺白步驟說明它怎麼運作:驗證器做一種靜態分析,模擬通過程式的每一條可能路徑。它對每條指令處的每個暫存器,追蹤它可能持有的值的集合,並檢查數項性質。它證明程式必定終止——歷史上靠完全禁止迴圈,現在則要求任何迴圈都要有可驗證的上界,於是沒辦法在核心裡永遠空轉。它證明每一次記憶體存取都在界內——每一次指標解參考、每一次 map 存取,都必須可證明落在一個已知、已檢查的範圍裡,於是沒有野指標。它證明程式只呼叫核可的輔助函式並遵守它們的引數型別。它還強制一個很小的堆疊與有限的指令數,讓工作量有界。只有通過全部這些檢查的程式,才會被交給即時編譯器並掛上它的鉤點。
它之所以重要,是因為它正是 eBPF 得以開放給較低特權使用者的原因,也是正式環境工程師敢在活機器上執行它的原因:安全是用證明建立的,而不是寄望作者夠小心。誠實的提醒既真實又經常被感受到。驗證器很保守——它會拒絕許多其實安全、但它證明不出安全的程式,這就是「驗證器拒絕了我的程式」這個 eBPF 經典挫折的由來。它的證明工作量隨程式複雜度增長,所以大程式可能撞到上限。而且它防範的是記憶體安全與終止性的故障——它不保證你程式的「邏輯」正確,只保證它不能弄當或卡死核心。
// 被拒:驗證器無法證明 i 留在界內 for (int i = 0; i < n; i++) buf[i] = 0; // n 未知 -> 「無界迴圈」/「可能越界」 // 被接受:一個它能證明的編譯期上界 #pragma unroll for (int i = 0; i < 64; i++) buf[i] = 0; // 固定、可證明
驗證器接受有界、可證明的迴圈,拒絕它無法確立上界的那個——在任何執行之前,靠證明來保證安全。
一個常見誤解:被拒不代表你的程式有 bug。驗證器證明的是安全,而由於保守,它也會拒絕許多其實「安全」、只是它無法證明其安全的程式——很多 eBPF 的功夫就是把正確的程式碼改寫成驗證器跟得上的樣子。