Android Binder Attack Matrix: CVE-2023–20938 & CVE-2023–21255 UAF Details (Article — 1)
Imagine you’re a detective, piecing together clues to solve a complex mystery. In the world of cybersecurity, our mysteries often involve uncovering vulnerabilities within intricate systems. Today, we delve into the Binder IPC in Android, exploring vulnerabilities that have significant implications for security. We’ll dissect how Binder translates objects during transactions, identify potential errors and their consequences, and discuss the remediation of specific vulnerabilities like CVE-2023–20938 and CVE-2023–21255.
The Magic of Binder: Translating Objects Across Boundaries
Binder acts as the translator in the bustling city of Android’s inter-process communication (IPC). When a transaction occurs, Binder translates objects from one form to another, akin to converting one language to another to ensure seamless communication.
Vulnerability Overview & Explanation
Vulnerability Description — 1: Translating Binder Objects
During a transaction, Binder can translate a Binder Node to a Binder Ref or vice versa. Additionally, it can install new file descriptors in another process for file sharing. This translation process is governed by a loop in the code:
binder_size_t buffer_offset = 0;
for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; buffer_offset += sizeof(binder_size_t)) {
// ...
}
However, this seemingly straightforward loop can be a breeding ground for vulnerabilities if not handled correctly.
The Domino Effect: Error Handling in Translation
Errors during object translation can be compared to a domino effect — one small misstep can lead to a cascade of issues.
Vulnerability Description — 2: Cleanup on Errors
If an error occurs in the translation loop, all objects translated so far need to be cleaned up. The cleanup function is called with the offset in the buffer it has reached and a flag indicating failure:
binder_size_t buffer_offset = 0;
for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; buffer_offset += sizeof(binder_size_t)) {
// if error: goto err_bad_offset
}
err_bad_offset:
binder_transactions_buffer_release(target_proc, NULL, t->buffer, buffer_offset, /*is_failure*/true);
Failing to clean up properly can leave remnants that might be exploited later.
Vulnerability Description — 3: Errors Before Processing
What if an error happens before any objects are processed? This scenario is like stopping a car before it even starts, yet it still requires handling:
binder_size_t buffer_offset = 0;
if (!IS_ALIGNED(tr->offsets_size, sizeof(binder_size_t))) {
goto err_bad_offset;
}
for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; buffer_offset += sizeof(binder_size_t)) {
// if error: goto err_bad_offset
}
err_bad_offset:
binder_transaction_buffer_release(target_proc, NULL, t->buffer,
buffer_offset, /*is_failure*/true);
Properly managing such situations ensures no loopholes are left open.
The Cleanup Conundrum: Ensuring Thorough Cleanup
Handling cleanup correctly, even when a buffer offset of zero is passed, is crucial. This ensures the entire buffer is cleaned, preventing any residual data from causing issues.
Vulnerability Description — 4: Cleaning Entire Buffers
In cases where a buffer offset of zero is passed with the failure flag set to true, the function needs to clean the entire buffer:
void binder_transaction_buffer_release(binder_proc *target_proc, binder_buffer *buffer, size_t failed_at /*buffer_offset*/, bool is_failure) {
off_start_offset = ALIGN(buffer->data_size, sizeof(void *));
off_end_offset = is_failure && failed_at /*0*/ ? failed_at
: off_start_offset + buffer->offsets_size;
for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; buffer_offset += sizeof(size_t)) {
case BINDER_TYPE_BINDER: {
flat_binder_object *fp = to_flat_binder_object(hdr);
binder_node *node = binder_get_node(target_proc, fp->binder);
binder_dec_node(node, hdr->type == BINDER_TYPE_BINDER, 0);
binder_put_node(node);
}
}
}
This meticulous cleanup prevents any leftover data from previous transactions from being exploited.
CVE-2023–20938: A Case Study in Fuzzing and Remediation
CVE-2023–20938 was identified via fuzzing on the android13–5.10 kernel. Before the patch, all binder transaction objects were copied from user space into the binder kernel buffer before translating the objects. The patch altered this process, copying transaction objects during translation, reducing the window for potential exploitation:
- if (binder_alloc_copy_user_to_buffer(
- &target_proc->alloc,
- t->buffer, 0,
- (const void __user *)
- (uintptr_t)tr->data.ptr.buffer,
- tr->data_size)) {
// ...
for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; buffer_offset += sizeof(binder_size_t)) {
+ if (copy_size && (user_offset > object_offset ||
+ binder_alloc_copy_user_to_buffer(
+ &target_proc->alloc,
+ t->buffer, user_offset,
+ user_buffer + user_offset,
+ copy_size))) {
// translate binder object
}
Attacker’s Vulnerability Workflow
CVE-2023–21255: Discovery and Fix
Another vulnerability, CVE-2023–21255, was discovered due to the binder kernel buffer not being zeroed between ioctls. This oversight allowed residual data from previous transactions to persist, potentially leading to use-after-free (UAF) conditions. The fuzzer revealed this issue, and it was fixed in the 2023–07–01 ASB. CVE-2023–20938 was identified via fuzzing on android13–5.10 kernel. The test case wasn’t reproducible on android13–5.15 ACK due to this patch.
Before Patch: All the binder transaction objects are copied from user space into the binder kernel buffer before translating the objects.
After Patch: Binder transaction objects are copied from user space into the binder kernel buffer during translation of the objects as they are being processed.
CVE-2023–20938 was fixed by back-porting the patch to the vulnerable kernels in 2023–02–01 ASB.
Conclusion
Understanding and addressing vulnerabilities in Binder is crucial for fortifying Android’s security. By dissecting these vulnerabilities and their fixes, we gain insights into the meticulous process of ensuring robust security. Just as a detective solves a mystery by piecing together clues, we can enhance security by understanding and addressing each vulnerability in detail. The journey of securing Binder highlights the importance of continuous vigilance, proactive measures, and thorough testing in the ever-evolving landscape of cybersecurity.