android 动态申请权限_你真的了解Android权限机制吗?

 2023-09-09 阅读 27 评论 0

摘要:码个蛋(codeegg)第 610 次推文作者:FeelsChaotic原文:https://www.jianshu.com/p/a17c8bed79d9前言android跳转到权限设置界面、Android将安全设计贯穿系统架构的各个层面,覆盖系统内核、虚拟机、应用程序框架层以及应用层各个环节,力求在开放的同时,也
765152f50506da3b1b27e5e91a4b0afe.gif

码个蛋(codeegg)第 610 次推文

作者:FeelsChaotic

原文:https://www.jianshu.com/p/a17c8bed79d9

前言

android跳转到权限设置界面、Android将安全设计贯穿系统架构的各个层面,覆盖系统内核、虚拟机、应用程序框架层以及应用层各个环节,力求在开放的同时,也最大程度地保护用户的数据、应用程序和设备的安全。Android安全模型主要提供以下几种安全机制:

30a65a05ef2facba7a5afb6f17a21b53.png

从技术架构角度来看,Android安全模型基于Linux操作系统内核安全性,有基本的用户和文件访问隔离,在这个基础上辅以内存管理技术和进程间通信机制,来适应移动端处理器性能与内存容量的限制。在应用层面,使用显式定义且经用户授权的权限控制机制,系统化地规范并强制各类应用程序的行为准则与权限许可,可以看到权限机制在安全模型中所处的位置。

那么为什么有权限机制?

我们知道 Android 应用程序是沙箱隔离的,每个应用都有一个只有自己具有读写权限的专用数据目录,应用只能访问自己的文件和一些设备上全局可访问的资源。

那如果我需要访问系统服务呢?这就有了 Android 的权限机制。所以根本原因一是沙箱隔离,二是服务支持的需求。

android最高权限名称。在本文开始之前,先抛出几个问题思考,什么是权限?权限是怎么进行赋予的?怎么判断一个组件是否拥有特定的权限?权限维护在哪?Android 6.0 运行时权限是什么原理?权限赋予后还能再更改吗?

带着问题和结构图,我们一一解开疑惑。

37b612f988e4b781b64aef9ff3ba316f.png

权限的本质

什么是权限?在 Android 中,一个权限,本质上是一个字符串,一个可以表示执行特定操作的能力的字符串。比如说:访问 SD 卡的能力,访问通讯录的能力,启动或访问一个第三方应用中的组件的能力。

使用 pm list permissions -f 命令可以详细查看 Android 所有预定义的权限。我们挑一个权限出来:

+ permission:android.permission.DELETE_PACKAGES
package:android
label:null
description:null
protectionLevel:signature|privileged

android动态权限框架,可以看到,一个权限的信息包括:定义的包名、标签、描述和保护级别,保护级别,嗯,这个我们需要详细讲讲。因为不是应用声明了权限,就一定会全部被自动赋予的,保护级别决定了包管理器是否应该赋予组件所申请的权限。

权限的级别

normal 级别。权限保护级别的默认值,无须用户确认,只要声明了,就自动默默授权。如:ACCESS_NETWORK_STATE。

dangerous 级别。赋予权限前,会弹出对话框,显式请求权限。如:READ_SMS。因为 Android 需要在安装时赋予权限,所以安装的确认对话框,也会显示列出权限清单。

signature 级别。signature 级别的权限是最严格的权限,只会赋予与声明权限使用相同证书的应用程序。

动态申请权限。以系统内置 signature 级别权限为例,Android 系统应用的签名由平台密钥签发,默认情况下源码树里有 4 个不同的密钥文件:platform、shared、media 和 testkey。所有核心平台的包(如:设置、电话、蓝牙)均使用 platform 密钥签发;搜索和通讯录相关的包使用 shared 签发;图库和媒体相关的包使用 media 密钥签发;其他的应用使用 testkey 签发。定义系统内置权限的 framework-res.apk 文件是使用平台密钥签发的,因此任何试图请求 signature 级别内置权限的应用程序,需要使用与框架资源包相同的密钥进行签名。

signatureOrSystem 级别。可以看做是一种折中的级别,可被赋予与声明权限具有相同签名证书密钥的应用程序(同 signature 级别)或者系统镜像的部分应用,也就是说这允许厂商无须共享签名密钥。Android 4.3 之前,安装在 system 分区下的应用会被自动赋予该保护级别的权限,而 Android 4.4 之后,只允许安装在 system/priv-app/ 目录下的应用才能被主动赋予。

权限的管理

那么,系统内置权限,自定义权限,是怎么维护和管理的?

在每个应用安装时,权限就已经赋予了,系统使用包管理服务来管理权限。打开我们系统目录下的 /data/system/packages.xml,可以看到文件包含了所有已定义的权限列表和所有 apk 的包信息,这可以看做是包管理服务维护的一个已安装程序的核心数据库,这个数据库,随着每次应用安装、升级或卸载而进行更新。

android权限?主要属性如图:

2e9d2452cec4be02cea4c0757bc1fae7.png

标签内,定义了目前系统中的所有权限,分为系统内置的(package 属性为 android 的)和 apk 自定义的(package 属性为 apk 的包名)。根元素 含有每个 apk 的核心属性,以一个应用程序的条目为例:

Android 6.0 以下 packages.xml

<package name="com.feelschaotic.demo" codePath="/data/app/com.feelschaotic.demo-1" nativeLibraryPath="/data/app/com.feelschaotic.demo-1/lib" primaryCpuAbi="x86" flags="5783366" ft="16349bcc4d0" it="16349bcc752" ut="16349bcc752" version="8220" userId="10097">
<sigs count="1">
 <cert index="7" />
sigs>
<perms>
 <item name="android.permission.READ_SMS" />
 <item name="android.permission.ACCESS_FINE_LOCATION" />
 <item name="android.permission.CHANGE_NETWORK_STATE" />
 <item name="android.permission.INTERNET" />
 <item name="android.permission.READ_EXTERNAL_STORAGE" />
 <item name="android.permission.ACCESS_COARSE_LOCATION" />
 <item name="android.permission.READ_PHONE_STATE" />
 <item name="android.permission.CALL_PHONE" />
 <item name="android.permission.CHANGE_WIFI_STATE" />
 <item name="android.permission.ACCESS_NETWORK_STATE" />
 <item name="android.permission.CAMERA" />
 <item name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <item name="android.permission.READ_CONTACTS" />
perms>
<proper-signing-keyset identifier="1686" />
 <signing-keyset identifier="1686" />
package>

Android 6.0 及以上 packages.xml

<package name="com.feelschaotic.demo" codePath="/data/app/com.feelschaotic.demo-Gi5ksdF6mUDLakfOugCcwQ==" nativeLibraryPath="/data/app/com.feelschaotic.demo-Gi5ksdF6mUDLakfOugCcwQ==/lib" primaryCpuAbi="x86" publicFlags="945307462" privateFlags="0" ft="16348dc3870" it="16343f1d6aa" ut="16348dc4c4d" version="8220" userId="10102">
<sigs count="1">
 <cert index="20" key="..." />
sigs>
<perms>
 
 <item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />
 <item name="android.permission.INTERNET" granted="true" flags="0" />
 <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
 <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
perms>
 <proper-signing-keyset identifier="48" />
package>

可以发现,其他信息没有太大差异,但是权限列表中,部分在 Android 6.0 被标记为高危的权限都不在 里了,如:READ_SMS、READ_EXTERNAL_STORAGE、CALL_PHONE 等。这是怎么一回事呢?

权限框架、Android 6.0 之前,权限都是在安装时自动赋予的,不卸载应用的情况下,不能更改或撤销(实际上有些厂商打开了appOps,使得 6.0 以下也能更改权限,这种特殊情况我们不讨论)。而 Android 6.0 版本对 permission 的管理做了部分改动,针对 dangerous 级别,不再安装的时候赋予权限,而是在运行时动态申请。

我们大胆地推断下,packages.xml 里保留的是不会再变更的权限,运行时权限一定是另外单独地维护。我们在 data/system 目录下来了个全盘搜索,找到了 /data/system/users/0/runtime-permissions.xml。

<pkg name="com.feelschaotic.demo">

 <item name="android.permission.ACCESS_FINE_LOCATION" granted="false" flags="1" />
 <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
 <item name="android.permission.ACCESS_COARSE_LOCATION" granted="false" flags="1" />
 <item name="android.permission.READ_PHONE_STATE" granted="true" flags="0" />
 <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
...
pkg>

没错了,就是这个文件。里面就记录着运行时权限的授予和拒绝状态。申请时,申请的结果会动态修改 granted 值。管理权限的仍然是 PMS,只不过 Android 6.0 之后新增了 runtime-permissions.xml 数据库。

权限的赋予

我们知道,Android 应用安装时,会被分配一个唯一的 UID,应用启动时,包管理器会设置新建进程的 UID 和 GID 为应用程序的 UID。如果应用已经被赋予了额外的权限,就把这些权限映射成一组 GID,作为补充 GID 分配给进程。低层就可以依赖于进程的 UID、GID 和补充 GID 来决定是否赋予权限了。

安卓动态获取权限,那么,上面流程的重点:

  1. 权限是如何映射到 OS 层的 UID、GID 上的呢?

  2. 映射完是怎么分配给进程的?

  3. 低层是怎么判断是否赋予权限的?

我们一个个来看,首先第一个,权限是如何映射的?内置权限到 GID 的映射是定义在 /etc/permission/platform.xml 中。

<permissions>
···
<permission name="android.permission.READ_EXTERNAL_STORAGE" >
 <group gid="sdcard_r" />
permission>

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
 <group gid="sdcard_r" />
 <group gid="sdcard_rw" />
permission>

<permission name="android.permission.INTERNET" >
 <group gid="inet" />
permission>
···
permissions>

安卓动态权限、值得注意的是:READ_EXTERNAL_STORAGE 这种运行时权限,在 Android 6.0 之后已经不会映射到 gid 了。动态赋予,动态申请,也就不需要映射了。



<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />

包管理器在启动时读取 platform.xml,并维护「权限-GID」对应的列表。当它给安装中的包授权时,会把权限对应的 GID 加入到该应用进程的补充 GID 中。我们举个例子,看下进程的属性:adb shell ps 拿到想要查找的进程的 PID。

USER PID PPID VSZ RSS WCHAN ADDR S NAME
...
u0_a88 2149 1624 1929916 112340 SyS_epoll_wait 0 S com.feelschaotic.demo

接着 adb shell -> cd proc -> cd 2149 (2149 为进程 PID,不固定) -> cat status

46831ec6b6fc38625bc55be55018993f.png

我们关注如下两行:

Gid:    10050   10050   10050   10050
Groups: 1006 1015 1028 3002 3003 50050

android权限处理。这里我们便看到了系统进程的权限配置信息,这里的数字具体代表意义,可以在Android
\system\core\include\private\android_filesystem_config.h 里面看到,其部分内容如下:

#define AID_CAMERA 1006 /* camera devices */ 
#define AID_SDCARD_RW 1015 /* external storage write access */
#define AID_SDCARD_R 1028 /* external storage read access */
···
static const struct android_id_info android_ids[] = {
{ "camera", AID_CAMERA, },
{ "sdcard_r", AID_SDCARD_R, },
{ "sdcard_rw", AID_SDCARD_RW, },
···
};

有没有发现什么?前文所述 sdcard_r 和 android.permission.READ_EXTERNAL_STORAGE 的映射已经定义在 /etc/permission/platform.xml 中了,此处 sdcard_r 映射 AID_SDCARD_R 对应 gid = 1015,这就是权限映射到 gid 的整个关系。总共分为三层:

9e5c8aae16deb6937d3de917d10a3eda.png

那么第二个问题,当我们安装应用完成,启动应用,应用的进程是如何启动并被赋予进程属性的呢?

每个应用都会运行在自己的 Dalvik 虚拟机进程中,但是为了提高启动效率,Android 不会为每个应用都新建一个 Dalvik 进程,而是采用 fork 的形式。每个进程都 fork form zygote 进程。那么 fork 比起 new ,效率提高在哪里?

因为 zygote 进程已经预加载了大部分核心和 Java 应用框架库,fork 的子进程会继承 zygote 的进程空间,也就是说,fork 的子进程,可以共享这些预加载的副本(记住,是副本,不是直接共享。fork 时,会 copy-on-write 预加载的内容),减少了重新加载核心库的时间。

安卓权限、当 zygote 收到启动新进程的请求时,它会 fork 自身出一个子进程,并对该子进程做特殊化处理。其源代码位于 dalvik/vm/native/dalvik_system_Zygote.c 中。forkAndSpecializeCommon() 的主要代码如下:

static pid_t forkAndSpecializeCommon(const u4* args, boolisSystemServer) {
...
pid = fork(); //创建新进程
if (pid == 0) //判断是否是root,有没有权限修改自己的进程属性
{
setgroupsIntarray(gids); //设置进程的所有组
setrlimitsFromArray(rlimits);
setgid(gid); //设置进程的组ID
setuid(uid); //设置进程的用户ID
}
...
}

如上所示:这里设置进程的组 ID 和用户 ID,通过 fork 创建的子进程调用 setgroups Intarray 设置该进程所属的组,这样应用程序就拥有了该组的权限,并且可以通过 setgid() 及 setuid() 确定应用程序的 GID 及 UID 值。刚刚开始 fork 时,子进程是以 root 执行的,所以它可以更改自己的进程属性,当属性都设置完成,子进程就以分配的 GID 和 UID 执行,此时,子进程无法再更改自己的进程属性了,因为用户 ID 已经不是 root 即 ! = 0 了,没有修改自己进程属性的权限了。

adb shell ps,看下进程列表:

USER PID PPID VSZ RSS WCHAN ADDR S NAME

root 1 0 10456 2352 SyS_epoll_wait 0 S init
...
root 1620 1 1614572 20312 poll_schedule_timeout 0 S zygote
...
u0_a88 3468 1620 1929916 112340 SyS_epoll_wait 0 S com.feelschaotic.demo
u0_a90 3574 1620 1696012 24176 SyS_epoll_wait 0 S com.demo.pushdemo
u0_a90 3607 1620 1708844 26748 SyS_epoll_wait 0 S com.demo.pushdemo:mult
u0_i0 3741 1879 1557800 11720 SyS_epoll_wait 0 S com.android.chrome:sandboxed
u0_a15 3774 1620 1690604 22056 SyS_epoll_wait 0 S com.google.android.ext.services
system 3805 1620 1687840 19688 SyS_epoll_wait 0 S com.android.keychain
u0_a42 3831 1620 1834408 45660 SyS_epoll_wait 0 S com.android.chrome
...

PID 表示应用的进程 ID,PPID 表示父进程 ID,NAME 表示进程名称(一般情况下 NAME 是应用包名)。可以看到 zygote 进程是由 init 进程启动,所有的应用进程的父进程都是 zygote。USER 表示的是进程的专有用户,这个我们下次再详细讲讲 Android 的用户管理机制。

好了,既然如此,每个应用进程都分配好自己的 GID、UID和补充 GID,系统内核和守护进程就可以用这些标识来决定,是否要赋予进程权限。

Android访问限制、权限的检查

1. 系统内核层权限检查

思考一下:如果我们的应用没有在 AndroidManifest.xml 中申请 android.permission.INTERNET 权限就进行网络请求,是不是会报 Permission denied 错误。这个权限,是谁来检查?其他进程来检查吗?明显不是,网络访问权限是由低层来进行控制的。

Android 的访问控制,和 Linux 是一样的,但 Android 增加了个特有的网络访问安全控制机制,也就是说,创建网络套接字的进程,必须属于 inet 组。

4caf9203b65f125bbf5ebea625834cc4.png

如上内核代码,current_has_network(void) 方法检查了进程的所在组。如果不在 inet 组,则直接返回错误。所以为了使我们的应用具有访问网络的能力,我们需要在 AndroidManifest.xml 中申请 INTERNET 权限,经过解析,逐步映射到内核层的组 ID 和用户 ID,最终才能通过内核层的检查。

service里面申请动态权限。你可能会有疑问,那非内核层的其他 C/C++ 层,要怎么拿到进程的所在组信息呢?在 PMS 初始化所有包信息之后,就会调用 mSettings.writeLPr()。

aa1d5ac1fb9b4767b84443fa188571c1.png5a52ff44c80bb8ba610e8b8283130177.png

这段代码的任务就是将mPackages 中保存的所有包的信息保存到 /data/system/packages.list。所以,packages.list中保存了所有应用申请的权限,C代码只要读这个文件就能判断某个应用是否申请了我们要求的权限。

0f24aad87c60d790ff546c6088470422.png

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)

public int checkUidPermission(String permName, int uid) {
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 perms = mSystemPermissions.get(uid);if (perms != null && perms.contains(permName)) {return PackageManager.PERMISSION_GRANTED;
}
}
}return PackageManager.PERMISSION_DENIED;
}

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

如果没有该 GrantedPermissions 对象,则检查目标权限是否可以被自动授予,实际上 mSystemPermissions 就是 platform.xml 文件中的 标签映射缓存,记录了一些系统级应用的 uid 对应的 permission。例:

...
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
...

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

@Override
public int checkUidPermission(String permName, int uid) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}

synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
...
final SettingBase settingBase = (SettingBase) obj;
final PermissionsState permissionsState = settingBase.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
BasePermission bp = mSettings.mPermissions.get(permName);
if (bp != null && bp.isInstant()) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
return PackageManager.PERMISSION_GRANTED;
}
}
...
} else {
ArraySet perms = mSystemPermissions.get(uid);if (perms != null) {if (perms.contains(permName)) {return PackageManager.PERMISSION_GRANTED;
}
...
}
}
}return PackageManager.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 必须从一开始就设计好。

近期文章:

  • 10段奇葩的代码注释

  • Android studio 3.4 稳定版来了

  • Flutter 入门指北之弹窗和提示(干货)

今日问题:

权限超出了你的想象了吧?

b888508ea7e1e5ee6556e426ce6a599a.png

在码个蛋(codeegg)后台回复「社群」即可加入学习群

e6d2f5b5065a78673fa3718c2f5d146b.gif

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/28746.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息