Storage Proofs Done Wrong: a Case Study
三句話摘要
一個部署於多條 L2 協議的 Merkle Patricia Tree 驗證函式庫,因 for 迴圈缺少結束後的檢查,導致攻擊者可偽造任意 storage slot 為零值。 Merkle proof 驗證函式若缺少 for 迴圈後的防禦,攻擊者只需截斷有效 proof 即可偽造任意 storage slot 為零,此類語義層漏洞唯有人工審計才能可靠偵測。 Merkle Patricia Tree 是 Ethereum storage 驗證的核心:每個合約的 storage 都組織在一棵 MPT 中,其 root 透過共識隨每個區塊廣播,L2 合約可透過橋接這個 root 來驗證 L1 上任意 storage slot 的值,而不必逐一搬移資料。
重點整理
重點- 1
Merkle Patricia Tree 是 Ethereum storage 驗證的核心:每個合約的 storage 都組織在一棵 MPT 中,其 root 透過共識隨每個區塊廣播,L2 合約可透過橋接這個 root 來驗證 L1 上任意 storage slot 的值,而不必逐一搬移資料。
- 2
Exclusion proof 的語義造成漏洞:在 storage 語境中,proof of exclusion(證明某個 slot 不存在)等同於該值為零(EVM 預設值)。此函式庫的驗證函式以空 bytes 作為「exclusion」的回傳語義,使得任何回傳空值的情況都會被呼叫者解讀為「該 slot 值為零」。
- 3
Bug 本體是缺少 for 迴圈後的防禦:驗證函式預設每次迭代都會在迴圈內部 return 或 revert;開發者未考慮「proof 節點被截斷、迴圈正常跑完」的情況。截斷後迴圈結束,Solidity 隱性回傳空 bytes,觸發 exclusion 語義。
- 4
傳統自動化工具對此類漏洞束手無策:Fuzzing 無法產生「截斷有效 proof」這種惡意輸入;SMT solver 難以建模 hash function、動態長度迴圈與 assembly 記憶體操作,此漏洞只能靠人工審計發現。
實用技巧與重點
乾貨- 漏洞發現時間:2024 年 3 月
- 發現者:Curve 白帽開發者(非審計公司)
- 影響範圍:多個部署於 L2 的協議,使用同一 Merkle proof 驗證函式庫
- 可操控的目標值:oracle 參數(可扭曲資產價格)、治理投票權重(可奪取 L2 治理控制權)
- 潛在損失規模:數百萬美元等級
- 觸發方式:截斷(truncate)一個有效的 inclusion proof,使 for 迴圈遍歷完所有節點後自然退出
- Solidity 隱性回傳:函式宣告回傳 `bytes memory`,for 迴圈後無 return 語句 → 回傳空 bytes
- 空 bytes 的語義:呼叫者解讀為 exclusion proof → storage slot 值為零
- 工具侷限:Fuzzing(無法構造截斷 proof)、Mythril/Z3(難以建模 hash + 動態迴圈 + assembly)
- 修復路徑:完整走完 responsible disclosure → patch → public disclosure 流程
結論
結論“Merkle proof 驗證函式若缺少 for 迴圈後的防禦,攻擊者只需截斷有效 proof 即可偽造任意 storage slot 為零,此類語義層漏洞唯有人工審計才能可靠偵測。”
完整解析
詳細這個漏洞的根源在於一個跨 L1/L2 的效率設計模式。許多協議希望在便宜的 L2 上運行,但又需要保持 L1 上的核心帳本(如 oracle 參數、投票權重)作為可信來源。為了避免將 L1 上的每一個 storage slot 都逐一橋接,工程師採用了 Merkle Patricia Tree(MPT)的方式:只橋接一個 L1 的 state root,再讓 L2 合約透過 Merkle proof 驗證任意 L1 storage slot 的值。這個設計節省了 gas,也是 Ethereum 原生支援的驗證機制,理論上十分優雅。
然而,這個函式庫的 proof 驗證函式存在一個致命的隱患。函式的核心是一個 for 迴圈,逐節點(extension node、leaf node、branch node)走訪傳入的 proof path,並在每個分支內部以 return 或 revert 結束——要嘛成功回傳 storage 值(inclusion),要嘛回傳空 bytes(exclusion,表示該 slot 未初始化),要嘛因 proof 格式錯誤而 revert。開發者的隱含假設是:迴圈一定會在內部結束,不可能把所有節點跑完後自然退出。
這個假設是致命的。攻擊者只需要截斷一個完整有效的 inclusion proof——例如把原本 5 個節點的 proof 截成 3 個——for 迴圈就會在處理完最後一個節點後沒有觸發任何 return,讓控制流落到函式末尾。Solidity 對於宣告回傳 `bytes memory` 但沒有顯式 return 語句的情況,會隱性回傳空 bytes。而呼叫這個函式的合約,遵循函式的語義約定,將空 bytes 解讀為 exclusion proof——也就是「這個 storage slot 的值是零」。
後果直接且嚴重。如果被橋接的值是 oracle 價格參數,攻擊者可以讓協議認為任意資產的價格為零,藉此大規模操縱價格;如果被橋接的是治理投票權重,攻擊者可以將任何人的投票權清空為零,或讓自己的權重不受 L1 實際狀態約束,進而奪取 L2 的治理控制權。估計潛在損失達數百萬美元等級,且這個漏洞在生產環境中沉睡了數年。幸運的是,它最終被 Curve 的一名白帽開發者發現,並完整走過了 responsible disclosure → patch → 公開披露的正規流程。
演講者最後提出了一個值得深思的問題:這類漏洞無法被現有自動化工具捕捉。Fuzzing 難以自動產生「截斷有效 proof」這種語義上精心構造的惡意輸入;SMT solver 則受限於對 hash function、動態長度迴圈與 assembly 記憶體操作的建模困難。這個案例再次說明,對於高度複雜的密碼學原語與 EVM 底層語義交織的程式碼,人工審計目前仍是不可替代的最後防線。
關鍵時刻
Pipeline v2帶時間戳的重點,會在逐字稿層級分析上線後產生。目前請先透過原始影片觀看。
事實查核
Pipeline v2說法查證是下一次管線升級的一部分。KeyFrame 只會顯示它真正能驗證的內容。

