你真的了解Android权限机制吗?@3

栏目: IOS · Android · 发布时间: 5年前

内容简介:接上文https://www.epubit.com/selfpublish/article/6093 @1https://www.epubit.com/selfpublish/article/6096 @2敏词shen查太坑人了,代码里不能有小写ID,比如pID,uID什么的。还有SettingBase代码中不可连用,以基地代替,只能拆着发

接上文https://www.epubit.com/selfpublish/article/6093 @1

https://www.epubit.com/selfpublish/article/6096 @2

敏词shen查太坑人了,代码里不能有小写ID,比如pID,uID什么的。还有SettingBase代码中不可连用,以基地代替,只能拆着发

2. 框架层

因为 ANDROID 6.0 之前组件不能在运行时改变权限,所以系统的权限检查执行过程是静态的。这个情况下,组件的角色和权限的等安全属性会被放置在元数据中,即 ANDROIDMANIFEST.XML 文件中,而不是组件的本身。系统包管理器会负责记录组件的权限,所以静态权限检查可以从包管理器拿到权限,由运行环境或容器来执行权限检查,这样子可以把业务逻辑和安全决策分离开来,但是灵活性不足。

那 ANDROID 组件可不可以不预先声明权限在 ANDROIDMANIFEST.XML 中呢?答案是:可以的。ANDROID 的动态权限执行,可以让组件自身执行权限检查,而不是运行环境。

所以接下来我们将深入了解框架层的动态和静态权限执行的原理。

动态权限执行

动态权限执行,最典型的场景,就是 IPC。ANDROID 的核心系统服务统一会注册到服务管理器,任何应用,只要知道服务的注册名称,就可以拿到对应的 BINDER引用,就可使用 BINDER IPC 机制调用服务。因为 BINDER 没有内置的访问控制机制,所以每个系统服务需要自己实现访问控制机制。

系统服务可以直接检查调用者的 uID,通过限定 uID 来控制访问权限,这种方式简单直接,但是对于非固定uID的应用,就比较棘手了。而且大部分服务,并不关心调用者的 uID,只需要检查调用者是否被赋予特定的权限即可。所以这种方式,比较适合只允许以 ROOT(uID:0) 或 SYSTEM(uID:1000) 运行的进程访问的服务检查。

那换一种方式,服务怎么拿到调用者的权限列表?我们知道,大部分 uID 都是和包一一对应的,除了共享 uID。(共享 UID 后面再详细解释)

使用 Binder.getCallingUid() 和 Binder.getCallingPid() 获取调用者的 UID 和 PID,通过 UID 在包管理器中查询到对应应用的权限。android.content.Context 类中就有 checkPermission(String permission, int pid, int uid) 方法。实质上会调用到 PMS 中的 checkUidPermission(String perName, int uid),如下:

Android 6.0 以下 PMS 中的 checkUidPermission(String perName, int uid)

publicintcheckUidPermission(String permName,intuid){

synchronized (mPackages) {

Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));

if(obj !=null) {

GrantedPermissions gp = (GrantedPermissions)obj;

if(gp.grantedPermissions.contains(permName)) {

returnPackageManager.PERMISSION_GRANTED;

}

}else{

HashSet

perms = mSystemPermissions.get(uid);

if(perms !=null&& perms.contains(permName)) {

returnPackageManager.PERMISSION_GRANTED;

}

}

}

returnPackageManager.PERMISSION_DENIED;

Android 6.0 以下的 checkUidPermission() 方法比较简单,首先,基于入参 uid 获取应用的 appId,拿到权限列表对象(也就是 packages.xml 里的 映射),如果 GrantedPermissions 类中的 grantedPermissions 集合包含目标权限,则检查通过。

如果没有该 GrantedPermissions 对象,则检查目标权限是否可以被自动授予,实际上 mSystemPermissions 就是 platform.xml 文件中的

标签映射缓存,记录了一些系统级应用的 uid 对应的 permission。例:

...

Android 6.0 及以上 PMS 中的 checkUidPermission(String perName, int uid)

@Override

publicintcheckUidPermission(String permName,intuid){

finalintcallingUid = Binder.getCallingUid();

finalintcallingUserId = UserHandle.getUserId(callingUid);

finalbooleanisCallerInstantApp = getInstantAppPackageName(callingUid) !=null;

finalbooleanisUidInstantApp = getInstantAppPackageName(uid) !=null;

finalintuserId = UserHandle.getUserId(uid);

if(!sUserManager.exists(userId)) {

returnPackageManager.PERMISSION_DENIED;

}

synchronized(mPackages) {

Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));

if(obj !=null) {

...

finalSetting基地 setting基地 = (Setting基地) obj;

finalPermissionsState permissionsState = setting基地.getPermissionsState();

if(permissionsState.hasPermission(permName, userId)) {

if(isUidInstantApp) {

BasePermission bp = mSettings.mPermissions.get(permName);

if(bp !=null&& bp.isInstant()) {

returnPackageManager.PERMISSION_GRANTED;

}

}else{

returnPackageManager.PERMISSION_GRANTED;

}

}

...

}else{

ArraySet

perms = mSystemPermissions.get(uid);

if(perms !=null) {

if(perms.contains(permName)) {

returnPackageManager.PERMISSION_GRANTED;

}

...

}

}

}

returnPackageManager.PERMISSION_DENIED;

}

可以注意到,6.0 之后 checkPermission() 方法有所改变。多了从 mSettings.mPermissions 去查询权限列表。关键就在于这个 mSettings 里面保存的这个 SettingBase 对象,它记录了 PermissionsState 也就是权限的授予情况。

// PermissionsState.java

public boolean hasPermission(String name, int userId) {

enforceValidUserId(userId);

if (mPermissions == null) {

return false;

}

PermissionData permissionData = mPermissions.get(name);

return permissionData != null && permissionData.isGranted(userId);

}

所以检查权限的流程是本来就有的,6.0 之后差异仅在于:危险级别权限可以动态修改授权情况,也就是修改 PermissionState 的 mGranted 值,所以每次权限执行,都会查询下 mGranted 值。

静态权限执行

静态权限执行的典型场景,是跨应用组件交互。我们使用隐式 Intent 来表达意图,搜索匹配的组件,如果有多个,弹出选择框,目标组件被选定后,会由 ActivityManagerService 执行权限检查,检查目标组件是否有相应的权限要求,如果有,则把权限检查的工作交给 PMS,去检查调用者有没有被授权这些权限。

接下来的总体的流程和动态执行流程大致相同:Binder.getCallingUid()和Binder.getCallingPid()获取调用者的 UID 和 PID,然后利用 UID 映射包名,再获得相关权限集合。如果权限集合中含有所需权限即启动,否则抛出 SecurityException 异常。静态权限执行这里,我们可以详细了解下,每种组件的权限检查时机和具体顺序是怎么样的。

组件权限执行

思考一下,什么时候会执行对调用者的权限检查?那肯定是在目标组件被调用的时候,去解析目标组件声明的权限,如果有,就执行权限检查。

Activity 和 Service。Activity 显而易见,会在 startActivity() 和 startActivityForResult() 里解析到声明权限的 Activity 时,就执行权限检查。而 Service startService()、stopService() 和 bindService(),这 3 个方法被调用时都会进行权限检查。

广播。我们注意到,发送广播除了常用的 sendBroadcast(Intent intent),还有个 sendBroadcast(Intent intent, String receiverPermission),该方法可以要求广播接受者具备特定的权限,但是,调用 sendBroadcast 是不会进行权限检查的,因为广播是异步的,所以权限检查会在 intent 传递到已注册的广播接受者时进行,如果接收者不具备特定的权限,则不会接收到该广播,也不会收到 SecurityException 异常。

反过来,接收者可以要求广播发送者必须具备的权限,所要求的权限在 manifest 文件中设置 标签的 permission 属性,或者动态注册时指定 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler),权限检查也是在广播传递时执行。

所以,收发广播可以分开指定权限。值得一提的是,一些系统广播被声明为 protected,并且只能由系统进程发送,比如 PACKAGE_INSTALLED。只能由系统进程发送,这个限制会在内核层进行检查,对调用者的 UID 进行匹配,只能是 SYSTEM_UID、PHONE_UID、SHELL_UID、BLUETOOTH_UID 或 root。如果其他 UID 的进程试图发送系统广播,则会收到 SecurityException 异常。

想了解所有的系统广播,可以打开/system/framework/framework-res.apk 中的 AndroidManifest.xml

标签详细了解。

ContentProvider。ContentProvider 可以为读写分别指定不同的权限,即:调用目标 provider、query() 方法 和 insert()、update()、delete() 都会进行权限检查。

总结

综上所述,Android 的权限的检查会在各个层次上实施。

高层的组件,例如应用和系统服务,通过包管理器查询应用程序被赋予的权限,并决定是否准予访问。

低层的组件,通常不访问包管理器,比如本地守护进程,依赖于进程的 UID、GID 和补充 GID 来决定赋予。

访问系统资源时,如设备文件、UNIX 域套接字和网络套接字,则由内核根据所有者、目标资源的访问权限和访问进程的进程属性或者 packages.list 来进行控制。

共享 UID

最后简单说下共享 UID,填一下前面挖的坑。虽说 Android 会为每一个应用分配唯一的 UID,但如果应用使用相同的密钥签发,就可以使用相同 UID 运行,也就是运行在同一个进程中。

这个特性被系统应用和核心框架服务广泛使用,比如:Google Play 和 Google 定位服务,请求同一进程内的 Google 登录服务,从而达到静默自动同步用户数据的体验。

值得注意的是:Android 不支持将一个已安装的应用,从非共享 UID 切换到共享状态,因为改变了已安装应用的 UID,会导致应用失去对自己文件的访问权限(在一些早期 Android 版本中),所以如果使用共享 UID 必须从一开始就设计好。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

CGI 程序设计自学通

CGI 程序设计自学通

(美)格里高利 / 徐丹/等 / 机械工业出版社 / 1998-08 / 28.00元

本书集中讨论CGI编程,以便利用一起来看看 《CGI 程序设计自学通》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具