原文地址:http://www.androiddesignpatterns.com/2013/07/binders-window-tokens.html
安卓的一项核心设计思想是希望能提供一个不须要依赖中央检验部门来检验程序请求的开放平台.为此,Android使用了程序沙盒和Linux的进程分离来防止程序以无法控制和不安全的方式訪问系统内部或者其它程序.这样的架构是开发人员与使用者共同选择的:既不须要额外的保护来防止恶意程序,同一时候系统要自己主动的处理好全部事情.
在非常长一段时间我对这样的安全机制都是知其然却不知其所以然,可是近期我開始好奇了起来.究竟是什么机制能防止我们欺骗系统,比如使我们不能通过一个程序来隐藏还有一个程序的界面?简单来说,Android的核心系统服务怎样既高效又安全地响应第三方应用程序的请求的呢?
出乎我意料的是,这全部问题的答案都异常简单:是Binder.Binders是Android系统架构的基石;他们为开发人员抽象了IPC底层的很多细节,使程序能简单地与系统服务或者其它远程服务组件对话.并且Binder还有更多非常cool的功能,所以它在系统中被广泛的使用,贯穿整个系统,使底层framework能解决好安全问题.这篇文章将具体解读这些功能中的当中一种,Binder 令牌(tokens).
Binder 令牌(Tokens)
binder有一个有趣的属性:不管跨越了多少个进程,每一个实例在整个系统中都仅仅维护同一个唯一的ID.这是由Binder内核驱动通过分析每一个Binder的transaction后为其分配的一个32位int值.为了保证Java中的"=="操作能适用于Binder的唯一性与跨进程的对象身份约定,Binder对象的引用的推断相对于其它对象有一些不同.准确的讲,每一个Binder对象引用都是由下面两者之中的一个分配的:
- 同一个进程中指向一个Binder对象的虚拟内存地址,或
- 一个唯一的32位句柄(由Binder内核驱动分配的)指向不同进程中Binder的虚拟内存地址.
Binder内核驱动中为每个Binder都维护了一个本地地址与远程Binder句柄的Map(反之亦然),然后为每个Binder对象的引用都分配了一个合适的值,保证他们在远程进程中也能相同的按预期工作.
Binder的唯一对象ID规则使它们有了一项特殊的用途:共享,安全訪问令牌(shared,security access token)(文档中明白提示了Binder能够被这样使用"你能够简单的实例化一个原始的Binder对象直接用于跨进程共享").Binders是全局唯一的,这意味着你生成了一个,没有其它不论什么人能生成还有一个全然同样的.因此,系统的frameword广泛地使用Binder令牌来保证跨进程交互的安全性:一个client能够通过创建一个Binder对象作为令牌与服务进程共享,而且服务端能使用它验证来自client的请求排除全部伪造请求.
我们来通过一个简单的样例看看它是怎样工作的.假如一个程序向PowerManager发出了一个请求,请求获得屏幕唤醒锁(后面会释放掉):
/**
* An example activity that acquires a wake lock in onCreate()
* and releases it in onDestroy().
*/
public class MyActivity extends Activity {
private PowerManager.WakeLock wakeLock;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "My Tag");
wakeLock.acquire();
}
@Override
protected void onDestroy() {
super.onDestroy();
wakeLock.release();
}
}
阅读PowerManager的源代码能够帮助我们理解底层发生了 一些什么(源代码经过精简):
/**
* The interface that applications use to talk to the global power manager
* system service.
*
* @see frameworks/base/core/java/android/os/PowerManager.java
*/
public final class PowerManager {
// Our handle on the global power manager service.
private final IPowerManager mService;
public WakeLock newWakeLock(int levelAndFlags, String tag) {
return new WakeLock(levelAndFlags, tag);
}
public final class WakeLock {
private final IBinder mToken;
private final int mFlags;
private final String mTag;
WakeLock(int flags, String tag) {
// Create a token that uniquely identifies this wake lock.
mToken = new Binder();
mFlags = flags;
mTag = tag;
}
public void acquire() {
// Send the power manager service a request to acquire a wake
// lock for the application. Include the token as part of the
// request so that the power manager service can validate the
// application's identity when it requests to release the wake
// lock later on.
mService.acquireWakeLock(mToken, mFlags, mTag);
}
public void release() {
// Send the power manager service a request to release the
// wake lock associated with 'mToken'.
mService.releaseWakeLock(mToken);
}
}
}
发生了一些什么呢?我们来一步步阅读代码:
- client程序在onCreate()中请求了PowerManager类的一个实例.PowerManager类提供给client程序一个与执行在系统服务进程中的负责设备电源状态(比如决定屏幕亮度,检查设备是否插入dock等)的全局PowerManagerService对话的接口.
- client程序在onCreate()中创建并获得了一个唤醒锁(wake lock).PowerManager发送了一个WakeLock的创建的唯一的Binder令牌作为acquire()请求的參数.当PowerManagerService接收到了请求,它将接收到的令牌安全的保存起来,并强制设备保持唤醒状态,直到...
- 客户程序在onDestroy()中释放了唤醒锁.PowerManager发送了WakeLock创建的唯一的Binder令牌作为请求的參数.当PowerManagerService接收到请求,它将这个令牌与它保存的全部令牌进行比較,假设发现同样的就释放唤醒锁.这额外的一步"确认步骤"是保证PowerManagerService不会被别的应用程序欺骗而释放唤醒锁的重要安全措施.
因为它们的对象唯一性,Binder令牌在系统中被广泛(任意选择一个文件 frameworks/base/services/java/com/android/server,就能发现它使用了几种形式的Binder令牌.还有一个非常cool的样例涉及状态栏,通知管理,和系统UI.详细的说,StatusBarManagerService维护一个全局的Binder令牌到通知的Map.当NotificationManagerService发出一个请求使状态栏管理器加入一个通知到状态栏,状态栏管理器就生成一个唯一的Binder令牌同一时候传递到通知管理器和系统UI.这样三方都知道了通知的Binder令牌,不论什么通知的改变(比如通知管理器取消了一条通知或者系统UI侦測到用户将一条通知划掉)都会首先通过状态管理器.这使的3个系统服务能更易保持同步:状态栏管理器能集中控制全部的当前正在显示的通知而不用与系统UI和通知管理器互相交互.)用于安全保障.也许是在全部framework中最有意思的样例就是"窗体令牌"(window
token)了,接下来我们来讨论它.
窗体令牌(Window Tokens)
假设你曾翻阅过官方关于View类的文档,你可能会困惑于getWindowToken()方法不知道它的意义.顾名思义,一个窗体令牌是一种特殊的Binder令牌,窗体管理器用于唯一标识系统中的一个窗体.窗体令牌对于安全十分重要,由于它们会阻止恶意程序出如今其它程序界面之上.窗体管理器通过要求应用程序将它们的窗体令牌作为加入或者删除一个窗体的參数传递过来(拥有 android.permission.SYSTEM_ALERT_WINDOW
权限的程序,即"在其它程序界面上绘制"权限,对此规则是个例外.Facebook Messenger和DicePlayer是两个经常使用的需求这个权限的程序,而且用此在后台服务中在其它程序的界面上加入窗体).假设令牌不匹配,窗体管理器拒绝请求并抛出一个BadTokenException,假设没有了窗体令牌,这必要的身份建议步骤就不可能实现,窗体管理器就没办法防止防止恶意程序了.
通过这一点,你可能想知道自己在实际开发中何时须要一个窗体令牌.这里有几个样例:
- 当一个应用程序第一次启动,ActivityMangerService(这是一个全局系统服务,执行于系统服务进程中,负责启动和管理新的组件,比如Activities和Services,同一时候它还涉及维护OOM调整,被用于内核低内存时的处理,权限,任务管理等)创建了一个特殊的窗体令牌称之为应用程序窗体令牌(application window token),它唯一地标识应用程序顶层容器的窗体(你能够通过调用getApplicationWindowToken()获得一个引用).Activity管理器将这个令牌同一时候传给应用程序和窗体管理器,然后每次应用程序想要加入窗体的时候都要向窗体管理器传入这个令牌.这确保了应用程序与窗体管理器的安全交互(由于使得别的程序不可能向顶层加入窗体),同一时候也让Activity管理器向窗体管理器直接发送请求变得更简单.比如,Activity管理器能够说,"隐藏这个令牌的全部窗体",然后窗体管理器就能正确的选择出须要关闭的窗体了.
- 实现自己定制桌面程序(Launchers)的开发人员能够与动态壁纸窗体交互,通过调用sendWallpaperCommand(IBinder windowToken, String action, int x, int y, int z, Bundle extras)使之直接位于后面.为了确保除了桌面没有别的应用程序可以与动态壁纸交互,framework须要开发人员传入一个窗体令牌作为该方法的第一个參数.假设窗体令牌与当前位于壁纸前的Activity的窗体不匹配,这条命令将被忽略而且打印出一条警告.
- 应用程序能通过调用hideSoftInputFromWindow(IBinder windowToken, int flags) 方法请求InputMethodManager隐藏软键盘,可是必须提供一个窗体令牌作为參数,假设令牌不匹配当前接受输入的窗体令牌,InputMethodManager会拒绝请求,这确保恶意程序无法强制关闭由其它程序打开的软键盘.
- 手动加入新窗体到屏幕上的应用程序(比如,使用addView(View,WindowManager.LayoutParams)方法)可能须要通过设置WindowManager.LayoutParams.token属性来指定他们应用程序的窗体令牌.一般正常的程序都不太会这样做,由于在使用getWindowManager()方法时返回的WindowManager对象已经自己主动为你设置好了令牌值.也就是说,假设在以后你遇到须要从后台服务向屏幕加入一个窗体这样的情况时,你要知道你应该手动设置你程序的窗体令牌才干成功.
总结
虽然它们的存在对于大部分开发人员来说是屏蔽掉的,可是Binder令牌在系统被广泛应用于安全性.Android是一个大规模的分布式协作系统依赖于Binder对象在整个设备上的全部进程中都是唯一的。Binder令牌是整个framework相互协作的背后驱动力,假设没有他们保证应用程序进程间的安全交互,整个系统将会非常难运作.