Android权限机制(二) 权限控制的设计

本文将会深入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...

posted on 2014-07-06 21:53  JacobChen2012  阅读(304)  评论(0编辑  收藏  举报

导航