Memory Safety

How Android moved from C/C++ to Rust, and the real numbers

TL;DR

Android didn't rewrite its old C/C++; it wrote new code in Rust and bridged the two with bindgen, cxx and AIDL. Memory bugs fell from 76% (2019) to below 20% (2025), with 1000x lower bug density, 4x fewer rollbacks and 25% faster reviews.

In 2019, 76% of the vulnerabilities in Android's security bulletins were memory safety bugs: buffer overflows, use-after-free, reads of uninitialised memory. By 2025 that share had dropped below 20%. Google didn't get there by rewriting tens of millions of lines of C/C++. It changed how new code gets written: new code goes into memory-safe languages, mainly Rust, while old code is left alone, hardened, and allowed to age. This article walks through each technical step they took (build, interop, component choice, firmware, training, measurement), with architecture diagrams and the officially published numbers.

The starting problem

  • Memory bugs dominated severe vulnerabilities: in 2021 Google said they consistently made up about 70% of Android's high-severity vulnerabilities. In 2022 they were still 86% of critical-severity and 89% of remotely exploitable vulnerabilities.
  • Sandboxing has limits: process isolation costs RAM and adds IPC latency. It doesn't remove bugs, and with high bug density attackers can still chain several bugs to escape.
  • Fuzzing and sanitizers only catch symptoms: they see a bug only when the faulty state is actually triggered. Bugs were being found faster than they could be fixed, and some fixes were wrong.
  • A full rewrite was impossible: the Android team said rewriting tens of millions of lines of code wasn't feasible, even with the whole team on it.

The core principle: vulnerabilities live in new code

Data from Android and Chromium shows that vulnerabilities decay exponentially, like a half-life. About 50% of memory bugs were in code less than a year old (2021 data). Five-year-old code has 3.4x to 7.4x lower vulnerability density than new code (Google Security blog, 2024).

That leads to the "Safe Coding" strategy: stop adding new unsafe code where it matters, instead of rewriting old code. Google's simulation used these assumptions: the codebase doubles every 6 years, the average vulnerability lifetime is 2.5 years, and moving new code to safe languages takes 10 years. The simulated result: total vulnerabilities still fall sharply, even while the amount of unsafe code keeps growing.

GenerationApproachLimitation
1. Reactive patchingWait for bugs to surface, then fix themOngoing cost, users stay exposed
2. Exploit mitigationStack canaries, CFI, sandboxingPerformance cost, an arms race with attackers
3. Proactive bug findingSanitizers, fuzzingTreats symptoms, low coverage
4. Safe CodingMemory-safe languages, safe API designUpfront investment in tooling and training

Rollout timeline

WhenMilestone
~2019Started prioritising memory-safe languages (Java/Kotlin, Rust) for new development
2021 (Android 12)AOSP officially supports Rust after about 18 months of groundwork; AIDL gains a Rust backend
2022 (Android 13)For the first time most new code is in memory-safe languages; about 21% of new native code is Rust; about 1.5 million lines of Rust in AOSP
2023 (Android 14)Protected VM firmware (pvmfw) rewritten in Rust, replacing U-Boot; Trusty TEE supports Trusted Apps written in Rust
2024Memory bugs down to 24% of all vulnerabilities; Rust changes have less than half the rollback rate of C++
2025About 5 million lines of Rust; net Rust additions slightly exceed C++ in the first three quarters; Android's 6.12 kernel ships the first production Rust driver

Architecture: where does Rust live in Android?

AppsAndroid appsJava/Kotlin, already memory-safeFrameworkAndroid FrameworkJava/KotlinNative servicesKeystore2key managementDNS-over-HTTP/3encrypted DNSUWB stackonly 2 unsafe blocksBluetoothC++ + Rust via cxxMedia / codecC/C++ + sandboxIPCBinder IPC · AIDL with a Rust backend (libbinder_rs)HALNew HALswritten in RustExisting HALsC/C++, kept as isKernelLinux coreCRust driversfrom Android's 6.12 kernelVirtualisation, TEEpVM firmware (pvmfw)replaced U-Boot, Android 14Trusty TEEKeyMint TA written in RustRustC/C++ (hardened)Java/Kotlin
Illustrative diagram drawn by binhAI from components Google has published. It is not a complete map of AOSP.

Everything from the Framework up is already memory-safe thanks to Java/Kotlin. The risk is concentrated in the native layers: system services, HALs, the kernel and firmware. That is exactly where Rust went. Components talk to each other over Binder/AIDL, so a Rust service can replace a C++ one without changing its callers.

How they did it, step by step

Step 1: Bring Rust into the build system (about 18 months)

Android.bprust_binary · rust_libraryrust_ffi · rust_bindgen · aidl_interfaceexternal/rust/cratesthird-party crates vendored in AOSPcc_library (C/C++)replaces build.rs scriptsSoong → rustchermetic, reproducibleclippy + lints on by default✗ no Cargo, no build.rsrlib / dylibdynamic linking by default on devicecdylib / staticlibso C/C++ can call into Rusttest · fuzz · benchfuzzers with HWASAN/ASAN
Rust in AOSP is built by Soong, not Cargo.
  • Soong invokes rustc directly: Android needs hermetic, reproducible builds and tight control over compiler versions, flags and dependency locations. Cargo couldn't guarantee that at the time, and it rebuilds the whole dependency graph on every run.
  • No build.rs: these scripts run arbitrary code on the build host and often read files outside the source tree. C dependencies are declared again as cc_library modules; code-generating scripts are replaced by a rust_binary used as a source generator. proc_macro is still allowed, because it's easier to sandbox and well scrutinised upstream.
  • Third-party crates are vendored into AOSP (under external/rust/crates), with patches sent upstream where possible.
  • Dynamic linking by default on device: almost every binary shares the same crates, so shared dylibs save RAM and disk.
  • Quality tooling on by default: clippy and lints run by default, with fuzzers (with HWASAN/ASAN), benchmarks and code coverage. Android turns on integer overflow checks by default for Rust.

A Rust module in AOSP looks like this (condensed from the AOSP documentation example):

// external/rust/hello_rust/Android.bp
rust_binary {
    name: "hello_rust",
    srcs: ["src/hello_rust.rs"],
    rustlibs: ["libsimple_printer"],   // Soong picks rlib or dylib
}

// external/rust/libsimple_printer/Android.bp
rust_library {
    name: "libsimple_printer",         // must be lib<crate_name>
    crate_name: "simple_printer",
    srcs: ["src/lib.rs"],
}
source build/envsetup.sh
lunch aosp_arm64-eng
m hello_rust

A Rust module that links C/C++ libraries adds shared_libs or static_libs, just like a normal C++ module.

Step 2: Survey interop before writing the first line of Rust

New Rust must be able to call existing C/C++ libraries. The Android team classified the types exported by the most widely used C++ libraries (liblog, libbase, libutils, libcutils, libbinder…). The result: 81% of types were fully supported immediately. For Mainline modules the figure was 88%. Most of the rest were types they deliberately chose not to support.

New Rust componentmostly safe Rustunsafe kept in the binding layerbindgenC ABI · same process · generatedC librariese.g. libcutils, liblogcxxstd::string, std::vector, methodsC++ librariese.g. in BluetoothAIDL + Binderacross processes · Rust backendOther servicesJava/Kotlin or C++
The three main interop mechanisms. Unsafe code stays in the binding layer, wrapped in safe APIs.

(a) bindgen for C libraries: the build system generates bindings automatically from a wrapper header.

rust_bindgen {
    name: "libbuzz_bindgen",
    crate_name: "buzz_bindgen",
    wrapper_src: "libbuzz_wrapper.h",  // header that includes the C headers you need
    source_stem: "bindings",
    shared_libs: ["libbuzz"],
}

(b) cxx for C++: declare the C++ functions in a bridge module; cxx checks types on both sides. An illustrative example:

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("legacy/checksum.h");
        fn legacy_checksum(data: &[u8]) -> u32;   // receives rust::Slice on the C++ side
    }
}

pub fn checksum(data: &[u8]) -> u32 {
    ffi::legacy_checksum(data)                   // callers only see a safe API
}

(c) AIDL for services in other processes: aidl_interface generates a Rust crate, and the service just implements a trait. An illustrative example following the API in the AOSP docs:

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();
}

About a year later, with more than 100,000 lines of Rust, the Android team confirmed that reality matched the survey: bindgen handled most interop, cxx was used in Bluetooth, AIDL in Keystore2, plus a few hand-written C wrappers (for example in profcollectd).

Step 3: Pick the right components to write in Rust

The priority is new code, or code that needs rewriting anyway, in high-risk places: handling untrusted data, running with high privilege, talking to the network. Android uses the "Rule of 2" to decide when extra isolation is needed, and Rust reduces the need for sandboxing.

ComponentWhy it was chosenReported outcome
Keystore2Cryptographic key management, highly sensitiveUses the AIDL Rust backend to serve other processes
New UWB stackRadio protocol, receives external dataOnly 2 unsafe blocks; runs in an existing process, saving several MB of RAM and some IPC latency
DNS-over-HTTP/3Handles network dataUses async/await, needs fewer threads for the same work
Nearby Presence, MLS (RCS)New protocols for device discovery and secure messagingWritten in Rust from the start; Nearby Presence runs in Google Play Services, MLS is coming to Google Messages

Step 4: Go down to firmware, the TEE and the kernel

  • pVM firmware (pvmfw): the old version was based on U-Boot, which wasn't designed for a hostile environment. It had many out-of-bounds accesses, integer underflows and memory corruption bugs in its VirtIO drivers. Google rewrote pvmfw in bare-metal Rust (shipped in Android 14), using the aarch64-paging (page tables), smccc (PSCI/SMCCC calls) and virtio-drivers crates.
  • Size: the Rust version is about 460 kB, versus 220 kB for the old C version. Google notes the comparison isn't fair: the new version adds features that allowed other components to be removed, so the total boot chain is a similar size.
  • Trusty TEE: supports Trusted Apps written in Rust; the reference KeyMint TA is written in Rust.
  • Kernel: Android's 6.12 kernel is the first with Rust support enabled and a production Rust driver. A kernel-mode GPU driver is being developed with Arm and Collabora.
  • Low-level challenges: MMIO, shared memory and interrupt handlers break safe Rust's assumptions, so more unsafe is required. Some niche cores (such as the Hexagon DSP) have weaker architecture support.

Step 5: Control unsafe and keep the old defences

  • About 4% of Android's Rust code is inside unsafe blocks. Each block is encapsulated behind a safe API and reviewed more carefully. In the UWB stack, scrutinising its 2 unsafe blocks helped uncover a potential race condition.
  • The remaining C/C++ is still hardened with the Scudo allocator, HWASAN, GWP-ASAN and KFENCE. Scudo's guard pages made the first near-miss Rust buffer overflow (CVE-2025-48530, in the CrabbyAVIF library) non-exploitable. It was fixed before release.

Step 6: Train engineers

The Android team wrote its own Comprehensive Rust course (public and free, taught since late 2022) for engineers with 2–3 years of C, C++ or Java experience:

  • A 3-day fundamentals course, 5 hours a day.
  • Deep dives: a half day on "Rust in Android" (interop with C, C++, Java), a full day on bare metal, a full day on concurrency.
  • By September 2023, more than 500 Google engineers had taken it. 96% said it was worth their time. Three months after the course, participants confirmed they could write and review Rust code.
  • In 2025 the course is adding an unsafe deep dive: soundness, undefined behaviour, safety comments and building safe abstractions.

Step 7: Measure continuously

Google tracks vulnerabilities through the monthly Android security bulletins and compares engineering metrics between Rust and C++ changes: rollback rate, review time, number of revisions. These numbers are what turned Rust from a "security cost" into a productivity advantage.

Results: the real numbers

0%25%50%75%76%201935%202224%2024<20%2025
Memory safety bugs as a share of all Android vulnerabilities. For 2025 Google only published "below 20%".
MetricResult
Memory bugs as a share of all vulnerabilities76% (2019) → 35% (2022) → 24% (2024) → below 20% (2025)
Memory safety vulnerabilities per year223 (2019) → 85 (2022)
Memory safety vulnerability densityAbout 0.2 per million lines for Rust, versus close to 1,000 per million lines for C/C++; more than a 1000x reduction
Rollback rate (medium and large changes)About 4x lower for Rust than for C++
Code review timeAbout 25% less
Revisions before approvalAbout 20% fewer for changes of similar size
PerformanceThe UWB stack saved several MB of RAM; DNS-over-HTTP/3 needs fewer threads. In Chromium, the Rust QR code generator got 20x faster once its sandbox could be removed

The most striking point: on Android, Rust is not only safer but also faster in productivity terms. Reviews go faster and there are fewer rollbacks. This is the strongest argument for convincing management.

Risks and lessons

  • Rust is not fully immune: the near-miss CVE-2025-48530 sat in exactly the kind of code that's unsafe. It was caught thanks to a hardened allocator, and Google also found and fixed a gap in its crash reporting.
  • The upfront cost is real: about 18 months of build integration, plus training and interop tooling. The rollout spans several years.
  • Low levels are harder: firmware and drivers need more unsafe code, so review has to be strict.
  • Logic bugs remain: Rust removes one class of bugs; it doesn't replace review, testing and threat modelling.

How to apply it

  • Dev: Start with a new, small module with clear boundaries (a parser, a codec, a service taking external input). Call existing code through bindgen/cxx, wrap every unsafe behind a safe API, and write a safety comment for each block.
  • Tech Lead: Follow Android's order. Integrate Rust into your existing build system first. Survey the types at your interop boundaries. Set a policy that "new code in high-risk components uses a memory-safe language". Keep the C/C++ hardening in place.
  • PM: Measure before and after (memory bug rate, rollbacks, review time). Budget for training (about 3–4 days per engineer) and a multi-year roadmap. Pitch with Android's numbers: memory vulnerabilities down from 76% to below 20%, 4x fewer rollbacks, 25% faster reviews.

Sources & references

  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