之前我們發現了一個Aptos Moveevm()的嚴重漏洞,經過深入研究,我們發現了另外一個新的整數溢出漏洞,這次一的漏洞觸發過程相對更有趣一點, 下面是對這個漏洞的深入分析過程,裏面包含了很多Move語言本身的背景知識.通過本文講解相信你會對move語言有更深入的理解。
衆所周知,Move 語言在執行字節碼之前會驗證代碼單元。驗證代碼單元的過程,分爲4步。這個漏洞就出現在 reference_safety 的步驟中。
如上面代碼所示, 此模塊定義了用於驗證過程主體的引用安全性的轉移函數。其檢查包括(但不限於)驗證沒有懸空引用、對可變引用的訪問是否安全、對全局存儲引用的訪問是否安全。
下面是引用安全驗證入口函數, 它將調用analyze_function.
在analyze_function中,函數將對每一個基本塊進行驗證,那么什么是基本塊呢?
在代碼編譯領域,基本塊是一個代碼序列,除了入口之外沒有分支指令,除了出口之外沒有分支指令。
在Move 語言中, 基本塊是通過遍歷字節碼、查找所有分支指令以及循環指令序列來確定的。以下是核心代碼:
接下來,我們來分享一個move ir代碼基本塊的例子, 如下所示, 它 3 個基本塊。分別由分支指令:BrTrue,Branch,Ret確定。
參考Rust語言的思想,Move 支持兩種類型的引用類型。不可變引用&(例如&T)和可變引用&mut(例如&mut T)。你可以使用不可變 (&) 引用從結構中讀取數據,使用可變 (&mut)引用 修改它們。通過使用恰當的引用類型,有助於維護安全性以及識別讀取模塊。這樣可以讓讀者清晰地知道此方法是更改值還是僅讀取。 下面是官方Move教程中的示例:
在示例中,我們可以看到mut_ref_t是 t 的可變引用。
所以在Move 引用安全模塊中,嘗試通過以函數爲單元,掃描函數中的基本塊中的字節碼指令驗證判斷所有引用操作是否合法。
下圖顯示了驗證引用安全性的主要流程。
這裏的state是AbstractState結構體, 它包含了borrow graph 和locals ,他們共同用於確保引用函數中的引用安全性。
這裏borrow graph是用來表示局部變量引用之間關系的圖。
從上圖中可以看到, 這裏有一個pre state ,其包含locals 和borrow graph (L ,BG)。然後執行了basic block 生成一個post state (L’, BG’)。然後將前後的state合並以更新塊狀態並將該塊的後置條件傳播到後續塊。這就像 V8 turbofan中的Sea of Nodes 思想。
下面的代碼是上圖對應的主循環。首先, 執行塊代碼(如果執行指令不成功,將返回 AnalysisError)然後嘗試通過join_result是否更改,來合並pre state和post state。如果更改並且當前塊本身包含一個後向的邊指向自己(這意味着有一個循環)將跳回到循環的开頭, 在下一輪循環仍將執行此基本塊,直到post state等於pre state或因某些錯誤而中止。
因此在引用安全模塊,如何判斷 join的結果是否改變?
通過上面的代碼,我們可以通過判斷locals和borrow關系是否發生變化來判斷join結果是否發生變化。 這裏的 join_ 函數用於更新本地變量和 borrow關系圖 。
下面是join_ 函數代碼,第 6 行是初始化一個新的 locals Map 對象。 第 9 行迭代 locals 中的所有索引,如果pre state與 post state都值爲 None,則不要插入到新的 locals 映射中,如果 pre state 有值,post state爲None,則需要釋放 brow_graph id ,意味着這裏消除該值的借用關系, 反之亦然。特別的,當pre state與 post state 兩個值都存在且相同時,像第30-33行一樣將它們插入到新的map中,然後在第38行合並 borrow graph。
通過上面代碼,我們可以看到 self.iter_locals() 是locals變量的個數。 請注意,此局部變量不僅包括函數的真實局部變量,還包括參數。
在這裏我們已經覆蓋了所有與漏洞相關的代碼,你找到漏洞了嗎?
如果你沒有發現漏洞也沒關系,下面我會詳細說明漏洞觸發過程。
首先在下面的代碼中,如果參數長度添加局部長度大於 256。這似乎沒有問題?
當函數 join_() 中是 function_view.parameters().len() 和 function_view.locals().len() 組合值大於 256,由於語句 for local in self.iter_locals()中 local 是 u8類型,此時執行此語句對造成溢出。
實際上Move有校驗locals個數的過程,可惜在check bounds模塊只校驗locals,並沒有不包括參數length。
开發者似乎只檢查了 Move Modules代碼中的 locals+parameter 長度,而忽略了script。
通過上面的介紹,我們知道有一個主循環來掃描代碼塊,然後調用execute_block函數,之後會合並執行前後的state,當move代碼中存在循環,則會跳轉到代碼塊开始,再次執行基本塊。 因此,如果我們制造一個循環代碼塊並利用溢出改變塊的state,使 AbstractState 對象中的新的locals map與之前不同,當再次執行 execute_block 函數時,在分析basic block中字節碼指令序列的時候會訪問新的locals map,這時候如果指令中需要訪問的索引在新的AbstractState locals map中不存在,將導致DoS。
在審核代碼後,我發現在 reference safety模塊中,MoveLoc/CopyLoc/FreeRef 操作碼,我們可以實現這個目標。
這裏讓我們看一下文件路徑中execute_block函數調用的copy_loc函數作爲一個說明:
move/language/move-bytecode-verifier/src/reference_safety/abstract_state.rs
在第287行,代碼嘗試通過LocalIndex作爲參數獲取本地值,如果LocalIndex不存在會導致panic,想象一下當節點執行滿足上述條件代碼的時候,會導致整個節點崩潰。
下面是你可以在git裏面重現的PoC:
commit:add615b64390ea36e377e2a575f8cb91c9466844
thread 'regression_tests::reference_analysis::PoC' panicked at 'called `Option::unwrap()` on a `None` value', language/move-bytecode-verifier/src/reference_safety/abstract_state.rs:287:39 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
我們可以看到PoC中代碼塊存在一個basic block,分支指令是一個無條件分支指令,每次執行最後一條指令時,branch(0) 將跳回第一條指令,因此這個代碼塊將多次調用 execute_block 和 join 函數。
1.在第一次執行完 execute_block 函數,當這裏設置 parameters 爲SignatureIndex(0),locals爲SignatureIndex(0)會導致num_locals爲132*2=264。 所以在執行join_函數下面這行代碼之後
將會導致新的locals map長度爲8.
2.在第二次執行execute_block函數時,執行move代碼第一條指令copyloc(57),57是locals需要壓入棧的offset,但是這次locals只有長度8,offset 57不存在,所以會導致 get(57).unwrap() 函數返回 none ,最後導致panic。
以上就是這個漏洞的來龍去脈。首先這個漏洞說明沒有絕對安全的代碼, Move語言在代碼執行之前確實做了很好的靜態校驗,但是就像這個漏洞一樣,可以通過溢出漏洞完全繞過之前的邊界校驗。再者代碼審計很重要,程序員難免會疏忽。作爲Move語言安全研究的領導者,我們將繼續深挖Move的安全問題。第三點,對於Move語言,我們建議語言設計者在move運行時增加更多的檢查代碼,以防止意外情況的發生。目前move語言主要是在verify階段進行一系列的安全檢查,但我覺得這還不夠,一旦驗證被繞過,運行階段沒有過多的安全加固,將導致危害進一步加深,引發更嚴重的問題。最後,我們還發現了Move語言的另一個漏洞,後續會繼續分享給大家。
https://github.com/move-language/move
https://github.com/MystenLabs/awesome-move
https://move-language.github.io/move/
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播信息之目的,不構成任何投資建議,如有侵權行為,請第一時間聯絡我們修改或刪除,多謝。
標題:Numen Cyber獨家發現move語言又一高危漏洞
地址:https://www.torrentbusiness.com/article/20964.html
標籤: