to top

Permissions

翻译:smilysas

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

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

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

Security Architecture

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

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

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

Application Signing

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

User IDs and File Access

在安装的时候,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标记允许其他应用程序来读/写文件。设置这些全局的读写权限标记后,该文件仍然为创建文件的应用程序所拥有,任何其他应用程序可以看到它。

Using Permissions

一个基本的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>

在安装应用程序时,安装者要基于应用程序的签名在交互时对应用程序所需的权限进行授权。应用程序运行时不会进行权限检查:它要么在安装后被给予一个特殊的权限,并且可以使用它期望的权限,要么就不被授予权限,任何使用这些权限的操作都会在没有用户提示的情况下自动失败。

通常如果请求权限失败应用程序会抛出一个SecurityException异常,但是也有特例。例如,sendBroadcast(Intent)函数在所有数据被投递到接收者时检查权限,当函数返回后,不会对数据的权限进行检查,也不能接收到任何权限异常。约大多数情况下,权限异常会记录在日志中。

T所有Android系统提供的权限可以在Manifest.permission中找到。任何应用程序也可以定义并执行其自己的权限,所以本文不是一个对于所有可能的权限的全面的清单。

在程序执行的任何阶段程序都可以定义并执行其自己的权限:

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

声明和实施许可-Declaring and Enforcing Permissions

为了执行你自己的权限,你必须首先在你的AndroidManifest.xml中使用一个或多个permission标签声明它们。

例如,一个应用程序想要控制谁能够开始它的一个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>

protectionLevel属性是必选的,它告诉系统当其它应用程序需要该权限时怎样通知用户或者谁可以使用该权限。

permissionGroup属性是可选的,用于帮助系统展示权限给用户。通常会将它设置为在android.Manifest.permission_group中的一个标准的系统组,在极少数情况下也可以由自己进行定义。最好是优先使用现有的组,便于简化权限UI展示给用户。

请注意,这两个标签和描述应提供许可。用户在查看的权限列表(android:label)或单个权限( android:description)的细节时,这些内容会被展现。标签应该简洁的介绍权限保护的关键功能。用几个简单的句子描述拥有该权限可以做什么。惯例是用两个句子,第一句描述权限,第二句警告用户当授权该权限后会发生什么。

这里是一个CALL_PHONE权限的标签和描述的的例子:

    <string name="permlab_callPhone">directly call phone numbers</string>
    <string name="permdesc_callPhone">Allows the application to call
        phone numbers without your intervention. Malicious applications may
        cause unexpected calls on your phone bill. Note that this does not
        allow the application to call emergency numbers.</string>

可以通过shell命令 adb shell pm list permissions来查看现在系统上的权限定义。特别地,-s选项可以用简单的表格形式来给用户呈现权限。:

$ adb shell pm list permissions -s
All Permissions:

Network communication: view Wi-Fi state, create Bluetooth connections, full
Internet access, view network state

Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location

Services that cost you money: send SMS messages, directly call phone numbers

...

在AndroidManifest.xml中声明权限设置-Enforcing Permissions in AndroidManifest.xml

进入系统或应用程序的组件的高级别权限可以在AndroidManifest.xml中实现。所有这些都可以通过在相应的组件中包含 android:permission 属性,以使其被用以控制进入的权限。

Activity权限(应用于activity标签)用于限制谁才可以启动相关的Activity。该权限在运行Context.startActivity()和Activity.startActivityForResult()期间被检查;如果调用方不具有相应必需的权限,那么将会从此次调用中抛出SecurityException 异常。

Service权限(应用于service标签)用于限制谁才可以启动或绑定该service。在运行Context.startService() , Context.stopService()和Context.bindService()调用的时候会进行权限检查。如果调用方没有所需的权限,则会抛出一个SecurityException异常。

BroadcastReceiver许可(应用于receiver标签)用于限制谁可以向相关的接收器发送广播。权限检查会在Context.sendBroadcast()返回后当系统去发送已经提交的广播给相应的Receiver时进行。最终,一个permission failure不会再返回给调用方一个异常,只是不会去实现该Intent而已。同样地,Context.registerReceiver()也可以通过自己的permission用于限制谁可以向一个在程序中注册的receiver发送广播。另一种方式是,一个permission也可以提供给Context.sendBroadcast() 用以限制哪一个BroadcastReceiver才可以接收该广播。(见下文)

ContentProvider许可(应用于provider标签)用于限制谁才可以访问ContentProvider提供的数据。(Content providers有一套额外的安全机制叫做URI permissions,这些在稍后讨论。)不像其他组件,它有两个单独的权限属性,你可以设置: android:readPermission用于限制谁能够读,android:writePermission用于限制谁能够写。需要注意的是如果provider同时需要读写许可,只有写许可的情况下并不能读取provider中的数据。当你第一次检索内容提供者和当完成相关操作时会进行权限检查。(假如没有任何权限则会抛出SecurityException异常。)使用ContentResolver.query()需要持有读权限;使用ContentResolver.insert(),ContentResolver.update(),ContentResolver.delete()需要写权限。在所有这些情况下,没有所需的权限将会导致抛出SecurityException异常。

发送广播时实施许可-Enforcing Permissions when Sending Broadcasts

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

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

其他权限设置-Other Permission Enforcement

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

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

URI许可-URI Permissions

到目前为止我们讨论的标准的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()函数。