Binder Security
Most of the world’s authentication and authorization (auth) techniques in computer science or even in real life are either based on “who you are” (identity based) or “what you own” (possession based). The purpose of auth is to grant or deny someone access to certain resources. For the purpose of easier description, let’s call the one who requests access to certain resources “subject”, and call the one who performs the auth “authenticator”. In a “who-you-are” based auth, the authenticator performs the auth by obtaining the identity of the subject under an identification system that the subject have no control with. In a “what-you-own” based auth, the subject presents the authenticator a kind of “token” that he owns; unlike the “who-you-are” based auth, the authenticator performs the authentication by retrieving the identity of the “token”, the auth result depends on the identity of the “token” not the identity of the subject.
For example, face recognition and fingerprint based login are examples of identify based auth in computer science. This kind of auth methods are also widely used in real life. For example, you need to present your passport to the custom when entering a country, because the government needs to know who you are. A typical example of possession based auth is the movie tickets distributed by a cinema. As long as you hold a valid movie ticket that the cinema can identify, you are granted entrance into the cinema.
Some kinds of auth methods are essential for an IPC framework like Binder, because the Binder service requesters usually come from third party apps that could be malicious. Binder supports both identity based auth and possession based auth. During a Binder transaction, the source thread is the auth subject and the target BBinder
is the authenticator.
Binder identity based security
During a Binder transaction, the source thread’s process id (pid
) and effective user id (euid
) need to be sent to remote BBinder
for authentication.
We have seen this binder_transaction_data
structure in the article "Binder transaction". When a source thread initiates a transaction, it needs to fill this data structure and send to Binder driver. This is the contract data structure between Binder driver and user space for Binder transactions.
As we can see from the writeTransactionData
method, the source thread sets both sender_pid
and sender_euid
to 0. Of course they are not the source thread's real ids. The two fields are just set to zero because the tr
structure is located on stack memory so they need to be cleared. Binder driver will fill in these fields when the binder_transaction_data
structure is sent to kernel. If the source user space thread is responsible for filling in the fields, then this identity based security becomes strictly useless. Because the source thread can always fill in a privileged process id and root user id.
When the target thread handles the BR_TRANSACTION
return code, the binder_transaction_data
it reads from Binder driver already contains the sender_pid
and sender_euid
that Binder driver fills in. The mCallingPid
and mCallingUid
fields will be set to the sender's pid
and euid
respectively during a transaction and restored afterwards. During the time when the transaction buffer is handled to target BBinder
to process (between line 31 and 40), the target BBinder
can retrieve the identities of the caller to make authorization decisions.
The Binder framework provides several utility methods for retrieving the mCallingPid
and mCallingUid
:
The clearCallingIdentity
and restoreCallingIdentity
methods are used to temporarily set the mCallingPid
and mCallingUid
to the target process's ids. This mechanism is widely used in system_server
process where many different system services live. Let's assume an app process is requesting a system service "S1" to perform an action, while "S1" is processing this request, it may need another system service "S2" to perform another action. Since both "S1" and "S2" live in the same system_server
process, if "S1" doesn't call clearCallingIdentity
before talking to "S2", "S2" will think this request comes from the app process. But in factor it is now "S1" making the request so "S2" should judge based on the identity of "S1". So this mechanism can help by temporarily setting the caller identity to that of system_server
.
Similar utility methods also exist in Java that just call their native counterparts:
Let’s look at an example of identity based auth in Android:
The ActivityManagerService
is a system service running inside the system_server
process. As its name suggests, it manages the runtime activities of applications in the system. In general, an app process is allowed to perform less sensitive activities like starting an Activity
. However, the action of killing other apps is more sensitive. At the beginning of killAllBackgroundProcesses
, the method checks whether the caller has permission KILL_BACKGROUND_PROCESSES
. The checkCallingPermission
method retrieves the caller's identities and eventually calls the checkComponentPermission
method in ActivityManager
to make the decision:
As we can see, root and system user ids are always granted permission but other user ids need more security checks. (In Android, every installed application is assigned a unique Linux user id by the system.) Basically the caller needs to have the KILL_BACKGROUND_PROCESSES
permission granted. After the caller passes the permission check, the killBackgroundProcesses
method is done with its security checks. So it calls clearCallingIdentity
method to temporarily set the transaction identities to that of system_server
process itself before calling other methods, so that if there are other security checks in these methods, it will be based on the system_server
identities not the original caller's identity.
Binder token
Binder tokens provide a widely used possession based authorization method during Binder transactions. Binder tokens are just normal BBinder
instances so the term "Binder token" refers to a special usage pattern of Binder objects, not another kind of objects in Binder system. Now think of a process in Android as a big cinema, then Binder tokens are the movie tickets this cinema produces. A key feature that makes BBinder
instances to work like a movie ticket is that they are unforgeable under Binder's identity system. The process who creates these BBinder
instances can uniquely identify them. Besides, a remote process that holds references to BpBinder
instances can also recognize the identities of the underlying BBinder
s; if you compare it to a cinema in real world, it basically means cinema customers should also be able identify the movie tickets. Since a BBinder
is unforgeable under Binder's identity system, it is usually used as a security token in Android system. In most common cases, a Binder token is just a plain BBinder
object in C++ or Binder
object in Java. However, instead of using these raw Binder objects, under some use cases you want the Binder tokens to implement an interface by creating a class that inherits both BBinder
and IInterface
(or both Binder
and IInterface
in Java), then this class inherits both the unforgeable feature of BBinder
and the remote service interface described in IInterface
. In addition to performing security checks, the Binder tokens are often used to manage system resources because being uniquely identifiable means they can be used as keys to system resources.
Now let’s look into how the unforgeable feature of BBinder
instances is achieved in Binder system. I will use the notation BN_x
to denote native Binder instances where the subscript "x" is a number; the notation ID_x
denotes the unique identifier of BN_x
under a certain identification system F_x
in Binder; the notation EQ(ID_x, ID_y)
denotes a procedure that takes in two unique identifiers and outputs a boolean. What we want is that when EQ(ID_x, ID_y)
returns true, the two native Binders that generates ID_x
and ID_y
are actually the same native Binder instance; otherwise, the two native Binders that generates ID_x
and ID_y
are two different native Binder instances. If this property holds, then the unforgeable feature of native Binders is achieved. We will see how Binder achieves this kind of behavior soon. By the way, you probably won't see this kind of notations elsewhere and you won't find code in Android Binder framework that corresponds to the notations. I make these abstractions in this article merely for you to better understand and for myself to easier describe the concepts.
When we talk about native and proxy Binders, usually we are referring to the BBinder
and BpBinder
instances. However these utility classes are provided by Android native framework to present an object oriented view of Binder system, but user space programs don't need to use Android frameworks to work with Binder. For example, you can side load a native program to an Android device and talk to the Binder driver directly. The unforgeable feature of native Binders should hold even if a user space program is not using Android framework.
So first let’s understand how bare metal Binder driver makes native Binders unforgeable. As we have learned from previous articles, the driver maintains a set of binder_node
structures for the native Binders created in a process. The keys of these binder_node
structures in Binder driver is the user space memory addresses of the native Binders. For a remote process that is referencing the remote Binder, a unique handle value is allocated for the binder_ref
structure and the kernel maintains the mapping between the binder_ref
and binder_node
. So the ID_x
that the F_x
generates is simple: a value pair (id, type)
where the numerical value id
is either the registered address in binder_node
or the handle value in binder_ref
and the type
is either BINDER_TYPE_BINDER
or BINDER_TYPE_HANDLE
from the flat_binder_object
structure. The EQ()
procedure is also simple: it only outputs true when both the id
and type
are equal. I will explain the correctness of this EQ(ID_1, ID_2)
in three cases:
(1) If the type
part of ID_1
and ID_2
are different (one BINDER_TYPE_BINDER
and one BINDER_TYPE_HANDLE
), then EQ()
always yields false. This is correct because in this case the BN_1
and BN_2
that ID_1
and ID_2
are generated from must be allocated in different processes, they cannot be the same instance.
(2) If both ID_1
and ID_2
have type
equal to BINDER_TYPE_BINDER
, then both BN_1
and BN_2
are created in local process. The EQ()
yields true iff the two objects have the same memory address registered to kernel. They must be the same Binder object instance if EQ()
yields true since you cannot allocate two objects in a process that have the same address. So EQ()
behavior is correct intuitively for this case.
(3) If both ID_1
and ID_2
have type
equal to BINDER_TYPE_HANDLE
, then both BN_1
and BN_2
are created in remote processes. EQ()
yields true iff the handle value in ID_1
and ID_2
are the same. As we learned from previous articles, the Binder driver allocates a unique handle value for the binder_ref
structure in Binder driver. In a certain process, at most one instance ofbinder_ref
will be allocated for a binder_node
instance. So basically the binder_ref
and binder_node
has a one to one mapping in the scope of a certain process. So the behavior of EQ()
is also correct for this case.
But there is an important caveat for the third case. The Binder driver only keeps the binder_ref
structure alive when the weak
and strong
fields in it are not both 0. In another word, the handle value that the user space receives becomes invalid after the last user space reference to the binder_ref
is lost. As we learned from the article "Binder lifecycle management", a process uses BC_ACQUIRE
, BC_INCREFS
, BC_DECREFS
and BC_RELEASE
to change the reference counts on a binder_ref
structure. In order for EQ()
to behave correctly for the third case, we need to make sure that user space never lost all references to the binder_ref
before the EQ()
is applied.
Let’s say a process receives the handle value V1
for a certain native Binder BN_1
and then it loses all reference count on the corresponding binder_ref
. Later it regains reference to the same BN_1
through another transaction, but since the previous binder_ref
is recycled, Binder driver will allocate a new binder_ref
instance and the new handle value V2
is likely to be different than V1
. The EQ(ID_1, ID_2)
yields false incorrectly even though they both refer to BN_1
. Similarly, after this process loses all reference on binder_ref
, the process can obtain reference to a different BN_2
through another transaction, but the allocated handle value V2
for BN_2
can be the same as V1
because the binder_ref
for BN_1
is already recycled; in this case EQ(ID_1, ID_2)
yields true incorrectly because BN_1
and BN_2
are different Binder objects.
As we can see, the unforgeable nature of Binders holds even working with Binder driver directly. But in reality programs seldom directly work with Binder driver. They use BpBinder
, BBinder
in C++ code and BinderProxy
, Binder
in Java code. But remember Android code always use the IBinder
polymorphic type to point to these objects, so the ID_x
is actually the memory addresses of IBinder
s. It would be good if EQ(ID_1, ID_2)
is as simple as ID_1 == ID_2
. As we will see, this is indeed the case in Android framework. In another word, in both C++ and Java, you can just compare the memory address of the two IBinder
instances to determine whether the underlying BBinder
instances are the same. Let's see why this is correct in Android framework by discussing it under the similar three cases. I will discuss the cases under C++ first. Let's assume there are two instances of sp<IBinder>
and we refer to them as ID_1
and ID_2
.
(1) If ID_1
actually points to a BpBinder
and ID_2
actually points to a BBinder
or visa versa, EQ(ID_1, ID_2)
(ID_1 == ID_2
) always yields false. This is because they are two different types of objects so they cannot have the same memory address. This behavior is correct because BN_1
and BN_2
are two BBinder
instances created in two different processes so they cannot be the same object.
(2) If both ID_1
and ID_2
actually point to a BBinder
, then EQ(ID_1, ID_2)
yields true iff they have the same memory address. This is correct behavior because in this case both Binder objects are created in local process, the two BBinder
objects can be the same instance iff they have the same memory address. This is quite a trivial case.
(3) If both ID_1
and ID_2
actually point to a BpBinder
, then EQ(ID_1, ID_2)
yields true iff they point to the same BpBinder
instance. On initial thought, this behavior cannot be correct because if there are two BpBinder
instances holding the same handle value, EQ(ID_1, ID_2)
yields false but they point to the same remote BBinder
instance. In order to make EQ()
behave correctly, we need to make sure that there will never be two BpBinder
instances holding the same handle value in Android framework. Thanks to the BpBinder
cache in ProcessState
, this requirement is satisfied.
All BpBinder
instances in Android framework are generated from this getStrongProxyForHandle
method. The mHandleToObject
field is a vector in ProcessState
containing a per-process cache for BpBinder
objects. The elements in this cache are handle_entry
structures and the binder
field stores the memory address of the cached BpBinder
and the refs
fields is the memory address of the weak pointer type in the BpBinder
. The index of a handle_entry
structure inside the vector cache is the handle value of the BpBinder
. The lookupHandleLocked
method is called to look up for an existing BpBinder
in the cache with a specific handle value, if not found, the method will return an uninitialized handle_entry
. Line 11 checks whether there is a BpBinder
in the cache, if not found then line 12 to 15 will create a new instance of BpBinder
, put it in the cache and return to caller. Otherwise, line 17 just returns the BpBinder
instance to the caller. So in short, this method makes sure that only one instance of BpBinder
exists for a certain handle value.
Now we know what the getStrongProxyForHandle
does in general, there are some details worth looking at. First of all, let's take a closer look at the constructor and destructor of BpBinder
:
We want a BpBinder
to mirror the lifetime of the corresponding binder_ref
in Binder driver; as we talked about earlier, user space needs to use the four BC_*
commands to keep the binder_ref
alive and BpBinder
does just that. It uses BC_INCREFS
in it's constructor to keep the binder_ref
alive and uses BC_DECREFS
in its destructor to free the weak reference on the binder_ref
to let the driver free up the binder_ref
. BC_ACQUIRE
and BC_RELEASE
will be sent to the driver in onFirstRef
and onLastStrongRef
to increment and decrement the strong reference count on the binder_ref
structure. However, in regular object lifetime, an object will be destructed when last strong reference on it is removed; but we don't want that to happen on BpBinder
because the binder_ref
structure can get recycled while there are still weak references on the user space BpBinder
. So a extendObjectLifetime
method is designed to handle this case. By setting the lifetime to OBJECT_LIFETIME_WEAK
, a BpBinder
and binder_ref
won't get prematurely deallocated when the last strong reference on the BpBinder
is gone. So in short, a BpBinder
will only gets deallocated when all references on it are gone and it has the same lifetime as binder_ref
. The smart pointer interface extendObjectLifetime
is specially designed for BpBinder
. (BpRefBase
also uses this feature but it's just because BpBinder
uses it.)
And because of the special lifetime of BpBinder
, a force_set
interface in sp
and a forceIncStrong
method in RefBase
need to be implemented so that onFirstRef
of BpBinder
will be called each time the BpBinder
gets a first strong reference and send a BC_ACQUIRE
to Binder driver. (Otherwise, an object will be deallocated when the last strong reference on it is gone, it cannot "get the first strong reference" for a second time.) This force_set
feature is only used by BpBinder
, specifically only in the getStrongProxyForHandle
method.
The attemptIncWeak
is another smart pointer interface that's specially designed for getStrongProxyForHandle
. The method returns true only when the current weak reference count on the object is greater than 0. This handles a race condition in getStrongProxyForHandle
: Note that in the destructor of BpBinder
, expungeHandle
is called to remove the cache entry in ProcessState
. Let's say there are two Binder thread T1
and T2
and T1
releases the last reference on a BpBinder
so it is calling the destructor of the BpBinder
to remove the cache entry; at the same time T2
receives the same handle value in a transaction and needs to get an instance of BpBinder
. In this race condition the BpBinder
instance is being released but its entry in the cache hasn't been cleared. T2
shouldn't get back an instance of BpBinder
that's about to be released so it calls attemptIncWeak
to detect such races. Under such race conditions, a new instance of BpBinder
needs to allocated.
All these code are here for the purpose to make EQ(ID_1, ID_2)
behave correctly under the third case and now we have seen why it works in C++ code under Android frameworks. So basically as long as the code has references to two IBinder
instances, you can use the equality operator to check the uniqueness of the underlying BBinder
objects. Note that you need to hold and compare the references not the memory address values, so the following comparison is wrong and may yield incorrect results:
The code doesn’t hold a reference to sp1
so it is possible that the after line 7 the original BpBinder
that sp1
points to is destructed and removed from the cache. In this case, even if sp2
actually references the same remote BBinder
as sp1
, the BpBinder
that sp2
points to is a different BpBinder
instance, so raw_ptr1 == raw_ptr2
will probably be false even if sp1
and sp2
actually reference the same remote BBinder
. Similarly, raw_ptr1
and raw_ptr2
might happen to be equal when sp1
and sp2
reference different remote BBinder
instances.
Now I will briefly touch on why the simple equality comparison works in Java code as well. I will not go into detail about it since the idea that make this possible in Java is similar to that in C++.
We talked about the relationship between Java Binder
and C++ BBinder
in the article "Binder architecture and core components", basically a Java BBinder
has a peer JavaBBinderHolder
and it will be deallocated at the same time the Java Binder
is garbage collected. JavaBBinderHolder
lazily initializes a JavaBBinder
class which inherits BBinder
and it is the kind of BBinder
peered by Java Binder
. So basically a Java Binder
and its peer BBinder
have the same lifetime.
The Java class BinderProxy
peers a native BpBinder
class. There is a special kind of BinderProxy
cache in this class called ProxyMap
that serves a similar purpose as the BpBinder
cache in ProcessState
. What this ProxyMap
does is that for a given handle value, the same BinderProxy
instance will be used as long as the BinderProxy
is still strongly referenced in Java code. So in Java code you can also compare two instances of IBinder
under any of the three cases discussed above.
As a conclusion, BBinder
instances are unforgeable objects under Binder frameworks. The way you can check their identities is by simply using the intuitive equality operator ==
. You can take this simplicity for granted but the Binder framework tried hard to make that simplicity possible. It will helpful to understand how that kind of simplicity is achieve by Binder framework.
For example, in the HamKing sample project [1], the unforgeable feature of Binder objects are exploited in a lot of places:
The mOrders
field is a list of pending orders and the elements in the list are OrderRecord
objects which extend from an auto-generated Stub
so the OrderRecord
class is a local Binder object. It might not be that obvious, but you can use a list to manage the orders simply because Binder objects are unforgeable. These OrderRecord
instances will be sent to client app in requestOrder
and sent back from client app in pickupOrder
later, even though it passes process boundaries it can still maintain its identity so the pickupOrder
method can just traverse the list and find out the corresponding order.
Besides, the cancelOrder
method uses the ==
operator to find out all orders to remove based on the input session
, and the removeDeadOrders
method filters all orders based on the mCreditCard
field. These operation are possible all because these Binder objects are unforgeable.
Window token
Window token is one of the best examples in Android to study the usages of Binder tokens. A window refers to a rectangle layer on the display that has certain properties like size, z-order, transparency, focusability, etc. The WindowManagerService
(WMS) orchestrates all windows on a display based on the properties of these windows. Note that WMS doesn't manage the content rendering inside the windows, a window to WMS is just a rectangle in a 3-dimensional coordinate system. Content rendering is managed by another system service called SurfaceFlinger
. A window token is a kind of Binder token used by WMS to identify a window. When a process wants to add, remove or change a window in WMS, it needs to provide a window token in the Binder request. The WMS will identify the window token and check the validity and permissions of this token before performing the actions requested.
There are many kinds of windows in Android, probably the most common type of window is the one used by Activity
s. Before an application launches an Activity
, it needs to make a request to ActivityTaskManagerService
(ATMS) and it will receives a Binder token in the response that ATMS created and this Binder token is basically an identifier of the Activity
instance. The ATMS will keep a record of this token itself of course. After the application receives the token, it needs to use this token to add a window of type TYPE_BASE_APPLICATION
to WMS. When WMS receives the request, it checks the validity of this token with the help of ATMS and adds an application window if everything looks fine. This token is only possessed by this application, so a malicious app cannot mess with this window since it cannot duplicate a token to pass the security check of WMS.
Besides, WMS also uses these tokens as identifiers of windows to group and manage them. For example, there are some kinds of sub-windows that must reside within parent windows. For example, the window type of PopupWindow
is TYPE_APPLICATION_PANEL
which is one kind of sub-window. Unlike most other kinds of widgets, you cannot inflate a PopupWindow
in an Activity
's view tree because it floats on top of everything else, so it needs to create a separate window instance. However, by definition a PopupWindow
needs an anchor in the Activity
's view tree to display. (Otherwise you would rather just use a Dialog
.) So the window of PopupWindow
is a child of the Activity
's window so it needs to use the Activity
's window token to add the child window to WMS. WMS groups the two windows so that when the Activity
's window is removed, the PopupWindow
's window also gets removed.
Also, when an application crashes, all its windows in WMS need to be removed. Since both ATMS and WMS manages the tokens as keys, it would be very easy for WMS to identify all the windows under this application and have them removed at once.
Let’s now look into the source code of how window token is used in Android:
When ATMS handles a request to launch an Activity
, it will create an instance of ActivityRecord
for book keeping. Inside the ActivityRecord
definition there is an appToken
field which will be used as a unique identifier of an Activity
by ATMS. ATMS will send it to the application where this Activity
lives in. The application will then use this token as the window token to work with WMS. The class Token
is almost a raw Binder
: the IApplicationToken
interface only has a trivial getName
interface method. Like we talked about, any Binder
in Java can be used as a Binder token but you can piggyback some interface methods with it if you want.
The ActivityThread
is the place where an application process handles incoming Binder transactions. The inner class ApplicationThread
inside ActivityThread
is a Binder service that implements the IApplicationThread
Binder interface. While Android launches an application, an instance of ApplicationThread
will be created in the application process and ATMS will always hold a reference to it. When ATMS wants an application to do something, it will call some service methods on this IApplicationThread
. The ApplicationThread
class handles these incoming requests on Binder threads, so it needs to post messages to the application UI thread's message queue.
After ATMS receives a request to launch an Activity
, it creates a ClientTransaction
object and sends it to the application process via the scheduleTransaction
interface on mClient
. Note that ATMS processes the request to launch an Activity
but it cannot create the actual Activity
instance, because that's the job of the application. The mActivityToken
in it is the same token we have seen in ActivityRecord
. The mActivityCallbacks
and mLifecycleStateRequest
contains a list of actions the ATMS wants the application to do and the desired final state of the new Activity
. For example, ATMS may tell the application process to launch a new instance of the Activity
and the desired final state of the Activity
should be ON_RESUME
. After the application process receives the request in ApplicationThread
, a message will be posted to UI thread message queue and the message will be handled in the Handler
named mH
. The ActivityThread
uses the class ActivityClientRecord
to represent an instance of Activity
created in current application; the token
field is the Binder token created and returned from ATMS and it is used as the key in mActivities
map.
If ATMS wants this new Activity
to be in a final state ON_RESUME
, then the handleResumeActivity
method will be called to move the Activity
to that state. Inside this method the application will add a window to WMS for this Activity, because the semantic of ON_RESUME
is that an Activity
's UI should become visible:
Line 22 calls the addView
method in WindowManagerImpl
to add a window to WMS. WindowManagerImpl
manages an app's interaction with WMS. Note that the window type of an Activity
is set to TYPE_BASE_APPLICATION
. Eventually ViewRootImpl
will add the Activity
's window to WMS:
The mWindowSession
points an IWindowSession
that the application gets from WMS. An application needs to first open a session before interacting with WMS. If you compare WMS with the RemoteService
in HamKing project, then the responsibility of IWindowSession
is similar to that of IOrderSession
.
Eventually IWindowSession
will call WMS to perform the action of adding a window. The security checks in addWindow
is quite comprehensive but let's just look at a few of them.
Line 14 checks whether the window to add is a sub-window, if so it’s parent window must have already been added so line 15 tries to get parent window’s WindowState
using the incoming window token as the key, otherwise an error code will return at line 17. The window type of a PopupWindow
is TYPE_APPLICATION_PANEL
which is one kind of sub-window, the parent Activity
's window must have been added to WMS before you add a PopupWindow
's window. For example, when a new Activity
is launched and you try to show a PopupWindow
in the onCreate
callback of the Activity
, you will receive a BadTokenException
due to this error.
Line 22 tries to get the WindowToken
instance using the incoming window token as the key. The class WindowToken
, despite its name, is just a class that WMS uses to manage and group window tokens. During the process that ATMS handles the request of launching an Activity
, it has sent the window token for this Activity
to WMS so this method will find the WindowToken
for the Activity
. Otherwise it means a process tries to display an application window without the consent of ATMS, so the request will be rejected.
If everything passes, a WindowState
instance will be created for this new window instance. The WindowState
class is what WMS uses to do book keeping for an added window. After that this WindowState
instance will be added to the global mWindowMap
using the IWindow
input as the key. The IWindow
is a Binder interface that an application uses to receive callbacks from WMS about that window instance.