Binders In Android (part I)

Ashu Tyagi
5 min readSep 26, 2020

--

Photo by Jonathan Kemper on Unsplash

Introduction

First of all, why do we even need to know about binder? Have you ever thought what prevents us from tricking the system into hiding the soft- input keyboard which is being used by some other application, or releasing a wake lock acquired by another application, or from hiding another application’s windows from the screen? Basically, how do Android’s core system services respond to requests made by third-party applications in a way that is secure?

The answer to all of these question is very simple: The Binder.

In the Android platform, the binder is used for everything that happens across processes in the core platform. Now we have good enough reasons to know about the binder so let’s dive into it.

Inter-Process Communication

Binder is an inter-process communication (IPC) mechanism. Before getting into detail about how Binder works, let’s briefly review IPC.

Processes in Android have separate address spaces and a process cannot directly access another process’s memory (this is called process isolation). This is usually a good thing, both for stability and security reasons: multiple processes modifying the same memory can be catastrophic, and you don’t want a potentially rogue process that was started by another user to dump your email by accessing your mail client’s memory. However, if a process wants to offer some useful service(s) to other processes, it needs to provide some mechanism that allows other processes to discover and interact with those services. That mechanism is referred to as IPC.

Binder

Because the standard IPC mechanisms weren’t flexible or reliable enough, a new IPC mechanism called Binder was developed for Android. Binders are the cornerstone of Android’s architecture. It abstracts the low-level details of IPC from the developer, allowing applications to easily talk to both the System Server and others’ remote service components.

Binder Implementation

As mentioned earlier, a process cannot access another process’s memory. However, the kernel has control over all processes and therefore can expose an interface that enables IPC. In Binder, this interface is the /dev/binder device, which is implemented by the Binder kernel driver. The Binder driver is the central object of the framework, and all IPC calls go through it.

But how is data actually passed between processes? The Binder driver manages part of the address space of each process. The Binder driver-managed chunk of memory is read-only to the process, and all writing is performed by the kernel module. When a process sends a message to another process, the kernel allocates some space in the destination process’s memory and copies the message data directly from the sending process. It then queues a short message to the receiving process telling it where the received message is. The recipient can then access that message directly (because it is in its own memory space).

Higher-level IPC abstractions in Android such as Intents (commands with associated data that are delivered to components across processes), Messengers (objects that enable message-based communication across processes), and ContentProviders (components that expose a cross-process data management interface) are built on top of Binder. Additionally, service interfaces that need to be exposed to other processes can be defined using the Android Interface Definition Language (AIDL), which enables clients to call remote services as if they were local Java objects.

Binder Tokens

An interesting property of Binder object is that each instance maintains a unique identity across all processes in the system, no matter how many process boundaries it crosses or where it goes. This facility is provided by the Binder kernel driver as explained above.

Each Binder’s object reference is assigned either:

  1. A virtual memory address pointing to a Binder object in the same process, or
  2. A unique 32-bit handle (as assigned by the Binder kernel driver) pointing to the Binder’s virtual memory address in a different process.

The Binder kernel driver maintains a mapping of local addresses to remote Binder handles (and vice versa) for each Binder it sees, and assigns each Binder’s object reference its appropriate value to ensure that equality will behave as expected even in remote processes. The Binder’s unique object identity rules allow them to be used for a special purpose: as shared, security access tokens.

Binders follow capability-based security model in which programs are granted access to a particular resource by giving them an unforgeable capability that both:

  1. References the target object and,
  2. Encapsulates a set of access rights to it.

Because capabilities are unforgeable, the mere fact that a program possesses a capability is sufficient to give it access to the target resource; there is no need to maintain access control lists (ACLs) or similar structures associated with actual resources. So it establishes the fact that:

Binders are globally unique, which means if you create one, nobody else can create one that appears equal to it.

For this reason, the application framework uses Binder tokens extensively in order to ensure secure interaction between cooperating processes: a client can create a Binder object to use as a token that can be shared with a server process, and the server can use it to validate the client’s requests without there being any way for others to spoof it.

Let’s see some code to understand better

Looking at the PowerManager source code helps us understand what’s happening under the hood (note that some of the source code has been cleaned up for the sake of brevity):

Let’s walk through the code step-by-step:

  1. The client application requests an instance of the PowerManager class in onCreate(). The PowerManager class provides an interface for the client application to talk to the global PowerManagerService, which runs in the System Server process and is in charge of managing the device’s power state.
  2. The client application creates and acquires a wake lock in onCreate(). The PowerManager sends the WakeLock’s unique Binder token as part of the acquire() request. When the PowerManagerService receives the request, it holds onto the token for safe-keeping and forces the device to remain awake.
  3. The client application releases the wake lock in onDestroy(). The PowerManager sends the WakeLock’s unique Binder token as part of the request. When the PowerManagerService receives the request, it compares the token against all other WakeLock tokens it has stored, and only releases the WakeLock if it finds a match. This additional “validation step” is an important security measure put in place to guarantee that other applications cannot trick the PowerManagerService into releasing a WakeLock held by a different application.

Wrapping Up

Binder tokens are the driving force behind interaction in the framework. Although their existence for the most part is hidden from developers, Binder tokens are used extensively in the system for security.

Two other notable Binder features are reference counting and death notification (also known as link to death). Reference counting guarantees that Binder objects are automatically freed when no one references them and is implemented in the kernel driver. We’ll cover this in part 2 of this article.

References:

If you like this article don't forget to clap and share!!

--

--