摘要
這份報告探討將 Rust 程式語言整合到 Pixel 基頻晶片韌體中的過程,重點在於 Rust 在提升記憶體安全性與整體系統安全所扮演的角色。分析內容深入技術動機、實作挑戰以及 Google 採用的解決方案,特別是有關開發使用 Rust 語言的 DNS 解析器(parser)。報告也從原始 Google 安全部落格文章及 Rust 在嵌入式安全中角色的補充研究中汲取見解,探討在低階嵌入式環境中使用記憶體安全語言的更廣泛影響。
1. 簡介
行動裝置中,基頻晶片 (cellular baseband modem) 的安全性至關重要,因為其功能複雜且暴露於遠端攻擊面。過去,這些元件主要使用 C 與 C++ 等非記憶體安全的語言開發,這類語言容易產生緩衝區溢位與釋放後使用 (use-after-free) 等漏洞。Google 意識到這些固有風險,已開始透過整合記憶體安全語言來強化 Pixel 基頻晶片。其中一個重要進展是針對關鍵元件採用 Rust,例如為 Pixel 10 開發使用 Rust 語言的 DNS 解析器。這項舉措目的在減少一整類的記憶體安全漏洞,並為在嵌入式韌體中更廣泛採用記憶體安全程式碼奠定基礎 [1] 。
2. 基頻晶片記憶體安全的迫切性
基頻晶片是非常複雜的系統,通常包含數十 MB 的可執行程式碼。其遠端攻擊面使其成為攻擊者與安全研究人員的目標。例如,Google 的 Project Zero 曾展示透過網際網路在 Pixel 基頻晶片上達成遠端程式碼執行,凸顯潛在漏洞的嚴重性 [1] 。由於韌體開發中普遍使用非記憶體安全的語言,即使持續投入安全相關工作,仍可能存在關鍵的記憶體安全漏洞。這些漏洞可能導致從阻斷服務 (denial-of-service) 到裝置完全遭入侵等後果,顯示在韌體層級需要更強健的安全措施。
3. DNS 作為關鍵攻擊面
DNS 協定雖然常與網頁瀏覽聯想在一起,但在現代行動通訊中扮演基礎角色。即使是來電轉接等基本操作也依賴 DNS 服務。DNS 本質上相當複雜,且涉及解析不受信任的資料,因此其實作特別容易出現漏洞,尤其是使用非記憶體安全語言開發時。一個顯著的例子是 CVE-2024-27227,這是一個可能由這類實作引發的 DNS 相關漏洞 [1] 。Google 透過在 Rust 中實作 DNS 解析器,目的在顯著減少此關鍵元件中與記憶體非安全性相關的攻擊面。
4. Rust 如何強化嵌入式安全
Rust 對嵌入式系統提供了極具吸引力的優勢,因為它在不犧牲效能的前提下,強烈強調記憶體安全。其所有權(Ownership)與借用系統(Borrowing system)由編譯器強制執行,可在編譯時期防止緩衝區溢位、釋放後使用及雙重釋放 (double-free) 等常見記憶體錯誤。這種確定性的記憶體管理對於即時與安全關鍵的嵌入式環境至關重要。此外,Rust 明確的錯誤處理機制鼓勵開發者主動處理潛在的失敗案例。在典型用於裸機嵌入式系統的
no_std
環境中,Rust 的依賴範圍明顯小於 C 或 C++ 的對應方案
[2]
。
然而,記憶體安全並不等同於系統安全。即使使用記憶體安全的語言,嵌入式裝置仍可能因邏輯錯誤、對硬體行為的錯誤假設、不安全的抽象層以及可信邊界中的缺陷而存在漏洞,這些是編譯器無法預防的。Rust 可以降低風險,但並未消除對更具客製化、更針對性的測試需求 [2] 。
儘管 Rust 能大幅減少與記憶體相關的漏洞,但並未消除所有安全風險。邏輯錯誤、對硬體互動的錯誤假設以及外部函式介面 (Foreign Function Interface, FFI) 中的缺陷仍可能引入漏洞。因此,即使對於使用 Rust 語言的嵌入式系統,仍需要全面的安全測試(包括針對邏輯缺陷的滲透測試) [2] 。
5. 將 Rust 整合至 Pixel 基頻韌體
將 Rust 整合到現有的 C/C++ 韌體程式碼庫中會面臨一些技術挑戰,特別是在像 Pixel 基頻晶片這樣的裸機環境。Google 的方法包括處理
no_std
支援、建置系統整合以及外部函式介面 (FFI) 機制。
5.1. 針對裸機環境的
no_std
支援
對於沒有作業系統的嵌入式系統,
no_std
支援至關重要。Google 團隊對所選的 DNS 函式庫
hickory-proto
及其外部套件(Dependencies )進行修改,以啟用
no_std
相容性。這項工作主要是機械式的,涉及調整函式庫使其能在沒有標準函式庫(通常在裸機環境中無法使用)的情況下運作
[1]
。這項努力也貢獻給上游專案,提供一個對其他嵌入式 Rust 專案有幫助的
no_std
URL 解析器。
5.2. 建置系統整合
將 Rust 編譯整合到現有的 C/C++ 建置系統(例如 Pixel 基頻韌體使用的、由 Pigweed 驅動的系統)需要仔細評估。主要考慮了兩種選項:
-
在建置基頻韌體之前,使用
cargo建置staticlib並將其連結。 -
將
rustc直接整合到現有的建置系統中。
由於連結多個靜態函式庫時可能產生重複符號的錯誤,選項一被認為不具可擴充性。因此,Google 選擇了選項二,將 Rust 編譯步驟直接整合到 Pigweed 驅動的建置系統中,該系統透過 GN 中定義的 Rust 工具,直接呼叫
rustc
來支援 Rust 目標
[1]
。所有的 Rust 套件 crate(包括
hickory-proto
與核心元件)都被編譯成
rlib
檔案,然後透過
extern crate
關鍵字,由單一
staticlib
目標參照這些檔案。
5.3. 自訂記憶體分配與當機處理
Pixel 基頻韌體使用專門的全域記憶體分配系統。為了整合 Rust,Google 實作了
GlobalAlloc
trait,並透過 FFI 呼叫現有的 C 分配器。這確保 Rust 的動態記憶體分配能由韌體既有的記憶體管理系統處理。
- use core::alloc::{GlobalAlloc, Layout};
- extern "C" {
- // C function for memory allocation
- fn mem_malloc(size: usize, alignment: usize) -> *mut u8;
- // C function for memory deallocation
- fn mem_free(ptr: *mut u8, alignment: usize);
- }
- // Custom allocator struct
- struct MemAllocator;
- // Implement GlobalAlloc trait for MemAllocator
- unsafe impl GlobalAlloc for MemAllocator {
- // Allocate memory using the C mem_malloc function
- unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
- mem_malloc(layout.size(), layout.align())
- }
- // Deallocate memory using the C mem_free function
- unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
- mem_free(ptr, layout.align());
- }
- }
- // Register MemAllocator as the global allocator
- #[global_allocator]
- static ALLOCATOR: MemAllocator = MemAllocator;
類似地,在當機處理(Crash handling)方面,Pixel 基頻韌體使用 Pigweed 的 crash facade。為了統一 Rust 與 C/C++ 程式碼之間的當機處理,Rust 的
panic_handler
透過 FFI 暴露給現有的 C crash backend。
- #![no_std]
- use core::panic::PanicInfo;
- extern "C" {
- // C function for crash handling backend
- pub fn PwCrashBackend(signature: *const i8, file_name: *const i8, line: u32);
- }
- // Custom panic handler for Rust
- #[panic_handler]
- fn panic(panic_info: &PanicInfo) -> ! {
- let mut filename = "";
- let mut line_number: u32 = 0;
- // Extract file name and line number from panic info
- if let Some(location) = panic_info.location() {
- filename = location.file();
- line_number = location.line();
- }
- // Convert filename to C-compatible string (simplified for example)
- // In a real scenario, this would involve careful string conversion and safety checks.
- let c_filename = filename.as_ptr() as *const i8;
- // Call the C crash backend
- unsafe {
- PwCrashBackend(b"Rust Panic\0".as_ptr() as *const i8, c_filename, line_number);
- }
- // Loop indefinitely after panic
- loop {}
- }
這些 FFI 實作對於實現 Rust 與現有 C/C++ 程式碼庫之間的無縫互通至關重要,讓 Rust 能夠利用既有的韌體服務,同時維持其記憶體安全保證。
6. 程式碼大小分析
程式碼大小是嵌入式開發中的一個重要因素。雖然
hickory-proto
最初並未針對嵌入式使用進行最佳化,但 Pixel 基頻晶片的記憶體限制尚未緊迫到成為障礙。Rust 實作的墊片(shim)、核心元件以及
hickory-proto
與其外部套件的總大小約為 371KB
[1]
。其中包含可重複使用的 17KB(用於 core、alloc 和 compiler_builtins)以及 350KB(用於
hickory-proto
函式庫及其外部套件)。未來可透過加入條件編譯的功能旗標,來減少在記憶體更受限系統上的佔用空間。
7. Rust 整合的架構概觀
Rust 整合到 Pixel 基頻晶片的過程可以視為一個分層架構,其中 Rust DNS 解析器在既有的韌體環境中運作,並透過 FFI 與 C/C++ 元件溝通。
Modem Firmware] --> B{C/C++ Core Logic} B --> C["Foreign Function Interface
(FFI)"] C --> D["Rust DNS Parser
(hickory-proto)"] D --> E["Rust Core Libraries
(no_std)"] E --> F[Custom Global Allocator] E --> G[Panic Handler] F --> B G --> B H[Untrusted Network Data] --> D D --> I[DNS Resolution Service] subgraph Rust Components D E F G end subgraph C/C++ Components A B C I end style A fill:#f9f,stroke:#333,stroke-width:2px style B fill:#bbf,stroke:#333,stroke-width:2px style C fill:#fcf,stroke:#333,stroke-width:2px style D fill:#ccf,stroke:#333,stroke-width:2px style E fill:#eef,stroke:#333,stroke-width:2px style F fill:#ddf,stroke:#333,stroke-width:2px style G fill:#ddf,stroke:#333,stroke-width:2px style H fill:#fcc,stroke:#333,stroke-width:2px style I fill:#bbf,stroke:#333,stroke-width:2px
圖 1:Rust DNS 解析器整合至 Pixel 基頻韌體的簡化架構。
此圖說明了 Rust DNS 解析器及其核心函式庫與自訂處理程式,如何透過 FFI 層與現有的 C/C++ 核心邏輯互動。不受信任的網路資料由記憶體安全的 Rust 元件處理,然後將 DNS 解析服務提供回 C/C++ 韌體。
8. 結論
將 Rust 整合到 Pixel 基頻韌體中,代表了行動裝置安全性的重大進展。透過運用 Rust 固有的記憶體安全保證,Google 主動減輕了高度敏感元件中一個關鍵類別的漏洞。這項技術歷程涉及克服
no_std
環境、建置系統整合以及透過 FFI 與現有 C/C++ 程式碼無縫互通等挑戰。雖然 Rust 為安全的嵌入式系統提供了強固的基礎,但仍需認識到全面的安全性仍需要對基於邏輯的缺陷保持警惕並持續測試。這項計畫不僅增強了 Pixel 裝置的安全性,也為業界在低階韌體開發中更廣泛採用記憶體安全語言樹立了先例。