Two weeks ago Google added Rust to the Android build, and the write-ups read it as the start of a rewrite. The post says the opposite, and this is about the number behind that: about half of Android's memory bugs are less than a year old. That puts the safe language at the boundary of new work rather than in the legacy tree, and it makes a conversion program the worst place to spend the budget.
23 April 2021·7 min read·languagessecurity
The post went up on the sixth, and the headline claim is that the Android Open Source Project now supports Rust for developing the OS itself. That is a real piece of engineering. Adding a language to a platform build is not a matter of installing a compiler; it is toolchains, dependency management, test infrastructure, packaging, and the long tail of things that assume every artifact came out of one compiler. Google says it has been at it for eighteen months, and calls scaling it across more of the OS a multi-year project, which is the honest version of a number most announcements round down.
The number people quoted was the other one. Memory safety bugs are consistently about 70 percent of Android's high severity security vulnerabilities, which means that everything else combined - logic errors, permission mistakes, bad crypto, the whole rest of the discipline - accounts for less than a third of the serious problems in one of the most heavily reviewed codebases on earth. Chromium publishes roughly the same proportion for its serious bugs, and Microsoft's security response center reported roughly the same for the CVEs it assigns each year, back in 2019. Three enormous C++ codebases, three independent counts, one answer.
The obvious response, and I have had it myself, is that the goal must be to get the old code into the new language on some schedule, because a class of defect that is seventy percent of your serious problems and largely cannot be written in Rust is a rewrite argument. Every conversation I have watched about memory safety arrives there within ten minutes. The sentence in Google's post that nobody quoted says they are not doing it. The analysis of when Android's memory bugs were first introduced, they write, demonstrates why the effort is best focused on new development and not on rewriting mature C and C++ code. The supporting figure is that about half of their memory bugs are less than a year old. Not half the bugs found this year. Half of them, introduced within the previous twelve months, in a codebase that has been accumulating for well over a decade.
Old code is not dangerous in proportion to its size. A file that has been in production for eight years has been read, fuzzed, sanitized, exercised by every user and attacked by everyone who cared to try. The bugs in it have been leaving at some rate the whole time, and nothing new has been added. Google's own framing is that they would expect bug counts in maintained but not actively developed code to fall over time, and the data says they do. Volume of old code is a bad proxy for risk in it.
New code is dangerous out of all proportion to its size. The corollary is the uncomfortable half. Whatever fraction of the tree was written or substantially changed in the last year is carrying about half the memory defects, and it is by definition the smallest and newest part. The rate at which a codebase manufactures this class of bug is roughly the rate at which people are typing into it, which makes the dangerous region a function of your commit log rather than your line count.
Safety per engineer-hour peaks at the boundary of new work. Put those together and the placement decision answers itself. An hour spent making new code safe removes defects that have not been found yet, in the region producing them fastest. An hour spent converting old code removes defects that were mostly found years ago, from the region producing them slowest. Both hours cost the same. Only one of them is buying at the front of the queue.
A rewrite resets the clock it was meant to stop. This is the part that makes the rewrite worse than merely expensive. Rewritten code is new code, and new code is where memory bugs are. A conversion is not a transfer of behavior into a safer container; it is thousands of fresh decisions made by people reconstructing intent from an implementation, and every one of them is an opportunity. You would be spending the safety budget to move code from the low-defect region into the high-defect region, in a different language, which recovers some of it but not all.
Nine days ago a patch series proposing Rust support arrived on the Linux kernel mailing list, and LWN's write-up on Wednesday describes the scope the author set: drivers and similar leaf modules, written in Rust, rather than any change to core kernel code. That is the same placement decision as Android's, reached by different people under different constraints, and stated with the same care about what is deliberately out of scope. Nobody is proposing to convert the scheduler.
Leaves and new work are the same region wearing two labels. A driver is where the hardware-specific code that nobody else reviews gets written, it parses input from a device you do not control, and there is a fresh one every time somebody ships a new chip. Its blast radius is enormous and its review population is small. If you could pick one part of a kernel to make memory-safe and had to leave the rest, that is the part, and it is also the part where the volume of new writing is highest.
It also reframes an argument I have watched go nowhere for years. The debate is usually posed as safe language against unsafe language, which is a religious question with no budget attached and no possible resolution. The useful axis is not which language, it is where the line goes, and that one has an answer, is specific to your repository, and can be revised next year without anyone changing their mind about anything.
You now run two toolchains, permanently. Eighteen months of work before the first component shipped is the honest signal here, and Google has more build engineering than almost anyone. Every organization doing this acquires a second compiler in the release pipeline, second dependency management, second set of reproducibility problems, second thing to upgrade, and second thing that breaks the build on a Friday. None of that produces a feature. All of it has to work before the safety argument pays anything at all, and afterward every review, rotation and hiring decision spans two systems languages rather than one.
The foreign function interface is where the safety stops. Rust's guarantees hold inside Rust. At the boundary with C or C++, someone writes the binding, and that binding is unsafe code by construction: it asserts lifetimes and aliasing rules the compiler cannot check across the seam. Move the seam to a hot, chatty interface and you have written a large amount of exactly the code the exercise was meant to eliminate, with worse review coverage because fewer people are fluent in both sides.
Pick the seam where the data is already narrow. Which makes seam placement the real design work. A boundary at a parser, a codec, a driver, a protocol handler - somewhere a byte buffer goes in and a well-defined structure comes out - is cheap, because the interface is already narrow and already the thing you wanted to isolate. A boundary drawn through a shared object graph is not cheap at any price, and a proposal that cannot say where the seam goes has not been designed yet.
The bug age figure measures when defects were introduced among the defects that were found, and finding is not neutral. New code gets fuzzed by campaigns pointed at recent changes, reviewed by people with the design fresh in mind, and exercised by developers who are still looking at it. Old code gets none of that attention, so a bug sitting in a rarely reached branch of an eight-year-old file may be absent from the data because nobody looked rather than because it is not there. The safest reading of the number is that half of the memory bugs we discover are young. Whether half of the memory bugs that exist are young is a different claim, and the data does not settle it.
The other problem is that age and exposure are independent. Old code holds the parsers facing the network and the routines every request passes through, and an attacker does not care how long a vulnerability has been sitting there. So the honest version of the strategy is narrower than "never rewrite." It is that a general conversion program, sized by lines of legacy code, is the worst way to spend the money, and that a small number of targeted rewrites chosen by exposure rather than by age is a good way. The parser that has produced four CVEs and sits in front of untrusted input is worth converting even though it is old, precisely because its history says the discovery process there is not finished.
What I would still refuse is the version where the rewrite is the whole plan and the boundary rule is not adopted at all, because that combination leaves you converting the past while continuing to manufacture the future in the unsafe language. If only one of the two gets done, do the boundary. It costs less, it starts paying immediately, and unlike the rewrite it makes the problem smaller every quarter rather than the same size in a different font.
I should be careful not to convert one company's data into a law. Android is a specific codebase with a specific age profile, a specific review culture and an unusually well-funded discovery process, and a shop whose product has not been substantially rewritten in a decade will have a different distribution and possibly the opposite conclusion. The figure worth carrying is not fifty percent. It is that the age distribution of your own defects is a measurable thing that almost nobody measures, and that the safety strategy that follows from it may not be the one everybody assumed.
What has stayed with me is how completely this inverts the usual picture. The legacy code is supposed to be the liability and the new work is supposed to be the safe part, because the new work is where the care is, where the standards are current and where the people are paying attention. The data says the care is not enough and the danger is arriving on the same commits as the features. The most dangerous code in your repository is not the file nobody has touched since 2013. It is the one that merged this morning.