Skip to content

02 — Android Binder IPC

Overview

Binder is Android's custom inter-process communication mechanism — a kernel driver that has been part of Android since its first release in 2008. Almost every meaningful operation on Android goes through Binder: starting an Activity, making a network request, playing audio, accessing the camera, querying content providers, and nearly all communication between apps and system services. Android's architecture is explicitly a system of cooperating processes separated by Linux UIDs, and Binder is the glue that allows them to communicate.

What makes Binder interesting is a set of design decisions that distinguish it from the UNIX IPC alternatives available on Linux: one-copy data transfer instead of two-copy (making large transfers faster), kernel-enforced identity (callers cannot lie about their UID/PID), and an object-oriented remote procedure call model that generates Java, C++, and Rust client/server code from interface definitions. Binder is not just a data pipe — it is a capability system where holding a Binder reference is itself a proof of authorization.

Prerequisites

  • Linux IPC mechanisms: pipes, UNIX sockets, shared memory (see 15-networking/06-unix-sockets.md)
  • Linux kernel driver basics and ioctl
  • Android architecture overview (see 36-mobile-operating-systems/01-android-architecture.md)
  • Basic understanding of Java proxies and remote procedure calls

Historical Context

Binder was originally written by Dianne Hackborn at Be, Inc. for the BeOS operating system's OpenBinder project. When Google acquired Android, Inc. in 2005, Binder came with the platform. It was adapted as a Linux kernel driver and has been the core of Android's IPC architecture ever since.

Binder is notable as a kernel driver specifically for a use case (application IPC) that Linux upstream has not merged — the Linux kernel community has historically preferred improving existing IPC mechanisms rather than accepting the Binder driver. Multiple attempts to upstream Binder have been made (with partial success in recent years for the Binder driver itself, which landed in mainline Linux 5.x), but it remains primarily an Android-specific component.

Why Not UNIX Sockets or Pipes?

The design rationale for a custom IPC mechanism:

Mechanism Copies Identity Object refs Overhead
Pipe 2 None No Low
UNIX socket 2 SCM_CREDS (opt) No Medium
Shared memory 0 None No Setup cost
Binder 1 Kernel-enforced Yes Medium
D-Bus (Linux) 2 Yes No High

Two copies with sockets: Data goes from user process A's buffer -> kernel socket buffer -> kernel socket buffer -> user process B's buffer. Two memcpy operations.

One copy with Binder: Binder uses mmap to create a shared region between the kernel driver and the receiving process. Data goes from user process A's buffer -> Binder-managed kernel/receiver shared region. One memcpy. The receiver reads directly from mapped memory.

Kernel-enforced identity: With UNIX sockets, SCM_CREDS can be used optionally and is somewhat complex. With Binder, every transaction carries the caller's UID and PID as filled in by the kernel — the caller cannot forge these values. This is the foundation of Android's permission model.

Binder One-Copy IPC Diagram

Process A (caller)                  Process B (callee)

User space:                         User space:
  +------------------+                +--------------------+
  | Binder proxy     |                | Binder stub        |
  | (AIDL-generated) |                | (AIDL-generated)   |
  +--------+---------+                +--------+-----------+
           |                                   |
           | transact()                        | onTransact()
           |                                   |
           v                                   ^
  [data buffer in                    [mmap-shared region]
   process A's heap]                  |
           |                          |
           | ioctl(BINDER_WRITE_READ) |
           |                          |
+----------v--------------------------+---------+
|            KERNEL - Binder Driver             |
|                                               |
|  1. Receive transaction from A               |
|  2. Identify target process B                |
|  3. Allocate in B's mmap region              |
|  4. Copy data: A's buffer -> B's mmap region |  <- ONE COPY
|     (memcpy: one copy only)                  |
|  5. Attach caller UID/PID to transaction     |
|  6. Wake B's Binder thread                   |
+-----------------------------------------------+

Result: B reads directly from its mmap region
        No second copy needed

Binder Kernel Driver Internals

The Binder driver lives at /dev/binder (or /dev/hwbinder for HAL services, /dev/vndbinder for vendor services). Processes open the device, then:

  1. mmap: Each process mmaps a region from the Binder driver's address space (up to 1MB by default, 4MB for system server). This shared region is where incoming transaction data is placed.

  2. ioctl BINDER_WRITE_READ: The primary interface. The argument is a binder_write_read struct containing a write buffer (commands to send) and read buffer (responses/incoming transactions to receive).

struct binder_write_read {
    binder_size_t    write_size;
    binder_size_t    write_consumed;
    binder_uintptr_t write_buffer;   // Commands: BC_TRANSACTION, etc.
    binder_size_t    read_size;
    binder_size_t    read_consumed;
    binder_uintptr_t read_buffer;    // Responses: BR_TRANSACTION, etc.
};
  1. binder_transaction: The kernel structure for one IPC call:
struct binder_transaction_data {
    binder_uintptr_t target;    // Binder node handle or ptr
    __u32            code;      // Method code (AIDL-defined)
    __u32            flags;     // One-way vs. two-way, etc.
    pid_t            sender_pid;  // Set by kernel
    uid_t            sender_euid; // Set by kernel
    binder_size_t    data_size;
    binder_size_t    offsets_size;
    binder_uintptr_t data;       // Points into receiver's mmap region
    binder_uintptr_t offsets;    // Offsets of embedded Binder objects
};

The offsets field is critical: it tells the kernel where Binder object references (other Binder interfaces, file descriptors) are embedded in the data buffer. The kernel fixes up these references as part of the transaction — translating handle numbers between the caller and callee's namespaces.

AIDL: Android Interface Definition Language

AIDL (Android Interface Definition Language) defines the interface contract for a Binder service. The AIDL compiler generates: - A Java/C++/Rust Proxy class for the client side - A Java/C++/Rust Stub abstract class for the server side to implement - Java Parcel marshaling/unmarshaling code

// IRemoteService.aidl
interface IRemoteService {
    int add(int a, int b);
    void registerCallback(ICallback cb);
}

Generated Java client proxy (simplified):

public int add(int a, int b) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    try {
        data.writeInterfaceToken(DESCRIPTOR);
        data.writeInt(a);
        data.writeInt(b);
        mRemote.transact(ADD_TRANSACTION, data, reply, 0);
        reply.readException();
        return reply.readInt();
    } finally {
        data.recycle();
        reply.recycle();
    }
}

The mRemote.transact() call invokes ioctl(BINDER_WRITE_READ) on /dev/binder.

Binder Threading Model

Each process has a Binder thread pool to handle incoming transactions. The thread pool is managed by the Binder driver in cooperation with the process:

  • BC_ENTER_LOOPER: The main Binder thread registers itself (typically the main thread via ProcessState::self()->startThreadPool())
  • BC_REGISTER_LOOPER: Additional Binder threads added dynamically by the driver request
  • Default maximum: 16 Binder threads per process (configurable, set at process startup)
Process B's Binder thread pool:
  Thread-1 (looper)   <-- handling transaction from Process A
  Thread-2 (looper)   <-- idle, waiting
  Thread-3 (spawned)  <-- handling transaction from Process C
  ...
  Thread-16 (max)     <-- all threads occupied = no more concurrent IPC

When all threads are occupied, incoming transactions block on the caller's side until a thread frees up. This is a common source of ANR (Application Not Responding) in Android applications — slow Binder transactions block the caller's thread.

The driver spawns a new thread (sends BR_SPAWN_LOOPER to the process) when all current threads are busy and the thread count is below the maximum.

ServiceManager: The Binder Context Manager

servicemanager is a privileged process (UID 1000, system) that acts as the Binder name registry. The "context manager" is special: it holds handle 0 in every process's Binder handle space. To find a service by name:

// Java framework layer:
IBinder binder = ServiceManager.getService("audio");
IAudioService audioService = IAudioService.Stub.asInterface(binder);

Internally: 1. ServiceManager.getService("audio") calls transact() to context manager (handle 0) 2. Context manager returns the Binder handle for the audio service 3. The process caches this handle and uses it for future audio service calls

System services (AudioService, WindowManagerService, ActivityManagerService, PackageManagerService) register themselves with servicemanager at startup.

Binder Security Model

Kernel-Enforced Identity

Every Binder transaction carries sender_pid and sender_euid filled in by the kernel driver, not the calling process. The callee can always call Binder.getCallingUid() and Binder.getCallingPid() to retrieve verified caller identity:

@Override
public void doSomethingSensitive() {
    int callerUid = Binder.getCallingUid();
    // Check permission based on UID
    if (!isAuthorized(callerUid)) {
        throw new SecurityException("Not authorized");
    }
    // proceed
}

This identity cannot be spoofed. Even if a malicious app could forge a Binder transaction byte-for-byte, the kernel overwrites sender_euid with the actual UID of the calling process before delivering the transaction.

Permission Checking

Android's permission system is layered on top of Binder: - Signature-level permissions: Only apps signed with the platform certificate can call certain system APIs - Dangerous permissions: User-granted at runtime, checked by system services via PackageManagerService - UID-based isolation: Each app has a unique UID; accessing another app's files or Binder interfaces requires explicit grants

App calls Camera API:
  App (UID 10055)
    -> Binder -> CameraService (UID system)
       -> CameraService.checkPermission("android.permission.CAMERA", 10055)
          -> PackageManagerService (via Binder)
             -> Returns: permission GRANTED (user previously approved)
          -> Open camera hardware

Binder vs. UNIX Socket Performance

Binder's one-copy advantage matters for larger messages. For small messages (< 64 bytes), the overhead differences are in the noise. For large data transfers:

Message size: 1 MB

UNIX socket:
  1. memcpy: app_A_buf -> kernel_socket_buf    (~250 µs)
  2. memcpy: kernel_socket_buf -> app_B_buf    (~250 µs)
  Total: ~500 µs + syscall overhead

Binder:
  1. memcpy: app_A_buf -> app_B_mmap_region    (~250 µs)
  Total: ~250 µs + ioctl overhead
  (+ initial mmap setup, amortized)

In practice for Android's usage patterns (many small transactions for UI events, occasional large transactions for content), the performance advantage is real but not the primary reason Binder was chosen. Identity enforcement and object lifecycle management are the more important properties.

Production Examples

Activity Manager Service

The most heavily-used Binder service on Android is ActivityManagerService (AMS), which manages the application lifecycle. Starting an app:

Launcher app -> Binder -> AMS: startActivity(intent)
AMS -> Binder -> Zygote: forkAndSpecialize()  [fork new process]
New app process -> Binder -> AMS: attachApplication()
AMS -> Binder -> New app: bindApplication() [send app info]
New app -> Binder -> AMS: activityIdle() [resume complete]

This entire sequence involves ~10–20 Binder transactions.

SurfaceFlinger

SurfaceFlinger (the compositor) communicates with apps via Binder for Surface allocation and configuration. Each View.draw() cycle that updates a frame involves Binder transactions to acquire and release Surface buffers.

Hardware Abstraction Layer (HAL)

Android's HAL uses /dev/hwbinder for communication between the Android framework and hardware-specific vendor implementations. This isolation means the Android OS can be updated independently of vendor hardware drivers — a key architectural decision for Android's update model.

Debugging Notes

Dumping Binder State

# Dump all Binder services registered with ServiceManager
adb shell service list

# Dump specific service state
adb shell dumpsys activity   # ActivityManagerService state
adb shell dumpsys window     # WindowManagerService state
adb shell dumpsys meminfo    # Memory info including Binder allocations

# See Binder transactions in flight
adb shell cat /proc/binder/transactions
adb shell cat /proc/binder/stats
adb shell cat /proc/binder/state

Tracing Binder Calls

# Enable Binder tracing via systrace
adb shell atrace --list_categories | grep binder
adb shell atrace -b 8096 binder_driver binder_lock -t 5 -o /sdcard/trace.html
adb pull /sdcard/trace.html
# Open in chrome://tracing

Common Issues

  • ANR (Application Not Responding): Main thread blocked on a synchronous Binder transaction for > 5 seconds. Use adb shell dumpsys activity to see the current stack trace. Fix: move slow Binder calls off the main thread.
  • Binder buffer overflow: A process receives more data than its mmap region (1MB default) can hold. Results in FAILED_TRANSACTION error. Fix: batch transactions, use ashmem/MemoryFile for large data.
  • Dead Binder: A service process dies while clients hold its Binder reference. Clients receive a DeadObjectException. Fix: implement IBinder.DeathRecipient.binderDied() to handle service death and reconnect.
  • Thread pool exhaustion: All 16 Binder threads occupied; new incoming transactions block. Monitor with /proc/binder/state. Fix: reduce synchronous blocking in Binder service implementations.

Security Implications

  • Binder as capability: Holding a Binder reference proves the holder was explicitly given access. Passing a Binder reference through an IPC is explicitly delegating capability. This makes Android's security model auditable — you can trace which Binder objects each process holds.
  • Binder token pattern: Use an opaque Binder reference as an unforgeable token. WindowManagerService uses this pattern: each Window has a unique Binder token; operations on a window require presenting the token, which only the creating process possesses.
  • UID-based access control: Android's app sandbox is enforced at the kernel level via UIDs. Binder's kernel-provided UID prevents apps from impersonating each other at the IPC layer.
  • Reflection attacks on AIDL: Prior to Android 9, Java reflection could access hidden AIDL interfaces. Since Android 9, a blocklist restricts reflection on internal APIs — this closed a class of privilege escalation that bypassed Binder permission checks by calling internal methods directly.

Performance Implications

  • Synchronous blocking: Default Binder calls are synchronous — the calling thread blocks until the server returns. On the main thread, this causes janky UI if server-side processing is slow.
  • One-way transactions (oneway in AIDL): Fire-and-forget. The caller does not wait for a response and the transaction is queued on the server. Used for event callbacks where the caller should not block (e.g., IApplicationThread.scheduleResumeActivity()).
  • Parcel allocation: Parcel.obtain() uses a pool to avoid allocation on every transaction. Improper Parcel.recycle() in error paths causes pool exhaustion and GC pressure.
  • Large object transfer: For > 64KB data, use MemoryFile (anonymous shared memory) and pass the file descriptor through Binder rather than copying data through the Binder buffer.

Failure Modes

  • ServiceManager not running: If servicemanager dies (rare on production Android), all service lookups fail. Android's init system restarts it, but during the restart window apps receive null from ServiceManager.getService().
  • Binder transaction too large: Transactions > 1MB fail with FAILED_TRANSACTION. The sender receives a TransactionTooLargeException. Particularly common with Bitmap data passed between processes.
  • Circular Binder deadlock: Process A holds lock L1 and calls Binder to Process B. Process B holds lock L2 and calls Binder back to Process A. Process A's Binder thread tries to acquire L1 — deadlock. Solution: never hold locks across Binder calls, or use oneway (non-blocking) transactions.

Modern Usage

  • Stable AIDL: Starting with Android 10, AIDL interfaces for HAL communication have stable versioned interfaces. The Binder ABI is frozen per-version, allowing vendor implementations to be updated independently of the Android framework.
  • Rust AIDL backend: Android 12+ includes a Rust code generator for AIDL. New system services and HAL implementations can be written in memory-safe Rust.
  • Incremental Android Framework updates via Mainline: Google Mainline (Android 10+) delivers framework updates via the Play Store. The isolation of system components via Binder enables components to be updated and restarted independently.

Future Directions

  • Binder mainline (Linux kernel): The Binder driver has been incrementally upstreamed into mainline Linux (5.x series). This improves security auditing and potentially enables non-Android use cases.
  • Reducing IPC overhead via io_uring: Research into using io_uring for Binder-style zero-copy messaging in non-Android Linux contexts.
  • Trusty TEE integration: Android's Trusty Trusted Execution Environment uses a Binder-like protocol for IPC between the normal world (Android) and the secure world, extending the Binder model into hardware security enclaves.

Exercises

  1. On a rooted Android device or emulator, run cat /proc/binder/state and cat /proc/binder/transactions. Identify: (a) how many processes have open Binder connections, (b) which process has the most threads in the Binder thread pool, (c) any transactions currently in flight.
  2. Write a minimal Android AIDL service: define an interface with int add(int a, int b), implement the service in a background process, bind from an Activity, and call the method. Use Log.d to print the caller's UID received via Binder.getCallingUid().
  3. Measure Binder transaction overhead: send 10,000 synchronous Binder calls with an empty payload and measure average round-trip time. Repeat with 1KB payload. Compare with a UNIX socket echo server under the same conditions.
  4. Implement the Binder token pattern: create a service that issues opaque Binder objects as tokens on acquireToken(), and validates tokens on subsequent calls using IBinder.equals(). Verify that a forged Binder handle is rejected.
  5. Use Android systrace to capture a trace during an Activity launch. Open in chrome://tracing and identify all Binder transactions that occur between launcher tap and the launched Activity's first frame. Count and categorize them.
  6. Read the Binder kernel driver source (drivers/android/binder.c in AOSP kernel source). Trace the code path from ioctl(BINDER_WRITE_READ) with a BC_TRANSACTION command to the point where the data is copied into the receiver's mmap region. Identify the exact memcpy call.

References

  • Hackborn, D. "Android Binder." Android Open Source Project documentation.
  • AOSP Binder documentation: https://source.android.com/docs/core/architecture/hidl/binder-ipc
  • Binder kernel driver source: https://android.googlesource.com/kernel/common/+/refs/heads/android-mainline/drivers/android/binder.c
  • Elenkov, N. (2014). Android Security Internals: An In-Depth Guide to Android's Security Architecture. No Starch Press.
  • Android AIDL documentation: https://developer.android.com/develop/background-work/services/aidl
  • Mulliner, C. & Miller, C. (2009). "Exploitation of a Linux Kernel Vulnerability in the Android Kernel." DEF CON 17.
  • Google Project Zero: "One-day Android exploits via Binder." Various writeups.
  • Android Mainline project: https://source.android.com/docs/core/architecture/mainline
  • "Binder IPC Mechanism" talk, Android Builders Summit 2013.