PS:感覺前幾天好嚴肅,和朋友討論之後希望接下來能帶向較輕快的節奏~
接下來看看控制流的說法,它借自傳統軟體資安裡的Control-Flow Integrity(CFI),2005年Abadi、Budiu、Erlingsson、Ligatti在CCS提出,意思是程式的執行只會動態地依循某些路徑,這些路徑由一個靜態政策規定,在每一次間接的控制轉移(跳轉、呼叫、返回)都要落在一張事先算好的控制流圖(CFG)允許範圍內,執行期檢查目標,不合法就終止,它原本要擋的是ROP、JOP這類劫持既有程式碼片段拼湊惡意行為的攻擊,跟prompt injection沒有任何關係。
CFI原本要擋的ROP(return-oriented programming)跟JOP(jump-oriented programming),它示範了一種很聰明的攻擊思路。一般要讓程式執行攻擊者想要的行為,要注入一整段新的惡意程式碼進去執行,但現代作業系統普遍能擋掉把資料當程式碼執行這件事,讓注入新程式碼變得困難。ROP的做法是反過來,不注入任何新程式碼,而是利用程式本來就有的合法程式碼片段,通常是每個函式結尾的幾行特定內容,攻擊者透過竄改堆疊上的返回位址,硬是把這些片段串接起來,讓程式在完全沒有執行任何一行新指令的情況下,拼湊出攻擊者要的行為;JOP是同樣的概念,只是串接用的是間接跳轉指令而不是返回指令。CFI要擋的正是這種合法片段被串成非法順序的手法,跟接下來要講的agent控制流劫持,在概念上是同樣問題,就是在既有資料本身沒有變壞的情況下,被騙走樣的是原本該遵守的執行順序。
ControlValve論文把這個詞直接搬進agent領域:「control-flow integrity is a generalization of the principle of least privilege, restricting the order of execution and not just the set of available agents」,把CFI從「限制程式跳轉目標」,類比成「限制agent呼叫的順序」。對照起來:

資料來源:本文自行整理
傳統CFI的控制流圖,來自編譯後的程式二進位結構,客觀、可窮舉、由確定性的編譯流程產生。而ControlValve論文提到,情境規則是zero-shot生成的,也就是這張可信圖本身很可能是另一個LLM產生的,不是從確定性流程分析出來的。
我認為這個差異不必然讓整個類比失效,CFG本身用什麼方式產生,跟產生出來之後,有沒有一個嚴謹的把關機制去驗證它、執行期強制遵守它是兩件事,只要後面這道關卡夠硬,前面那張圖是誰畫的,重要性會降低,這個問題可以被限制在可接受範圍,前提是把關機制本身要經得起檢視,而不是假設圖一定畫對了。
程式執行時函式呼叫的返回位址本來只存在一份,寫在一般的堆疊上,這份資料跟程式其他資料混在一起,理論上可以被攻擊者用緩衝區溢位這類手法竄改,shadow stack做到讓「檢查點不能被繞過」這件事在資安裡實際落地,做法是額外開一塊獨立的記憶體,只存返回位址的副本,而且往往由硬體或作業系統直接保護,一般程式邏輯根本碰不到,函式真正要返回時系統同時檢查兩份,一般堆疊上的返回位址跟shadow stack上的副本,兩者對不上就代表被竄改過,直接中止。
這個設計的關鍵是拿來比對的基準內容是被放在攻擊者的手完全碰不到的地方,在agent世界裡由誰扮演這個角色,是接下來要面對的核心問題,我認為這正是判讀層要處理的事,而且判讀層的設計架構,會跟我們規劃中的動態權限縮減機制直接相關:檢查「這一步呼叫合不合法」和「這一步該不該有這個權限」,某種程度上是同一件事的兩個切面:合法性檢查得先過,權限才有意義;權限縮減得夠即時,合法性檢查才不會形同虛設。
前面對照的是CFI「靜態」的那一面,也就是控制流圖本身怎麼來的。但傳統CFI除了靜態分析出圖,還有一個「執行期強制」的角色,這個角色平常運作時幾乎感覺不到,只有在真的被攻擊的那一刻才會現身:程式每次要做一次間接跳轉,都得先過這道檢查,檢查本身要做到幾乎零延遲,不然沒人會想用一個讓程式變得又慢又頓的資安機制。這對agent版本的CFI是另一層提醒:判讀關卡不能只在紙上談兵地說「檢查合不合法」,還得考慮這道檢查會不會拖慢整個agent的反應速度,尤其我們的設計裡,判讀關卡本身盡量走決定性邏輯、不呼叫模型,某種程度上就是在向傳統CFI的這個「執行期要快」的要求靠攏,而不是每次都額外跑一次模型推論,把延遲堆得更高。
CaMeL用capability標記,管的是資料流,資料從哪來、能不能流到危險動作;ControlValve用控制流圖,管的是控制流,下一步能不能呼叫這個對象。我認為這兩者是互補的兩層防禦,不是同一個問題的兩種解法:資料乾淨,不代表呼叫順序沒被劫持;呼叫順序合法,不代表傳進去的資料是乾淨的。一個完整的判讀層應該兩層都要顧到。
具體例子來說,假設有一個agent系統,控制流合法(呼叫順序完全照著計畫走),但資料流不乾淨(呼叫某個工具時,傳進去的參數其實衍生自一段未受信任的內容),這種情況下,光有ControlValve式的控制流檢查完全抓不到問題,因為它只在意「這一步呼叫合不合法」,不在意「這一步用的資料乾不乾淨」。反過來説,如果資料流乾淨(每個值的來源都清清楚楚、都受信任),但控制流被劫持(呼叫順序被誘導成攻擊者想要的樣子),CaMeL的capability標記一樣抓不到,因為它檢查的是「這筆資料准不准流到這裡」,不是「這個呼叫順序合不合理」。兩種攻擊手法鑽的洞不一樣,只做其中一層防禦,另一層永遠無法確認,這也是我認為能夠操作的地方,做出一個完整的判讀關卡,兩層檢查缺一不可。
下一篇要正式進入ControlValve的原文,把它怎麼具體產生控制流圖、怎麼在多agent系統裡強制執行,查看有哪些部分能夠幫助到我們設計的框架。