安全边界,有时也会称为信任边界,是系统中分隔不同信任级别的特殊区域。一个最直接的例子就是内核空间与用户空间之间的边界。内核空间中的代码可以对硬件执行一些底层操作并访问所有的虚拟和物理内存,而用户空间中的代码则由于CPU的安全边界控制,无法访问所有内存。 Android操作系统应用了两套独立但又相互配合的权限模型。在底层,Linux内核使用用户和用户组来实施权限控制,这套权限模型是从Linux继承过来的,用于对文件系统实体进行访问控制,也可以对其他Android特定资源进行控制。这一模型通常被称为Android沙箱。以DalvikVM和Android框架形式存在的Android运行时实施了第二套权限模型。这套模型在用户安装应用时是向用户公开的,定义了应用拥有的权限,从而限制Android应用的能力。事实上,第二套权限模型中的某些权限直接映射到底层操作系统上的特定用户、用户组和权能(Capability)。 2.2.1 Android沙箱 Android从其根基Linux继承了已经深入人心的类Unix进程隔离机制与最小权限原则。具体而言,进程以隔离的用户环境运行,不能相互干扰,比如发送信号或者访问其他进程的内存空间。因此,Android沙箱的核心机制基于以下几个概念:标准的Linux进程隔离、大多数进程拥有唯一的用户ID(UID),以及严格限制文件系统权限。 Android系统沿用了Linux的UID/GID(用户组ID)权限模型,但并没有使用传统的passwd和group文件来存储用户与用户组的认证凭据,作为替代,Android定义了从名称到独特标识符Android ID(AID)的映射表。初始的AID映射表包含了一些与特权用户及系统关键用户(如system用户/用户组)对应的静态保留条目。Android还保留了一段AID范围,用于提供原生应用的UID。Android 4.1之后的版本为多用户资料档案和隔离进程用户增加了额外的AID范围段(如Chrome沙箱)。你可以从AOSP树的system/core/include/private/android_filesystem_config.h文件中找到AID的定义。以下是一个简化过的示例。 #define AID_ROOT 0 /*传统的unix跟用户*/ #define AID_SYSTEM 1000 /*系统服务器*/ #define AID_RADIO 1001 /*通话功能子系统,RIL*/ #define AID_BLUETOOTH 1002 /*蓝牙子系统*/ ... #define AID_SHELL 2000 /*adb shell与debug shell用户*/ #define AID_CACHE 2001 /*缓存访问*/ #define AID_DIAG 2002 /*访问诊断资源*/ /*编号3000系列只用于辅助用户组们,表示出了内核所支持的Android权能*/ #define AID_NET_BT_ADMIN 3001 /*蓝牙:创建套接字*/ #define AID_NET_BT 3002 /*蓝牙:创建sco、rfcomm或l2cap套接字*/ #define AID_INET 3003 /*能够创建AF_INET和AF_INET6套接字*/ #define AID_NET_RAW 3004 /*能够创建原始的INET套接字*/ ... #define AID_APP 10000 /*第一个应用用户*/ #define AID_ISOLATED_START 99000 /*完全隔绝的沙箱进程中UID的开始编号 */ #define AID_ISOLATED_END 99999 /*完全隔绝的沙箱进程中UID的末尾编号*/ #define AID_USER 100000 /*每一用户的UID编号范围偏移*/ 除了AID,Android还使用了辅助用户组机制,以允许进程访问共享或受保护的资源。例如,sdcard_rw用户组中的成员允许进程读写/sdcard目录,因为它的加载项规定了哪些用户组可以读写该目录。这与许多Linux发行版中对辅助用户组机制的使用是类似的。 注意 尽管所有的AID条目都映射到一个UID和GID,但是UID在描述系统上的一个用户时并不是必需的。例如,AIDD_SDCARD_RW映射到sdcard_rw,但是它仅仅用作一个辅助用户组,而不是系统上的UID。 除了用来实施文件系统访问,辅助用户组还会被用于向进程授予额外的权限。例如,AID_INET用户组允许用户打开AF_INET和AF_INET6套接字。在某些情况下,权限也可能以Linux权能的形式出现,例如,AID_INET_ADMIN用户组中的成员授予CAP_NET_ADMIN权能,允许用户配置网络接口和路由表。本节最后还会介绍与网络相关的其他相似用户组。 在4.3及之后的版本中,Android提升了对Linux权能的使用,比如Android 4.3将二进制程序/system/bin/run-as从原先设置成set-UID root权限,修改为使用Linux权能来访问特权资源。在这里,这一权能方便了对packages.list文件的访问。 注意 对Linux权能的完整讨论已经超出了本章的范围。你可以分别从Linux内核的Documentation/security/credentials.txt文档和capabilities的用户手册页面获得更多关于Linux进程安全和Linux权能的信息。 在应用执行时,它们的UID、GID和辅助用户组都会被分配给新创建的进程。在一个独特UID和GID环境下运行,使得操作系统可以在内核中实施底层的限制措施,也让运行环境能够控制应用之间的交互。这就是Android沙箱的关键所在。 下面的代码给出了在一台HTC One V手机上运行ps命令后的输出结果,注意,最左侧显示的UID对于每个应用的进程都是独特的。 app_16 4089 1451 304080 31724 ... S com.htc.bgp app_35 4119 1451 309712 30164 ... S com.google.android.calendar app_155 4145 1451 318276 39096 ... S com.google.android.apps.plus app_24 4159 1451 307736 32920 ... S android.process.media app_151 4247 1451 303172 28032 ... S com.htc.lockscreen app_49 4260 1451 303696 28132 ... S com.htc.weather.bg app_13 4277 1451 453248 68260 ... S com.android.browser 通过使用应用包中的一种特殊指令,应用也可以共享UID,这一点我们会在2.3.1节详细讨论。 实际上,进程显示的用户与用户组名称是由一种POSIX函数的Android专有实现所提供的,这种函数通常就是用来设置和获取这些值的。例如,考虑在Bionic库的stubs.cpp文件中定义的getpwuid函数。 345 passwd* getpwuid(uid_t uid) { // NOLINT:实现不良函数 346 stubs_state_t* state = __stubs_state(); 347 if (state == NULL) { 348 return NULL; 349 } 350 351 passwd* pw = android_id_to_passwd(state, uid); 352 if (pw != NULL) { 353 return pw; 354 } 355 return app_id_to_passwd(uid, state); 356 } 与它的同胞函数一样,getpwuid函数会调用一些额外的Android专有函数,如android_id_to_passwd()和app_id_to_passwd()函数。这些函数会把Unix的口令结构填充上相应的AID映射信息表。android_id_to_passwd()函数会调用android_iinfo_to_ passwd()函数来完成这一替换。 static passwd* android_iinfo_to_passwd(stubs_state_t* state, const android_id_info* iinfo) { snprintf(state->dir_buffer_, sizeof(state->dir_buffer_), "/"); snprintf(state->sh_buffer_, sizeof(state->sh_buffer_), "/system/bin/sh"); passwd* pw = &state->passwd_; pw->pw_name= (char*) iinfo->name; pw->pw_uid = iinfo->aid; pw->pw_gid = iinfo->aid; pw->pw_dir = state->dir_buffer_; pw->pw_shell = state->sh_buffer_; return pw; } 2.2.2 Android权限 Android的权限模型是多方面的,有API权限、文件系统权限和IPC权限。在很多情况下,这些权限都会交织在一起。正如前面提到的,一些高级权限会后退映射到低级别的操作系统权能,这可能包括打开套接字、蓝牙设备和文件系统路径等。 要确定应用用户的权限和辅助用户组,Android系统会处理在应用包的AndroidManifest.xml文件中指定的高级权限(Manifest文件和权限会在2.3.1节详细描述)。应用的权限由PackageManager在安装时从应用的Manifest文件中提取,并存储在/data/system/packages.xml文件中。这些条目然后会在应用进程的实例化阶段用于向进程授予适当的权限(比如设置辅助用户组GID)。下面的代码片段显示了packages.xml文件中的Chrome浏览器条目,包括这个应用的唯一UID以及它所申请的权限。 <package name="com.android.chrome" codePath="/data/app/com.android.chrome-1.apk" nativeLibraryPath="/data/data/com.android.chrome/lib" flags="0" ft="1422a161aa8" it="1422a163b1a" ut="1422a163b1a" version="1599092" userId="10082" installer="com.android.vending"> <sigs count="1"> <cert index="0" /> </sigs> <perms> <item name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <item name="android.permission.NFC" /> ... <item name="android.permission.WRITE_EXTERNAL_STORAGE" /> <item name="android.permission.ACCESS_COARSE_LOCATION" /> ... <item name="android.permission.CAMERA" /> <item name="android.permission.INTERNET" /> ... </perms> </package> 权限至用户组的映射表存储在/etc/permissions/platform.xml文件中。它被用来确定应用设置的辅助用户组GID。下面的代码片段显示了一些映射。 ... <permission name="android.permission.INTERNET" > <group gid="inet" /> </permission> <permission name="android.permission.CAMERA" > <group gid="camera" /> </permission> <permission name="android.permission.READ_LOGS" > <group gid="log" /> </permission> <permission name="android.permission.WRITE_EXTERNAL_STORAGE" > <group gid="sdcard_rw" /> </permission> ... 在应用包条目中定义的权限后面会通过两种方式实施检查:一种检查在调用给定方法时进行,由运行环境实施;另一种检查在操作系统底层进行,由库或内核实施。 1. API权限 API权限用于控制访问高层次的功能,这些功能存在于Android API、框架层,以及某种情况下的第三方框架中。一个使用API权限的常见例子是READ_PHONE_STATE,这个权限在Android文档中定义为允许“对手机状态的只读访问”。应用若申请该权限,随后就会授予该权限,从而可以调用关于查询手机信息的多种方法,其中包括在TelephonyManager类中定义的方法,如getDeviceSoftwareVersion和getDeviceId等。 前面提到过,一些API权限与内核级的安全实施机制相对应。例如,被授予INTERNET权限,意味着申请权限应用的UID将会被添加到inet用户组(GID 3003)的成员中。该用户组的成员具有打开AF_INET和AF_INET6套接字的能力,而这是一些更高层次API功能(如创建HttpURLConnection对象)所必需的。 在第4章中,我们还将讨论了API权限及实施检查机制中的一些疏忽和问题。 2. 文件系统权限 Android的应用沙箱严重依赖于严格的Unix文件系统权限模型。默认情况下,应用的唯一UID和GID都只能访问文件系统上相应的数据存储路径。注意,以下代码清单中的UID和GID(分别在第2列和第3列)对于目录都是唯一的,它们的权限被设置为只有这些UID和GID才能访问这些目录。 root@android:/ # ls -l /data/data drwxr-x--x u0_a3 u0_a3 ... com.android.browser drwxr-x--x u0_a4 u0_a4 ... com.android.calculator2 drwxr-x--x u0_a5 u0_a5 ... com.android.calendar drwxr-x--x u0_a24 u0_a24 ... com.android.camera ... drwxr-x--x u0_a55 u0_a55 ... com.twitter.android drwxr-x--x u0_a56 u0_a56 ... com.ubercab drwxr-x--x u0_a53 u0_a53 ... com.yougetitback.androidapplication.virgin. mobile drwxr-x--x u0_a31 u0_a31 ... jp.co.omronsoft.openwnn 相应地,由这些应用创建的文件也会拥有相应的权限设置。以下代码清单中显示了某个应用的数据目录,子目录和文件的属主和权限都被只设置给该应用的UID和GID。 root@android:/data/data/com.twitter.android # ls -lR .: drwxrwx--x u0_a55 u0_a55 2013-10-17 00:07 cache drwxrwx--x u0_a55 u0_a55 2013-10-17 00:07 databases drwxrwx--x u0_a55 u0_a55 2013-10-17 00:07 files lrwxrwxrwx install install 2013-10-22 18:16 lib -> /data/app-lib/com.twitter.android-1 drwxrwx--x u0_a55 u0_a55 2013-10-17 00:07 shared_prefs ./cache: drwx------ u0_a55 u0_a55 2013-10-17 00:07 com.android.renderscript.cache ./cache/com.android.renderscript.cache: ./databases: -rw-rw---- u0_a55 u0_a55 184320 2013-10-17 06:47 0-3.db -rw------- u0_a55 u0_a55 8720 2013-10-17 06:47 0-3.db-journal -rw-rw---- u0_a55 u0_a55 61440 2013-10-22 18:17 global.db -rw------- u0_a55 u0_a55 16928 2013-10-22 18:17 global.db-journal ./files: drwx------ u0_a55 u0_a55 2013-10-22 18:18 com.crashlytics.sdk.android ./files/com.crashlytics.sdk.android: -rw------- u0_a55 u0_a55 80 2013-10-22 18:18 5266C1300180-0001-0334-EDCC05CFF3D7BeginSession.cls ./shared_prefs : -rw-rw---- u0_a55 u0_a55 155 2013-10-17 00:07 com.crashlytics.prefs. xml -rw-rw---- u0_a55 u0_a55 143 2013-10-17 00:07 com.twitter.android_preferences.xml 正如前面所提到的,特定的辅助用户组GID用于访问共享资源,如SD卡或其他外部存储器。作为一个例子,注意在HTC One V手机上运行mount和ls命令的输出结果,特别是/mnt/sdcard的路径。 root@android:/ # mount ... /dev/block/dm-2 /mnt/sdcard vfat rw,dirsync,nosuid,nodev,noexec,relatime, uid=1000,gid=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437, iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0 ... root@android:/ # ls -l /mnt ... d---rwxr-x system sdcard_rw 1969-12-31 19:00 sdcard 这里你可以看到SD卡被使用GID 1015进行挂载,对应为sdcard_rw用户组。应用请求WRITE_EXTERNAL_STORAGE权限后,会将自己的UID添加到这个组中,得到对这一路径的写权限。 3. IPC权限 IPC权限直接涉及应用组件(以及一些系统的IPC设施)之间的通信,虽然与API权限也有一些重叠。这些权限的声明和检查实施可能发生在不同层次上,包括运行环境、库函数,或直接在应用上。具体来说,这个权限集合应用于一些在Android Binder IPC机制之上建立的主要Android应用组件。关于这些组件和Binder的详细信息,本章后面会详细描述。