系统权限

系统权限

Android是一种特权分隔的操作系统,在Android上运行的每个应用程序都具有各自独立的系统标识(Linux用户ID和组ID)。 系统各部分有不同的身份标识。因此,Linux上运行的各个应用程序相互独立且与系统无关。

Android的“权限许可”机制通过限定特定的进程能够执行的指定操作和限定对每一个资源点对点的访问的URI许可来提供附加细粒度的安全功能。

本文档介绍了应用程序开发人员如何使用由Android提供的安全功能。 在Android 开放源代码项目AOSP(Android Open Source Project)中提供了更一般的Android安全性概述。

安全架构

Android安全体系架构设计的核心是在默认情况下没有任何一个程序可以执行对其他程序、操作系统或者用户有害的操作,包括读写用户的隐私数据(例如联系人或者电子邮件),读写其他程序的文件,进行网络访问或者唤醒设备等等。

由于内核让每个应用程序运行在独立的沙盒中,应用程序必须通过声明所需要而沙盒没有提供的权限来明确的分配资源和数据。Android没有采用会使用户体验复杂并且不利于安全的动态授权机制。应用程序静态的声明他们所需要的权限,在程序安装时Android系统会提示用户同意它们获取这些权限。

沙盒程序独立于生成普通应用程序的机制。特别地,Dalvik虚拟机不是一个安全的边界,任何的应用程序都能够运行本地代码(参照Android NDK)。所有类型的应用程序——java、native和混合的——均用相同的方式置以相同的安全等级在沙盒中运行。

应用签名

所有的Android应用程序(apk文件)都必须使用一个开发人员掌握私钥、用于识别应用程序作者的证书进行签名。该证书要求很宽松,并不需要由专门的证书颁发机构进行签名,Android应用程序可以使用自签名的证书。Android证书的目的是区分应用程序的作者,可以允许操作系统授予或者拒绝应用程序使用签名级别的权限和操作系统授予或者拒绝应用程序请求和其他应用程序相同的Linux身份。

User IDs和文件访问

在安装的时候,Android会给每个程序分配一个不同的Linux用户身份(UID)。软件在设备上的生命周期中这个身份标识保持恒定不变。在不同的设备上,相同的软件可能会有一个不同的UID;重要的是在给定的设备上不同的包是不同的UID。

因为安全是在进程级别上实现的,两个软件包的代码在同一个进程中不能够同时正常运行,他们必须以不同的Linux用户运行。可以在每个程序包的AndroidManifest.xml中将manifest标签的shareUserId属性分配相同的用户ID,把两个应用程序看作拥有同样的用户ID和文件权限的同一个应用程序。为了保持安全,只有具有相同签名(请求的sharedUserId也相同)的应用程序才会分配相同的用户ID。

任何由应用程序存储的数据将被赋予应用程序的用户ID,正常情况不能被其它应用程序访问。当使用getSharedPreferences(String, int), openFileOutput(String, int), 或openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)创建一个新的文件时,可以使用MODE_WORLD_READABLE或MODE_WORLD_WRITEABLE标记允许其他应用程序来读/写文件。设置这些全局的读写权限标记后,该文件仍然为创建文件的应用程序所拥有,任何其他应用程序可以看到它。

使用权限

一个基本的android程序是没有任何权限的。也就是说,无论是从用户体验上和设备数据上都没有什么危害。在产品需求下,为了能够使用设备的受保护特性,你必须在AndroidManifest.xml里声明至少一种所需要的权限。
例如,一个程序需要管理收到的短信,需要指定:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>

如果你的应用程序中列出的正常在其清单权限(即,不构成太大风险,用户的隐私或设备的操作权限)时,系统自动授予这些权限。如果你的应用程序中列出的危险在其清单的权限(即权限可能会影响用户的隐私或设备的正常运行),系统会要求用户明确授予这些权限。Android可以请求的方式取决于系统版本,并通过您的应用程序有针对性的系统版本:

  • 如果设备运行的是Android 6.0(API级别23)或更高版本,以及应用程序的targetSdkVersion 为23或更高,从在运行时用户的应用请求的权限。用户可以随时撤销的权限,因此应用程序需要检查是否有每次运行时的权限。
  • 如果设备运行的是Android 5.1(API级别22)以下,或 应用程序的targetSdkVersion 是22或更低时,系统会询问用户当用户安装应用程序授予的权限。如果添加一个新的许可应用程序的更新版本中,系统会询问用户,当用户更新应用程序授予这个权限。一旦用户安装应用程序,他们可以撤销许可的唯一途径是通过卸载应用程序。

很多时候,一个权限失败将导致SecurityException异常被抛回给应用程序。但是,这不能保证到处发生。例如,sendBroadcast(Intent)方法检查的权限,数据被传递到每个接收器,该方法调用返回之后,因此,如果有权限的失败,你将不会收到异常。在几乎所有的情况下,然而,一个许可失败将被打印到系统日志。

通过Android系统提供的权限可以在这里找到Manifest.permission。任何应用程序还可以定义并执行它自己的权限,所以这不是所有可能的权限的完整列表。
特定权限可以在一些程序的运行过程中的地方被执行:

  • 在调用系统的时候,阻止应用程序执行某些功能。
  • 当启动一个Activity,阻止应用程序启动其他应用程序的Activity。
  • 在发送或接受广播时,控制谁可以接受你的广播或者谁可以向你发送广播。
  • 谁可以访问或操作一个特定的内容提供者。
  • 绑定或启动服务。

自动调整许可

Android 的版本变更还是比较快的,在不同的版本上,对一些权限做出的一定的修改,为了避免因为系统升级给app 带来的问题,Android 提供了一个自动调整的机制。比如WRITE_EXTERNAL_STORAGE权限在API leve 4 的时候加入,如果你的targetSdkVersion是3 或者更低的话,这个权限你自动就获取了,不过在安装的时候,这个权限会提示给用户的,即使你没有申请。

Google 建议targetSdkVersion尽量使用最高的版本。

普通权限和危险权限

系统权限被分成几个不同的保护等级,其中有两个重要的等级是普通和危险权限。

  • 普通权限(Normal permissions)
    普通权限是指那些对用户的私人数据或者其他应用不大可能会产生威胁的权限。比如设置时区(time zone)是一个普通等级的权限。系统会自动授权给应用这种普通等级的权限。
  • 危险权限(Dangerous permissions)
    危险等级权限指那些可能会去用户私人数据或者其他应用产生威胁的权限。比如读取用户通讯录就是一个危险的权限。如果你申请了这种危险等级的权限,系统需要用户明确的授权给应用。

权限组

所有危险Android系统权限属于权限组。如果设备运行的是Android 6.0(API级别23)和应用程序的targetSdkVersion为23或更高,当你的应用程序请求一个危险的权限以下系统行为适用:

  • 如果一个应用程序要求在其清单中列出的风险的权限,并且应用目前不具有的权限组中的任何权限,系统显示一个对话框,说明该应用程序要访问权限组的用户。对话框中没有描述组内的特定权限。例如,如果一个应用程序请求READ_CONTACTS权限,系统会弹出对话框,只是说该应用程序需要访问设备上的通讯录。如果用户授权审批,系统给出的应用只是将其要求的权限。
  • 如果一个应用程序要求在其清单中列出的风险的权限,以及应用程序已经有相同权限的组中的其他风险的权限,系统立即授予权限,而不与用户进行任何交互。例如,如果一个应用程序以前曾要求和被授予READ_CONTACTS权限,然后它请求WRITE_CONTACTS权限时,系统立即授予这个权限。

如果设备运行的是Android 5.1(API级别22)以下,或应用程序的 targetSdkVersion是22或更低时,系统会要求用户授予在安装时的权限。再次,系统只是告诉用户什么权限组的应用需求,而不是个人的权限。

表1.危险权限和权限组。

Permission Group Permissions
CALENDAR
  • READ_CALENDAR
  • WRITE_CALENDAR
  • CAMERA
  • CAMERA
  • CONTACTS
  • READ_CONTACTS
  • WRITE_CONTACTS
  • GET_ACCOUNTS
  • LOCATION
  • ACCESS_FINE_LOCATION
  • ACCESS_COARSE_LOCATION
  • MICROPHONE
  • RECORD_AUDIO
  • PHONE
  • READ_PHONE_STATE
  • CALL_PHONE
  • READ_CALL_LOG
  • WRITE_CALL_LOG
  • ADD_VOICEMAIL
  • USE_SIP
  • PROCESS_OUTGOING_CALLS
  • SENSORS
  • BODY_SENSORS
  • SMS
  • SEND_SMS
  • RECEIVE_SMS
  • READ_SMS
  • RECEIVE_WAP_PUSH
  • RECEIVE_MMS
  • STORAGE
  • READ_EXTERNAL_STORAGE
  • WRITE_EXTERNAL_STORAGE
  • 定义和执行权限

    若打算自定义权限,您需要在manifest文件中使用标签声明。

    比如说,若打算控制某个Activity的启动权(谁可以启动该Activity),您可以配置如下代码:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.me.app.myapp" >
        <permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
            android:label="@string/permlab_deadlyActivity"
            android:description="@string/permdesc_deadlyActivity"
            android:permissionGroup="android.permission-group.COST_MONEY"
            android:protectionLevel="dangerous" />
        ...
    </manifest>
    

    其中

    • name:就是权限的名字,一个字符串。
    • label:权限的标签
    • description:权限的说明
    • permissionGroup:你可以指定这个权限所属的权限组。当然你也可以自己定义一个权限组。
    • protectionLeval:你可以定义这个权限的安全等级。

    在manifest文中强制实行的权限

    若您在manifest文件中声明了高级权限(High-level permissions),那么这种权限不是所有组件都能获取的,若希望针对某个组件获取高级权限,您应当在该组件的标签中用android:permission 属性声明该高级权限。

    • Activity权限
      在activity标签中声明的高级权限可以控制 谁可以启动该activity,您应当在调用Context.startActivity()和Activity.startActivityForResult()方法时判断是否能启动该activity。若未被赋予该权限,则这两个方法将抛出SecurityException异常;
    • Service权限
      在service标签中声明的高级权限可以控制 谁可以启动或绑定该service,您需要在调用Context.startService(), Context.stopService()和Context.bindService()方法时判断是否可以启动或绑定该service,若未被赋予该权限,则这三个方法将抛出SecurityException异常;
    • BroadcastReceiver权限
      在receiver标签中声明的高级权限可以控制 谁可以发送广播给该BroadcastReceiver,您应当在Context.sendBroadcast()方法返回以后再判断权限是否被赋予,因为会尝试将该broadcast发送给其他满足条件的receiver,所以,如果声明了高级权限的receiver没有接受到broadcast,sendBroadcast()方法也不会抛出异常;若使用Context.registerReceiver() 方法动态注册receiver,同样可以赋予该receiver高级权限;
    • ContentProvider权限
      在provider标签中声明的高级权限可以控制 谁有权访问该provider提供的数据(contentprovider还提供了URI Permission,将在稍后介绍)。与其他的组件稍有不同,provider中可以声明两种权限属性: android:readPermission和android:writePermission,前者可以控制谁能读取该provider的数据,后者可以控制谁能写入该provider的数据。仅有写入该provider权限的组件不代表您也能读取,反之亦然。当您第一次检索该provider时,该高级权限将被检测(如果没有权限,SecurityException将被抛出),使用ContentResolver.query()需要持有读权限;使用ContentResolver.insert(),ContentResolver.update(),ContentResolver.delete()需要写权限。在所有这些情况下,没有所需的权限将会导致抛出SecurityException异常。

    发出广播时强制执行的权限

    除了之前说过的权限(用于限制谁可以发送广播给相应的BroadcastReceiver),还可以在发送广播的时候指定一个许可。在调用Context.sendBroadcast()的时候使用一个permission string,你就可以要求接收器的宿主程序必须有相应的权限。

    值得注意的是接收器和广播都可以要求许可。当这种情况发生时,这两种permission检查都需要通过后才会将相应的intent发送给相关的目的地。

    其他权限设置

    在调用service的过程中可以设置更加细化的许可。这是通过Context.checkCallingPermission()方法来完成的。调用的时候使用一个想得到的permission string,返回给调用方一个整数判断是否具有相关权限。需要注意的是这种情况只能发生在来自另一个进程的调用,通常是一个service发布的IDL接口或者是其他方式提供给其他的进程。

    Android提供了很多其他有效的方法用于检查许可。如果有另一个进程的pid,可以通过Context.checkPermission(String, int, int)去检查该进程的权限设置。如果有另一个应用程序的包名,可以直接用PackageManager.checkPermission(String, String)来确定该包是否已经拥有了相应的权限。

    URI许可

    到目前为止我们讨论的标准的permission系统对于内容提供者(content provider)来说是不够的。一个内容提供者可能想保护它的读写权限,而同时与它对应的直属客户端也需要将特定的URI传递给其它应用程序以便对该URI进行操作。一个典型的例子是邮件应用程序的附件。访问邮件需要使用permission来保护,因为这些是敏感的用户数据。然而,如果有一个指向图片附件的URI需要传递给图片浏览器,那个图片浏览器是不会有访问附件的权利的,因为它不可能拥有所有的邮件的访问权限。

    针对这个问题的解决方案就是per-URI permission:当启动一个activity或者给一个activity返回结果的时候,调用方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这赋予接收活动(activity)访问该意图(Intent)指定的URI的权限,而不论它是否有权限进入该意图对应的内容提供者。

    这种机制允许一个通常的能力-风格(capability-style)模型,以用户交互(如打开一个附件, 从列表中选择一个联系人)来驱动细化的特别授权。这是实现减少应用程序所需要的权限而只留下和程序行为直接相关的权限时很关键的一步。

    这些URI permission的获取需要内容提供者(包含那些URI)的配合。强烈建议在内容提供者中实现这种功能,并通过android:grantUriPermissions或者grant-uri-permissions标签来声明支持。
    更多的信息可以参考Context.grantUriPermission(), Context.revokeUriPermission()和Context.checkUriPermission()函数。

    Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1