Rust 大步跨入 Android 平臺
作者 | 萬佳、王強
Rust 越來越流行,開始大步跨入 Android 世界!
1 內存安全錯誤
目前,C 和 C++ 的內存安全錯誤仍然是不正確性來源中最難解決的問題。
谷歌指出,內存安全錯誤是穩定性問題的罪魁禍首,它在 Android 嚴重安全漏洞中長期佔據大約 70% 的比例。
Android 操作系統廣泛使用 Java,以有效保護 Android 平臺的大部分內容免受內存錯誤的影響。但不幸的是,系統中比較低級的層是不能選擇 Java 和 Kotlin 的。
OS 中的較低級別需要的是系統編程語言,如 C、C++ 和 Rust 等。這些語言設計時考慮到了可控和可預測性的目標。它們提供對低級系統資源和硬件的訪問。它們的資源需求相對輕量,並具有更可預測的性能特徵。
對於 C 和 C++,開發人員負責管理內存的生命週期。但是,這樣做很容易出錯,尤其是在複雜的多線程代碼庫中更是如此。
Rust 使用編譯時檢查(強制執行對象生命週期 / 所有權)和運行時檢查(確保內存訪問有效)的組合來提供內存安全保證。提供這種安全性的同時,Rust 的性能表現足以匹敵 C 和 C++。
谷歌表示,“除了正在進行和即將到來的改進內存錯誤檢測的措施外,我們還在進一步努力設法防患於未然。內存安全語言是預防內存錯誤的成本效益最優的手段。”
2 沙箱的侷限性
谷歌披露,所有 Android 進程均已沙箱化。它們通過遵循三選二規則(Rule of 2)來確定功能是否需要額外的隔離和特權。這種規則很簡單:給定三個選項,開發人員只能選擇以下三個選項中的兩個。
對 Android 來說,這意味着如果代碼是用 C/C++ 編寫並解析了不可靠的輸入,則應將其包含在一個嚴格受限和無特權的沙箱中。
遵守三選二規則的好處是可以有效降低安全漏洞的嚴重性和暴露程度,但是它也存在一些侷限:
-
沙箱的成本很高。它需要的新進程會消費額外的開銷並引入延遲,這是由於 IPC 和額外的內存佔用導致的。
-
沙箱無法消除代碼中的漏洞。高漏洞密度會降低其有效性,讓攻擊者可以將多個漏洞鏈接在一起。
像 Rust 這樣的內存安全語言可以通過兩種方式來克服這些限制:
-
降低代碼中錯誤的密度,從而提高當前沙箱的效率。
-
減少谷歌對沙箱的需求,從而引入更安全、更省資源的新特性。
3 近 50% 的錯誤出現短於一年
根據分析,大多數內存錯誤出現在新的或最近修改的代碼中,大約有 50%錯誤出現還不到一年。
因此,谷歌表示,“我們在內存安全語言方面的工作最好專注於新開發的代碼,而不是重寫成熟的 C/C++ 代碼。並且,引入一種新的編程語言並不能解決我們現有 C/C++ 代碼中的錯誤。即便我們重新分配 Android 團隊中所有軟件工程師的工作,要重寫幾千萬行代碼也是完全不可行的。“
相比之下,較舊的內存錯誤很少見,很多人可能會覺得很驚訝。谷歌還發現,較舊的代碼並不是亟需改進的。"隨着時間的推移,軟件錯誤會被逐漸發現和修復,因此我們可以預期還在維護但未處於活躍開發狀態的代碼中的錯誤量會逐漸減少"。
谷歌還指出,對於複雜的 C/C++ 代碼庫來說,通常只有少數人能夠開發和審查錯誤修復,而且即使花費大量精力來修復錯誤,有時修復本身也會是不正確的。
當錯誤相對少見且危險的錯誤得到優先關注時,錯誤檢測纔是最有效的。爲了獲得從錯誤檢測中獲得的改進所帶來的好處,谷歌稱 “首先防止引入新的錯誤”。
4 預防爲主
Rust 語言實現了一系列現代化的語言特性,從而提高了代碼的正確性:
-
內存安全性:搭配編譯器和運行時檢查來增強內存安全性。
-
數據併發:防止數據爭用。它讓用戶能輕鬆編寫高效、線程安全的代碼,也讓 Rust 打出了 “無畏併發” 的口號。
-
更具表現力的類型系統:幫助防止邏輯編程錯誤(例如 newtype 包裝器、帶有內容的 enum 變體)。
-
引用和變量是默認不可變的:幫助開發人員遵循最小特權的安全原則,僅在實際需要時纔將引用或變量標記爲可變。C++ 有常量,而且傾向於偶爾且不一致地使用常量。相比之下,Rust 編譯器會爲永不突變的可變值提供警告,來幫助避免散亂的可變性註釋。
-
標準庫中更好的錯誤處理:將 Result 中可能失敗的調用包裝起來,於是編譯器會要求用戶檢查失敗,即使是未返回所需值的函數也是如此。這樣就避免了 RageAgainstCage 之類的漏洞,這種漏洞是來源於未處理錯誤的。Rust 簡化了通過? 運算符傳播錯誤的過程,並優化了 Result 以降低開銷,從而鼓勵用戶以相同的樣式編寫易錯的函數並獲得相同的保護。
-
初始化:要求所有變量在使用前都初始化。未初始化的內存漏洞一直是 Android 上 3-5%的安全漏洞的源頭。在 Android11 中,谷歌開始在 C/C++ 中自動初始化內存以減少這種問題。然而,初始化到零並不總是安全的,尤其是對於返回值這樣的事物,因爲它可能成爲新的錯誤不當處理來源。而 Rust 要求每個變量在使用前都要初始化爲該類型的合法成員,避免了無意初始化爲不安全值的問題。類似 Clang for C/C++,Rust 編譯器意識到了初始化的要求,並避免了雙重初始化的所有潛在性能開銷。
-
更安全的整數處理:默認情況下,Rust 調試版本會啓用溢出清理功能。如果程序員確實打算讓一個計算溢出,則鼓勵他們指定一個 wrap_add;否則,則指定一個 saturating_add。谷歌打算爲 Android 的所有版本啓用溢出清理功能。此外,所有整數類型轉換都是顯式轉換:在分配給變量或嘗試對其他類型進行算術運算時,開發人員不會在函數調用期間意外地轉換。
5 未來
據悉,在過去 18 個月中,谷歌一直在向 Android 開源項目增加對 Rust 的支持。不過,它也坦言,” 在 Android 平臺上添加新語言是一項艱鉅的任務。不僅需要維護很多工具鏈和依賴項,更新測試基礎架構和工具鏈,還要培訓開發人員 “。
谷歌稱,未來幾年計劃將 Rust 擴展到 OS 的更多部分。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/x9EaOujAlnNoLpvFGo5m-g