Binder: app to system service

Trace inter-process communication through actual framework code.

10 min readChapter 24 of 50Intermediate
In this chapter

Baseline: AOSP android-14.0.0_r1. Binder is Android's IPC system (Binder overview). A client gets a reference to an interface and makes a method-like call; when the endpoint is remote, generated/userspace code marshals data and the Binder kernel driver routes a transaction to the server process. Binder transports calls; it does not decide whether the operation is authorized.

Before you start#

Read Java, ART, and Native Interfaces, init and Android services, Zygote, ART, and system_server, The Android Security Model, and Concurrency & Object Lifetime. You need to understand Java interfaces/exceptions, process boundaries, explicit serialization, UID, permissions, and basic threads/locks. No hidden-API invocation or system modification is required.

Learning goals#

Trace one ordinary Android API operation through framework code and an AIDL service; explain proxy, transaction code, Parcel, driver, stub, server implementation, reply, and caller identity; distinguish interface-token validation from authorization; and classify common failures without assuming “Binder is broken.”

Binder building blocks#

An AIDL interface defines operations and supported data types. Generated code provides client and server bindings for a selected backend. The client-side proxy implements the interface by encoding fields and requesting a transaction. The server-side stub decodes and dispatches it to the implementation. A Parcel is a typed serialization container: it is not an arbitrary Java heap dump. A Binder node is a server endpoint; a handle is a process-local reference to that endpoint, not a global raw pointer. Binder objects and file descriptors use special reference-transfer rules.

A synchronous remote call normally blocks the calling thread while the server handles the request and returns a reply. A method marked oneway in AIDL is asynchronous: the caller returns after submission, without an ordinary result/reply (Binder threading); this is not proof that work completed. A process can die or freeze before processing it. Binder servers need one or more Binder threads to handle incoming work. Nested synchronous calls can re-enter a process, so code must consider concurrency, lock ordering, and callbacks. Never infer “one call, one thread, serialized” unless the actual implementation guarantees it.

The generated interface descriptor/token is written so the receiver can verify that a transaction is for the expected interface. It is not a secret, password, or authorization credential. The kernel can provide transaction caller identity, and framework service code must apply permission/app-op/ownership checks. Some APIs also perform local checks before IPC. Possessing an IBinder is not equivalent to having permission.

Concrete source trace: a vibration request#

This example follows a framework API operation, not a hidden ServiceManager app API. Source paths and call names are pinned to AOSP 14; follow the linked method bodies because surrounding details and public API signatures may differ in later releases. The exact path depends on which overload is used, device state, and implementation branch.

1. App-facing API and manager

An app obtains a Vibrator through the public Android API (for example the vibrator system service) and invokes a documented vibrate(...) overload. In Vibrator.java, the vibrate(...) overloads are annotated @RequiresPermission(VIBRATE). In AOSP 14, inspect Vibrator.java and SystemVibrator.java. SystemVibrator delegates to a VibratorManager (SystemVibrator.java); the concrete SystemVibratorManager holds the IVibratorManagerService proxy, obtained with ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE). The abstract VibratorManager class does not hold that field. The public API layer converts an effect/attributes and supplies caller package/UID context. This is framework Java and is distinct from a direct app call to the hidden ServiceManager lookup API.

2. AIDL contract and generated binding algorithm

The contract is IVibratorManagerService.aidl. Inspect the vibrate declaration and its argument types; among them are caller identity/package information, a CombinedVibration, vibration attributes/reason, and a Binder token. The declaration is the schema, not the implementation. Soong invokes the AIDL compiler and emits backend-specific Java bindings during a build (AIDL backends). Generated Java is not necessarily checked into the source tree. Do not mistake the following algorithm sketch for a verbatim generated file:

text
ALGORITHM SKETCH — not checked-in/generated AOSP source
proxy.vibrate(arguments):
    data = Parcel.obtain()
    data.writeInterfaceToken(DESCRIPTOR)
    write arguments in AIDL-declared order
    remote.transact(TRANSACTION_vibrate, data, reply, flags)
    read exception/result from reply, when synchronous
    recycle parcels

For an exact generated implementation, build the relevant AIDL module from the pinned checkout and inspect its generated output, or read the AIDL compiler/backend source and identify its version. Do not claim exact byte order, transaction integer, flags, or helper calls from a generic AIDL example: generation/backend options affect details. The general rule is that both sides agree on field order and type; generated code is responsible for this paired encoding/decoding.

3. Userspace, driver, dispatch, and server implementation

For a remote Binder, generated Java calls IBinder.transact (IBinder.java); framework native plumbing reaches libbinder/Binder driver interaction. The native Binder library issues BINDER_WRITE_READ ioctls to the process's Binder device (IPCThreadState.cpp, Android 14 tag). The Linux Binder driver resolves the target handle/node and queues work for a Binder thread in the server (binder_transaction in binder.c, which records from_pid and sender_euid; Binder overview). The server's generated Stub dispatches the transaction code, reads the Parcel fields in the declared order, and calls implementation code.

The service implementation is VibratorManagerService.java. Follow its AIDL method implementation and the service publication/registration path; the implementation applies policy and routes work toward the vibrator subsystem. The physical HAL and driver are a later boundary covered by HAL, Treble, and the Vendor Boundary. This call trace should stop at the framework service rather than pretend that a Java Binder call itself vibrates hardware.

For a synchronous method, a reply transaction returns through driver/userspace to the proxy; generated code reads exception/result fields and the framework method returns to the app. A local Binder can dispatch directly without a kernel round trip. Some public calls may do work locally, cache, or enqueue asynchronously, so not every call at the Java API is exactly one remote transaction.

4. Where identity and policy apply

Interface token is not authority
Descriptor / interface token
Is this transaction encoded for the expected interface? Not a secret or credential.
Binder caller UID / PID
Who is making the transaction? Use the actual transaction identity, not client-supplied claims alone.
Service authorization
Is that caller allowed this operation with these arguments under permission, app-op, and service policy?
Conceptual model. Separate questions at the service boundary. Possessing a handle or successfully submitting a transaction does not prove authorization or a hardware effect.

Follow the actual service method and its helper calls to identify authorization. The server can inspect Binder caller UID/PID during transaction handling, then enforce permissions, app-ops, package ownership, and service-specific rules. These checks are not supplied by the descriptor token. If code clears Binder calling identity to make a downstream call under the service's own identity, it must restore identity correctly (clearCallingIdentity() / restoreCallingIdentity(long) in the Binder API reference); the point where identity is cleared changes the security meaning. Do not generalize from method signatures—verify where checks occur in the pinned method path. SELinux and service-manager policy constrain which processes can discover/contact endpoints as well.

End-to-end diagram and failure distinctions

A remote synchronous Binder call
A remote synchronous Binder call1. App / proxy to Binder driver: encode Parcel → transact / libbinder. 2. Binder driver to Server / stub: route endpoint and caller identity. 3. Server / stub to Binder driver (return): dispatch, authorize, handle; reply or error. 4. Binder driver to App / proxy (return): decode reply; calling thread resumes.App / proxyBinder driverServer / stubencode Parcel → transact/ libbinderroute endpoint andcaller identitydispatch, authorize,handle; reply or errordecode reply; callingthread resumes
  1. App / proxyBinder driver

    encode Parcel → transact / libbinder

  2. Binder driverServer / stub

    route endpoint and caller identity

  3. Server / stubBinder driver

    Return. dispatch, authorize, handle; reply or error

  4. Binder driverApp / proxy

    Return. decode reply; calling thread resumes

Time runs downward. Dashed arrows show returns.

Read the text version
  1. App / proxy → Binder driver: encode Parcel → transact / libbinder
  2. Binder driver → Server / stub: route endpoint and caller identity
  3. Server / stub → Binder driver (return): dispatch, authorize, handle; reply or error
  4. Binder driver → App / proxy (return): decode reply; calling thread resumes
Source map. A conceptual remote synchronous route, not exact generated source or a recorded vibration. Local Binder can dispatch directly; oneway submission has no ordinary reply and is not completion.
text
app calls public Vibrator API
  → framework SystemVibrator / VibratorManager
  → IVibratorManagerService proxy encodes AIDL arguments
  → IBinder.transact → libbinder → Binder driver
  → server Binder thread → generated Stub dispatch
  → VibratorManagerService policy + implementation
  ← reply/exception (sync path) through driver and proxy
  → public API returns

The driver is transport and dispatch machinery; it does not validate that a vibration is allowed by app-level policy. A failure can mean: service not registered; caller denied; argument rejected; transaction too large/malformed; server died; Binder thread blocked/deadlocked; hardware/HAL unavailable; or a deliberately asynchronous API returned before completion. Each has different evidence. Do not treat a Binder handle or successful transaction submission as proof of successful hardware output.

Practice#

Prerequisites: browser/Gitiles or pinned AOSP 14 checkout. Optional runtime observation requires workstation ADB Platform-Tools and a booted disposable emulator/Cuttlefish or authorized device. The required task is source-only. Do not call hidden APIs, send arbitrary transactions, or trigger vibration during this exercise.

Trace worksheet

Use the linked source files above and fill these rows with method/symbol and file path. If you cannot verify a hop, mark it unverified rather than inserting a guessed implementation:

HopRecord
Public entryVibrator overload and API-level permission annotation/check.
DelegationSystemVibrator and VibratorManager methods that pass the request onward.
InterfaceAIDL method signature and declared argument types/order.
Generated bindingAIDL module/backend; generated output location if built, otherwise clearly labeled algorithm sketch.
TransportJava transact; native/libbinder boundary; Binder driver source entry/dispatch evidence.
ServerStub dispatch, VibratorManagerService implementation method, downstream helper.
IdentityExact caller UID/permission/app-op checks; note any clearCallingIdentity scope.
ReturnIs it synchronous or oneway? What result/error reaches caller?

Expected result is a source-linked sequence, not a claim that you executed the call. A method in an interface is not proof the server implementation is registered; a service listing is not proof of authorization or successful output. Generated code absent from the checkout is an expected limitation, not permission to fabricate its source.

Virtual evidence cases (reason from supplied facts)

  1. Name absent from service list: possible unregistered/not-yet-started service or listing visibility limitation. Do not infer permission denial or hardware failure without more evidence.
  2. Server throws SecurityException after reaching implementation: transport worked far enough to dispatch; investigate the policy check and caller identity, not the Parcel's interface token.
  3. Synchronous call hangs: consider a blocked server Binder thread, lock cycle, slow downstream call, or deadlock. The fact that the caller is blocked is not evidence the driver corrupts bytes.
  4. Oneway method returns but no visible effect: submission is not completion; server may be frozen/dead, policy may reject, or downstream hardware may not support the operation.
  5. Large payload transaction fails: inspect transaction size and consider a file descriptor/shared memory interface where the contract supports it; do not repeatedly increase payload or retry blindly.

Optional read-only device queries, from the HOST terminal:

sh
adb devices -l
adb shell service list
adb shell dumpsys -l

Expected: service names vary by image and shell visibility. unauthorized means stop and authorize only your own workstation; no device means stop, do not use a daily device. These commands list registrations, not AIDL methods, permission policy, or hardware health. Keep diagnostic dumps private. No Android runtime transaction was exercised as part of this chapter.

Knowledge check#

  1. What does the AIDL interface token establish, and what does it not establish?
  2. Explain proxy → driver → stub in one sentence each.
  3. Which thread blocks for a synchronous remote call, and where does server code run?
  4. Does successful transact submission prove hardware action completed?
  5. Name two independent causes of a failed call.

Answers#

  1. It identifies the expected interface for dispatch; it is not authentication or authorization.
  2. Proxy encodes and submits; driver routes/queues; stub decodes and dispatches to implementation.
  3. The calling thread waits for a reply; server work runs on an available Binder thread (nested calls can re-enter).
  4. No. Transport acceptance and framework/service/HAL/hardware completion are distinct.
  5. Examples: permission denial, missing service, dead server, malformed/oversized Parcel, thread starvation/deadlock, or downstream HAL failure.

Checkpoint#

Submit the filled trace worksheet with source links to the public API, AIDL contract, implementation, and one verified identity/policy check. Include a sync/oneway note and classify the five virtual evidence cases by layer. Pass if no generated code is presented as verbatim unless you actually built and inspected it, and if you distinguish descriptor validation from permission enforcement. Remediation: return to the first unverified hop, locate its caller/definition in the pinned source, and mark unavailable implementation details honestly.

Sources#

Next: Binder lifetime and deadlocks. Work through handle transfer, death notifications, nested callbacks, deferred caller identity, oneway ordering, and ambiguous retries before moving into the HAL boundary.

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.