The Binder Character Device

The Binder Framework spans across two worlds, the user space and the kernel space. The kernel space is required, so the Binder Driver can send messages to a process. The user space on the other hand is necessary so an application can communicate with the Binder Driver. The Binder Driver is part of the Android Linux kernel and handles many low-level operations. The driver creates a character device file. In older Android versions that file was the /dev/binder file. In newer versions, the location changed to /dev/binderfs/binder with /dev/binder being a symlink to it. This file serves as the gateway for the processes in user space to send commands and data to the driver. The `ioctl` commands are sent here. The Android API provides a lot of abstractions in the form of Proxies and Managers, so a developer can use a more high-level approach for the Inter-Process Communication (IPC), instead of hand-crafting the ioctl messages.

Figure 1: Character device of the Binder interface

It should be noted that the /dev/binderfs/binder file is used for general purpose binder communication, whereas the file /dev/binderfs/hwbinder is responsible for hardware specific binder communication. The file /dev/binderfs/vndbinder is used for vendor specific binder communication. Even though there are different binder device files, all of them communicate with the same kernel driver.

Organizing the Communication

Although communication between different processes can be established by the Binder Framework, some sort of organization should be implemented. Usually, numerous applications and services interact with other services at the same time, so there must be some sort of organizational process.

The Binder Framework uses queues to organize incoming and outgoing messages. Android's Binder Framework processes messages in the order in which they have been received. Each Android app that wants to interact with other processes has its own queue. In fact, it has two!

Figure 2: Process communication queues of the Binder Driver

Each service or application that sends an IPC request will have a queue for incoming messages and one queue for outgoing messages. The outgoing queue manages and stores the messages that are generated by the requesting processes, hence the apps that requested the inter-process communication. The incoming queue, on the other hand, collects and stores all the messages from the targeted services that are intended for the requesting application process. Each queue will be overseen by the Binder Driver. A message is encapsulated as a transaction. A transaction contains a Parcel structure.

Parcels

Parcels are specific data containers that store the actual payload and some metadata. Parcels act as standard containers for transferring different data types across process boundaries. When an application uses an IPC operation, usually an Intent will be used. The Android documentation states that the Intent class extends the Parcelable interface. According to the documentation, the Parcelable interface ensures that class instances can be written to and restored from a Parcel object.

Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a non-null public static field called CREATOR of a type that implements the Parcelable.Creator interface.

Figure 3: The class Intent is, among others, extended by the Parcelable interface

Image Source: https://developer.android.com/reference/android/content/Intent 

Parcel objects themselves are data buffers that are optimized for transmission across Android. The objects inside these buffers support a range of primitive data types, e.g., integers, long integers, floating point numbers, double floating point numbers and more. Parcel objects also support custom data, such as classes created by developers. To make a custom class suitable for a parcel, the class must be extended with the interface Parcelable. The Parcelable interface is a method of serialization which was optimized for Android.

Intents

Data that is to be sent to another process must be converted to a parcel object. The parcel object itself will then be wrapped with another object, to get sent. One of the most common types of wrappers is the class Intent. Intents are a special message type and probably the most common methods for IPC within Android. Intents are used for interacting with Android Services, applications and components inside of applications, such as Activities.


Codeblock

class MainActivity : ComponentActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {

     super.onCreate(savedInstanceState)

     enableEdgeToEdge()

    setContent {

       CallerApplicationTheme {

        val mContext = LocalContext.current

        Surface {

           CallerScreen(mContext, Modifier.fillMaxSize())

        }

      }

    }

  }

}

 

@Composable

fun CallerScreen(mContext: Context, modifier: Modifier) {

  Column(

     horizontalAlignment = Alignment.CenterHorizontally,

     verticalArrangement = Arrangement.Center,

    modifier = modifier.padding(8.dp)

  ) {

    Text(

      "Tap the Button"

    )

    Button(

      onClick = {

        Intent( Intent.ACTION_MAIN).also {it ->

           it.component = ComponentName(

             "com.bdosecurity.test.calledapplication", 

             "com.bdosecurity.test.calledapplication.MainActivity" 

          ) 

           it.putExtra("keyWord", "Sent by Caller App") 

           mContext.startActivity(it)  

        }

    ) 

    { 

      Text("Open the 2nd App") 

    } 

  }

}

Figure 4: Calling another activicty via Intents (1/2)

Figure 5: Calling another activity via Intents (2/2)

After diving very deep into the called functions and classes that are used for starting the activity, the inner components call functions that handle the Parcel objects and the IBinder interface. The latter is used as an interface for the /dev/binderfs/binder file.

Figure 6: Parcel object used by Binder

Security

In the Android Binder framework, communication between different processes is tightly restricted, even though it is coordinated through a centralized manager. For security reasons, only one service manager exists within the system, serving as the authoritative point of control. This service manager prevents untrusted services from binding to it, thereby maintaining the integrity of inter-process interactions. Moreover, when the Binder forwards a request to a system process, it includes information about the calling client process. Services can then use this data to verify whether the caller is authorized to invoke specific functions of the target process. A “trusted service” in this context refers to a system component that has been granted permission to interact with other secure services or perform sensitive operations, typically enabled through system-level signatures, permissions, or predefined access control policies.

Permissions

Permissions in Android define which actions an app or service is allowed to perform, such as accessing the camera, reading contacts, or interacting with privileged system services. These permissions are granted to apps based on their UID and manifest declarations, and the Binder framework uses the calling UID and PID to check whether a caller is allowed to access a given API. Within Binder based IPC, caller side permissions are not simply assumed to be trustworthy but are always verified on the receiver side of a Binder transaction. A service typically obtains the caller’s identity using functions such as Binder.getCallingUid() and Binder.getCallingPid(), then uses APIs like Context.checkPermission or enforceCallingPermission to ensure the caller has the required permission before executing the requested operation. A simplified flow of this process is depicted below:

Figure 7: Verifying the required permissions for requesting a system service

The Binder framework not only transports method calls between processes but also carries the caller’s security context so that permission checks are consistent and centralized on the callee side. By combining UID/PID identity with the permission system, Binder ensures that only authorized callers can invoke privileged functions, forming a key part of Android’s defense-in-depth security architecture.

Binder as an Attack Surface

Despite its security-focused design, Binder is a large and complex kernel subsystem and is exposed to every untrusted and even isolated app, which makes it a high‑value attack surface. Vulnerability research over the last years has repeatedly focused on Binder’s object lifetime management, reference counting, and internal allocators, where subtle bugs can lead to powerful kernel exploits.

CVE‑2023‑20938 (use‑after‑free in Binder driver)

CVE-2023-20938 is a critical use-after-free (UAF) vulnerability in the Android Binder kernel driver, specifically in the binder_transaction_buffer_release function of binder.c. It stems from improper input validation during transaction buffer cleanup, where an unaligned offsets_size causes Binder to skip processing objects in a transaction but still attempts to clean them up, decrementing reference counts on unprocessed binder_node objects and freeing them prematurely. An untrusted app could exploit this via crafted Binder transactions to create dangling pointers in binder_buffer structures, enabling heap leaks (e.g., reading ptr and cookie fields from freed nodes during binder_thread_read), unlinking primitives for arbitrary kernel memory writes, and ultimately root privilege escalation on devices with GKI kernels 5.4/5.10; a full exploit chain was demonstrated by Google's Android Red Team at OffensiveCon 2024, affecting fully patched devices until the complete fix in July 2023 Android Security Bulletin (initial patch in February was incomplete, leading to CVE-2023-21255).

For a more detailed description, see the references.

CVE‑2022‑20421 (improper cleanup / UAF)

CVE-2022-20421 represents another UAF flaw in Binder's improper cleanup logic, affecting the driver’s handling of binder_node reference counting during transaction processing and error paths. The root cause lies in mismatched decrementing of node references when transactions fail midway, allowing a local attacker to free kernel objects while pointers to them remain active in transaction buffers or thread states. Exploitation involved sending malformed Binder transactions to trigger the UAF, leaking kernel heap data and achieving arbitrary read/write primitives for sandbox escape and privilege escalation; public analysis ties it to patterns in Binder's object lifetime management, with patches issued in the November 2022 Android Security Bulletin.

For a more detailed description, see the references.

This article was written by