Memory Safe Languages in Android 13

For more than a decade, memory security vulnerabilities have consistently represented more than 65% of vulnerabilities across products and across industries. On Android, we’re now seeing something else – a significant drop in memory security vulnerabilities and a corresponding drop in the severity of our vulnerabilities.

Looking at vulnerabilities reported in the Android Security Bulletin, which includes critical/high severity vulnerabilities reported through our Vulnerability Reward Program (VRP) and vulnerabilities reported internally, we see that the number of memory security vulnerabilities has decreased significantly over the past few years/releases. From 2019 to 2022, the annual number of memory security vulnerabilities dropped from 223 to 85.

This decline coincides with a shift in the use of programming languages ​​away from memory-insecure languages. Android 13 is the first Android release where the majority of new code added to the release is in a memory-safe language.

As the amount of new memory-safe code entering Android has decreased, so has the number of memory-safe vulnerabilities. From 2019 to 2022, it has dropped from 76% down to 35% of Android’s total vulnerabilities. 2022 is the first year in which memory security vulnerabilities do not represent the majority of Android vulnerabilities.

While correlation does not necessarily mean causation, it is interesting to note that the percentage of vulnerabilities caused by memory safety issues seems to correlate quite closely with the development language used for new code. This matches the expectations published in our blog post 2 years ago about the age of memory safety vulnerabilities and why our focus should be on new code, not rewriting existing components. Of course, there may be other contributing factors or alternative explanations. However, the shift is a significant departure from industry-wide trends that have persisted for more than a decade (and likely longer) despite significant investment in improvements to memory-insecure languages.

We continue to invest in tools to improve the security of our C/C++. Over the past few releases, we’ve introduced the hardened Scudo allocator, HWASAN, GWP-ASAN, and KFENCE on production Android devices. We’ve also increased our obfuscation coverage on our existing code base. Vulnerabilities found using these tools helped prevent both vulnerabilities in new code as well as vulnerabilities found in old code that are included in the above evaluation. These are important tools and critically important to our C/C++ code. However, these alone do not account for the large shift in vulnerabilities that we see, and other projects that have implemented these technologies have not seen a major shift in their vulnerability mix. We believe that Android’s ongoing shift from memory-insecure to memory-safe languages ​​is an important factor.

In Android 12, we announced support for the Rust programming language in the Android platform as a memory-safe alternative to C/C++. Since then, we’ve scaled up our Rust experience and usage within the Android Open Source Project (AOSP).

As we noted in the original announcement, our goal is not to convert existing C/C++ to Rust, but rather to move development of new code to memory-safe languages ​​over time.

In Android 13, about 21% of all new native code (C/C++/Rust) is in Rust. It’s about. 1.5 million lines of rust code in total in AOSP across new functionality and components such as Keystore2, the new Ultra-wideband (UWB) stack, DNS-over-HTTP3, Android’s Virtualization framework (AVF) and various other components and their open source dependencies. These are low-level components that require a system language that would otherwise have been implemented in C++.

Safety impact

To date, there have been zero memory security vulnerabilities discovered in Android’s Rust code.

We don’t expect that number to stay zero forever, but given the amount of new Rust code across two Android releases and the security-sensitive components where it’s being used, it’s a significant result. It shows that Rust is fulfilling its intended purpose of preventing Android’s most common source of vulnerabilities. Historical vulnerability density is greater than 1/kLOC (1 vulnerability per thousand lines of code) in many of Android’s C/C++ components (eg media, Bluetooth, NFC, etc.). Based on this historical vulnerability density, it is likely that the use of Rust has already prevented hundreds of vulnerabilities from reaching production.

What about unsafe Rust?

Developing operating systems requires access to resources that the compiler cannot reason about. For memory-safe languages, this means that an escape hatch is required to perform system programming. For Java, Android uses JNI to access low-level resources. When using JNI, care must be taken to avoid introducing unsafe behavior. Fortunately, it has proven significantly simpler to review small snippets of C/C++ for security than entire programs. There are no pure Java processes in Android. It’s all built on top of JNI. Despite that, memory safety vulnerabilities are exceptionally rare in our Java code.

Rust also has the insecure{} escape hatch, which allows interaction with system resources and non-Rust code. As with Java + JNI, using this escape hatch comes with additional controls. However, like Java, our Rust code proves to be significantly more secure than pure C/C++ implementations. Let’s look at the new UWB stack as an example.

There are exactly two uses of insecure in UWB code: one to materialize a reference to a Rust object stored inside a Java object, and another to decompose the same. Unsafe was actively helpful in this situation because the extra attention to this code allowed us to detect a possible race condition and protect against it.

In general, using insecure in Android’s Rust seems to work as intended. It’s rarely used, and when it is, it’s encapsulating behavior that’s easier to reason about and review just in case.

Safeguards make memory-insecure languages ​​slow

Mobile devices have limited resources, and we’re always trying to make better use of them to give users a better experience (for example, by optimizing performance, improving battery life, and reducing latency). Using insecure memory codes often means we have to make tradeoffs between security and performance, such as adding additional sandboxing, sanitizers, runtime restrictions, and hardware protection. Unfortunately, these all negatively impact code size, memory, and performance.

Using Rust in Android allows us to optimize both security and system health with fewer compromises. For example, with the new UWB stack, we could save several megabytes of memory and avoid some IPC latency by running it in an existing process. The new DNS over HTTP/3 implementation uses fewer threads to do the same amount of work by using Rust’s async/await feature to process many tasks on a single thread in a safe manner.

The number of vulnerabilities reported in the bulletin has remained fairly stable over the past 4 years at around 20 per month, although the number of memory security vulnerabilities has decreased significantly. So what gives? A few thoughts on that.

A drop in difficulty

Memory security vulnerabilities disproportionately represent our most serious vulnerabilities. In 2022, despite representing only 36% of security bulletin vulnerabilities, memory security vulnerabilities accounted for 86% of our critical security vulnerabilities, our highest rating, and 89% of our remotely exploitable vulnerabilities. Over the past few years, memory security vulnerabilities accounted for 78% of confirmed exploited “in-the-wild” vulnerabilities on Android devices.

Many vulnerabilities have a well-defined scope of impact. For example, a permissions bypass vulnerability generally allows access to a specific set of information or resources and is generally only available if code is already running on the device. Memory security vulnerabilities tend to be much more versatile. Getting code execution in a process gives access not just to a specific resource, but everything that process has access to, including the attack surface for other processes. Memory security vulnerabilities are often flexible enough to allow multiple vulnerabilities to be chained together. That high versatility is perhaps one of the reasons why the vast majority of exploit chains we’ve seen use one or more memory security vulnerabilities.

With the decrease in memory security vulnerabilities, we see a corresponding decrease in vulnerability severity.

With the decrease in our most severe vulnerabilities, we are seeing more reports of less severe vulnerability types. For example, about 15% of the vulnerabilities in 2022 are DoS vulnerabilities (requires a factory reset of the device). This represents a decrease in security risk.

Android values ​​our security research community and all contributions to the Android VRP. We apply higher payouts for more severe vulnerabilities to ensure incentives are aligned with vulnerability risk. As we make it harder to find and exploit memory security vulnerabilities, security researchers are turning their focus to other vulnerability types. Perhaps the total number of vulnerabilities found is primarily limited by the total researcher time spent finding them. Or maybe there is another explanation that we haven’t considered. In any case, we hope that if our vulnerability researchers find fewer of these powerful and versatile vulnerabilities, so will adversaries.

Attack surface

Despite the fact that most of the existing code in Android is in C/C++, most of Android’s API surface is implemented in Java. This means that Java is disproportionately represented in the operating system attack surface available to apps. This provides an important security property: most of the attack surface available to apps is not susceptible to memory corruption errors. This also means that we expect Java to be overrepresented when looking at out-of-memory security vulnerabilities. However, it is important to note that the types of vulnerabilities we see in Java are mostly logic errors and, as mentioned above, generally lower in severity. Moving forward, we’ll explore how Rust’s richer type system can also help prevent common types of logic errors.

Google’s ability to respond

With the types of vulnerabilities we’re seeing now, Google’s ability to detect and prevent abuse is significantly better. Apps are scanned to help detect abuse of APIs before they are published to the Play Store, and Google Play Protect alerts users if they have installed abusive apps.

Migrating away from C/C++ is challenging, but we’re making progress. Use of Rust is growing in the Android platform, but that’s not the end of the story. To meet the goals of improving the security, stability, and quality of all of Android, we need to be able to use Rust anywhere in the codebase where native code is required. We implement userspace HALs in Rust. We are adding support for Rust in trusted applications. We have migrated VM firmware in Android Virtualization Framework to Rust. With support for Rust landing in Linux 6.1, we’re excited to bring memory safety to the core, starting with kernel drivers.

As Android migrates away from C/C++ to Java/Kotlin/Rust, we expect the number of memory safety vulnerabilities to continue to decrease. Here’s to a future where memory corruption errors on Android are rare!


Leave a Reply

Your email address will not be published. Required fields are marked *