Android 安全机制

Table of Contents

Android 安全机制概述

Android 是一个权限分离的系统 。 这是利用 Linux 已有的权限管理机制,通过为每一个 Application 分配不同的 uid 和 gid , 从而使得不同的 Application 之间的私有数据和访问( native 以及 java 层通过这种 sandbox 机制,都可以)达到隔离的目的 。 与此 同时, Android 还 在此基础上进行扩展,提供了 permission 机制,它主要是用来对 Application 可以执行的某些具体操作进行权限细分和访问控制,同时提供了 per-URI permission 机制,用来提供对某些特定的数据块进行 ad-hoc 方式的访问。

uid 、 gid 、 gids

Android 的权限分离的基础是建立在 Linux 已有的 uid 、 gid 、 gids 基础上的。

uid gid gids 的 详细 设置过程: 请参考 Act i vityManagerService 中的 startProcessLocked 。在通过 zygote 来启动一个 process 时,直接将 uid 传给 给了 gid 。再通过 zygote 来 fork 出新的进程( zygote.java 中的 forkAndSpecialize ),最终在 native 层( dalvik_system_zygote.c )中的 forkAndSpecializeCommon 中通过 linux 系统调用来进行 gid 和 uid 和 gids 的设置。

permission

一个权限主要包含三个方面的信息:权限的名称;属于的权限组;保护级别。一个权限组是指把权限按照功能分成的不同的集合。每一个权限组包含若干具体权限,例如在 COST_MONEY 组中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和费用相关的权限。

每个权限通过 protectionLevel 来标识保护级别: normal , dangerous , signature , signatureorsystem 。不同的保护级别代表了程序要使用此权限时的认证方式。 normal 的权限只要申请了就可以使用;dangerous 的权限在安装时需要用户确认才可以使用; signature 和 signatureorsystem 的权限需要使用者的 app 和系统使用同一个数字证书。

Package 的权限信息主要 通过在 AndroidManifest.xml 中通过一些标签来指定。如 permission 标签, permission-group 标签 permission-tree 等标签。如果 package 需要申请使用某个权限,那么需要使用 use-permission 标签来指定。

Android permission 管理机制

Framework permission 机制

安装入口

permission 的初始化,是指 permission 的向系统申请,系统进行检测并授权,并建立相应的数据结构。绝大多数的情况下 permission 都是从一个 package 中扫描所得,而这发生在 package 安装和升级的时候。一般有如下几种 安装入口:

  1. packageInstaller , package 被下载安装时会触发使用。 packageInstaller 会通过 AppSecurityPermissions 来检查 dangerous 的权限,并对用户给出提示。
  2. pm 命令 。
  3. adb install 。最终还是 调用 pm install 来安装 apk 包。
  4. 拷贝即安装。 PackageManagerService 中使用 AppDirObserver 对 /data/app/ 进行监视 ,如果有拷贝即触发安装。

这些安装方式 最终都会通过调用 PackageManagerService 中的函数来完成程序的安装。

permission 创建

shared uid, 指定与其它 package 共享同一个 uid 。

permission, 取 permissions 标签指定属性。它使用 permissionInfo 来描述一个权限的基本信息。需要指定 protectedLevel 信息,并指定所属 group 信息。它将被添加到这个 package 的 permissions 这个 list 结构中。

permission-tree, 提取 permissions-tree 标签属性。 permissions-tree 也通过 permissionInfo 来描述,并被添加到 package 的 permissions 这个 list 结构中。 permission-tree 只是一个名字空间,用来向其中动态添加一些所谓 Dynamic 的 permission ,这些 permission 可以动态修改。这些 permission 名称要以 permission-tree 的名称开头。它本身不是一种权限,没有 protectedLevel 和所属 group 。只是保存了所属的 packge 和权限名(带有 package 前缀的)。

permission-group, 定义 permission 组信息,用 PermissionGroup 表示。本身不代表一个权限,会添加进入 package 的 permissionGroups 这个 list 中。

uses-permission, 定义了 package 需要申请的权限名。将权限名添加到 package 的 requestedPermissions 这个 list 中。

adopt-permissions, 将该标签指定的 name 存入 package 的 mAdoptPermissions 这个 list 中。 Name 指定了这个 package 需要从 name 指定的 package 进行权限领养。在 system package 进行升级时使用。

Dynamic permission 的管理

PackageManagerService 提供了 addPermission/ removePermission 接口用来动态添加和删除一些权限。但是这些权限必须是所谓的动态权限( BasePermission.TYPE_DYNAMIC )。

一个 Package 如果要添加 Dynamic permissions ,首先必须要在 manifest 中申明 permission-tree 标签,它实际上是一个权限的名字空间(例如,“ com.foo.far ”这个权限就是 permission-tree “com.foo ”的成员),本身不是一个权限。一个 Package 只能为自己的 permission-tree 或者拥有相同的 uid 的 package 添加或者删除权限。

Package 不能够通过这种接口去修改在 manifest 中静态申请的权限,否则抛出异常。

首先查找这个 permission 在全局 permission 列表 mSettings.mPermissions 中是否存在。如果存在,而且类型为 BasePermission.TYPE_DYNAMIC 那么根据传入的权限信息修改全局表中的权限信息,并触发 permissions.xml 的持久化。

如果在全局的 permission 列表 mSettings.mPermissions 中没有找到,先找到这个 permission 所在 permissionTree ,然后添加到全局 permission 列表 mSettings.mPermissions 中去,并触发 permissions.xml 的持久化。

Uri permission 的管理

下面两个 接口 主要用于 Uri permission 的管理 (其实现在 ActivityManagerService 中)。

// 为指定的 uid 和 targetPkg 添加对某个 content Uri 的读或者写权限。

public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode) throws RemoteException;

// 清除所有通过 grantUriPermission 对某个 Uri 授予的权限。

public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode) throws RemoteException;

grantUriPermission 主要的实现过程分析。

grantUriPermission 分析:

  1. 验证 caller 的 ProcessRecord 和 targetPkg 不为空。否则检测不通过。
  2. 验证所请求的 mode 为 Intent.FLAG_GRANT_READ_URI_PERMISSION 或者为 Intent.FLAG_GRANT_WRITE_URI_PERMISSION ,否则不通过。
  3. 确保参数 Uri 是一个 content Uri 。否则,则检测不通过。
  4. 通过 Uri 得到目标 ContentProvider ,如果不存在,则检测不通过。
  5. 从 PackageManagerService 中获得 targetPkg 对应的 uid 。
  6. 检查 target uid 所对应的 package 是否真正需要这个权限?
    • 先判断要申请的是读还是写权限,然后查看对应的 ContentProvider 中对应的 readPermission writePermission 字段是否保存了权限名称。 如果该字段不为空,则以 target uid 和该权限名去PackageManagerService 中去查找该 uid 是否被 granted 了该权限。如果已经获得了该权限,那么无需再去为这个 Activity 去申请这个 Uri 权限了,返回。否者继续执行如下操作。
  7. 检查这个 ContentProvider 的 grantUriPermissions 开关变量,是否允许对其它 package 进行权限的 grant 操作。如果禁止,那么抛出异常。
  8. 检查这个 ContentProvider 是否设置了 Uri 的过滤类型 uriPermissionPatterns ,如果设置了过滤类型,则将需要申请权限的 Uri 与之匹配。匹配不同过,则抛出异常。
  9. 检查调用者自己是否有权限访问这个 Uri 。如果没有,抛出异常。
  10. 从 mGrantedUriPermissions 中取得 target uid 对应的 HashMap Uri, UriPermission 数据结构。用 target uid 和 Uri 生成 UriPermission 并保存在 mGrantedUriPermissions 中。

revokeUriPermission 实现分析。

找到该 Uri 对应的 ContentProvider ,然后删除 mGrantedUriPermissions 中与 Uri 对应的所有权限。

permission 的动态检查

这里的动态检查是指是 package 在程序运行过程中进行某些操作或者数据访问时才进行的 check ,与之对应的是应用程序安装或者升级时 PackageManagerService 通过扫描包中的静态权限信息相对应。

系统与权限 检查 相关的机制的实现主要集中在 PackageManagerService 和 ActivityManagerService 中。 ActivityManagerService 主要负责的是底层的 uid 层次的身份检查; PackageManagerService 则维护了 uid 到自己拥有的和被授予的权限的一张表。在通过 ActivityManagerService 的身份检查后, PackageManagerService 根据请求者的 uid 来查看这张表,判断其是否具有相应的权限。

除此之外, per-URI permission 机制的实现也需要一张表,它维护在 ActivityManagerService 中,它建立了从 content URI 到被授权访问这个 URI 的 component 之间的映射。但是它也需要借助 PackageManagerService 的机制来辅助实现。

framework 提供的接口

Android framework 中提供了一些接口用来对外来的访问(包括自己)进行权限检查 。 这些接口 主要通过 ContextWrapper 提供,具体实现在 ContextImpl 中 。如果 package 接受到外来访问者的操作请求,那么可以调用这些接口进行权限检查。一般情况下可以把这些接口的检查接口分为两种,一种是返回错误,另一种是抛出异常。

主要包含如下几组:

下面这一组和上面类似,如果遇到检查不通过时,会抛出异常,打印消息 。

public void enforcePermission(String permission, int pid, int uid, String message)
public void enforceCallingPermission(String permission, String message)
public void enforceCallingOrSelfPermission(String permission, String message)

为某个 package 添加访问 content Uri 的读或者写权限。

public void grantUriPermission(String toPackage, Uri uri, int modeFlags)
public void revokeUriPermission(Uri uri, int modeFlags)

检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,返回值表示是否被 granted 。

public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags)
public int checkCallingUriPermission(Uri uri, int modeFlags)
public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags)
public int checkUriPermission(Uri uri, String readPermission,String writePermission, int pid, int uid, int modeFlags)

检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,如果失败则抛出异常,打印消息 。

public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message)
public void enforceCallingUriPermission(Uri uri, int modeFlags, String message)
public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)
public void enforceUriPermission(Uri uri, String readPermission, String writePermission,int pid, int uid, int modeFlags, String message)

实现分析

ContextImpl.java 中提供的 API ,其实都是由 ActivityManagerService 中的如下几个接口进行的封装。

public int checkPermission(String permission, int pid, int uid) throws RemoteException; // 主要用于一般的 permission 检查
public int checkUriPermission(Uri uri, int pid, int uid, int mode) throws RemoteException; // 主要用于 Content Uri 的 permission 检查

Android 签名机制

关于签名机制,其实分两个阶段。

  1. 包扫描阶段需要进行完整性和证书的验证。普通 package 的签名和证书是必须要先经过验证的。具体做法是对 manifest 下面的几个文件进行完整性检查。完整性检查包括这个 jar 包中的所有文件。如果是系统 package 的话,只需要使用 AndroidMenifest.xml 这个文件去提取签名和验证信息就可以了。
  2. 在权限创建阶段。如果该 package 来自 system img (系统 app ),那么 trust it ,而且使用新的签名信息去替换就的信息。前提是如果这个 package 与其它 package 共享一个 uid ,那么这个共享 uid 对应的 sharedUser 中保存的签名与之不一致,那么签名验证失败。有些时候系卸载一个 app ,但是不删除数据,那么其 PackageSettings 信息会保留,其中会保存签名信息。这样再安装是就会出现不一致。

Android Package 签名原理

android 中系统和 app 都是需要签名的。可以自己通过 development/tools/make_key 来生成公钥和私钥。

android 源代码中提供了工具 ./out/host/linux-x86/framework/signapk.jar 来进行手动签名。签名的主要作用在于限制对于程序的修改仅限于同一来源。系统中主要有两个地方会检查。如果是程序升级的安装,则要检查新旧程序的签名证书是否一致,如果不一致则会安装失败;对于申请权限的 protectedlevel 为 signature 或者 signatureorsystem 的,会检查权限申请者和权限声明者的证书是否是一致的。签名相关文件可以从 apk 包中的 META-INF 目录下找到。

signapk.jar 的源代码在 build/tools/signapk ,签名主要有以下几步:

首先将除了 CERT.RSA , CERT.SF , MANIFEST.MF 之外的所有非目录文件分别用 SHA-1 计算摘要信息,然后使用 base64 进行编码,存入 MANIFEST.MF 中。 如果 MANIFEST.MF 不存在,则需要创建。存放格式是 entry name 以及对应的摘要

对 整个 MANIFEST.MF 进行 SHA1 计算,并将摘要信息存入 CERT.SF 中 。然后对之前计算的所有摘要信息使用 SHA1 再次计算数字签名,并写入 CERT.SF 中。

把之前整个的签名输出文件 使用私有密钥计算签名。同时将签名结果,以及之前声称的公钥信息写入 CERT.RSA 中保存。

Package 的签名验证

安装时对一个 package 的签名验证的主要逻辑在 JarVerifier.java 文件的 verifyCertificate 函数中实现。 其主要的思路是通过提取 cert.rsa 中的证书和签名信息,获取签名算法等信息,然后按照之前对 apk 签名的方法进行计算,比较得到的签名和摘要信息与 apk 中保存的匹配。


title: Android 安全机制
date: 2010-08-21 13:10
author: 路人甲
site: 帐号、业务安全|岂安