The Official Google security blog announced that Android has added support for the Rust language.
The correctness of the code in the Android platform is the key to the security, stability and quality of every Android release. Memory security holes in C and C++ remain the most intractable cause of incorrectness. We put a lot of effort and resources into detecting, fixing, and mitigating these bugs, and these efforts have effectively prevented a lot of bugs from creeping into the Android distribution. However, despite these efforts, memory security vulnerabilities remain the leading cause of stability issues and consistently account for around 70% of Android’s most serious security vulnerabilities.
In addition to ongoing and upcoming efforts to improve memory error detection, we are stepping up our efforts to prevent memory errors in the first place. Memory-safe languages are the most cost-effective means of preventing memory errors. In addition to memory-safe languages like Kotlin and Java, we are pleased to announce that the Android Open Source Project (AOSP) now supports the Rust programming language to develop the operating system itself.
System programming
Managed languages like Java and Kotlin are the best choices for Android application development. These languages are designed for ease of use, portability, and security. Android Runtime (ART) manages memory on behalf of developers. The Android operating system makes extensive use of Java, effectively preventing most of the Android platform’s memory errors. Unfortunately, Java and Kotlin are not optional at the bottom of the operating system.
Mmbiz. Qpic. Cn/mmbiz_png/p…
image.png
The underlying operating system requires system programming languages such as C, C++, and Rust. These languages are designed with control and predictability in mind. They provide access to the underlying system resources and hardware. They require less resources and have more predictable performance characteristics.
Rust provides memory security by using a combination of compile-time checking to enforce the declaration cycle and ownership of execution objects and run-time checking to ensure the validity of memory access. This security is implemented while providing performance comparable to C and C++.
Limitations of sandboxes
The C and C++ languages do not provide these same security guarantees and require strong isolation. All Android processes are sandboxed, and we follow Rule 2 to determine if features require additional isolation and stripping. Rule 2 is simple: given three options, the developer can choose only two of the following three options.
Mmbiz. Qpic. Cn/mmbiz_png/p…
For Android, this means that if code is written in C/C++ and parses untrusted input, it should be kept in a tightly constrained no-permissions sandbox. While compliance with “Rule 2” effectively reduces the severity and accessibility of security vulnerabilities, it also has limitations. Sandbox costs a lot: the new processes it requires consume extra overhead and introduce latency due to IPC and extra memory usage. Sandboxes do not eliminate bugs in code, and their effectiveness is reduced by high bug density, allowing attackers to chain multiple bugs together.
Memory-safe languages like Rust help us overcome these limitations in two ways.
- Reduced bug density in our code, thus improving the effectiveness of our current sandbox.
- Reduces our need for sandboxes and allows the introduction of new features that are both secure and resource lightweight.
But what about all that existing C++ code?
Of course, introducing a new programming language does nothing to fix bugs in our existing C/C++ code. Even if we put every software engineer on the Android team into refactoring, rewriting tens of millions of lines of code is simply not feasible.
Mmbiz. Qpic. Cn/mmbiz_png/p…
The above life analysis of memory security bugs in Android (measured from the time they were first introduced) shows why our memory security language efforts are best focused on developing new features rather than rewriting mature C/C++ code. Most of our memory bugs are in new or recently modified code, with about 50% occurring in the last year or so.
It may come as a surprise to some that old memory bugs are relatively rare, but we found that old code was not the most urgent area for improvement. Software bugs are found and fixed over time, so we expect the number of bugs in code that is being maintained but not in development to decrease over time. Just as reducing the number and density of bugs improves the effectiveness of sandboxes, it also improves the effectiveness of bug detection.
Limitations of detection
Error detection through robust testing, sanitation, and fuzzification is critical to improving the quality and correctness of all software, including software written in Rust. A key limitation of the most effective memory security detection techniques is that an error state must actually be triggered in the tool code to be detected. Even in a code base with high test/fuzz coverage, this can result in many bugs going undetected.
Another limitation is that bug detection can scale faster than bug fixes. In some projects, detected bugs are not always fixed. Bug fixes are a long and expensive process.
Mmbiz. Qpic. Cn/mmbiz_png/p…
Each of these steps is costly, and missing any one of them will leave some or all users without fixing their bugs. For complex C/C++ code bases, often only a few people have the ability to develop and review fixes, and even if they spend a lot of effort fixing bugs, sometimes the results are incorrect.
Bug detection is most effective when bugs are rare, and dangerous bugs can be given the urgency and priority they deserve. If we want to benefit from improved error detection, we must prioritize preventing the introduction of new errors.
Give priority to prevention
Rust has modernized a number of other languages to improve code correctness.
- “Memory security” : Enhanced memory security through a combination of compile-time and run-time checks.
- “Data concurrency” : Preventing data races. This makes it easy for users to write efficient, thread-safe code, hence Rust’s fearless concurrency slogan.
- “More expressive type system” : helps prevent logical programming errors (e.g., newType wrappers, enum variants with content).
- “By default, references and variables are immutable” : To help developers follow the security principle of minimal permissions and mark references or variables as mutable only if they really intend to mutate them. While C++ has const, it tends to be used infrequently and inconsistently. By contrast, the Rust compiler helps avoid stray variability annotations by providing warnings for mutable values that never mutate.
- “Better error handling in standard libraries”: wraps potential failed calls in
Result
This causes the compiler to require the user to check for failures even for functions that do not return the desired value. This prevents bugs like cage rage caused by unhandled errors. By making it easy to pass?
The operator propagates errors and optimizesResult
To achieve low overhead, Rust encourages users to write their error-prone functions in the same style and get the same protection. - “Initialize” : Requires that all variables be initialized before use. Uninitialized memory holes have historically been the root cause of 3-5% of security vulnerabilities on Android. In Android 11, we started automatic memory initialization in C/C++ to reduce this problem. However, initializing to zero is not always safe, especially for things like return values, which can be a new source of error handling. Rust requires that each variable be initialized as a legitimate member of its type before use, avoiding the problem of accidentally initializing an unsafe value. Like Clang in C/C++, the Rust compiler is aware of the initialization requirements and avoids any potential performance costs associated with double initialization.
- “Safer Integer Handling”Rust Debug builds enable overflow cleanup by default, encouraging programmers to specify overflow if they really intend to compute it
wrapping_add
, or if overflow is not intendedsaturating_add
. We intend to enable overflow cleaning on all Android builds. In addition, all integer type conversions are explicit operations: a developer cannot accidentally convert a type during a function call when assigning a value to a variable or when trying to perform an operation on another type.
Where do we go from here
Adding a new language to the Android platform is a big undertaking. Tool chains and dependencies need to be maintained, testing infrastructure and tools must be updated, and developers need to be trained. Over the past 18 months, we have been adding Rust support to Android open source projects, and we have some early adoptions that will be shared in the coming months. Extending it to more operating systems is a multi-year project. Stay tuned and we will post more updates on this blog.
“Java is a registered trademark of Oracle or its affiliates.”
Thanks to Matthew Maurer, Bram Bonne and Lars Bergstrom for contributing to this article. Special thanks to our colleague Adrian Taylor for his insight into the age of memory bugs, and Chris Palmer for his work on the sections “Rule 2” and “Sandbox limits.”