android安全架构-权限


(1)简介
android应用程序是沙漏隔离的,默认情况下,只能访问他们自己的文件和非常有限的系统服务。为了与系统和其他应用交互,android应用程序可以在安装时请求一组额外的权限,并且之后不能改变。
在android中,一个权限,简单来说只是一个字符串,表示执行特定操作的能力。目标操作可以是任何操作,从访问一个物理资源,或是共享数据,到启动或访问一个第三方应用中的组件。android内置了一组预定义的权限。内置权限可以查看api参考文档。附加的权限(自定义权限),可以由系统定义,也可以由用户安装的应用程序来定义。
使用 pm list permissions 可查看当前系统已知的权限列表。加 -f 可以现实权限的额外信息,包括定义的报名,标签和保护界别。
(2)权限申请
应用程序通过在AndroidManifest.xml 文件内添加一个后多个<uses-permission>标签来申请权限,并且使用<permission>标签来定义新的权限。
(3)权限管理
在每个应用程序(使用包名称进行识别)安装时,系统使用 包管理器服务,将权限附给他们,包管理器维护一个已安装程序包的核心数据库,包括预安装和用户安装的程序包,其中包括安装路径,版本,签名证书和每个包的权限,还包括一个所有已定义权限的列表。这个包数据库以XML文件的形式存放于/data/system/packages.xml,它随着每次应用的安装,升级或卸载而进行更新。下面是一个典型的packages.xml文件:
**************************************************************
<package name="com.google.android.apps.translate"
codePath="/data/app/com.google.android.apps.translate.zpk"
nativeLibraryPath="/data/app-lib/com.google.android.apps.translate-2"
flag="4767300" ft="1430dfab9e0" it="142cdf04d67" ut="130dfabd8d"
version="20000028"
userId="10204"
installer="com.android.vending">
<sigs count="1">
<cert index="7">
</sigs>
<perms>
<item name="android.permission.READ_EXTERNAL_STORAGE" />
......
</perms>
<signing-keyset identifier="17" />
<signing-keyset identifier="6" />
</package>
**************************************************************
(4)权限级别
一个权限的保护级别是:暗示权限中隐含的潜在风险,并指出当决定是否赋予权限时,系统应遵循的校验程序的流程。实际上就是说,是否赋予权限,一定程序上取决于它的保护级别。有下面4中保护界别
normal级别
权限保护级别的默认值,定义了访问系统或其他应用程序的低风险权限。normal保护级别的权限无需用户确认,自动授权
dangerous级别
这个级别的保护权限,可以访问到用户数据或者在某种形式上控制设备。如READ_SMS和CAMERA。在赋予dangerous级别的权限之前,android会弹出一个确认对话框,并显示所请求的权限信息。因为android需要在安装时赋予权限,所以用户可以同意安装应用,因此会赋予所请求的dangerous级别权限,也可以取消安装。这里dangerous级别的权限会以组的形式显示,而normal级别的权限不会显示。


signature级别
signature级别的权限只会赋予那些与声明权限使用相同证书的应用程序。这是最严格的权限级别,因为它需要持有加密密钥,而这往往由应用或平台的所有者控制着。因此使用signature级别的权限,通常被用于执行设备管理任务的系统应用。
signatureOrSystem级别
它可以被赋予系统镜像的部分应用,或与声明权限具有相同签名密钥的应用程序。它允许厂商无需共享签名密钥,即可预装自己的应用来共享一个需要权限的特定功能。4.4之后的版本,安装在system/priv-app目录下的应用,才能被赋予这个级别的保护。
(5)权限的赋予
权限的执行会在android的各个测个层次上实施。高层的组件,如应用后系统服务,通过包管理器查询应用程序被赋予的是哪些权限,并决定是否准予访问。底层的组件,如本地守护进程,通常不访问包管理器,而依赖于进程的UID, GID和补充GID来决定赋予权限。访问系统资源,如设备文件,UNIX域套接字和网络套接字,则由内核根据所有者,目标资源的访问权限和访问进程的UID和GID来进行控制。

权限和进程属性
与任何linux系统类似,android进程有一组进程相关的属性,其中最为重要的是真实和有效的UID,GID,以及一组补充GID. 每个android应用在安装时,都会被分配一个独一无二的UID,在专有的进程内执行。 应用启动时,进程的UID和GID由包管理器服务设置为应用程序的UID。如果应用已被赋予了额外的权限,就把这些权限映射成一组GID,作为补充GID分配给进程。内置权限到GID的映射定义在/etc/permission/platfrom.xml文件内,如下:
file:///tmp/ct_tmp/2.png

说明:这里INTERNET权限与inet GID相关联,WRITE_EXTERNAL_STORAGE权限关联到sdcard_r和sdcard_rw GID。因此任意一个已被赋予INTERNET权限的应用,其进程的补充GID中都包含inet组对应的GID;被赋予WRITE_EXTERNAL_STORAGE权限的进程,其补充GID中包括sdcard_r和sdcard_rw对应的GID.
<assign-permission>标签用于相反的目的,它用于给那些运行在特定UID下的,没有对应包文件的系统进程赋予更高层次的权限。上述文件中,MODIFY_AUDIO_SETTINGS分配给了以media UID运行的进程。
android系统中并没有/etc/group文件。所以组名称到GID的映射是静态的,在android_filesystem_config.h头文件中定义。如下:
file:///tmp/ct_tmp/3.png

file:///tmp/ct_tmp/4.png

android_filesystem_config.h中同样定义了android系统核心目录和文件的所有者,访问模式和权能(只有可执行问价拥有)。
包管理器在启动时读取platfrom.xml,并维护一个权限到GID的列表。当它给一个安装中的包授权时,包管理器检查每个权限是否有对应的GID。如果有,则此GID加入到应用的补充GID列表中。补充GID列表写在packages.list文件中的最后一个字段。

赋予进程属性
每个应用实际上就是一个执行应用字节码的Dalvik虚拟机进程。为了减少应用程序所占内存并加速启动时间,android使用zygote的已部分初始化的进程,当需要启动新的应用时,就fork()函数来复制zygote进程。其与启动本地进程调用exec()函数不同,它只执行java类的main。这个过程叫做特殊化,因为它从一般的zygote进程转化为具体的某个应用进程。fork进程会继承zygote的进程空间,该进程空间已经预加载了大部分核心和java应用框架库。因为这些类从来不变,并且linux在fork进程时,使用了copy-on-write机制,所以所有的zygote子进程(android应用进程)会共享相同的java类的副本。
zygote进程是由init.rc初始化脚本启动的,从一个名为zygote的UNIX域套接字接收指令。当zygote受到启动新应用的请求时,它首先fork自身,子进程大致使用如下代码做特殊化(dalvik_system_Zygote.cpp文件中的forkAndSPecializeCommon()方法),如下:
file:///tmp/ct_tmp/5.png

上面,子进程首先使用setgroups()(由setgroupsIntarray()所调用)设置补充GID(与权限对应)。然后使用setrlimit()(由setrlimitsFromArray()所调用)设置资源限制,再调用setresgid()和setresuid()设置实际用户/组ID,有效用户/组ID和保存用户/组ID。
因为子进程和zygote一样,是以root执行的,所以它可以更改自己的资源限制和进程属性。新进程属性设置完成后,子进程会以分配的UID和GID执行,再也无法切换回root了,因为其保存的用户ID不是0.
设置完UID和GID之后,进程使用capset()(由setCapabilities()所调用)设置进程的权能。然后通过将自己加入一个预定义的控制组,来设置调度策略。最后在7处,进程设置自身的nice name和 seinfo标签。最后再根据需要开启调试功能。

(6)权限执行
如上所述,每个应用进程在从zygote进程fork时,均分配了UID,GID和补充GID。系统内核和守护进程使用这些表示来决定,是否赋予进程到特定系统资源或功能的访问权限。

内核层的权限执行
android系统对普通的文件,设备节点和本地套接字的访问控制,和任何linux系统一样。android新增的特有控制是,那些创建网络套接字的进程需要属于inet组,这也被成为android的 paranoid网络访问安全控制机制(paranoid network security),由android内核中的一个额外检查实现。
file:///tmp/ct_tmp/6.png

那些不属于AID_INET组的进程,不会拥有CAP_NET_RAW的权能,因而会受到一个拒绝服务的错误。非android内核不会定义CONFIG_ANDROID_PARANOID_NEEWORK,因此也就不需要有忒嗯的组对应创建套接字的操作(2).为使inet组可以被分配到应用进程,那么就需要赋予进程INTERNET权限。因此,仅当应用有INTERNET权限时,才可以创建套接字。
paranoid网络安全控制也被用于Bluetooth套接字和内核隧道驱动(VPN)。

原生守护线程级别的权限执行
虽然binder是android中的首选IPC机制,但底层的原生守护进程常常使用UNIX域套接字进行进程间通信。UNIX域套接字使用文件系统上的节点(node)来表示,所以可以使用标准的文件系统权限机制进行权限控制。
大多数套接字的访问权限为只允许同用户/组的进程访问,而以不同UID和GID运行的客户端,是无法连接套接字的。系统守护进程的本地套接字由init.rc定义,该文件由init进程创建,如下是vold在init.rc内的定义:
file:///tmp/ct_tmp/7.png

vold声明了一个同样名为vold,访问权限为0660的套接字,该套接字属于root,所属组为mount,vold手机进程需要以root运行,用以挂载/卸载卷设备,而mount组的成员可以通过本地套接字向他们发送指令,而无需以root用户执行。Android守护进程的本地套接字创建在/dev/socket目录中。

框架层的权限执行
可以通过在应用包的manifest文件中申请所需权限,来对android组件的访问进行控制。系统会记录与每个组件相关联的权限,并在允许访问组件之前,检查调用者是否具有所需权限。因为组件不能在运行时改变权限,所以系统权限检查执行过程是静态的。静态权限是声明安全机制的一个例子。当使用声明安全机制时,如角色和权限等安全属性均被放置在组件的元数据中(AndroidManifest.xml文件中),而不是组件自身,然后由运行环境或容器来执行权限检查。这样做优点,可以从业务逻辑中将安全策略隔离出来。但与在组件内部实现安全检查相比,其不足之处就是缺乏灵活性。
Android组件也可以动态检查调用进程是否被赋予某个权限,而调用进程并不需要在manifest中预先生命权限。动态执行权限是命令式安全机制的一个例子,安全策略是由每个组件自身执行的,而不是运行环境。
(1)动态权限执行
android的核心是一系列相互协作的系统服务实现的,它们可被使用Binder IPC机制的其他进程调用。核心服务注册到服务管理器,任何应用程序,只要知道他们注册的名称,就可以获取一个BInder引用。因为Binder没有一个内置的访问控制机制,当客户端拥有一个引用时,它们就可以通过向 Binder.transact()传递适当的参数,调用任何系统服务的方法。因此,每个系统服务均需要实现访问控制机制。
系统服务通过直接检查调用者UID,来控制调用者对该服务的导出操作的访问权限,其中调用者UID通过Binder.getCallingUid()方法获取。然而,这种方法需要服务事先知道许可的UID列表,这只对那些众所周知的固定UID有效,如root,system,同样,大部分服务并不关心调用者的具体UID,他们只是想简单地检查调用者是否被赋予特定权限。
因为在android中,每个应用的UID与某个包唯一相关,并且包管理器负责记录那些赋予每个包的权限,所以通过查询包管理器即可获取某个应用的权限。检查某个调用者是否具备某个权限,这是非常常见的操作。android在android.content.COntext中提供了很多辅助方法,专门用于这种检查。
如 int Context.checkPermission(String permission, int pid, int uid),这个方法在传递的UID具有权限时,返回PERMISSION_GRANTED,否则返回PERMISSION——DENIED。如果调用者是root或system,权限会被自动赋予。如果请求的权限已经被调用程序实现声明,那么为了性能优化,这里不会检查,而是直接授权。如果均不是以上情况,那么该方法会检查目标组件是否公开或似有,然后拒绝对似有组件的所有访问请求。最后该代码查询包管理器服务,确认调用者是否被赋予所请求的权限。
Context类中其他权限检查的辅助方法均遵循类似的处理流程。int checkCallingOrSelfPermission(String permission)方法先调用Binder.getCallingUid()和BInder.getCallingPid(),然后使用得到的值调用checkPermission(String permission, int pid, int uid)。 enforcePermission()函数在权限未被赋予时,不返回具体的值,而是抛出一个附带特定消息的SecurityException一场。
(2)静态权限执行
当某个应用试图与另一个应用所声明的组件进行交互时,便会涉及静态权限执行。权限执行过程充分考虑到每个目标组件声明的权限,然后允许那些有相应权限的调用进程与目标组件进行交互。
android使用intent机制来描述一个需要执行的操作,那些明确制定目标组件的intent叫做intent。换句话说,隐式intent包含一些系统用来找到相匹配组件的数据,它们一般不明确制定到特定组件。
当系统受到一个隐式intent时,它会搜索相匹配的组件。如果匹配到不止一个组件,则向用户呈现一个选择对话框。目标组件选定以后,android检查它是否具有相应的权限,如果有,检查这些权限是佛已被授权给调用者。
总体流程与动态权限执行类似:使用Binder.getCallingUid()和Binder.getCallingPid()获取调用者的UID和PID,然后调用者UID映射到包名,接下来获取相关权限。如果调用者权限集合中包含目标组件需要的权限,则组件启动,否则抛出SecurityException异常。
权限检查由AMS执行,它负责解析具体的intent,并检查目标组件是否拥有相关的权限属性。如果有,则它将权限检查工作移交给包管理器执行。不同组件,其权限检查的时机和具体顺序略有不同。
》》》activity和service权限执行
如果传递到Context.startActivity()或startActivityForResult()方法的intent,解析到一个声明权限的activity时,就需要执行activity的权限检查。如果调用者不具备该权限,则抛出SecurityException异常。因为android服务可以被启动,终止和绑定,如果目标服务声明权限的话,对其Context.startService(), stopService(), bindService()方法的调用都会受到权限检查。
》》》content provider权限执行
content provider权限可以保护整个组件或特定的导出URI,并且可以分别为读写指定不同的权限。如果已为读写分别制定了不同权限,那么读权限控制谁可以调用目标provider或URI的ContentResolver.query()方法,而写权限控制谁可以对目标provider或其某个暴露的URI调用 ContentResolver.insert(), update()和delete()方法。当某个方法被调用时,权限检查操作被同步执行。
》》》广播权限执行
当发送一个广播,应用程序可以使用Context.sendBroadcast()方法,来要求接收者应具有某一个特定权限。因为广播是异步的,所以调用这个方法时,不会进行权限检查。权限检查操作在intent被传递到已注册的广播接收者时执行。如果目标接收者不具有该权限,直接忽略,不会接收广播,也不会有一场抛出。反过来,广播接收者可以要求广播发送者必须具有某个指定权限,以便锁定发送者。
》》》protected广播和sticky广播
一些系统广播被声明为protected(如BOOT——COMPLETED和PACKAGE_INSTALLED),并且只能由系统进程发送,这些系统程序必须运行在以下UID:SYSTEM_UID, PHONE_UID, SHELL_UID, BLUETOOTH_UID或root。 如果以其他UID进行的进程试图发送一个protected广播,那么当其调用sendBroadcast()方法时会抛出SecurityException异常。发送sticky广播(如果标记为sticky,系统在广播结束之后,还会保留发送的intent对象),需要发送者具有BROADCAST——STICKY权限,否则会抛出SecurityException异常,并且广播不会被发送出去。

(7)系统权限
android内置权限定义在以android开头的包中,这些包有时会被叫做框架或平台。android框架的核心是一组由系统服务共享的类,其中的一些会通过公开SDK暴露。框架类均被打包成jar包,保存在system/framewokr目录下。
除了jar库,框架还包含一个单独的apk文件,framework-res.apk. 它主要将框架资源打包在一起(动画,画板和布局等),不包含实际代码。最重要的是,它定义了android包和系统权限。framework-res.apk的AndroidManifest.xml文件中声明了权限组和权限。其中声明了系统的protected广播。一个权限组为一组相关的权限制定了一个名称。单个权限可以通过在permissionGruop属性里,指定组名称,来将该权限加到权限组里。
权限组用于在系统用户界面显示一组相关的权限,但每个权限依旧需要单独申请。也就是说,应用程序不能将所有权限合在一起申请权限。
每个权限都使用protectionLevel属性来说米功能相应的保护级别。
保护级别可以结合保护标志做进一部的授权约束。当前定义的标志是system和development。 system标志要求应用必须是系统镜像的一部分,方可授权(即安装在system分区中的应用)。development标志表示开发权限。

signature权限
所有android应用均需要代码签名,其签名密钥由开发者管理。这同样食用于系统应用程序和框架资源包。
系统应用由平台密钥(platform key)签发。默认情况下,andorid源码树下有4中不同的密钥文件: platform, shared, media, testkey(也叫releasekey)。所有核心平台的包(如系统界面,设置,电话,蓝牙等)均使用平台密钥签发;搜索和通讯录相关的包使用共享密钥(shared)签发;图库和媒体相关的包使用媒体密钥(media)签发;其他所有应用(包括那些没有在makefile文件中显式指定密钥的包)使用发布密钥(testkey)签发。定义系统权限的framrwork-res。apk文件是使用平台密钥签发的。因此,任何试图请求signature保护级别系统权限的应用程序,均需要使用与框架资源包相同的密钥进行签名。
注意:AOSP包括预生成的testkey,用于已编译包的默认签名密钥。但他们绝不能用于构建产品,因为这些密钥是公开的。发布新版本应使用新生成的私钥进行签发。密钥可以使用make_key(development/tools/)脚本生成。查看build/target/product/security/README文件,可了解平台密钥的生成细节。
development 权限
传统的android模型不允许动态的授权和撤销权限,所授权限在安装时就已固定。从android4.2开始,这个规则放宽,这个版本增加了若干权限(READ_LOGS和WRITE_SECURE_SETTINGS等)。开发权限可根据需要使用 pm grant 和 pm revoke 命令,进行授权和撤销。当然,这个操作不是所有人都可用,并且受 GRANT_REVOKE_PERMISSIONS 签名权限保护。它被赋予android.uid.shell,并且也会赋予所有从android shell启动的进程。

(8)共享用户ID
使用相同密钥签发的android应用可以使用相同的UID运行,并且也可以运行在同一进程内。这个特性就是共享用户ID(shared user ID), 该特性被核心框架服务和系统应用广泛使用。 因为它可以对进程统计和应用进程管理带来细微影响,所有android项目组并不推荐第三方应用使用这个特性,但对于用户自安装程序是可以的。另外android不支持将一个已安装的应用,从非共享用户ID状态切换为共享用户ID状态,所以使共享应用ID来做应用写作,需要从一开始就设计好。
共享用户ID可以通过在AndroidManifest.xml文件根元素中添加sharedUserId属性开启。在manifest文件中指定用户ID,如果指定的共享UID不存在,则它会马上创建。如果另一个有相同共享UID的包已经安装,签名证书会与已存在的包进行比较,如果他们不匹配,则返回 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE 错误,并且安装失败。
向一个已安装应用的新版本中添加sharedUserId属性,会造成它改变自身的UID,从而导致失去对自己文件的访问权限。因此系统不允许这么做,系统会返回 INSTALL_FAILED_UID_CHANGED错误,从而拒绝更新应用。见而言之,如果计划在应用中共享UID,必须从一开始就设计好,从第一个发布版本就使用该共享UID。
共享UID自身是系统包数据中的一级对象,作为应用一样对待:共享UID也有相应的签名证书和权限。android内置了5个共享UID,它们在系统引导时自动添加:
android.uid.system(SYSTEM_UID, 1000) android.uid.phone(PHONE_UID, 1001)
android.uid.bluetooth(BLUETOOTH_UID, 1002) android.uid.log(LOG_UID, 1007)
android.uid.nfc(NFC_UID, 1027)
下面看看android.uid.system是如何定义的:
file:///tmp/ct_tmp/8.png

上述定义与包的声明类似,包继承共享用户的权限,这些权限集合了所有以相同共享UID安装包的申请权限。这样一个副作用是,如果一个包是共享用户的一部分,它可以访问它没有明确申请权限的API,而只要其他有相同共享UID的包已经申请过该API即可。<shared-user>定义下的权限可随着包的安装或写在,动态地添加或移除。
共享UID不仅仅是一个包管理组件,它在运行时也实际映射了一个共享的linux UID。
属于共享用户的应用,可以运行在同一个进程内部,因为他们有相同的linux UID,并且访问相同的资源,这样通常也不需要做额外的改动。所有需要运行在同一进程内的应用,可以在应用的manifest文件内的<application>标签下的process属性里,制定同一个进程名称来实现。显然这样可以通过共享内存来实现进程间通信,但一些系统服务还同时允许对同一进程内的组件进行特殊访问控制(如直接访问缓存密码后后去认证令牌,而不会在用户界面进行提示)。google应用程序利用这一点,请求同一进程内的google登陆服务,从而达到无需用户交互,后台自动同步数据的功能。当然,他们使用了相同的证书进行签名,并且均属于com.google.uid.shared共享用户。

(9)自定义权限
自定义权限是那些第三方应用简单地进行声明的权限。权限声明以后,他们可以被加到应用的组件中,由系统进行静态权限执行,或应用通过使用Context类的checkPermission()或enforcePermission()方法,动态检查调用者是否已被授权。和内置权限一样,应用可以定义权限组,将自定义权限加进去。下面是一个权限组和属于该组权限的声明:
file:///tmp/ct_tmp/9.png


和系统权限一样,如果权限保护级别是normal后dangerous, 那么自定义权限会在用户点击确认后自动授权。为了能够控制那个应用被赋予自定义权限,需要将自定义权限声明为signature保护级别,以确保只有以同一个密钥签名的应用程序可以被赋予该权限。
应用可以使用android.content.pm.PackageManager.addPermission()接口动态添加新的权限,使用removePermission()删除权限。动态添加的权限,必须属于应用定义的权限树。应用只能对自身或共享同一UID的包,在权限树中添加,删除权限。
权限树名称采用反向域名表示法,并且只要权限名的前缀是某一权限树名加点号,那么该权限会被认为是属于这个权限树。
file:///tmp/ct_tmp/10.png

动态添加的权限会被写进应用包数据库中(/data/system/packages.xml)。他们在系统重启后还会存在,就像manifest内定义的权限一样,不过会多出一个type为dynamic的额外属性。

(10)公开和私有组件
AndroidManifest.xml中定义的组件可以是公开的,也可是似有的。似有的组件只能被所生命的特定应用调用,而公开组件可以被其他应用调用。
组件可通过设定exported属性为true,使组件公开;或通过声明一个intent filter,隐式地使组件公开。具有intent filter的组件,可设置exported为false,使组件不公开。如果组件私有,不存调用进程是否被授权,从外部应用的调用都会被活动管理器所阻塞。

(11)activity和service权限
actiivty和service均可通过设置目标组件的permission属性进行保护。下面是2个自定义权限,分别设置在activtity和service上。那些想要使用这些组件的应用,需要在它们的manifest文件中使用<uses-permission>标签来申请相应的权限。
file:///tmp/ct_tmp/11.png

file:///tmp/ct_tmp/12.png


(12)广播权限
广播权限可以由发送方或接收方来指定。

(13)content provider权限
静态provider权限
file:///tmp/ct_tmp/13.png

虽然可以使用单一的权限控制对整个provider的访问,但大部分provider针对读,写使用不同的权限,甚至还可以为每个URI指定各自的权限。如上。provider可以指定per-URI权限,从而保护他们数据的一个特定子集。per-URI权限比组件级权限优先级高。因此如果一个应用想要访问一个有关联权限的content provider URI时,它只需要具有目标URI的权限,而不需要拥有组件级的权限。
动态provider权限







































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值