Google Rewriting Android's Binder In Rust With Promising Results
Google engineers on Wednesday posted an initial "request for comments" set of patches that re-implement Android's Binder code within the Linux kernel in the Rust programming language rather than C.
Binder remains a critical piece of Android's software stack and for increasing the robustness and security, Google is pursuing a rewrite of the C code in Rust. Binder is responsible for inter-process communication (IPC) and other tasks on Android while replacing it with memory-safe Rust code should be a big step-up for system security.
Binder has become incredibly complex to maintain and further evolve and is critical to Android's sandboxing strategy.
Learn more about the Rust'ed Binder implementation via this RFC announcement on the Linux kernel mailing list.
The Rust-written Binder passes all the tests for the correctness of Binder in the context of the Android Open-Source Project. The Rust code can yield a booting and working device. With the exception of some debugging features, the Rust version implements all the features of the C Binder implementation.
Rust Binder IPC benchmarks are showing promising results compared to the C Binder implementation, but further testing with real-world workloads still needs to take place. At least in a Binder throughput benchmark the Rust version is +/- 2% of the C version.
Binder remains a critical piece of Android's software stack and for increasing the robustness and security, Google is pursuing a rewrite of the C code in Rust. Binder is responsible for inter-process communication (IPC) and other tasks on Android while replacing it with memory-safe Rust code should be a big step-up for system security.
Binder has become incredibly complex to maintain and further evolve and is critical to Android's sandboxing strategy.
"It's #1 (high complexity) that has made continuing to evolve Binder and resolving #2 (tech debt) exceptionally difficult without causing #3 (security issues). For Binder to continue to meet Android's needs, we need better ways to manage (and reduce!) complexity without increasing the risk.
The biggest change is obviously the choice of programming language. We decided to use Rust because it directly addresses a number of the challenges within Binder that we have faced during the last years. It prevents mistakes with ref counting, locking, bounds checking, and also does a lot to reduce the complexity of error handling. Additionally, we've been able to use the more expressive type system to encode the ownership semantics of the various structs and pointers, which takes the complexity of managing object lifetimes out of the hands of the programmer, reducing the risk of use-after-frees and similar problems.
Rust has many different pointer types that it uses to encode ownership semantics into the type system, and this is probably one of the most important aspects of how it helps in Binder. The Binder driver has a lot of different objects that have complex ownership semantics; some pointers own a refcount, some pointers have exclusive ownership, and some pointers just reference the object and it is kept alive in some other manner. With Rust, we can use a different pointer type for each kind of pointer, which enables the compiler to enforce that the ownership semantics are implemented correctly.
Another useful feature is Rust's error handling. Rust allows for more simplified error handling with features such as destructors, and you get compilation failures if errors are not properly handled. This means that even though Rust requires you to spend more lines of code than C on things such as writing down invariants that are left implicit in C, the Rust driver is still slightly smaller than C binder: Rust is 5.5kLOC and C is 5.8kLOC. (These numbers are excluding blank lines, comments, binderfs, and any debugging facilities in C that are not yet implemented in the Rust driver. The numbers include abstractions in rust/kernel/ that are unlikely to be used by other drivers than Binder.)
Although this rewrite completely rethinks how the code is structured and how assumptions are enforced, we do not fundamentally change *how* the driver does the things it does. A lot of careful thought has gone into the existing design. The rewrite is aimed rather at improving code health, structure, readability, robustness, security, maintainability and extensibility. We also include more inline documentation, and improve how assumptions in the code are enforced. Furthermore, all unsafe code is annotated with a SAFETY comment that explains why it is correct.
We have left the binderfs filesystem component in C. Rewriting it in Rust would be a large amount of work and requires a lot of bindings to the file system interfaces. Binderfs has not historically had the same challenges with security and complexity, so rewriting binderfs seems to have lower value than the rest of Binder."
Learn more about the Rust'ed Binder implementation via this RFC announcement on the Linux kernel mailing list.
The Rust-written Binder passes all the tests for the correctness of Binder in the context of the Android Open-Source Project. The Rust code can yield a booting and working device. With the exception of some debugging features, the Rust version implements all the features of the C Binder implementation.
Rust Binder IPC benchmarks are showing promising results compared to the C Binder implementation, but further testing with real-world workloads still needs to take place. At least in a Binder throughput benchmark the Rust version is +/- 2% of the C version.
37 Comments