内容简介:接上文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{
perms = mSystemPermissions.get(uid);
if(perms !=null&& perms.contains(permName)) {
returnPackageManager.PERMISSION_GRANTED;
}
}
}
returnPackageManager.PERMISSION_DENIED;
Android 6.0 以下的 checkUidPermission() 方法比较简单,首先,基于入参 uid 获取应用的 appId,拿到权限列表对象(也就是 packages.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{
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 文件中设置
所以,收发广播可以分开指定权限。值得一提的是,一些系统广播被声明为 protected,并且只能由系统进程发送,比如 PACKAGE_INSTALLED。只能由系统进程发送,这个限制会在内核层进行检查,对调用者的 UID 进行匹配,只能是 SYSTEM_UID、PHONE_UID、SHELL_UID、BLUETOOTH_UID 或 root。如果其他 UID 的进程试图发送系统广播,则会收到 SecurityException 异常。
标签详细了解。
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 必须从一开始就设计好。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Django权限机制实现代码详解
- ZooKeeper ACL 权限控制机制
- Django 默认权限机制介绍及实践
- KodExplorer 4.40 发布,权限机制优化
- 你真的了解Android权限机制吗?@2
- 你真的了解Android权限机制吗?(1)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。