ANDROID权限机制

摘要:
上述清单的许可申请正在进行中;软件包名为Android,代表系统的内置权限。用户定义的权限包括系统预安装和用户安装应用程序定义的权限。例如,com.android.launcher.permission。READ_ SETTINGS权限是启动器系统应用程序的用户定义权限,具有内置权限和用户定义权限。2) 权限保护级别<(读短信)以上两个权限是系统内置的权限;

详细分析Android权限机制实现,分析APP组件、Android框架层、系统服务、原生守护进程的权限控制实现

Android APP运行在受限沙箱内,为了完成与其它APP或系统的交互,需要申请额外权限。权限在APP安装时被授权给应用,且在APP生命周期内保持不变。权限可以被映射为Linux补充GID,用于内核在授权访问系统资源时进行权限检查。

Binder文中讲过,APP与其它APP、系统服务间通信使用Binder的IPC机制。服务端可以通过getCallingUid()获取APP的UID,并通过查找包管理器数据库中保存的权限,来执行权限检查。

与组件关联的权限,在APP的Manifest文件中声明,并由系统自动执行,但是APP也可以选择额外的动态检查。除了使用内置权限,APP还可以自定义权限,并将它们与组件相关联,从而用于组件的访问控制。

一、APP权限申请与管理

APP在AndroidManifest.xml文件中,使用 标签来申请权限,通过 标签来自定义权限。

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="com.tianlutech.ebus" 
    platformBuildVersionCode="21" 
    platformBuildVersionName="5.0-1521886">
    
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS"/>
    --snip--
</manifest>

上述Manifest的权限申请中,其实隐含了两处关键信息:

1)权限分类

Android权限命名,是包名+permission字符串+具体权限名的形式。其中包名是android的代表系统内置权限,如上面的android.permission.READ_SMS权限。而非android包定义的则是自定义权限。自定义权限包括系统预装及用户安装应用定义的权限,比如上面com.android.launcher.permission.READ_SETTINGS 权限是launcher系统应用的自定义权限。

所以Android的权限有两种:内置权限和自定义权限

2)权限保护级别

<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
(访问Accounts Service服务中的帐户列表)
<uses-permission android:name="android.permission.READ_SMS"/>
(读取短信)

上述2个均为系统内置权限,但我们发现在APP安装时,GET_ACCOUNTS的权限申请系统会默认允许,而READ_SMS权限则要用户手动确认。这是因为这两个权限拥有不同的权限保护级别。

Android系统上有4种不同的权限保护级别,以下分别介绍:

1、normal 级别

这是权限保护级别的默认值,它定义了访问系统或其它APP的低风险权限。normal级别的权限无需用户确认,会自动授权。例如上面的GET_ACCOUNTS权限。

2、dangerous 级别

dangerous 级别的权限可以访问用户数据或某种程序的控制用户设备。例如READ_SMS允许应用读取手机短信,因此在赋予dangerous 的权限前,Android会弹出一个确认对话框显示请求信息,并由用户手动确认。

3、signature 级别

signature 级别的权限只会授权给那些与权限声明者使用相同证书的APP。这是最严格的权限级别,因为申请这个权限你得持有APP或平台的签名私钥。

4、signatureOrSystem 级别

这是一种折中的方案,权限可被赋予系统镜像的部分应用或拥有相同签名的应用。在4.4版本之后,安装在/system/priv-app/目录下的应用,才能被赋予这个保护权限。

拿到一个内置权限,如果查看它的保护级别呢?

我们知道Android系统内置权限定义在以android开头的包中,这些包的集合其实就是平常说的Android 框架层。Android 框架的核心是一组由系统服务共享的类,这些类被打包成JAR文件放到/system/framework/目录下。到这个目录下可以发现,除了JAR文件,Android框架还包含了一个framework-res.apk的APK文件。

所有的内置权限,都在APK的AndroidManifest.xml文件中定义,搜索上面申请的2个权限GET_ACCOUNTS和READ_SMS,它们的权限保护级别分别是normal和dangerous,这也是上面一个需要用户手动确认,一个不需要的原因:

<permission android:description="@string/permdesc_getAccounts" 
    android:label="@string/permlab_getAccounts" 
    android:name="android.permission.GET_ACCOUNTS" 
    android:permissionGroup="android.permission-group.ACCOUNTS" 
    android:protectionLevel="normal"
/>
<permission android:description="@string/permdesc_readSms" 
    android:label="@string/permlab_readSms" 
    android:name="android.permission.READ_SMS" 
    android:permissionGroup="android.permission-group.MESSAGES" 
    android:protectionLevel="dangerous"
/>

每个APP权限,是通过一个名叫包管理器的系统服务进行管理的。包管理器维护着一个已安装APP的核心数据库,其中包括安装路径、版本、签名证书和每个包的权限,另外还包含了一个所有已定义权限列表。这个核心数据库以XML文件的形式保存于/data/system/packages.xml。

以网易云音乐为例,看一下packages.xml中的应用程序条目:

<package name="com.netease.cloudmusic" 
    codePath="/data/app/com.netease.cloudmusic-1.apk" 
    nativeLibraryPath="/data/app-lib/com.netease.cloudmusic-1" 
    flags="1588804" ft="1553ac745d0" it="1553ac75849" ut="1553ac75849" 
    version="72" 
    userId="10074">
    <sigs count="1">
        <cert index="15" key="3082038……" />
    </sigs>
    <perms>
        <item name="android.permission.READ_EXTERNAL_STORAGE" />
        <item name="com.android.launcher.permission.INSTALL_SHORTCUT" />
        <item name="android.permission.GET_TASKS" />
        <item name="com.netease.cloudmusic.permission.OPERATOR_FREE" />
        <item name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <item name="android.permission.ACCESS_WIFI_STATE" />
        <item name="android.permission.ACCESS_COARSE_LOCATION" />
        <item name="android.permission.READ_CONTACTS" />
        <item name="android.permission.MODIFY_AUDIO_SETTINGS" />
        <item name="android.permission.REORDER_TASKS" />
        <item name="android.permission.READ_PHONE_STATE" />
        <item name="android.permission.SYSTEM_ALERT_WINDOW" />
        <item name="android.permission.WRITE_SETTINGS" />
        <item name="android.permission.INTERNET" />
        <item name="android.permission.BLUETOOTH" />
        <item name="android.permission.ACCESS_FINE_LOCATION" />
        <item name="android.permission.BLUETOOTH_ADMIN" />
        <item name="android.permission.ACCESS_NETWORK_STATE" />
        <item name="android.permission.WAKE_LOCK" />
        <item name="android.permission.RECORD_AUDIO" />
    </perms>
    <signing-keyset identifier="3" />
</package>

每个APP包都用 标签来表示,包含UID(userId属性)、签名证书( 标签)和所授权限( 下的子标签中)。

使用android.content.pm.PackageManager类的getPackageInfo()方法,即可获取 标签下所有信息,比如经常使用的APK签名校验。

以上基于包管理器的权限机制,对Android高层组件,如APP、framework和系统服务已经足够,它们通过包管理器查询APP被赋予哪些权限,并决定是否允许访问。

对于低层的组件,如原生守护进程,通常不访问包管理器,而依赖于进程的UID、GID和补充GID来决定是否允许访问。而访问系统资源,如设备文件、UNIX域套接字和网络套接字,则由内核根据资源所有者、目标资源的访问权限和访问进程的UID和GID来控制。

关于上面提到的高层和低层组件,参考下图Android架构:

ANDROID权限机制第1张

二、权限到UID/GID映射及权限执行

前面介绍,低层的组件如原生守护进程,或内核控制的一些系统资源的访问,其权限控制通常并不访问包管理器,而是通过UID、GID和补充GID来进行授权。

这就需要将权限映射成相应的UID、GID,并不是所有的权限都会被映射,所以被映射的权限都定义在/etc/permissions/platform.xml文件的 标签中:

<permissions>
    <!-- The following tags are associating low-level group IDs with
         permission names.  By specifying such a mapping, you are saying
         that any application process granted the given permission will
         also be running with the given group ID attached to its process,
         so it can perform any filesystem (read, write, execute) operations
         allowed for that group. -->

    <permission name="android.permission.READ_EXTERNAL_STORAGE" >
        <group gid="sdcard_r" />
    </permission>
    --snip--

    <!-- The following tags are assigning high-level permissions to specific
         user IDs.  These are used to allow specific core system users to
         perform the given operations with the higher-level framework.  For
         example, we give a wide variety of permissions to the shell user
         since that is the user the adb shell runs under and developers and
         others should have a fairly open environment in which to
         interact with the system. -->

    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
    --snip--

    <library name="android.test.runner"
            file="/system/framework/android.test.runner.jar" />
    <library name="javax.obex"
            file="/system/framework/javax.obex.jar"/>

</permissions>

注释很关键,描述了映射原因。这里只有权限名到组名的映射关系,Android系统中没有/etc/group文件,组名到GID的映射是静态的,定义在android_filesystem_config.h文件中,可以在aosp源码中找到,我们以网易云音乐的READ_EXTERNAL_STORAGE权限为例,如下:


#define AID_SDCARD_R 1028 /* external storage read access */ #define AID_CLAT 1029 /* clat part of nat464 */ #define AID_LOOP_RADIO 1030 /* loop radio devices */ #define AID_MEDIA_DRM 1031 /* MediaDrm plugins */ static const struct android_id_info android_ids[] = { …… { "sdcard_r", AID_SDCARD_R, }, { "clat", AID_CLAT, }, { "loop_radio", AID_LOOP_RADIO, }, { "mediadrm", AID_MEDIA_DRM, }, }


通过APP申请的READ_EXTERNAL_STORAGE权限找到对应组名sdcard_r,在android_ids结构中通过组名sdcard_r找到GID为AID_SDCARD_R,查看宏定义GID为1028,因此1028这个GID会作为补充GID赋予云音乐APP。

可稍作验证:


root@hammerhead:/ # grep "com.netease.cloudmusic" /data/system/packages.list grep "com.netease.cloudmusic" /data/system/packages.list com.netease.cloudmusic 10074 0 /data/data/com.netease.cloudmusic default 3002,3001,3003,1028,1015


云音乐的包名为com.netease.cloudmusic,UID为10074,补充GID为(3002,3001,3003,1028,1015),发现GID1028在列。

APP、系统服务等高层组件,通过包管理器进行权限控制,用不到上面的进程补充GID。下面以低层组件,内核层、原生守护进程为例,说明补充GID在权限控制中的使用。

1、内核层权限执行

Android系统对普通文件、设备节点文件和本地套接字的访问控制,和普通Linux系统一样。但对使用网络套接字,Android增加了特有的控制机制,那些创建网络套接字的进程需要属于inet组。

看一下Android内核中网络访问控制的实现(af_inet.c):

static int inet_create(struct net *net, struct socket *sock, int protocol,
               int kern)
{
    --snip--

    if (!current_has_network())
        return -EACCES;
    --snip--
}
#ifdef CONFIG_ANDROID_PARANOID_NETWORK
#include <linux/android_aid.h>

static inline int current_has_network(void)
{
    return in_egroup_p(AID_INET) || capable(CAP_NET_RAW);
}
#else
static inline int current_has_network(void)
{
    return 1;
}
#endif

那些不属于AID_INET组(GID 3003,名称inet)的进程,不会拥有CAP_NET_RAW的权能(允许使用RAW和PACKET套接字),因而会收到一个拒绝服务的错误(return -EACCES)。非Android的内核不会定义CONFIG_ANDROID_PARANOID_NETWORK宏,因此没有这个访问控制机制。

通过前面权限到GID映射可以知道,申请了INTERNET权限的APP,inet组GID会被自动映射到其补充GID,因此具有了使用网络套接字的权力。

可以看到内核的权限执行,使用了APP的进程补充GID,非包管理器中记录的APP权限信息。

2、原生守护进程的权限执行

原生守护进程一般使用UNIX域套接字进行进程间通信,而非Android首先的Binder。UNIX域套接字使用文件系统上的节点来表示,因此可以使用Linux标准的文件系统权限机制进行权限控制。

大多数套接字访问权限为只允许同用户/组的进程访问。而以不同UID和GID运行的客户端,是无法连接套接字的。系统守护进程的本地套接字由init.rc定义,该文件在init进程创建时使用。

查看用于设备卷管理的守护进程vold在init.rc中的定义:


service vold /system/bin/vold class core socket vold stream 0660 root mount ioprio be 2


vold声明了一个同样名为vold,访问权限为0660的套接字。该套接字属于root,所属组为mount。vold守护进程需要以root运行,用以挂载/卸载卷设备,而mount组(AID_ MOUNT,GID 1009)的成员可以通过本地套接字向它发送指令,而无需以root用户运行,这也减小了root提权的攻击面。

除此之外,UNIX域套接字还提供了一种更细粒度的权限控制。

UNIX域套接字允许使用SCM_CREDENTIALS控制消息和SO_PEERCRED套接字选项,来传递和查询用户凭证。和有效UID、GID是Binder事务处理的一部分一样,与本地套接字相关联的凭证由内核填写,从而无法被用户进程伪造。

看一个例子,vold如何使用套接字客户端凭证进行细粒度访问控制:

//CommandListener.cpp
int CommandListener::CryptfsCmd::runCommand(SocketClient *cli,
                                                      int argc, char **argv) {
    if ((cli->getUid() != 0) && (cli->getUid() != AID_SYSTEM)) {
        cli->sendMsg(ResponseCode::CommandNoPermission, 
            "No permission to run cryptfs commands", false);
        return 0;
    }
    --snip--
}

vold守护进程只允许以root或system运行的客户端发送加密的容器管理指令。这里UID通过SocketClient->getUid()方法获得,这个值是由getsocketopt(SO_PEERCRED)从客户端获得并初始化,这跟Binder中服务端实现的权限控制十分类似:

/* SocketClient.cpp */
void SocketClient::init(int socket, bool owned, bool useCmdNum) {
    --snip--

    struct ucred creds;
    socklen_t szCreds = sizeof(creds);
    memset(&creds, 0, szCreds);

    int err = getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
    if (err == 0) {
        mPid = creds.pid;
        mUid = creds.uid;
        mGid = creds.gid;
    }
}

同时,本地套接字的连接功能封装在了android.net.LocalSocket类中,可以被JAVA应用调用,允许高层系统服务和本地守护进程间进行通信,而无需使用JNI。例如,MountService框架类使用LocalSocket向vold进程发送指令。

三、APP 组件权限控制

基于包管理器的权限检查,貌似也没什么好说的。只简单总结一下。

1、Activity

如果传递到Context.startActivity()或startActivityForResult()方法的intent解析到一个声明权限的Activity时,就需要进行activity权限检查。如果调用者未申请权限,则抛出SecurityException异常。

2、Service

与Activity一样,只是权限检查时机不同,Service发生在Context.startService()、stopService()和bindService()方法调用时。

3、Broadcast(广播)

发送一个广播时,APP可以使用Context.sendBroadcast(Intent intent, String receiverPermission)方法,来要求接收者权限。接收者需要的权限,可以在manifest文件中指定,也可以在动态注册广播时指定。广播是异步的,因此权限执行发生在广播接收器接收到广播时。

与Activity和Service不同,广播的权限检查可以是双向的,也就是广播接收者同样可以要求广播发送者拥有某个特定权限,以便锁定发送者。

4、Content Provider

Content Provider的权限执行是最复杂的。可以保护整个组件或特定的导出URI,因此拥有更细的权限控制粒度。并且可以分别为读写指定不同的权限。

如果为读写分别指定了不同权限,那读权限控制谁可以调用目标Provider或URI的ContentResolver.query()方法,写权限控制谁可以对目标Provider或某个暴露的URI调用ContentResolver.insert()、update()和delete()方法。当某个方法被调用时,权限检查同步执行。

虽然权限检查的时机不同、粒度不同,但组件权限检查的原理都是前面讲的基于包管理器的权限管理机制。

免责声明:文章转载自《ANDROID权限机制》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇appium 弹窗处理cron定时任务介绍下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

android Studio 二维码扫一扫 使用精简过的zxing

今天学习做个扫一扫  于是就上百度找找前人的作品,终于找到了,于是就开始搞 我使用的是 最新的android Studio Android Studio 3.1.2Build #AI-173.4720617, built on April 14, 2018JRE: 1.8.0_152-release-1024-b02 amd64JVM: OpenJDK 6...

canvas遇到的一些问题

1、移动端无法全屏问题 问题描述:由于canvas的width和height只能设置px值,不支持rem单位,所以想在移动设备屏幕分辨率繁杂的情况下达到canvas铺满全屏的效果很困难。   解决方法:通过js获取到手机屏幕的clientWidth值,赋给canvas,以此来达到适配全屏的效果; 1 2 3 4 5 varclientWidth...

用PopupWindow做下拉框

    最近在做下拉框,本来想用spinner,可是spinner达不到项目要求,跟同学同事问了一圈,都在用popwindow, 网上看了一下,popwindow挺简单的,可定制性挺强的,符合我的要求,所以,借鉴网上看的代码,自己撸了一 遍。写篇博客以防忘记。     首先,先写个自定义布局,代码如下 <?xml version="1.0" enc...

Android 菊花加载工具类

先看看实现效果图 1.首先自定义一个类继承系统ProgressDialog /** * Created by hanbao0928 on 2018/11/1. */ public class DialogUtils extends ProgressDialog { public DialogUtils(Context context)...

三层架构+存储过程实现分页

首先在项目下加入BLL,DAL,DataAccess,MODEL类库 -------前台界面--------- <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="原始刷新分页.aspx.cs" Inherits="分页.原始刷新分页" %> <!DOCTYPE html P...

部署在IIS服务器的asp.net 网站,禁止访问指定类型文件

网站上的一些文件不希望用户访问,可以通过下面的方式简单实现。不需写代码(在IIS6下试验过)。 第一步,在IIS中实现映射。 哪些文件需要特殊处理。 通俗的将就是将哪种类型的文件交给特定的工厂来处理。通过这种方式可以实现自定义的请求方式。 请求到达IIS,然后IIS将请求交给我们设定的工厂处理。 对于aspx文件,默认是aspnet_isapi.dll由来...