本文将会深入Framework层了解Android权限机制是如何起作用的。
由于Android尚未引入权限控制功能,我们将会讨论如何修改Android代码来达到权限控制的目的,以及一些解决方案。
一、调用需要权限的API
我们将通过一个API调用的例子来了解Android系统是如何判断权限的:
ContentResolver resolver = getContentResolver(); Cursor cur = resolver.query( ContactsContract.Contacts.CONTENT_URI, null, null, null, ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
以上代码用于读取联系人,所以需要在AndroidManifest中加上:
<uses-permission android:name="android.permission.READ_CONTACTS" />
OK,开始通过时序图一探究竟。
/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
PackageManagerService.checkUidPermission(String, int):
public int checkUidPermission(String permName, int uid) { final boolean enforcedDefault = isPermissionEnforcedDefault(permName); synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; // 判断是否具有该权限 if (gp.grantedPermissions.contains(permName)) { return PackageManager.PERMISSION_GRANTED; } } else { HashSet<String> perms = mSystemPermissions.get(uid); if (perms != null && perms.contains(permName)) { return PackageManager.PERMISSION_GRANTED; } } if (!isPermissionEnforcedLocked(permName, enforcedDefault)) { return PackageManager.PERMISSION_GRANTED; } } return PackageManager.PERMISSION_DENIED; }
我们可以从“Android权限机制(一) 权限申请” -> “四、两个数据结构:PackageParser.Package和Settings.mUserIds” -> “2. Settings.mUserIds”得知权限信息是如何被存放进去的。
二、检查是否具有权限
因此,调用需要权限的API时候,部分会通过Context.checkPermission(String permission, int pid, int uid)来查询该app进程是否声明了对应的permission。
最后调用到PackageManagerService的checkUidPermission()(检查大部分permissions)或checkPermission()(如RECEIVE_BOOT_COMPLETED,ACCESS_ALL_EXTERNAL_STORAGE)。
如果缺乏权限的话,最终会返回PackageManager.PERMISSION_DENIED,于是调用API处根据判断会抛出异常,停止执行。
有人可能会问:为什么从Context.checkPermission()到最终的PackageManagerService.checkUidPermission(),中间要经过那么多个类?
这是为了先通过pid来判断,如果不符合条件就会直接返回PackageManager.PERMISSION_DENIED,节省时间。
三、如何在安装后控制app的权限(修改Framework代码)
根据以上的代码分析,我们可以得知大部分permission的查询都会经过
ActivityManagerService.checkComponentPermission() -> ActivityManager.checkComponentPermission() ->
PackageManagerService.checkUidPermission()
的流程,那么我们便可以在这些地方插入我们想要的权限控制代码(以下称为BlockedPermission)。
1. BlockedPermission的保存
根据“Android权限机制(一)”的分析我们得知,保存uid/package的permission的数据结构是在PKMS当中,那么为了方便,可以把BlockedPermission的数据结构放在这里面。
为了简单,可以创建一个HashMap<string, string="">,两个string分别对应permission和package。(当然这样就不如前面以uid为下标的ArrayList高效,但实际实践中发现没什么影响。)
由于HashMap只能在PKMS运行时存在,所以需要保存到文件系统中。可以采用XML的方式(如Framework内部提供的FastXmlSerializer),方便备份和恢复。
备份时间:每一次更新BlockedPermission的时候备份;
恢复时间:PKMS构造的时候。
XML存放位置:具有系统权限的目录下,如/data/system/
2. API的设计
写:
PKMS需要向App层开放写BlockedPermission的API,如writeBlockedPermission()方法,那么除了在PackageManagerService中实现这个方法之外,还要在IPckageManager.aidl中声明。
读:
同样在PKMS中实现方法readBlockedPermission(),通过对permission和package的查询返回是否被block。
安全检查:
对于写API而言,只能允许被特定的system app调用,所以需要对调用进程的pid和uid做检查。
3. API的调用
写API的调用:
在完成的Framework的改进之后,需要在App层编写具有UI的system app,方便用户直观地控制每个app的权限。注意system app需要platform的signature。
读API的调用:
通过前面的permisssion查询流程,我们可以选择在AMS,AM和PKMS中调用API检查是否需要拦截。为了在API拦截之后做好错误处理,最好在AMS中调用。
在返回PackageManager.PERMISSION_DENIED之后,Framework往往会抛出SecurityException。但是大部分第三方app并不会去捕获这个异常,所以会导致app crash。
当app crash时,AMS的crashApplication()将会被调用,为了将BlockedPermission与普通的缺乏permission情况区分出来,所以需要在调用读BlockedPermission的API之后留下足够的信息,这也就是为什么把读API放在AMS的原因。
四、其他方法
to be continued...