美文网首页Android开发Android技术知识Android开发
「Android 安全架构」之权限机制剖析(含运行时权限)

「Android 安全架构」之权限机制剖析(含运行时权限)

作者: FeelsChaotic | 来源:发表于2018-05-26 17:49 被阅读27次

    前言

    为什么有权限机制?

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

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

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

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

    权限的本质

    什么是权限?

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

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

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

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

    权限的级别

    • 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 的包信息,这可以看做是包管理服务维护的一个已安装程序的核心数据库,这个数据库,随着每次应用安装、升级或卸载而进行更新。

    主要属性如图:

    <permissions> 标签内,定义了目前系统中的所有权限,分为系统内置的(package 属性为 android 的)和 apk 自定义的(package 属性为 apk 的包名)。根元素 <package> 含有每个 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>
              <!-- 此处普通权限的 granted 全都默认是 true,且不可改变 granted 值-->
                <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 被标记为高危的权限都不在 <perms> 里了,如:READ_SMSREAD_EXTERNAL_STORAGECALL_PHONE 等。这是怎么一回事呢?

    Android 6.0 之前,权限都是在安装时自动赋予的,且不能更改或撤销(不卸载应用的情况下)。而 Android 6.0 版本对 permission 的管理做了部分改动,针对 dangerous 级别,不再安装的时候赋予权限,而是在运行时动态申请。

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

    <pkg name="com.feelschaotic.demo">
       <!-- 该demo我们故意拒绝了定位权限,可以看到:ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 的 granted 为 false -->
        <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 值。

    管理权限的仍然是包管理服务器,只不过 Android 6.0 之后新增了 runtime-permissions.xml 数据库。

    权限赋予

    低层依赖于进程的 UID、GID 和补充 GID 来决定赋予权限,那么,权限是如何映射到 OS 层的 UID、GID 上的呢?

    我们知道,Android 应用安装时,会被分配一个唯一的 UID,应用启动时,包管理器会设置新建进程的 UID 和 GID 为应用程序的 UID。如果应用已经被赋予了额外的权限,就把这些权限映射成一组 GID,作为补充 GID 分配给进程。内置权限到 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 了。

        <!-- These are permissions that were mapped to gids but we need
             to keep them here until an upgrade from L to the current
             version is to be supported. These permissions are built-in
             and in L were not stored in packages.xml as a result if they
             are not defined here while parsing packages.xml we would
             ignore these permissions being granted to apps and not
             propagate the granted state. From N we are storing the
             built-in permissions in packages.xml as the saved storage
             is negligible (one tag with the permission) compared to
             the fragility as one can remove a built-in permission which
             no longer needs to be mapped to gids and break grant propagation. -->
             
    <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  4687  1624 1929916 112340 SyS_epoll_wait      0 S com.feelschaotic.demo
    

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

    我们关注如下三行:

    Uid: 10103 10103 10103 10103
    Gid: 10103 10103 10103 10103
    Groups: 3002 3003 9997 20103 50103

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

    #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[] = {
    { "sdcard_r", AID_SDCARD_R, },
    { "sdcard_rw", AID_SDCARD_RW, },
        ···
    };
    

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

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

    每个应用都会运行在自己的 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,系统内核和守护进程就可以用这些标识来决定,是否要赋予进程权限。

    权限检查(权限执行)

    1. 系统内核层权限检查

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

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

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

    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<String> 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 里的 <package> 映射),如果 GrantedPermissions 类中的 grantedPermissions 集合包含目标权限,则检查通过。

    如果没有该 GrantedPermissions 对象,则检查目标权限是否可以被自动授予,实际上 mSystemPermissions 就是 platform.xml 文件中的 <assing-permission> 标签映射缓存,记录了一些系统级应用的 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<String> 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 之后差异仅在于:危险级别权限可以动态修改授权情况,也就是修改 PermissionStatemGranted 值,所以每次权限执行,都会查询下 mGranted 值。

    静态权限执行

    静态权限执行的典型场景,是跨应用组件交互。

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

    接下来的总体的流程和动态执行流程大致相同:Binder.getCallingUid()Binder.getCallingPid()获取调用者的 UID 和 PID,然后利用 UID 映射包名,再获得相关权限集合。如果权限集合中含有所需权限即启动,否则抛出 SecurityException 异常。高层系统服务通过使用 Binder,获取请求应用的 UID,并查找包管理器中保存的权限,来执行权限检查。

    不过静态权限执行这里,我们可以详细了解下,每种组件的权限检查时机和具体顺序是怎么样的。

    组件权限执行

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

    • Activity 和 Service

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

    • 广播

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

    反过来,接收者可以要求广播发送者必须具备的权限,所要求的权限在 manifest 文件中设置 <receiver> 标签的 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 <protected-broadcast> 标签详细了解。

    • ContentProvider

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

    需要注意一点:如果组件私有,那么从外部调用都会被 ActivityManagerService 活动管理器阻塞,不论调用者是否已被授权(除非调用进程以 root 或 system 运行)。那么我希望组件可以被特定应用调用,又不完全公开呢?我们可以声明一个 Intent Filter,同时保持 exported 为 false,从而隐式地公开。

    3. 总结

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

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

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

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

    共享 UID

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

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

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

    相关文章

      网友评论

        本文标题:「Android 安全架构」之权限机制剖析(含运行时权限)

        本文链接:https://www.haomeiwen.com/subject/tntidftx.html