search

Rust-ot a Pixel Baseband-ba hozza

person Szerző Edward Fernandez
source Forrás: Google Online Security Blog
calendar_today
schedule 8 perc olvasás

O Google folyamatosan fejleszti a Pixel eszközök biztonságát. Koncentráltunk arra, hogy a mobil alapbázismodemet támadások ellen védjük. Tudva a komplex modemszoftverrel járó kockázatokat, a Pixel 9 a memóriabiztonságra összpontosító intézkedésekkel szállt. A Pixel 10-hez, a Google tovább folytatja a proaktív biztonsági intézkedések fejlesztését. Az “Deploying Rust in Existing Firmware Codebases” című korábbi megbeszélésünk alapján, ez a bejegyzés egy konkrét alkalmazást mutat be: egy memóriabiztonságos Rust DNS (Domain Name System) parszert a modemszoftverbe integrálva. Az új Rust-alapú DNS parszer jelentősen csökkenti a kockázatunkat, miközben egy kockázatos területen teljes körűen lefedezve, a memóriabiztonságos kód szélesebb körű alkalmazását is megalapozza. Itt osztjuk meg tapasztalatainkat, és reméljük, hogy inspirálja mások a biztonságosabb nyelvek használatát a mély réteken. Miért a modemszoftver biztonsága fontos? Az elmúlt években egyre nagyobb figyelmet kapott a mobilmodem, támadók és biztonsági kutatók részéről. Például a Google’s Project Zero távoli kódvégrehajtást lehetővé tett a Pixel modemeken az Interneten keresztül. A Pixel modem több tucat megabyte-nyi futtatható kódot tartalmaz. Mivel a modem komplex és távoli támadásra érzékeny, a memóriabiztonságra vonatkozó további kritikus veszélyek is létezhetnek a převládően memóriabiztonságra vonatkozó firmware-ben. Miért a DNS? A DNS protokoll a legtöbbször a böngészők weboldalok megtalálásával kapcsolatban ismert. A mobiltechnológia fejlődésével, a modern mobilkommunikáció digitális adatrendszerekre került; következésképpen, alapvető műveletek, mint például a hívás továbbítás, is a DNS szolgáltatásokat használják. A DNS egy összetett protokoll, amelyhez megbízható adatok elemzése szükséges, ami különösen veszélyes, ha egy memóriabiztonságos nyelven (pl. CVE-2024-27227) implementálják. A DNS parszer implementálása Rustban értéket nyújt, csökkentve a memóriabiztonság hiányából eredő támadáskeresztényeket. Rust-ban egy DNS könyvtárat kiválasztani, a Rust közösség már támogatja. Több nyílt forrású könyvtárat értékelünk, amelyek implementálják a DNS-t. Az eddigi bejegyzésekben megosztott kritériumok alapján, a hickory-proto-t választottuk. Excellent maintenance, over 75% test coverage, and widespread adoption in the Rust community. Its pervasiveness shows its potential as the de-facto DNS choice and long term support. Bár a hickory-proto kezdetben nem támogatta a no_std támogatást, ami a Bare-metal környezetekhez szükséges (lásd a korábbi bejegyzésünk), sikerült ezt megoldanunk. No_std támogatás hozzáadása A hickory-proto-hoz a no_std támogatás hozzáadása egy nagyrészt mechanikus feladat. A folyamatot egy korábbi bejegyzésben osztottam meg. A hickory_proto és a függő könyveinek módosításával hoztuk meg a no_std támogatást. A upstream no_std munka is no_std URL parser-t eredményezett, ami más projektek számára is hasznos. https://github.com/hickory-dns/hickory-dns/pull/2104 https://github.com/servo/rust-url/pull/831 https://github.com/krisprice/ipnet/pull/58 Az fenti PR-k jó példákat mutatnak arra, hogyan bővíthető a no_std támogatást a meglévő std-only crate-okra. Code size study Kód mérete a DNS könyvtár kiválasztásakor egy fontos tényező. Code size by category Rust implemented Shim that calls Hickory-proto on receiving a DNS response 4KB core, alloc, compiler_builtins (reusable, one-time cost) 17KB Hickory-proto library and dependencies 350KB Sum 371KB We built prototypes and measured size with size-optimized settings. Expectedly, hickory-proto is not designed with embedded use in mind, and is not optimized for size. As the Pixel modem is not tightly memory constrained, we prioritized community support and code quality, leaving code size optimizations as future work. However, the additional code size may be a blocker for other embedded systems. This could be addressed in the future by adding additional feature flags to conditionally compile only required functionality. Implementing this modularity would be a valuable future work. Hook-up Rust to modem firmware Before building the Rust DNS library, we defined several Rust unit tests to cover basic arithmetic, dynamic allocations, and FFI to verify the integration of Rust with the existing modem firmware code base. Compile Rust code to staticlib While using cargo is the default choice for compilation in the Rust ecosystem, it presents challenges when integrating it into existing build systems. We evaluated two options: Using cargo to build a staticlib before the modem builds. Then add the produced staticlib into the linking step. Directly work with rustc and integrate the Rust compilation steps into the existing modem build system. Option #1 does not scale if we are going to add more Rust components in the future, as linking multiple staticlibs may cause duplicated symbol errors. We chose option #2 as it scales more easily and allows tighter integration into our existing build system. Our existing C/C++ codebase uses Pigweed to drive the primary build system. Pigweed supports Rust targets (example) with direct calls to rustc through rust tools defined in GN. We compiled all the Rust crates, including hickory-proto, its dependencies, and core, compiler_builtin, alloc, to rlib. Then, we created a staticlib target with a single lib.rs file which references all the rlib crates using extern crate keywords. Build core, alloc, and compiler_builtins Android’s Rust Toolchain distributes source code of core, alloc, and compiler_builtins, and we leveraged this for the modem. They can be included to the build graph by adding a GN target with crate_root pointing to the root lib.rs of each crate. Pixel modem firmware already has a well-tested and specialized global memory allocation system to support some dynamic memory allocations. alloc support was added by implementing the GlobalAlloc with FFI calls to the allocators C APIs: use core::alloc::{GlobalAlloc, Layout}; extern C { fn mem_malloc(size: usize, alignment: usize) -> *mut u8; fn mem_free(ptr: *mut u8, alignment: usize); } struct MemAllocator; unsafe impl GlobalAlloc for MemAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { mem_malloc(layout.size(), layout.align()) } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { mem_free(ptr, layout.align()); } } #[global_allocator] static ALLOCATOR: MemAllocator = MemAllocator; Pixel modem firmware already implements a backend for the Pigweed crash facade as the global crash handler. Exposing it into Rust panic_handler through FFI unifies the crash handling for both Rust and C/C++ code. #![no_std] use core::panic::PanicInfo; extern C { pub fn PwCrashBackend(sigature: *const i8, file_name: *const i8, line: u32); } #[panic_handler] fn panic(panic_info: &PanicInfo) -> ! { let mut filename = ; let mut line_number: u32 = 0; if let Some(location) = panic_info.location() { filename = location.file(); line_number = location.line(); } let mut cstr_buffer = [0u8; 128]; // Never writes to the last byte to make sure cstr_buffer is always zero // terminated. let (_, writer) = cstr_buffer.split_last_mut().unwrap(); for (place, ch) in writer.iter_mut().zip(filename.bytes()) { *place = ch; } unsafe { PwCrashBackend( Rust panic\0.as_ptr() as *const i8, cstr_buffer.as_ptr() as const i8, line_number, ); } loop {} } Link Rust staticlib The Pixel modem firmware linking has a step that calls the linker to link all the objects generated from C/C++ code. By using llvm-ar -x to extract object files from the Rust combined staticlib and supplying them to the linker, the Rust code appears in the final modem image. There was a performance issue we experienced due to weak symbols during linking. The inclusion of Rust core and compiler_builtin caused unexpected power and performance regressions on various tests. Upon analysis, we realized that modem optimized implementations of memset and memcpy provided by the modem firmware are accidentally replaced by those defined in compiler_builtin. It seems to happen because both compiler_builtin crate and the existing codebase defines symbols as weak, linker has no way to figure out which one is weaker. We fixed the regression by stripping the compiler_builtin crate before linking using a one line shell script. llvm-ar -t | grep compiler_builtins | xargs llvm-ar -d Integrating hickory-proto Expose Rust API and calling back to C++ For the DNS parser, we declared the DNS response parsing API in C and then implemented the same API in Rust. int32_t process_dns_response(uint8_t, int32_t); The Rust function returns an integer standing for the error code. The received DNS answers in the DNS response are required to be updated to in-memory data structures that are coupled with the original C implementation, therefore, we use existing C functions to do it. The existing C functions are dispatched from the Rust implementation. pub unsafe extern C fn process_dns_response( dns_response: *const u8, response_len: i32, ) -> i32 { //… validate inputs dns_response and response_len. // SAFETY: // It is safe because dns_response is null checked above. response_len // is passed in, safe as long as it is set correctly by vendor code. match process_response(unsafe { slice::from_raw_parts(dns_response, response_len) }) { Ok(()) => 0, Err(err) => err.into(), } } fn process_response(response: &[u8]) -> Result<()> { let response = hickory_proto::op::Message::from_bytes(response)?; let response = hickory_proto::xfer::DnsResponse::from_message(response)?; for answer in response.answers() { match answer.record_type() { hickory_proto::RecordType:… => { // SAFETY: // It is safe because the callback function does not store // reference of the inputs or their members. unsafe { callback_to_c_function(…)?; } // … more match arms omitted. } } } Ok(()) } In our case, the DNS responding parsing function API is simple enough for us to hand write, while the callbacks back to C functions for handling the response have complex data type conversions. Therefore, we leveraged bindgen to generate FFI code for the callbacks. Build third-party crates Even with all features disabled, hickory-proto introduces more than 30 dependent crates. Manually written build rules are difficult to ensure correctness and scale poorly when upgrading dependencies into new versions. Fuchsia has developed cargo-gnaw to support building their third party Rust crates. Cargo-gnaw works by invoking cargo metadata to resolve dependencies, then parse and generate GN build rules. This ensures correctness and ease of maintenance. Conclusion The Pixel 10 series of phones marks a pivotal moment, being the first Pixel device to integrate a memory-safe language into its modem. While replacing one piece of risky attack surface is itself valuable, this project lays the foundation for future integration of memory-safe parsers and code into the cellular baseband, ensuring the baseband’s security posture will continue to improve as development continues. Special thanks to Armando Montanez, Bjorn Mellem, Boky Chen, Cheng-Yu Tsai, Dominik Maier, Erik Gilling, Ever Rosales, Hungyen Weng, Ivan Lozano, James Farrell, Jeffrey Vander Stoep, Jiacheng Lu, Jingjing Bu, Min Xu, Murphy Stein, Ray Weng, Shawn Yang, Sherk Chung, Stephan Chen, Stephen Hines.

Kapcsolódó cikkek

cybersecurity

Új dél-korei kampány hamis programozási interjúkat használ a fejlesztők adatai ellopásához

Koreai-ligához tartozó hackerek a SVG zászló képeken rejtették el a rosszindulatú szoftvert, hogy a fejlesztővizsgálatok során a programozási feladatokat is megvizsgálhassák. Egyetlen antivírus-szolgáltató sem talált rá.

cybersecurity

Az Abbott két számítógépes incidenset vizsgál meg, miután kártételezési vádak merültek fel.

Az Abbott Laboratories két különálló számítógépes biztonsági incidenset vizsgál, miután megerősítette, hogy a Cancer Diagnostics üzletágában található belső Exact Sciences rendszerekhez történt jogosulatlan hozzáférést. Emellett egy másik vádot is vizsgál, amely szerint támadók megsebeztek a LabCentral portált, és vállalatadatokat elloptak.

cybersecurity

A HollowByte-nak talált DDoS-es hibát az OpenSSL szerveren, 11 bájtos payloadokkal.

Egy biztonsági rést, melyet HollowByte-nak neveztek, segítségével hitelesítés nélküli támadók az OpenSSL szervereken elindíthatnak egy szolgáltatási elutasítás (DoS) állapotot, csak 11 bájtnyi kártékony terheléssel.