1.Android 中的安全增强型 Linux

作为 Android 安全模型的一部分,Android 使用安全增强型 Linux (SELinux) 对所有进程强制执行强制访问控制 (MAC),甚至包括以 Root/超级用户权限运行的进程(Linux 功能)。很多公司和组织都为 Android 的 SELinux 实现做出了贡献。借助 SELinux,Android 可以更好地保护和限制系统服务、控制对应用数据和系统日志的访问、降低恶意软件的影响,并保护用户免遭移动设备上的代码可能存在的缺陷的影响。

SELinux 按照默认拒绝的原则运行:任何未经明确允许的行为都会被拒绝。SELinux 可按两种全局模式运行:

  • 宽容模式:权限拒绝事件会被记录下来,但不会被强制执行。
  • 强制模式:权限拒绝事件会被记录下来强制执行。

Android 中包含 SELinux(处于强制模式)和默认适用于整个 AOSP 的相应安全政策。在强制模式下,非法操作会被阻止,并且尝试进行的所有违规行为都会被内核记录到 dmesglogcat。开发时,您应该先利用这些错误信息对软件和 SELinux 政策进行优化,再对它们进行强制执行。如需了解详情,请参阅实现 SELinux

此外,SELinux 还支持基于域的宽容模式。在这种模式下,可将特定域(进程)设为宽容模式,同时使系统的其余部分处于全局强制模式。简单来说,域是安全政策中用于标识一个进程或一组进程的标签,安全政策会以相同的方式处理所有具有相同域标签的进程。借助基于域的宽容模式,可逐步将 SELinux 应用于系统中越来越多的部分,还可以为新服务制定政策(同时确保系统的其余部分处于强制模式)。

1.1背景

Android 安全模型部分基于应用沙盒的概念。每个应用都在自己的沙盒内运行。在 Android 4.3 之前的版本中,这些沙盒是通过为每个应用创建独一无二的 Linux UID(在应用安装时创建)来定义的。Android 4.3 及更高版本使用 SELinux 进一步定义 Android 应用沙盒的边界。

基于 Android 4.3(宽容模式)和 Android 4.4(部分强制模式),在 Android 5.0 及更高版本中,已全面强制执行 SELinux。通过此项变更,Android 已从对有限的一组关键域(installdnetdvoldzygote)强制执行 SELinux 转为对所有域(超过 60 个)强制执行 SELinux。具体而言:

  • 在 Android 5.x 及更高版本中,所有域均处于强制模式。
  • init 以外的任何进程都不应在 init 域中运行。
  • 出现任何常规拒绝事件(对于 block_devicesocket_devicedefault_service),都表示设备需要一个特殊域。

Android 6.0 通过降低我们政策的宽容度强化了系统安全,从而实现更好的用户隔离和 IOCTL 过滤、降低可从设备/系统之外访问的服务面临的威胁、进一步强化 SELinux 域,以及高度限制对 /proc 的访问。

Android 7.0 更新了 SELinux 配置,以进一步锁定应用沙盒并缩小受攻击面。此版本还将单片式 mediaserver 堆栈拆分为较小的进程,以缩小其权限范围。如需了解详情,请参阅利用更多的 Linux 内核防护功能保护 Android 系统媒体堆栈安全强化

Android 8.0 更新了 SELinux 以便与 Treble 配合使用,后者可将较低级别的供应商代码与 Android 系统框架分离开来。此版本更新了 SELinux 政策以允许设备制造商和 SOC 供应商更新自己的政策部分、构建自己的映像(vendor.imgboot.img 等),然后更新这些映像而不受平台影响,反之亦然。

虽然可以在设备上运行更高/更新版本的平台(框架),但反之并不成立;供应商映像 (vendor.img/odm.img) 的版本不能高于平台 (system.img) 的版本。因此,较新版平台可能会带来 SELinux 兼容性问题,因为平台 SELinux 政策的版本要比该政策的供应商 SELinux 部分更新。Android 8.0 模型提供了一种保持兼容性的方法,以免进行不必要的同时 OTA。

1.2其他资源

如需关于构建实用 SELinux 政策的帮助,请参阅以下资源:

2.SELinux 概念

请查看此页中的内容,熟悉 SELinux 概念。

2.1强制访问控制

安全增强型 Linux (SELinux) 是适用于 Linux 操作系统的强制访问控制 (MAC) 系统。作为 MAC 系统,它与 Linux 中用户非常熟悉的自主访问控制 (DAC) 系统不同。在 DAC 系统中,存在所有权的概念,即特定资源的所有者可以控制与该资源关联的访问权限。这种系统通常比较粗放,并且容易出现无意中提权的问题。MAC 系统则会在每次收到访问请求时都先咨询核心机构,再做出决定。

SELinux 已作为 Linux 安全模块 (LSM) 框架的一部分实现,该框架可识别各种内核对象以及对这些对象执行的敏感操作。其中每项操作要执行时,系统都会调用 LSM 钩子函数,以便根据不透明安全对象中存储的关于相应操作的信息来确定是否应允许执行相应操作。SELinux 针对这些钩子以及这些安全对象的管理提供了相应的实现,该实现可结合自己的政策来决定是否允许相应访问。

通过结合使用其他 Android 安全措施,Android 的访问控制政策能够大大降低遭到入侵的计算机和帐号可能蒙受的损失。Android 的自主访问控制和强制访问控制等工具可为您提供一种结构,确保您的软件仅以最低权限级别运行。这样可降低攻击造成的影响,并降低错误进程重写数据甚至是传输数据的可能性。

在 Android 4.3 及更高版本中,SELinux 开始为传统的自主访问控制 (DAC) 环境提供强制访问控制 (MAC) 保护功能。例如,软件通常情况下必须以 Root 用户帐号的身份运行,才能向原始块设备写入数据。在基于 DAC 的传统 Linux 环境中,如果 Root 用户遭到入侵,攻击者便可以利用该用户身份向每个原始块设备写入数据。不过,可以使用 SELinux 为这些设备添加标签,以便被分配了 Root 权限的进程可以只向相关政策中指定的设备写入数据。这样一来,该进程便无法重写特定原始块设备之外的数据和系统设置。

如需更多安全威胁示例以及使用 SELinux 解决安全威胁的方法,请参阅用例

2.2强制执行级别

SELinux 可以在各种模式下实现:

  • 宽容模式 - 仅记录但不强制执行 SELinux 安全政策。
  • 强制模式 - 强制执行并记录安全政策。如果失败,则显示为 EPERM 错误。

在选择强制执行级别时只能二择其一,您的选择将决定您的政策是采取操作,还是仅允许您收集潜在的失败事件。宽容模式在实现过程中尤其有用。

2.3标签、规则和域

SELinux 依靠标签来匹配操作和政策。标签用于决定允许的事项。套接字、文件和进程在 SELinux 中都有标签。SELinux 在做决定时需参照两点:一是为这些对象分配的标签,二是定义这些对象如何交互的政策。

在 SELinux 中,标签采用以下形式:user:role:type:mls_level,其中 type 是访问决定的主要组成部分,可通过构成标签的其他组成部分进行修改。对象会映射到类,对每个类的不同访问类型由权限表示。

政策规则采用以下形式:allow *domains* *types*:*classes* *permissions*;,其中:

  • Domain - 一个进程或一组进程的标签。也称为域类型,因为它只是指进程的类型。
  • Type - 一个对象(例如,文件、套接字)或一组对象的标签。
  • Class - 要访问的对象(例如,文件、套接字)的类型。
  • Permission - 要执行的操作(例如,读取、写入)。

使用政策规则时将遵循的结构示例:

1
allow appdomain app_data_file:file rw_file_perms;

这表示所有应用域都可以读取和写入带有 app_data_file 标签的文件。请注意,该规则依赖于在 global_macros 文件中定义的宏,您还可以在 te_macros 文件中找到一些其他非常实用的宏。其中提供了一些适用于常见的类、权限和规则分组的宏。应尽可能使用这些宏,以便降低因相关权限被拒而导致失败的可能性。这些宏文件位于 system/sepolicy 目录中。在 Android 8.0 及更高版本中,它们与其他受支持的公共 sepolicy 一起位于 public 子目录中。

除了在规则中逐个列出域或类型之外,还可以通过属性引用一组域或类型。简单来说,属性是一组域或类型的名称。每个域或类型都可以与任意数量的属性相关联。当编写的规则指定了某个属性名称时,该名称会自动扩展为列出与该属性关联的所有域或类型。例如,domain 属性与所有进程域相关联,file_type 属性与所有文件类型相关联。

使用上述语法可以创建构成 SELinux 政策基本内容的 avc 规则。规则采用以下形式:

1
RULE_VARIANT SOURCE_TYPES TARGET_TYPES : CLASSES PERMISSIONS

该规则指明了,当带有任何 source_types 标签的主体尝试对包含任何 classes 类且带有 target_types 标签的对象执行与任何 permissions 对应的操作时,应该发生什么情况。这些规则的一个最常见示例是 allow 规则,例如:

1
allow domain null_device:chr_file { open };

该规则允许具有与 domain 属性关联的任何 domain 的进程对 target_type 标签为 null_device 的 class chr_file(字符设备文件)的对象执行 permission open 所描述的操作。在实践中,该规则可能会扩展,包含其他权限:

1
allow domain null_device:chr_file { getattr open read ioctl lock append write};

当了解到 domain 是分配给所有进程域的属性,并且 null_device 是字符设备 /dev/null 的标签时,该规则基本上会允许对 /dev/null 进行读写操作。

一个 domain 通常对应一个进程,而且具有与其关联的标签。

例如,典型的 Android 应用会在自己的进程中运行,并且具有 untrusted_app 标签(用于向其授予特定受限权限)。

系统中内置的平台应用会以单独的标签运行,并会被授予一组不同的权限。作为核心 Android 系统的一部分,系统 UID 应用以表示另一组权限的 system_app 标签运行。

在任何情况下,都不应直接允许域访问以下通用标签;而应为一个或多个对象创建一个更具体的类型:

  • socket_device
  • device
  • block_device
  • default_service
  • system_data_file
  • tmpfs

3.实现 SELinux

SELinux 被设置为“默认拒绝”模式,这表示,对于在内核中存在钩子的每一次访问,都必须获得政策的明确许可。这意味着政策文件中包含规则、类型、类、权限等方面的大量信息。关于 SELinux 的完整注意事项不在本文档的讨论范围之内,现在您必须要了解的是在启动新的 Android 设备时如何编写政策规则。目前有大量关于 SELinux 的信息可供您参考。关于建议的资源,请参阅支持文档

3.1关键文件

如需启用 SELinux,请集成最新的 Android 内核,然后整合 system/sepolicy 目录中的文件。这些文件在编译后会包含 SELinux 内核安全政策,并涵盖上游 Android 操作系统。

通常情况下,您不能直接修改 system/sepolicy 文件,但您可以添加或修改自己的设备专用政策文件(位于 /device/manufacturer/device-name/sepolicy 目录中)。在 Android 8.0 及更高版本中,您对这些文件所做的更改只会影响供应商目录中的政策。如需详细了解 Android 8.0 及更高版本中的公共 sepolicy 分离,请参阅在 Android 8.0 及更高版本中自定义 SEPolicy。无论是哪个 Android 版本,您都仍需要修改以下文件:

3.2政策文件

*.te 结尾的文件是 SELinux 政策源代码文件,用于定义域及其标签。您可能需要在 /device/manufacturer/device-name/sepolicy 中创建新的政策文件,但您应尽可能尝试更新现有文件。

3.3上下文的描述文件

您可以在上下文的描述文件中为您的对象指定标签。

  • file_contexts 用于为文件分配标签,并且可供多种用户空间组件使用。在创建新政策时,请创建或更新该文件,以便为文件分配新标签。如需应用新的 file_contexts,请重新构建文件系统映像,或对要重新添加标签的文件运行 restorecon。在升级时,对 file_contexts 所做的更改会在升级过程中自动应用于系统和用户数据分区。此外,您还可以通过以下方式使这些更改在升级过程中自动应用于其他分区:在以允许读写的方式装载相应分区后,将 restorecon_recursive 调用添加到 init.board.rc 文件中。
  • genfs_contexts 用于为不支持扩展属性的文件系统(例如,procvfat)分配标签。此配置会作为内核政策的一部分进行加载,但更改可能对内核 inode 无效。要全面应用更改,您需要重新启动设备,或卸载并重新装载文件系统。此外,通过使用 context=mount 选项,您还可以为装载的特定系统文件(例如 vfat)分配特定标签。
  • property_contexts 用于为 Android 系统属性分配标签,以便控制哪些进程可以设置这些属性。在启动期间,init 进程会读取此配置。
  • service_contexts 用于为 Android Binder 服务分配标签,以便控制哪些进程可以为相应服务添加(注册)和查找(查询)Binder 引用。在启动期间,servicemanager 进程会读取此配置。
  • seapp_contexts 用于为应用进程和 /data/data 目录分配标签。在每次应用启动时,zygote 进程都会读取此配置;在启动期间,installd 会读取此配置。
  • mac_permissions.xml 用于根据应用签名和应用软件包名称(后者可选)为应用分配 seinfo 标记。随后,分配的 seinfo 标记可在 seapp_contexts 文件中用作密钥,以便为带有该 seinfo 标记的所有应用分配特定标签。在启动期间,system_server 会读取此配置。

3.4 BoardConfig.mk makefile

修改或添加政策文件和上下文的描述文件后,请更新您的 /device/manufacturer/device-name/BoardConfig.mkmakefile 以引用 sepolicy 子目录和每个新的政策文件。如需详细了解 BOARD_SEPOLICY 变量,请参阅 system/sepolicy/README 文件

1
2
3
4
5
6
7
BOARD_SEPOLICY_DIRS += \
<root>/device/manufacturer/device-name/sepolicy

BOARD_SEPOLICY_UNION += \
genfs_contexts \
file_contexts \
sepolicy.te

重新进行构建后,您的设备会启用 SELinux。现在,您可以根据您向 Android 操作系统添加的内容自定义 SELinux 政策(如自定义中所述),也可以验证现有设置(如验证中所述)。

在新政策文件和 BoardConfig.mk 更新部署到位后,新政策设置会自动内置到最终的内核政策文件中。如需详细了解如何在设备上构建 sepolicy,请参阅构建 sepolicy

3.5 实现

如需开始使用 SELinux,请执行以下操作:

  1. 在内核中启用 SELinux:CONFIG_SECURITY_SELINUX=y

  2. 更改 kernel_cmdline 参数,以便:

    1
    BOARD_KERNEL_CMDLINE := androidboot.selinux=permissive

    这仅适用于初始制定设备政策的情况。在拥有初始引导程序政策后,请移除此参数,以便将设备恢复强制模式,否则设备将无法通过 CTS 验证。

  3. 以宽容模式启动系统,看看在启动时会遇到哪些拒绝事件:

    在 Ubuntu 14.04 或更高版本中,请运行以下命令:

    1
    adb shell su -c dmesg | grep denied | audit2allow -p out/target/product/BOARD/root/sepolicy

    在 Ubuntu 12.04 中,请运行以下命令:

    1
    2
    adb pull /sys/fs/selinux/policy
    adb logcat -b all | audit2allow -p policy
  4. 评估与以下内容类似的警告的输出:init: Warning! Service name needs a SELinux domain defined; please fix!。如需查看相关说明和工具,请参阅验证

  5. 标识设备以及需要添加标签的其他新文件。

  6. 为您的对象使用现有标签或新标签。查看 *_contexts 文件,了解之前是如何为内容添加标签的,然后根据对标签含义的了解分配一个新标签。这个标签最好是能够融入到政策中的现有标签,但有时也需要使用新标签,而且还需要提供关于访问该标签的规则。将您的标签添加到相应的上下文的描述文件中。

  7. 标识应该拥有自己的安全域的域/进程。您可能需要为每一项分别编写一个全新的政策。例如,从

    1
    init

    衍生的所有服务都应该有自己的安全域。以下命令有助于查看保持运行的服务(不过所有服务都需要如此处理):

    1
    adb shell su -c ps -Z | grep init
    1
    adb shell su -c dmesg | grep 'avc: '
  8. 查看 init.device.rc 以发现没有域类型的域。请在开发过程早期为其提供相应的域,以避免向 init 添加规则或将 init 访问权限与其自身政策中的访问权限混淆。

  9. 设置 BOARD_CONFIG.mk 以使用 BOARD_SEPOLICY_* 变量。如需详细了解如何进行此项设置,请参阅 system/sepolicy 中的 README

  10. 检查 init.device.rc 和 fstab.device 文件,确保每一次使用 mount 都对应一个添加了适当标签的文件系统,或者指定了 context= mount 选项。

  11. 查看每个拒绝事件,并创建 SELinux 政策来妥善处理每个拒绝事件。请参阅自定义中的示例。

建议从 AOSP 中的政策入手,然后在这些政策的基础上创建自己的自定义政策。要详细了解政策策略以及其中一些步骤,请参阅编写 SELinux 政策

3.6 用例

下面列举了一些在开发软件以及制定关联的 SELinux 政策时需要注意的具体漏洞:

符号链接 - 由于符号链接以文件形式显示,因此系统通常将其作为文件进行读取,而这可能会导致漏洞。例如,某些特权组件(例如 init)会更改某些文件的权限,有时会使之极度开放。

这样一来,攻击者便可以将这些文件替换成指向其控制的代码的符号链接,从而重写任意文件。但如果您知道自己的应用绝不会遍历符号链接,则可以通过 SELinux 来禁止您的应用遍历符号链接。

系统文件 - 以应该只有系统服务器可以修改的一系列系统文件为例。由于 netdinitvold 是以 Root 身份运行的,因此它们也可以访问这些系统文件。这样一来,如果 netd 遭到入侵,这些文件乃至系统服务器本身都可能遭到入侵。

借助 SELinux,您可以将这些文件标识为系统服务器数据文件。这样一来,系统服务器就是唯一对这些文件具有读写权限的域。即使 netd 遭到入侵,它也无法将域切换到系统服务器域并访问这些系统文件,就算它是以 Root 身份运行的也是如此。

应用数据 - 另一个示例是必须以 Root 身份运行但不应获得应用数据访问权限的一系列函数。这一项非常有用,因为它可以做出广泛的声明,例如禁止与应用数据无关的特定域访问互联网。

setattr - 对于 chmodchown 等命令,您可以标识关联域可以在哪些文件中进行 setattr 操作。这样一来,便可以禁止对这些文件之外的任何文件进行这类更改,即使以 Root 身份进行也不例外。因此,应用可以对带 app_data_files 标签的文件运行 chmodchown,但不能对带 shell_data_filessystem_data_files 标签的文件运行这些命令。