Memory Safety

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àmHạn chế
1. Vá sau khi lộChờ lỗi xuất hiện rồi sửaTốn kém liên tục, người dùng bị phơi nhiễm
2. Giảm thiểu khai thácStack canary, CFI, sandboxTốn hiệu năng, chạy đua vũ trang với kẻ tấn công
3. Chủ động tìm lỗiSanitizer, fuzzingChỉ chữa triệu chứng, độ phủ thấp
4. Safe CodingNgôn ngữ an toàn bộ nhớ, thiết kế API an toànCần đầu tư ban đầu cho công cụ và đào tạo

Lộ trình triển khai

Thời điểmCột mốc
~2019Bắ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
2024Lỗ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++
2025Khoả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?

Ứng dụngỨng dụng AndroidJava/Kotlin, an toàn bộ nhớ sẵnFrameworkAndroid FrameworkJava/KotlinDịch vụ nativeKeystore2quản lý khoáDNS-over-HTTP/3DNS mã hoáStack UWBchỉ 2 khối unsafeBluetoothC++ + Rust qua cxxMedia / codecC/C++ + sandboxIPCBinder IPC · AIDL có backend Rust (libbinder_rs)HALHAL mớiviết bằng RustHAL hiện cóC/C++, giữ nguyênKernelLõi LinuxCDriver Rusttừ kernel 6.12 của AndroidẢo hoá, TEEFirmware pVM (pvmfw)thay U-Boot, từ Android 14Trusty TEEKeyMint TA viết bằng RustRustC/C++ (đã gia cố)Java/Kotlin
Sơ đồ mô phỏng do binhAI vẽ, dựa trên các thành phần Google đã công bố. Đây không phải sơ đồ đầy đủ của AOSP.

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)

Android.bprust_binary · rust_libraryrust_ffi · rust_bindgen · aidl_interfaceexternal/rust/cratescrate bên thứ ba, vendor vào AOSPcc_library (C/C++)thay cho script build.rsSoong → rustcbuild kín, tái lậpclippy + lint bật mặc định✗ không dùng Cargo, không chạy build.rsrlib / dylibliên kết động mặc định trên thiết bịcdylib / staticlibđể code C/C++ gọi vào Rusttest · fuzz · benchfuzzer có HWASAN/ASAN
Rust trong AOSP được build bằng Soong, không qua Cargo.
  • 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 module cc_library; script sinh code được thay bằng một rust_binary làm bộ sinh mã. Riêng proc_macro vẫ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: clippy và 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ợ.

Thành phần Rust mớiphần lớn là safe Rustunsafe gói ở lớp bindingbindgenC ABI · cùng tiến trình · tự sinhThư viện Cví dụ libcutils, liblogcxxstd::string, std::vector, methodThư viện C++ví dụ trong BluetoothAIDL + Binderkhác tiến trình · backend RustDịch vụ khácJava/Kotlin hoặc C++
Ba cơ chế interop chính. Phần unsafe chỉ nằm ở lớp binding và được bọc bằng API an toàn.

(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ầnVì sao chọnKết quả ghi nhận
Keystore2Quản lý khoá mật mã, rất nhạy cảmDùng AIDL backend Rust để phục vụ các tiến trình khác
Stack UWB mớiGiao thức vô tuyến, nhận dữ liệu từ bên ngoàiChỉ 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/3Xử lý dữ liệu mạngDù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ậtViế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 unsafe hơ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

0%25%50%75%76%201935%202224%2024<20%2025
Tỷ lệ lỗ hổng an toàn bộ nhớ trong tổng lỗ hổng Android. Năm 2025, Google chỉ công bố "dưới 20%".
Chỉ sốKết quả
Tỷ lệ lỗi bộ nhớ trong tổng lỗ hổng76% (2019) → 35% (2022) → 24% (2024) → dưới 20% (2025)
Số lỗ hổng bộ nhớ mỗi năm223 (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ăngStack 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 unsafe sau 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

  1. Rust in Android: move fast and fix things — Jeff Vander Stoep, Google (11/2025)
  2. Eliminating Memory Safety Vulnerabilities at the Source — Google Online Security Blog (09/2024)
  3. Memory Safe Languages in Android 13 — Google Online Security Blog (12/2022)
  4. Rust/C++ interop in the Android Platform — Google Online Security Blog (06/2021)
  5. Integrating Rust Into the Android Open Source Project — Google Online Security Blog (05/2021)
  6. Rust in the Android platform — Google Online Security Blog (04/2021)
  7. Comprehensive Rust — Google
  8. Bare-metal Rust in Android — Google Online Security Blog (10/2023)
  9. Scaling Rust Adoption Through Training — Google Online Security Blog (09/2023)
  10. Build Rust modules: Hello Rust example — Android Open Source Project
  11. Bindgen binding modules — Android Open Source Project
  12. AIDL backends — Android Open Source Project