A recent post from the Android Security team confirms the positive impact that Rust and other memory-safe languages are having on security vulnerabilities. The most critical categories of exploits are dropping rapidly, leaving hackers and researchers to focus on less severe vulnerabilities.
Memory Safe Languages in Android 13
Jeffrey Vander Stoep, a software engineer on the Android Security team, posted a new article on the Google Security Blog last week that has been making big waves. "Memory Safe Languages in Android 13" was posted on December 1st, and it starts out by reminding everyone of Alex Gaynor's post from a few years ago where he showed that large projects full of memory-unsafe code consistently have security vulnerabilities that are caused at least 65% of the time by memory safety issues.
In Android, however, this trend is reversing. "Memory safety vulnerabilities have dropped considerably over the past few years", Vander Stoep says, "from 76% down to %35 of Android's total vulnerabilities". This is a huge shift, and it takes Android from a place where it was about average to a place where it now significantly beats the average!
How was this outstanding achievement accomplished? Was it by "rewriting in Rust"? No, actually - in a Google post from nearly 2 years ago Vander Stoep explained the team's position of focusing on the safety of new code, rather than rewriting existing code. It's delightfully surprising to me that they were able to achieve such notable gains just by writing new code in Rust, but the results speak for themselves!
This was certainly not achieved through Rust alone. In fact, the "New Code by Language" pie chart shows that there's more Java and Kotlin being added to Android than Rust right now, and still quite a bit of memory-unsafe C and C++ as well. Rust isn't supported everywhere yet, though the team has plans to introduce it in more places (such as userspace HALs, Trusted Applications, drivers, etc.)
Java and Kotlin are memory safe languages, which may catch some people off-guard. In Java-based languages, it's typically rather easy to encounter a dreaded Null-Pointer Exception, or NPE - which can stop a process in its tracks and derail a whole application if it's not resilient enough. The difference between an NPE and a memory safety issue is that NPE's don't lead to exploitable conditions. Yes, Java is an unsound language - meaning that it cannot guarantee a well-typed program won't encounter unexpected runtime errors like NPEs. That doesn't make it unsafe, however.
u/anttirt describes the distinction well in a Reddit thread discussing this article:
Memory safety is a specific technical term with a specific technical meaning, and it does not apply to throwing a NullPointerException in Java. Programming languages [...] designate parts of memory to be either uninitialized, or initialized with a live object of a particular type. Memory safety means never reading uninitialized memory (including memory that previously contained an object that is no longer considered live), and never operating on initialized memory through a pointer/reference to an incompatible object type.
Java crashes don't lead to exploitable issues like buffer over-reads, use-after-frees, invalid page faults, wild pointers, etc. Thus, Java may be unsound, but is still memory-safe. For those like me who have used memory-safe languages their entire career, the concept that a crash could lead to things like secret information leaking or arbitrary code execution can be quite astonishing.
That doesn't mean that memory-safe languages like Java and Rust are free of security vulnerabilities entirely, but those vulnerabilities are overwhelmingly related to logic issues rather than memory issues. The severity of logic-related security issues is dramatically lower, because they don't allow for things like accessing memory that is out-of-bounds or arbitrary execution. Google's results confirm the expected drop in severity, with the number of critical and remotely reachable vulnerabilities swiftly dropping.
Rust to the Rescue
Though Java provides good memory safety, it doesn't easily provide the same level of performance with minimal resource usage that a native C/C++ or Rust implementation can. Java execution can be quite fast, but it comes at the cost of high memory usage. Like many other languages it can be easy to use Java in ways that lead to poor performance, putting too much pressure on garbage collection or using inefficient data models.
Go is often seen as a solution to this, providing a much simpler and more constrained language that compiles to a low-level binary and doesn't require a virtual machine. It has innovative approaches to garbage collection and concurrency, and generally performs better than Java in many situations. It's easier to learn, though complexity is slowly increasing somewhat as things like type system generics have been introduced to the language. It's not without downsides, however. It is generally memory-safe, but you can still trigger data races and out-of-bounds access scenarios - though they should always cause a crash. Whether it provides the same level of safety that something like Java does is somewhat debatable, and the garbage collector can still cause problems at times.
Google's post says that in Android 13, 21% of all new native code is in Rust. "To date, there have been zero memory safety vulnerabilities discovered in Android’s Rust code." Rust is providing an exceptional level of memory safety, outstanding performance on par with C/C++ with zero-cost abstractions in many cases, and still supports approaches based on advanced type system features with a reasonably great developer experience.
The point is often made that you can write C/C++ code with the same level of memory safety as Rust, but I think that it is often much more challenging and we still see seasoned teams with a specific focus on memory safety fall short of that goal on a regular basis. Google's post also talks about the added overhead of such solutions, which can remarkably affect the performance characteristics.
"Using memory unsafe code often means that we have to make tradeoffs between security and performance, such as adding additional sandboxing, sanitizers, runtime mitigations, and hardware protections. Unfortunately, these all negatively impact code size, memory, and performance."
The Momentum Grows
There has been a flurry of reaction to Vander Stoep's post on social media and in tech news coverage. ZD Net's summary points out that this is "the first year that memory safety vulnerabilities are not the biggest category of security flaws, and comes a year after Google made Rust the default for new code." "Google's decision to use Rust [...] appears to be paying off."
The Register points out that "Google is not the only large tech company to recognize the benefits of memory safe code. Meta has voiced its appreciation of Rust. Several months ago, Microsoft CTO Mark Russinovich declared that C/C++ should no longer be used to start new projects." Even the NSA got in on the debate. "The US National Security Agency recently observed that while languages like C++ can provide a lot of flexibility, they rely on the programmer to provide the necessary memory reference checks."
The debate still rages on, though, with people like C++ creator Bjarne Stroustrup cautioning against becoming "enamored with new and shiny things that promise to make their lives easier." He described it as "far more exciting" than mature languages, and likened supporters to "enthusiasts" who "tend to be rather one-sided in their comments."
If you want to discuss this post, you can find me on Mastadon at @BKonkle@Fosstodon.org, on Discord in various Rust and TypeScript programming communities, on YouTube @deterministic-dev, and at Formidable Labs. Thanks for reading!