《Android Security Internals》第一章安卓安全模型翻译

安卓架构如下图

1.1 linux内核

作为一个unix系统,android内核能驱动运行硬件、网络、文件系统、进程管理。但是不同于通常的Linux内核,android有着low memory killer, wakelocks, anonymous shared memory (ashmem), alarms, paranoid networking, Binder机制。其中最重要的是Binder和paranoid networking。

  • Android使用文件系统权限和特定的内核补丁称为 paranoid networking,用来限制低级系统功能的访问,比如网络套接字、摄像机设备、外部存储器、日志读取能力等
  • Binder是Android系统中进程间通讯(IPC)的一种方式

1.2 本地用户空间

内核之上是本地用户空间层,由_init_二进制文件(第一个启动的进程,用于启动所有其它进程),几个本地守护进程以及在整个系统中使用的几百个本地库组成

1.3 Dalvik VM

大部分Android都是用java实现的,并且由java虚拟机(JVM)执行。Android当前JVM实现被称为Dalvik。Dalvik不能直接运行Java字节码(.class文件):其原生输入格式称为Dalvik Executable(DEX),并且被打包在.dex文件中。反过来,dex文件被打包在系统Java库(JAR文件)或者安装在Android应用程序(APK文件)中。

Dalvik和Oracle的JVM有不同的体系结构——基于寄存器的Dalvik和基于堆栈的JVM,以及有着不同的指令集。JVM会使用x条指令将x个参数加载到堆栈,而Dalvik则是使用寄存器,用指令进行运算。一般来说,基于寄存器的Dalvik使用较少的指令,但代码大于基于堆栈的虚拟机中相应的代码。但是在大多数体系架构中,加载代码比指令调度成本要小,因此基于寄存器的虚拟机效率更高。

在大多数生产设备中,系统库和预安装的应用程序不直接包含独立于设备的DEX代码。在性能优化上,DEX代码被转换为设备相关格式并存储在Optimized DEX(.odex)文件中,该文件通常位于与其父JAR或者APK文件相同目录中。在安装时为用户安装的应用程序执行类似的优化过程。

1.4 Java运行时库

Java语言的实现需要一组运行时库(runtime libraries),主要定义在java.*和javax.*包中。Android的核心Java库最初是从Apache Harmony project4中派生出来的。随着Android的发展,原来的Harmony代码已经发生的很大变化,在这个过程中,一些功能已经完全被替换(比如国际化支持,加密服务,和一些相关类),而其他功能则得到了扩展和改进。核心库主要以Java开发,但它们也具有一些本机代码依赖性。本机代码使用标准Java Native Interface(JNI)链接到Android的Java库中,该代码允许Java代码调用本机代码,反之亦然。Java运行时库直接从系统服务和应用程序访问。

1.5 系统服务

迄今为止引入的各种层构成了实现Android系统服务核心所需的管道。系统服务(从4.4版本开始)实现了大部分基本的Android功能,包括显示和触摸屏支持、电话和网络连接。大多数系统服务都是用Java实现的,一些基本的是用本机代码编写的。

除少数例外情况,每个系统服务都定义了可以从其他服务和应用程序调用的远程接口。再加上Binder提供的服务发现、中介和IPC,系统服务在Linux之上有效地实现了一个面向对象操作系统。

1.6 进程间通信(IPC)

如前所述,Binder是一个IPC机制。与任何类Unix系统一样,Android中的进程是具有独立的地址空间的,进程无法直接访问另一进程的内存(进程隔离)。但是如果进程想为其他进程提供一些有用的服务,则需要提供一些机制,以允许其他进程发现这些服务并与之交互,这个机制被称为IPC。对于标准IPC机制的需求并不新鲜,在Android之前就有好几个选择,包括文件、信号、套接字(sockets)、管道(pipes)、信号量(semaphores)、共享内存(shared memory)、消息队列(message queues)等等。虽然Android使用了其中一些(如本地套接字),但是它不支持其他(即系统V IPC,比如信号量、共享内存和消息队列)

1.7 Binder

由于标准IPC机制不够灵活可靠,因此为Android开发了一种名为Binder的新的IPC机制。虽然Android的Binder是一个新的实现,但它基于OpenBinder的体系结构和思想。

Binder实现了基于抽象接口的分布式组件结构,它类似于Unix上的Windows通用对象模型(COM)和通用对象代理请求结构(CORBA),但与这些框架不同,它运行在单个设备上,不支持跨网络的远程调用(RPC)(尽管RPC支持可以在Binder上实现)。下面简要介绍Binder主要组件。

1.7.1 Binder实现

如前面所述,在类Unix系统上,进程无法访问另一个进程的内存,但是内核可以控制所以进程,因此可以公开一个启用IPC的接口。在Binder中,此接口是/dev/binder,由Binder内核驱动程序实现。Binder driver是框架的中心,所有的IPC调用都会通过它。进程间通信通过一个ioctl()调用实现,调用通过binder\_write\_read结构(由包含驱动程序命令的write_buffer和包含用户空间需要执行的命令的read_buffer组成)发送和接收数据。

但是数据如何在实际进程中传递的?Binder驱动程序管理每个进程的部分地址空间。Binder驱动程序管理的内存块对进程是只读的,并且所有的写入都有内核模块执行。当进程向另一进程发送消息的时候,内核会在目标进程的内存中分配一些空间,然后直接从发送进程中复制消息数据。然后它将一条短消息排队到接收过程,告诉它收到的消息在哪里。接受者可以直接访问该消息,因为它在自己的内存空间中。当一个进程结束时,它会通知Binder驱动程序将内存标记为空闲。下图是Binder IPC体系结构简化图

Android中更高级别的IPC抽象,例如Intents(带有相关数据的命令,这些数据通过进程传递给组件)、Messengers(支持跨进程的基于消息通信的对象)和ContentProviders(公开跨进程数据管理接口的组件)建立在Binder之上。另外需要暴露给其它进程的服务接口可以使用Android接口定义语言(AIDL)进行定义,AIDL使客户端可以调用远程服务,就好像它们是本地Java对象一样。关联的AIDL工具自动生成stubs存根(客户端表示的远程对象),和将接口方法映射到较低级别的transact()Binder方法并负责将参数转换为Binder可以传输的格式的proxies代理(这称为parameter marshalling/unmarshalling参数的编组/解组)。因为Binder本质上还是无类型的,所以AIDL生成的存根和代理还通过在每个Binder事务(在代理中)包含目标接口名称并在存根中验证它来提供类型安全性。

1.7.2 Binder安全

在更高级别上,可以通过Binder框架访问每个对象实现的IBinder接口并且这被称为Binder对象。对Binder对象的调用在Binder事务中执行,该事务包括对目标对象的引用、要执行的方法的ID以及数据缓冲区。Binder驱动程序会自动将调用进程的进程标识(PID)和有效用户标识(EUID)添加到事务数据中。被调用进程(被调用者)可以检查PID和EUID,并根据其内部逻辑或关于调用应用程序的系统元数据来决定是否应该执行请求的方法。

由于PID和EUID由内核填充,因此调用者进程不能伪造其身份以获得比系统允许的更多特权(Binder阻止提权)。这是Android安全模型的核心部分之一,所有更高级别的抽象(比如权限)都是基于它的。调用者的EUID和PID可以通过android.os.Binder类的getCallingPid()getCallingUid()方法范围,该类是Android的公有API一部分。

注意: 如果多个应用程序在同一个UID下执行,则调用进程的EUID可能无法映射到单个应用程序,但是这不会影响安全决策,因为在相同的UID下运行的进程通常都会被授予相同的权限和特权(除非已定义特定于进程的SELinux规则)

1.7.3 Binder标识

Binder对象最重要的属性之一是它们在流程中保持唯一的身份。因此,如果进程A创建一个Binder对象并将其传递给进程B,而进程B又将其传递给进程C,则来自所有三个进程的调用将由同一个Binder对象进行处理。实际上,进程A将直接通过其内存地址引用Binder对象(因为它在进程A的内存空间中),而进程B和C只会接收到Binder对象的句柄。

内核维护其他进程中"实时"Binder对象及其句柄之间的映射。由于Binder对象的身份是唯一的,并且由内核维护,所以用户空间进程不可能创建Binder对象的副本或获取对其的引用,除非已通过IPC交给IPC。因此,一个Binder对象是一个独特的,不可伪造的,可传播的对象,可以充当安全令牌(token)。这使得在Android中使用Capability-Based Security(基于能力的安全性)成为可能。

1.7.4 Capability-Based Security

在基于能力的安全模型中,程序被授予对特定资源的访问权限,为其提供一种不可伪造的功能,该功能既引用目标对象又封装一组访问权限。由于功能是不可伪造的,程序拥有一项功能的事实足以让它能够访问目标资源,不需要维护与实际资源相关联的访问控制列表(ACL)或类似结构。

1.7.5 Binder Tokens

在Android中,Binder对象可以充当功能,并以这种方式使用时称为Binder token。Binder token既可以是能力也可以是目标资源。拥有一个Binder token赋予拥有进程完全访问一个Binder对象的权利,使它能够对目标对象执行Binder事务。如果Binder对象实现多个动作(通过根据Binder事务的代码参数选择要执行的动作),则调用者可以在引用该Binder对象时执行任何动作。如果需要更细化的访问控制,则每个操作的实施需要执行必要的权限检查,通常通过使用调用者进程的PID和EUID。

Android中的一种常见模式是运行对系统(UID 1000)和根(UID 0)运行的调用者执行所有操作。但对所有其他进程执行额外的权限检查。因此访问重要的Binder对象(如系统服务)的方式有两种:通过限制谁可以获取对该Binder对象的引用,以及在对Binder对象执行操作之前检查调用者身份。(如果需要,这个检查是可选的,并且由Binder对象本身实现)

另外,一个Binder对象只能作为一个功能使用,而不需要实现任何其他功能。在这种使用模式中,同一个Binder对象由两个(或更多)合作进程持有,而充当服务器(处理某种客户端请求)的Binder对象使用Binder token来验证其客户端,就像Web服务器使用session cookies一样。这种使用模式由Android框架在内部使用,并且大多数应用程序不可见。在公有API中可见的Binder token的一个值得注意的用例是窗口 token。每个活动的顶层窗口都与一个Binder token(称为一个窗口token)相关联,该窗口token是Android的窗口管理器(负责管理应用程序窗口的系统服务)跟踪的。应用程序可以获取自己的窗口token,但无法访问其他应用程序的窗口token。通常您不希望其他应用程序在您自己的顶部添加或者删除窗口,每个请求都必须提供与应用程序关联的窗口标记,从而保证窗口请求来着您自己的应用程序或者系统。

1.7.6 Accessing Binder Object

虽然Android处于安全目的控制对Binder对象的访问,并且与Binder对象进行通信的唯一方法是引用它,但是某些Binder对象(最显著的是系统服务),需要普遍可访问。然而,将所有系统服务的引用分发黑每个进程是不切实际的,因此我们需要一些机制来允许进程根据需要发现和获取对系统服务的引用。

为了启动服务发现,Binder框架具有单个上下文管理器,该管理器维护对Binder对象的引用。Android的上下文管理器实现是服务管理器本地守护进程。它在引导过程的早期就开始了,以便系统服务可以在启动时注册。通过将服务名称和Binder引用传递给服务管理器来注册服务。一旦注册了服务,任何客户端都可以使用其名称获取其Binder引用。但是,大多数系统服务都会执行额外的权限检查,因此获取引用不会自动保证访问其所有功能。因为任何人在向服务管理器注册时都可以访问Binder引用,所有只有一小部分被列入白名单的系统进程可以注册系统服务。例如,只有以UID 1002(AID_BLUETOOTH)执行的进程才可以注册蓝牙系统服务。

你可以使用service list命令查看已注册服务的列表,该命令将返回每个已注册服务的名称和已实现的Binder界面。

1.7.7 其它Binder功能

虽然和Android安全模型没有直接关系,但是另外两个著名的Binder功能是引用计数和死亡通知(也称为死亡链接)。引用计数可确保当没有人引用它们时,Binder对象会自动释放,并在内核驱动程序中使用BC_INCREFS,BC_ACQUIRE,BC_RELEASE和BC_DECREFS命令实现。引用计数集成在Android框架的各个级别,但不直接显示给应用程序。

死亡通知允许使用由其他进程托管的Binder对象的应用程序在内核终止这些进程并执行任何必要的清理时得到的通知。死亡通知是通过内核驱动程序中的BC_REQUEST_DEATH_NOTIFICATION和BC_CLEAR_DEATH_NOTIFICATION命令以及框架中的IBinder接口的linkToDeath()unlinkToDeath()方法实现的。(本地binder的死亡通知是不会发送的,因为没有托管过程死亡,本地binder也不会死亡)

1.8 Android框架库

有时候简称为"框架"(the framework)。框架包括所有不属于标准Java运行时(java.* , javax.*等)的Java库,并且适用于大多数部分托管在android顶层的包中。该框架包含构建Android应用程序的基本块,例如 活动,服务和内容提供者(在android.app.*包中),GUI小部件(在android.view.*和android.widget包中),文件和数据库访问类(主要在android.database.*和android.content.*包中)。它还包括允许您与设备硬件交互的类以及利用系统提供的更高级别服务的类。

尽管内核级以上的几乎所有Android操作系统功能都是作为系统服务实现的,但它并不直接暴露在框架中,而是通过称为管理器的外观类来访问。通常每个管理器都要相应的系统服务支持,例如BluetoothManager是BluetoothManagerService的表面。

1.9 应用

最高层是应用程序(或app),它们是用户直接与之交互的程序。虽然所有应用程序都具有相同的结构,并构建在Android框架之上,但我们区分了系统应用程序和用户安装的应用程序。

1.9.1 系统app

系统应用程序包含在操作系统映像(image)中,该映像在生产设备上是只读的(通常以/system身份登录),并且不能由用户卸载或更改。因此,这些应用程序被认为是安全的,并且被赋予比用户安装的应用程序更多的特权。系统app可以是核心Android操作系统的一部分,也可以只是预先安装的用户应用程序,例如电子邮件客户端或浏览器。虽然安装在/system下的所有应用程序在早期版本的Android中平等对待(除了通过检查应用程序签名证书的操作系统功能除外),Android 4.4和更高版本将安装在/system/priv-app/中的应用程序视为特权应用程序,保护级别为signatureOrSystem的权限授予应用程序,而不是安装在/system下的所有应用程序。使用平台签名密钥签名的应用可以被授予具有签名保护级别的系统权限,因此可以获得操作系统级别的权限,即使它们未预安装在/system下。虽然无法卸载或更改系统应用程序,但只要更新使用相同的私钥签名,用户就可以更新系统应用程序,有些可以由用户安装应用。例如,用户可以选择用第三方应用程序替换预安装的应用程序启动器或输入法。

1.9.2 用户安装的app

用户安装的应用程序安装在专用的读写分区上(通常以/data的形式安装),可以托管用户数据并可以随意卸载。 每个应用程序都位于专用安全沙箱中,通常不会影响其他应用程序或访问其数据。 此外,应用程序只能访问他们明确被授予使用权限的资源。 特权分离和最小特权原则是Android安全模型的核心。

1.10 Android应用程序组件

Android应用程序与传统应用程序不同,可以有多个入口点。每个组件都可以提供多个入口点,这些入口点可以基于同一应用程序或另一个应用程序中的用户操作达到,或者由应用程序已注册要通知的系统事件触发。组件及其入口点以及其他元数据在应用程序的清单文件中定义,称为AndroidManifest.xml。与大多数Android资源文件一样,此文件在捆绑到应用程序包(APK)文件之前被编译为二进制XML格式(类似于ASN.1),以减小文件大小并加快解析速度。清单文件中定义的最重要的应用程序属性是应用程序包名称,它唯一标识系统中的每个应用程序。包名称与Java包名称(反向域名表示法;例如com.google.email)格式相同。

AndroidManifest.xml文件在应用程序安装时解析,并且它定义的包和组件在系统中注册。 Android需要使用开发人员控制的密钥对每个应用程序进行签名。这保证已安装的应用程序不能被声称具有相同程序包名称的另一个应用程序替代(除非它使用相同的密钥签名,在这种情况下现有应用程序已更新)。下面列出了Android应用程序的主要组件。

1.10.1 Activities

activity是带有用户界面的单个屏幕。 activity是Android GUI应用程序的主要构建块。 一个应用程序可以有多个activity,虽然它们通常被设计为以特定顺序显示,但每个activity都可以独立启动,可能由不同的应用程序(如果允许)启动。

1.10.2 Services

service是一个在后台运行并且没有用户界面的组件。service通常用于执行一些长时间运行的操作,例如下载文件或播放音乐,而不会阻止用户界面。service还可以使用AIDL定义远程接口并为其他应用程序提供一些功能。 但是,与作为操作系统一部分并始终运行的系统服务不同,应用程序的service是按需启动和停止的。

1.10.3 Content providers

content provider提供应用程序数据的接口,通常存储在数据库或文件中。 content provider可以通过IPC访问,主要用于与其他应用程序共享应用程序的数据。 content provider对数据的哪些部分是可访问的进行了细粒度的控制,允许应用程序仅共享其数据的一个子集。

1.10.4 Broadcast receivers

broadcast receiver 是响应全系统事件的组件,称为广播。 广播可以源自系统(例如,宣布网络连接性的变化),或来自用户应用程序(例如,宣布后台数据更新已完成)。

1.11 Android's Security Model

与系统的其他部分一样,Android的安全模型也利用了Linux内核提供的安全功能。 Linux是一个多用户操作系统,内核可以隔离用户资源,就像隔离进程一样。 在Linux系统中,一个用户不能访问另一个用户的文件(除非明确授予权限),并且每个进程都以启动它的用户的标识(用户和组ID,通常称为UID和GID)运行,除非在相应的可执行文件上set-user-ID或者set-group-ID (SUID和SGID)。

Android利用了这种用户隔离,但与传统的Linux系统(桌面或服务器)对待用户不同。 在传统的系统中,UID被赋予一个物理用户,该用户可以登录系统并通过shell执行命令,或者在后台执行系统服务(守护进程)(因为系统守护进程通常可以通过网络访问 ,使用专用的UID运行每个守护进程可以在受到攻击时限制其受到的损害)。 Android最初是为智能手机设计的,而且由于手机是个人设备,因此不需要在系统中注册不同的物理用户。 物理用户是隐含的,UID用于区分应用程序。 这构成了Android应用沙盒的基础。

1.11.1 Application Sandboxing

Android会在安装时自动为每个应用程序分配一个唯一的UID(通常称为应用程序ID),并在作为该UID运行的专用进程中执行该应用程序。 此外,每个应用程序都有一个专用的数据目录,只有它有权读取和写入。 因此,应用程序在进程级别(通过使每个进程在专用进程中运行)和在文件级别(通过具有专用数据目录)都是隔离的或者沙箱化的。 这将创建一个内核级应用程序沙箱,该沙箱应用于所有应用程序,而不管它们是在本机还是虚拟机进程中执行。

系统守护进程和应用程序在定义好的常量UID下运行,很少的守护进程以root用户(UID 0)运行。 Android没有传统的/etc/password文件,它的系统UID是在android_filesystem_config.h头文件中静态定义的。系统服务的UID从1000开始,其中1000是系统(AID_SYSTEM)用户,具有特殊(但仍然有限)的权限。自动生成的应用程序的UID从10000开始(AID_APP),相应的用户名格式为app_XXX或uY_aXXX(在支持多个物理用户的Android版本上),其中XXX是来自AID_APP的偏移量,Y是Android用户ID(不是与UID相同)。例如,10037 UID对应于u0_a37用户名,可能会分配给Google电子邮件客户端应用程序(com.google.android.email包)。

电子邮件应用程序的数据目录以其包名称命名,并在单用户设备上的/data/data/下创建。数据目录内的所有文件均归专用Linux用户u0_a37所有。 应用程序可以选择使用MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE标志创建文件,以允许其他应用程序直接访问文件,这些应用程序分别有效地设置文件上的S_IROTH和S_IWOTH访问位。 但是,不鼓励直接共享文件,并且这些标志在Android 4.2及更高版本中已弃用。

应用程序UID与/data/system/packages.xml文件(规范源文件)中的其他程序包元数据一起管理,并且还写入/data/system/packages.list文件。

在这里,第一个字段是软件包名称,第二个是分配给应用程序的UID,第三个是可调试标志(1:可调试),第四个是应用程序的数据目录路径,第五个是seinfo标签(被SELinux使用的)。最后一个字段是应用程序启动的补充GID列表。每个GID通常都与Android权限相关联,并根据授予应用程序的权限生成GID列表。

可以使用相同的UID(称为共享用户ID)安装应用程序,在这种情况下,它们可以共享文件,甚至可以在相同的进程中运行。 共享用户标识被系统应用程序广泛使用,这些应用程序通常需要在不同的程序包中使用相同的资源以实现模块化。例如,在Android 4.4中,系统UI和keyguard(锁屏实现)共享UID 10012。

尽管不建议将非共享用户标识设施用于非系统应用程序,但它也可用于第三方应用程序。 为了共享相同的UID,应用程序需要使用相同的代码签名密钥进行签名。 此外,由于将共享用户ID添加到已安装应用的新版本会导致其更改其UID,因此系统不允许这样做。 因此,无法追溯添加共享用户标识,并且需要将应用程序设计为从一开始就使用共享标识。

1.11.2 Permissions

由于Android应用程序是沙盒式的,因此它们只能访问自己的文件和设备上的任何世界可访问的资源。 这样一个有限的应用程序虽然不会很有趣,并且Android可以为应用程序授予额外的细粒度访问权限,以便实现更丰富的功能。 这些访问权被称为权限,并且它们可以控制对硬件设备,互联网连接,数据或操作系统服务的访问。

应用程序可以通过在AndroidManifest.xml文件中定义它们来请求权限。 在应用程序安装时,Android会检查请求的权限列表并决定是否授予它们。 一旦被授予,权限不能被撤销,并且它们可以在没有任何额外确认的情况下被应用程序使用。 此外,即使请求应用程序已被授予相应权限,对于诸如私钥或用户帐户访问等功能,每个访问对象都需要明确的用户确认。 某些权限只能授予属于Android操作系统的应用程序,或者是因为它们是预安装的或使用与操作系统相同的密钥签名的。 第三方应用程序可以定义自定义权限并定义称为权限保护级别的类似限制,从而限制对同一作者创建的应用程序访问应用程序的服务和资源。

权限可以在不同的级别执行。 请求更低级别的系统资源(如设备文件)由Linux内核通过检查调用进程的UID或GID(针对资源的所有者和访问位)来强制执行。 当访问更高级别的Android组件时,强制执行由Android OS或每个组件(或两者)执行。

1.11.3 IPC

Android使用内核驱动程序和用户空间库的组合来实现IPC。 “Binder”中所述,Binder内核驱动程序保证不能伪造调用者的UID和PID,并且许多系统服务依赖Binder提供的UID和PID来动态控制对通过IPC公开的敏感API的访问。

更多粗粒度的权限影响通过IPC公开的服务的所有方法的粗粒度权限可以通过在服务声明中指定权限由系统自动执行。 与请求的权限一样,所需的权限在AndroidManifest.xml文件中声明。 与上例中的动态权限检查一样,每个组件的权限也可以通过查询从Binder获得的调用者UID来实现。 系统使用包数据库来确定被调用者组件所需的权限,然后将调用者UID映射到包名称并检索授予调用者的权限集。 如果所需的权限在该组中,则调用成功。 如果不是,则失败并且系统抛出SecurityException。

1.11.4 Code Signing and Platform Keys

所有Android应用程序必须由其开发人员签名,包括系统应用程序。由于Android APK文件是Java JAR包格式的扩展,因此所使用的代码签名方法也基于JAR签名。 Android使用APK签名来确保应用程序的更新来自同一作者(这称为相同的来源策略),并在应用程序之间建立信任关系。这两种安全功能都是通过将当前安装的目标应用程序的签名证书与更新证书或相关应用程序进行比较来实现的。系统应用程序由多个平台密钥签名。不同的系统组件可以共享资源,并在使用相同平台密钥签名时在同一进程内运行。平台密钥由维护安装在特定设备上的Android版本生成和控制:设备制造商,运营商,Google for Nexus设备或用户自建的开源Android版本。

1.11.5 Multi-User Support

由于Android最初是为具有单个物理用户的手机(智能手机)设备设计的,因此它为每个已安装的应用程序分配不同的Linux UID,并且传统上没有物理用户的概念。 Android在4.2版中获得了对多个物理用户的支持,但只有平板电脑才支持多用户支持,而平板电脑更有可能被共享。通过将用户的最大数量设置为1,手机设备上的多用户支持被禁用。每个用户被分配唯一的用户ID,从0开始,并且用户被赋予其自己的专用数据目录在/data/system/users/<用户ID>/,这被称为用户的系统目录。此目录托管用户特定的设置,例如主屏幕参数,帐户数据以及当前安装的应用程序的列表。虽然应用程序二进制文件在用户之间共享,但每个用户都会获得应用程序数据目录的副本。为了区分为每个用户安装的应用程序,安卓为特定用户安装时,Android会为每个应用程序分配一个新的有效UID。这个有效的UID基于目标物理用户的用户ID和应用程序在单用户系统中的UID(应用程序ID)。授予的UID的这种组合结构保证即使两个不同用户安装了相同的应用程序,两个应用程序实例也都可以获得他们自己的沙箱。此外,Android还为每个物理用户保证专用的共享存储(托管在旧设备的SD卡上),这些存储是世界可读的。 用户首先初始化设备称为设备所有者,只有他们可以管理其他用户或执行影响整个设备的管理任务(如出厂重置)。

1.11.6 SELinux

传统的Android安全模型在很大程度上依赖于授予应用程序的UID和GID。尽管这些内核是由内核保证的,并且默认情况下每个应用程序的文件都是私有的,但是没有任何东西阻止应用程序授予世界对其文件的访问权限(无论是有意还是由于编程错误)。同样,没有任何措施可以防止恶意应用程序利用系统文件或本地套接字的过度宽容访问位。实际上,分配给应用程序或系统文件的不适当权限已成为多个Android漏洞的来源。这些漏洞在Linux使用的默认访问控制模型中是不可避免的,称为自主访问控制(DAC)。这里的自由选择意味着一旦用户访问特定的资源,他们可以自行决定将其传递给另一个用户,例如通过将其中一个文件的访问模式设置为世界可读。相反,强制访问控制(MAC)确保对资源的访问符合系统范围内称为策略的一组授权规则。该策略只能由管理员进行更改,并且用户无法覆盖或绕过该策略,例如,授予每个人访问自己的文件的权限。安全增强型Linux(SELinux)是Linux内核的MAC实现,已经在主线内核中集成了10多年。从版本4.3开始,Android集成了来自Android安全增强(SEAndroid)项目的一个修改过的SELinux版本,该版本已被增强以支持Android特定的功能,例如Binder。在Android中,SELinux用于隔离不同安全域中的核心系统守护进程和用户应用程序,并为每个域定义不同的访问策略。从版本4.4开始,SELinux以强制模式进行部署(违反系统策略生成运行时错误),但策略强制只应用于核心系统守护进程。应用程序仍然以宽容模式运行,并且会记录违规,但不会导致运行时错误。

1.11.7 System Updates

Android设备可以通过空中传送(OTA)或通过将设备连接到PC并使用标准Android调试桥(ADB)客户端或某些供应商提供的具有类似功能的应用程序推送更新映像来进行更新。由于除系统文件之外,Android更新可能需要修改基带(调制解调器)固件,引导加载程序以及无法从Android直接访问的设备的其他部分,因此更新过程通常使用特殊用途的最小操作系统独家访问所有设备硬件。这被称为恢复操作系统或简单恢复。 OTA更新通过下载OTA包文件(通常是带有附加代码签名的ZIP文件)来执行,其中包含一个由恢复解释的小脚本文件,并在恢复模式下重新启动设备。或者,用户可以在引导设备时使用设备特定组合键进入恢复模式,并通过使用恢复的菜单界面手动应用更新,恢复菜单界面通常使用硬件按钮(音量增大/减小,电源和等等)的设备。在生产设备上,恢复只接受设备制造商签名的更新。更新文件通过扩展ZIP文件格式进行签名,以在注释部分的整个文件中包含签名,在安装更新之前,恢复将提取并验证这些签名。在某些设备(包括所有Nexus设备,专用开发人员设备和某些供应商设备)上,设备所有者可以替换恢复操作系统并禁用系统更新签名验证,从而允许他们通过第三方安装更新。将设备引导加载程序切换到允许替换恢复和系统映像的模式称为引导加载程序解锁(不要与SIM解锁混淆,它允许设备在任何移动网络上使用),并且通常需要擦除所有用户数据(工厂重置),以确保潜在的恶意第三方系统映像无法访问现有的用户数据。在大多数消费设备上,解锁引导加载程序会导致设备保修失效。

1.11.8 Verified Boot

从版本4.4开始,Android支持使用Linux Device-Mapper的verity target进行验证启动。 Verity使用加密哈希树提供对块设备的透明完整性检查。树中的每个节点都是加密哈希,叶节点包含物理数据块的哈希值,中间节点包含其子节点的哈希值。由于根节点中的散列基于所有其他节点的值,因此只需要信任根散列以验证树的其余部分。使用引导分区中包含的RSA公钥执行验证。设备块在运行时通过计算块读取时的散列值并将其与哈希树中记录的值进行比较来检查。如果这些值不匹配,则读取操作会导致I/O错误,指示文件系统已损坏。因为所有检查都是由内核执行的,所以引导过程需要验证内核的完整性,以便验证的引导起作用。此过程是特定于设备的,通常通过使用不可更改的硬件特定的密钥来实现,该密钥被“烧”(写入只写存储器)到设备中。该密钥用于验证每个引导加载程序级别和最终内核的完整性。

总结

Android是基于Linux内核的权限分离操作系统。 更高级别的系统功能是作为一套协作系统服务实现的,它们使用称为Binder的IPC机制进行通信。 Android通过运行具有不同系统标识(Linux UID)的应用程序来隔离彼此的应用程序。 默认情况下,应用程序被赋予极少的权限,并且必须请求精细的权限才能与系统服务,硬件设备或其他应用程序交互。 权限在每个应用程序的清单文件中定义,并在安装时授予。 系统使用每个应用程序的UID来找出它已授予的权限并在运行时强制执行它们。 在最近的版本中,系统进程隔离利用SELinux来进一步限制给予每个进程的权限。

你可能感兴趣的:(《Android Security Internals》第一章安卓安全模型翻译)