Android chuyển từ C/C++ sang Rust như thế nào, và những con số thật
Tóm tắt
Android không viết lại C/C++ cũ mà viết code mới bằng Rust, nối hai bên bằng bindgen/cxx/AIDL. Lỗi bộ nhớ giảm từ 76% (2019) xuống dưới 20% (2025), mật độ lỗi thấp hơn hơn 1000 lần, rollback ít hơn 4 lần và review nhanh hơn 25%.
Năm 2019, 76% lỗ hổng trong bản tin bảo mật Android là lỗi an toàn bộ nhớ: tràn bộ đệm, use-after-free, đọc vùng nhớ chưa khởi tạo. Đến 2025, tỷ lệ này xuống dưới 20%. Google không đạt được điều đó bằng cách viết lại hàng chục triệu dòng C/C++. Họ đổi cách viết code mới: code mới viết bằng ngôn ngữ an toàn bộ nhớ, chủ yếu là Rust, còn code cũ được để yên, được gia cố và tự "già" đi. Bài này đi qua từng bước kỹ thuật họ đã làm (build, interop, chọn thành phần, firmware, đào tạo, đo lường) kèm sơ đồ kiến trúc và số liệu công bố chính thức.
Bài toán ban đầu
- Lỗi bộ nhớ chiếm phần lớn lỗ hổng nghiêm trọng: năm 2021, Google cho biết loại lỗi này luôn chiếm khoảng 70% lỗ hổng mức cao của Android. Năm 2022, chúng vẫn chiếm 86% lỗ hổng mức critical và 89% lỗ hổng khai thác từ xa.
- Sandbox có giới hạn: cô lập tiến trình tốn RAM và gây độ trễ IPC. Nó không xoá được lỗi, và khi mật độ lỗi cao, kẻ tấn công vẫn xâu chuỗi nhiều lỗi để thoát ra.
- Fuzzing và sanitizer chỉ bắt được triệu chứng: chúng chỉ thấy lỗi khi trạng thái lỗi thật sự xảy ra. Lỗi được tìm ra nhanh hơn tốc độ sửa, và có bản vá lại sai.
- Viết lại toàn bộ là bất khả thi: theo nhóm Android, viết lại hàng chục triệu dòng code là không làm được, kể cả khi huy động cả đội.
Nguyên lý cốt lõi: lỗ hổng nằm ở code mới
Dữ liệu từ Android và Chromium cho thấy lỗ hổng "phân rã" theo hàm mũ, giống chu kỳ bán rã. Khoảng 50% lỗi bộ nhớ nằm trong code chưa tới 1 năm tuổi (số liệu năm 2021). Code 5 năm tuổi có mật độ lỗ hổng thấp hơn 3,4 đến 7,4 lần so với code mới (theo bài Google Security năm 2024).
Từ đó rút ra chiến lược "Safe Coding": ngừng tạo thêm code không an toàn ở chỗ quan trọng, thay vì viết lại code cũ. Mô phỏng của Google dùng các giả định: codebase gấp đôi sau mỗi 6 năm, tuổi thọ trung bình của lỗ hổng 2,5 năm, việc chuyển code mới sang ngôn ngữ an toàn kéo dài 10 năm. Kết quả mô phỏng: tổng số lỗ hổng vẫn giảm mạnh, dù lượng code không an toàn vẫn tăng.
| Thế hệ | Cách làm | Hạn chế |
|---|---|---|
| 1. Vá sau khi lộ | Chờ lỗi xuất hiện rồi sửa | Tốn kém liên tục, người dùng bị phơi nhiễm |
| 2. Giảm thiểu khai thác | Stack canary, CFI, sandbox | Tốn hiệu năng, chạy đua vũ trang với kẻ tấn công |
| 3. Chủ động tìm lỗi | Sanitizer, fuzzing | Chỉ chữa triệu chứng, độ phủ thấp |
| 4. Safe Coding | Ngôn ngữ an toàn bộ nhớ, thiết kế API an toàn | Cần đầu tư ban đầu cho công cụ và đào tạo |
Lộ trình triển khai
| Thời điểm | Cột mốc |
|---|---|
| ~2019 | Bắt đầu ưu tiên ngôn ngữ an toàn bộ nhớ (Java/Kotlin, Rust) cho phát triển mới |
| 2021 (Android 12) | AOSP chính thức hỗ trợ Rust sau khoảng 18 tháng chuẩn bị; AIDL có backend Rust |
| 2022 (Android 13) | Lần đầu phần lớn code mới viết bằng ngôn ngữ an toàn bộ nhớ; khoảng 21% code native mới là Rust; khoảng 1,5 triệu dòng Rust trong AOSP |
| 2023 (Android 14) | Firmware máy ảo được bảo vệ (pvmfw) viết lại bằng Rust thay cho U-Boot; Trusty TEE hỗ trợ Trusted App viết bằng Rust |
| 2024 | Lỗi bộ nhớ còn 24% tổng lỗ hổng; tỷ lệ rollback của thay đổi Rust chưa bằng một nửa C++ |
| 2025 | Khoảng 5 triệu dòng Rust; lượng Rust mới thêm vào (trừ phần xoá) nhỉnh hơn C++ trong 3 quý đầu năm; kernel 6.12 của Android có driver Rust đầu tiên chạy production |
Kiến trúc: Rust nằm ở đâu trong Android?
Mọi tầng phía trên Framework vốn đã an toàn bộ nhớ nhờ Java/Kotlin. Rủi ro tập trung ở tầng native: dịch vụ hệ thống, HAL, kernel và firmware. Vì vậy Rust được đưa vào đúng những tầng này. Các thành phần giao tiếp với nhau qua Binder/AIDL, nên một dịch vụ viết bằng Rust có thể thay một dịch vụ C++ mà không phải đổi bên gọi.
Cách làm chi tiết, từng bước
Bước 1: Đưa Rust vào hệ thống build (khoảng 18 tháng)
- Soong gọi thẳng
rustc: Android cần build kín (hermetic), tái lập được và kiểm soát chặt phiên bản compiler, flag, vị trí phụ thuộc. Cargo khi đó chưa đảm bảo được điều này, và lại build lại cả cây phụ thuộc mỗi lần chạy. - Không chạy
build.rs: script này chạy code tuỳ ý trên máy build và hay đọc file ngoài cây nguồn. Phụ thuộc C được khai báo lại bằng modulecc_library; script sinh code được thay bằng mộtrust_binarylàm bộ sinh mã. Riêngproc_macrovẫn được dùng, vì nó dễ sandbox hơn và đã được cộng đồng soi kỹ. - Crate bên thứ ba được vendor vào AOSP (thư mục
external/rust/crates). Bản vá được đẩy ngược lên upstream khi có thể. - Liên kết động mặc định trên thiết bị: hầu như binary nào cũng dùng chung crate, nên chia sẻ dylib tiết kiệm RAM và dung lượng.
- Bật sẵn công cụ chất lượng:
clippyvà lint chạy mặc định, có fuzzer (kèm HWASAN/ASAN), benchmark và đo coverage. Android bật kiểm tra tràn số nguyên mặc định cho Rust.
Một module Rust trong AOSP trông như sau (rút gọn từ ví dụ trong tài liệu AOSP):
// external/rust/hello_rust/Android.bp
rust_binary {
name: "hello_rust",
srcs: ["src/hello_rust.rs"],
rustlibs: ["libsimple_printer"], // Soong tự chọn rlib hay dylib
}
// external/rust/libsimple_printer/Android.bp
rust_library {
name: "libsimple_printer", // phải có dạng lib<crate_name>
crate_name: "simple_printer",
srcs: ["src/lib.rs"],
}
source build/envsetup.sh
lunch aosp_arm64-eng
m hello_rust
Module Rust muốn liên kết với thư viện C/C++ thì khai báo thêm shared_libs hoặc static_libs, giống module C++ bình thường.
Bước 2: Khảo sát interop trước khi viết dòng Rust đầu tiên
Rust mới phải gọi được vào thư viện C/C++ sẵn có. Nhóm Android đã phân loại các kiểu dữ liệu mà những thư viện C++ được dùng nhiều nhất xuất ra (liblog, libbase, libutils, libcutils, libbinder…). Kết quả: 81% kiểu được hỗ trợ đầy đủ ngay. Với các module Mainline, con số là 88%. Phần lớn số còn lại là những kiểu họ chủ động không hỗ trợ.
(a) bindgen cho thư viện C: binding được hệ thống build sinh tự động từ một header wrapper.
rust_bindgen {
name: "libbuzz_bindgen",
crate_name: "buzz_bindgen",
wrapper_src: "libbuzz_wrapper.h", // header gom các header C cần dùng
source_stem: "bindings",
shared_libs: ["libbuzz"],
}
(b) cxx cho C++: khai báo hàm C++ trong một module bridge; cxx kiểm tra kiểu ở cả hai phía. Ví dụ minh hoạ:
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("legacy/checksum.h");
fn legacy_checksum(data: &[u8]) -> u32; // nhận rust::Slice phía C++
}
}
pub fn checksum(data: &[u8]) -> u32 {
ffi::legacy_checksum(data) // bên gọi chỉ thấy API an toàn
}
(c) AIDL cho dịch vụ khác tiến trình: aidl_interface sinh crate Rust, và dịch vụ chỉ cần cài đặt trait. Ví dụ minh hoạ theo API trong tài liệu AOSP:
use my_iface::aidl::my::package::IFoo::{BnFoo, IFoo};
use binder::{BinderFeatures, Interface};
struct MyFoo;
impl Interface for MyFoo {}
impl IFoo for MyFoo {
fn doFoo(&self, input: &str) -> binder::Result<String> {
Ok(input.to_uppercase())
}
}
fn main() {
binder::ProcessState::start_thread_pool();
let service = BnFoo::new_binder(MyFoo, BinderFeatures::default());
binder::add_service("myservice", service.as_binder()).expect("register");
binder::ProcessState::join_thread_pool();
}
Khoảng một năm sau, khi đã có hơn 100.000 dòng Rust, nhóm Android xác nhận thực tế khớp với khảo sát: bindgen gánh phần lớn interop, cxx dùng trong Bluetooth, AIDL dùng trong Keystore2, và một ít wrapper C viết tay (ví dụ trong profcollectd).
Bước 3: Chọn đúng thành phần để viết bằng Rust
Ưu tiên là code mới hoặc code cần viết lại, ở nơi rủi ro cao: xử lý dữ liệu không tin cậy, chạy với đặc quyền cao, nói chuyện qua mạng. Android dùng "Rule of 2" để quyết định khi nào cần cô lập thêm, và Rust giúp giảm nhu cầu sandbox.
| Thành phần | Vì sao chọn | Kết quả ghi nhận |
|---|---|---|
| Keystore2 | Quản lý khoá mật mã, rất nhạy cảm | Dùng AIDL backend Rust để phục vụ các tiến trình khác |
| Stack UWB mới | Giao thức vô tuyến, nhận dữ liệu từ bên ngoài | Chỉ 2 khối unsafe; chạy trong tiến trình có sẵn nên tiết kiệm vài MB RAM và bớt độ trễ IPC |
| DNS-over-HTTP/3 | Xử lý dữ liệu mạng | Dùng async/await, cần ít thread hơn cho cùng khối lượng việc |
| Nearby Presence, MLS (RCS) | Giao thức mới, khám phá thiết bị và nhắn tin bảo mật | Viết bằng Rust từ đầu; Nearby Presence chạy trong Google Play Services, MLS sẽ có trong Google Messages |
Bước 4: Đi xuống firmware, TEE và kernel
- Firmware pVM (pvmfw): bản cũ dựa trên U-Boot, vốn không được thiết kế cho môi trường thù địch. Nó có nhiều lỗi truy cập ngoài biên, tràn số nguyên âm (integer underflow) và hỏng bộ nhớ trong driver VirtIO. Google viết lại pvmfw bằng Rust bare-metal (ra mắt trong Android 14), dùng các crate
aarch64-paging(bảng trang),smccc(gọi PSCI/SMCCC) vàvirtio-drivers. - Kích thước: bản Rust khoảng 460 kB, bản C cũ 220 kB. Google lưu ý so sánh này không công bằng: bản mới thêm chức năng để bỏ được các thành phần khác, nên tổng chuỗi khởi động có kích thước tương đương.
- Trusty TEE: hỗ trợ Trusted App viết bằng Rust; bản KeyMint TA tham chiếu được viết bằng Rust.
- Kernel: kernel 6.12 của Android là bản đầu tiên bật hỗ trợ Rust và có driver Rust chạy production. Một driver GPU chạy ở kernel đang được phát triển cùng Arm và Collabora.
- Khó khăn ở tầng thấp: MMIO, bộ nhớ chia sẻ và trình xử lý ngắt phá vỡ giả định của safe Rust, nên buộc phải dùng nhiều
unsafehơn. Một số lõi đặc thù (như DSP Hexagon) hỗ trợ kiến trúc còn yếu.
Bước 5: Kiểm soát unsafe và giữ các lớp phòng thủ cũ
- Khoảng 4% code Rust của Android nằm trong khối
unsafe. Mỗi khối được đóng gói sau API an toàn và review kỹ hơn. Ở stack UWB, chính việc soi 2 khối unsafe đã giúp phát hiện một race condition tiềm ẩn. - Code C/C++ còn lại vẫn được gia cố bằng allocator Scudo, HWASAN, GWP-ASAN và KFENCE. Nhờ guard page của Scudo, lỗi tràn bộ đệm Rust suýt lọt đầu tiên (CVE-2025-48530, trong thư viện CrabbyAVIF) trở thành không khai thác được. Lỗi được vá trước khi phát hành.
Bước 6: Đào tạo kỹ sư
Nhóm Android tự viết khoá Comprehensive Rust (công khai, miễn phí, bắt đầu dạy cuối năm 2022), dành cho kỹ sư đã có 2–3 năm kinh nghiệm C, C++ hoặc Java:
- Phần nền tảng 3 ngày, mỗi ngày 5 giờ.
- Các phần chuyên sâu: "Rust trong Android" nửa ngày (interop với C, C++, Java), bare-metal 1 ngày, concurrency 1 ngày.
- Đến tháng 9/2023, hơn 500 kỹ sư Google đã học. 96% đánh giá khoá học xứng đáng với thời gian bỏ ra. Ba tháng sau khoá học, học viên xác nhận họ viết và review được code Rust.
- Năm 2025, khoá học đang bổ sung phần chuyên sâu về unsafe: soundness, undefined behavior, safety comment và cách xây abstraction an toàn.
Bước 7: Đo lường liên tục
Google theo dõi lỗ hổng qua bản tin bảo mật Android hằng tháng và so sánh chỉ số kỹ thuật giữa thay đổi Rust và C++: tỷ lệ rollback, thời gian review, số lần sửa. Chính những số liệu này biến Rust từ "chi phí bảo mật" thành lợi thế năng suất.
Hiệu quả: số liệu thật
| Chỉ số | Kết quả |
|---|---|
| Tỷ lệ lỗi bộ nhớ trong tổng lỗ hổng | 76% (2019) → 35% (2022) → 24% (2024) → dưới 20% (2025) |
| Số lỗ hổng bộ nhớ mỗi năm | 223 (2019) → 85 (2022) |
| Mật độ lỗ hổng bộ nhớ | Khoảng 0,2 lỗi/triệu dòng với Rust, so với gần 1.000 lỗi/triệu dòng với C/C++; giảm hơn 1000 lần |
| Tỷ lệ rollback (thay đổi vừa và lớn) | Rust thấp hơn C++ khoảng 4 lần |
| Thời gian code review | Ít hơn khoảng 25% |
| Số lần sửa trước khi được duyệt | Ít hơn khoảng 20% với thay đổi cùng kích thước |
| Hiệu năng | Stack UWB tiết kiệm vài MB RAM; DNS-over-HTTP/3 cần ít thread hơn. Ở Chromium, bộ sinh mã QR bằng Rust nhanh hơn 20 lần khi bỏ được sandbox |
Điểm đáng chú ý nhất: ở Android, Rust không chỉ an toàn hơn mà còn nhanh hơn về năng suất. Code review nhanh hơn, ít phải rollback hơn. Đây là luận điểm mạnh nhất để thuyết phục cấp quản lý.
Rủi ro và bài học
- Rust không miễn nhiễm hoàn toàn: lỗi suýt lọt CVE-2025-48530 nằm đúng ở loại code unsafe. Nó được phát hiện nhờ allocator gia cố, và Google còn phát hiện thêm một lỗ hổng trong khâu báo crash rồi sửa.
- Chi phí ban đầu là có thật: khoảng 18 tháng tích hợp build, cộng thêm đào tạo và công cụ interop. Lộ trình kéo dài nhiều năm.
- Tầng thấp khó hơn: firmware và driver cần nhiều unsafe hơn, nên phải review chặt.
- Lỗi logic vẫn còn: Rust loại bỏ một lớp lỗi, không thay thế được review, test và threat modeling.
Áp dụng thế nào?
- Dev: Bắt đầu bằng một module mới, nhỏ, có ranh giới rõ (parser, codec, dịch vụ nhận input bên ngoài). Gọi code cũ qua bindgen/cxx, gói mọi
unsafesau API an toàn và viết safety comment cho từng khối. - Tech Lead: Làm theo thứ tự của Android. Tích hợp Rust vào hệ thống build sẵn có trước. Khảo sát các kiểu dữ liệu ở ranh giới interop. Đặt chính sách "code mới ở thành phần rủi ro cao dùng ngôn ngữ an toàn bộ nhớ". Giữ nguyên các lớp gia cố cho C/C++.
- PM: Đo trước và sau (tỷ lệ lỗi bộ nhớ, rollback, thời gian review). Dự trù ngân sách đào tạo (khoảng 3–4 ngày cho mỗi kỹ sư) và một lộ trình nhiều năm. Trình bày bằng số liệu Android: lỗ hổng bộ nhớ giảm từ 76% xuống dưới 20%, rollback ít hơn 4 lần, review nhanh hơn 25%.
Nguồn & tham khảo
- Rust in Android: move fast and fix things — Jeff Vander Stoep, Google (11/2025)
- Eliminating Memory Safety Vulnerabilities at the Source — Google Online Security Blog (09/2024)
- Memory Safe Languages in Android 13 — Google Online Security Blog (12/2022)
- Rust/C++ interop in the Android Platform — Google Online Security Blog (06/2021)
- Integrating Rust Into the Android Open Source Project — Google Online Security Blog (05/2021)
- Rust in the Android platform — Google Online Security Blog (04/2021)
- Comprehensive Rust — Google
- Bare-metal Rust in Android — Google Online Security Blog (10/2023)
- Scaling Rust Adoption Through Training — Google Online Security Blog (09/2023)
- Build Rust modules: Hello Rust example — Android Open Source Project
- Bindgen binding modules — Android Open Source Project
- AIDL backends — Android Open Source Project