本文内容摘录自:https://source.android.google.cn/security/selinux/ ,详细说明了SELinux在Android上的使用。
概述
简介
Android 安全模型部分基于应用沙盒的概念。每个应用都在自己的沙盒内运行。在 Android 4.3 之前的版本中,这些沙盒是通过为每个应用创建独一无二的 Linux UID(在应用安装时创建)来定义的。从 Android 4.3 版起,安全增强型 Linux (SELinux) 开始用于进一步定义 Android 应用沙盒的边界。
作为 Android 安全模型的一部分,Android 使用 SELinux 对所有进程强制执行强制访问控制 (MAC),其中包括以 Root/超级用户权限运行的进程(也称为 Linux 功能)。SELinux 能够限制特权进程并能够自动创建安全策略,从而可提升 Android 的安全性。
很多公司和组织都为此做出了卓越的贡献;android.googlesource.com 上公开了所有 Android 代码和贡献者,以供所有人查看。借助 SELinux,Android 可以更好地保护和限制系统服务、控制对应用数据和系统日志的访问、降低恶意软件的影响,并保护用户免遭移动设备上的代码可能存在的缺陷的影响。
Android 中包含 SELinux(处于强制模式)和默认适用于整个 Android 开放源代码项目的相应安全策略。在强制模式下,非法操作会被阻止,并且所有尝试进行的违规行为都会被内核记录到 dmesg 和 logcat 中。Android 设备制造商应收集与错误相关的信息,以便在实施其软件和 SELinux 策略之前先对其进行优化。
背景
SELinux 采用默认拒绝的方式运行。任何未经明确允许的行为都会被拒绝。SELinux 可以采用以下任一种全局模式运行:宽容模式和强制模式。在宽容模式下,权限拒绝事件会被记录下来,但不会被强制执行;在强制模式下,拒绝事件会被记录下来,并且会被强制执行。此外,SELinux 还支持特定域宽容模式。在这种模式下,可将特定域(进程)设为宽容域,同时使系统的其余部分处于全局强制模式。域简单来说就是安全策略中用于标识一个进程或一组进程的标签,安全策略会以相同的方式对待使用相同域作为标签的所有进程。借助特定域宽容模式,可逐渐将 SELinux 应用于系统中越来越多的部分。此外,借助特定域宽容模式,还可以为新服务制定策略,同时确保系统的其余部分处于强制模式。
在 Android 5.0 (L) 版本中,Android 开始全面强制执行 SELinux。这基于 4.3 版中的宽容模式和 4.4 中的部分强制模式。简而言之,Android 正在从对有限的一组关键域(installd、netd、vold 和 zygote)强制执行 SELinux 转为对所有域(超过 60 个域)强制执行 SELinux。这意味着,制造商必须要更好地了解并扩展其 SELinux 实现,以便提供兼容的设备。请注意:
- 在 5.0 版中,所有域均处于强制模式
- init 以外的任何进程都不应在 init 域中运行
- 如果出现任何常规拒绝事件(对于 block_device、socket_device、default_service 等),都表示设备需要一个特殊域
概念
强制访问控制
安全增强型 Linux (SELinux) 是适用于 Linux 操作系统的强制访问控制 (Mandatory Access Control, 简称MAC) 系统。作为 MAC 系统,它与 Linux 中用户非常熟悉的自主访问控制 (Discretionary Access Control,简称DAC) 系统不同。在 DAC 系统中,存在所有权的概念,即特定资源的所有者可以控制与相应资源关联的访问权限。这种系统通常比较粗放,并且容易出现无意中提权的问题。MAC 系统则会在决定是否允许每次访问尝试时都咨询核心机构。
SELinux 已作为 Linux 安全模块 (LSM) 框架的一部分实现,该框架可识别各种内核对象以及对这些对象执行的敏感操作。其中每项操作要执行时,系统都会调用 LSM 钩子函数,以便根据不透明安全对象中存储的关于相应操作的信息来确定是否应允许执行相应操作。SELinux 针对这些钩子以及这些安全对象的管理提供了相应的实现,该实现可结合自己的策略来决定是否允许相应访问。
通过结合使用其他 Android 安全措施,Android 的访问控制策略能够大大降低遭到入侵的计算机和帐号可能蒙受的损失。Android 的自主访问控制和强制访问控制等工具可为你提供一种结构,确保你的软件仅以最低权限级别运行。这样可降低攻击造成的影响,并降低错误进程重写数据甚至是传输数据的可能性。
从 Android 4.3 起,SELinux 开始为传统的自主访问控制 (DAC) 环境提供强制访问控制 (MAC) 保护功能。例如,软件通常情况下必须以 Root 用户帐号的身份运行,才能向原始块设备写入数据。在基于 DAC 的传统 Linux 环境中,如果 Root 用户遭到入侵,攻击者便可以利用该用户身份向每个原始块设备写入数据。不过,可以使用 SELinux 为这些设备添加标签,以便被分配了 Root 权限的进程只能向相关策略中指定的设备写入数据。这样一来,该进程便无法重写特定原始块设备之外的数据和系统设置。
强制执行级别
请熟悉以下术语,了解如何按不同的强制执行级别实现 SELinux。
- 宽容模式 - 仅记录但不强制执行 SELinux 安全策略。
- 强制模式 - 强制执行并记录安全策略。如果失败,则显示为 EPERM 错误。
在选择强制执行级别时只能二择其一,你的选择将决定你的策略是采取操作,还是仅允许你收集潜在的失败事件。宽容模式在实现过程中尤其有用。
- 不受限 - 一种非常宽松的策略,会在开发过程中禁止执行某些任务并提供暂时的权宜之计。不应对 Android 开放源代码项目 (AOSP) 之外的任何内容使用这种策略。
- 受限 - 针对相应服务编写的自定义策略。这种策略应精确定义允许的事项。 不受限策略可用于协助在 Android 中快速实现 SELinux。这种策略适用于大多数 Root 级应用。但应尽可能逐渐将这种策略转换为受限策略,以精确限制每个应用只能使用所需的资源。
你的策略最好是处于强制模式的受限策略。处于强制模式的不受限策略可以掩盖采用受限策略时在宽容模式下会记录的可能违规行为。因此,我们强烈建议设备实现人员实现真正的受限策略。
标签,规则和域名
SELinux 依靠标签来匹配操作和策略。标签用于决定允许的事项。套接字、文件和进程在 SELinux 中都有标签。SELinux 决定基本上是根据为这些对象分配的标签以及定义这些对象可以如何交互的策略做出的。在 SELinux 中,标签采用以下形式:user:role:type:mls_level
,其中 type 是访问决定的主要组成部分,可通过构成标签的其他组成部分进行修改。对象会映射到类,对每个类的不同访问类型由权限表示。
策略规则采用以下形式:allow domains types:classes permissions;
,其中:
- Domain - 一个进程或一组进程的标签。也称为域类型,因为它只是指进程的类型。
- Type - 一个对象(例如,文件、套接字)或一组对象的标签。
- Class - 要访问的对象(例如,文件、套接字)的类型。
- Permission - 要执行的操作(例如,读取、写入)。
使用策略规则时将遵循的结构示例:
allow appdomain app_data_file:file rw_file_perms;
这表示所有应用域都可以读取和写入带有 app_data_file 标签的文件。请注意,该规则依赖于在 global_macros 文件中定义的宏,你还可以在 te_macros 文件中找到一些其他非常实用的宏。这两个文件均位于 AOSP 源代码树的 system/sepolicy 目录中,其中提供了一些适用于常见的类、权限和规则分组的宏。应尽可能使用这些宏,以便降低因相关权限被拒而导致失败的可能性。
除了在规则中逐个列出域或类型之外,还可以通过属性引用一组域或类型。简单来说,属性是一组域或类型的名称。每个域或类型都可以与任意数量的属性相关联。当编写的规则指定了某个属性名称时,该名称会自动扩展为列出与该属性关联的所有域或类型。例如,domain 属性与所有进程域相关联,file_type 属性与所有文件类型相关联。
使用上述语法可以创建构成 SELinux 策略基本内容的 avc 规则。规则采用以下形式:
<rule variant> <source_types> <target_types> : <classes> <permissions>
该规则指明了,当带有任何 source_types 标签的主体尝试对某个对象执行与任何 permissions 对应的操作时,如果该对象包含带有任何 target_types 标签的任何 classes 类,会发生什么情况。这些规则的一个最常见示例是 allow 规则,例如:
allow domain null_device:chr_file { open };
该规则允许具有与“domain”属性关联的任何域的进程对 target_type 标签为“null_device”的“chr_file”类(字符设备文件)的对象执行“open”权限所描述的操作。在实际中,该规则可能会扩展为包含其他权限:
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
实现
SELinux 设为了“默认拒绝”模式,也就是说,对于在内核中存在钩子的每一次访问,都必须获得策略的明确许可。这意味着策略文件中包含规则、类型、类、权限等方面的大量信息。关于 SELinux 的完整注意事项不在这里的讨论范围之内,现在你必须要了解的是在启动新的 Android 设备时如何编写策略规则。目前有大量关于 SELinux 的信息可供参考。
步骤总结
下面简要总结了在 Android 设备上实现 SELinux 时需要执行的步骤:
- 在内核和配置中添加 SELinux 支持。
- 为通过 init 启动的每项服务(进程或守护进程)分配专用的域。
- 通过以下方式标识这些服务:
- 查看 init.<device>.rc 文件并找到所有服务。
- 检查
dmesg
输出中以下形式的警告:“init: Warning! Service name needs a SELinux domain defined; please fix!”。 - 检查
ps -Z | grep init
输出,看看哪些服务正在 init 域中运行。
- 为所有新进程、驱动程序、套接字等添加标签。需要为所有对象添加适当的标签,以确保它们能够与你应用的策略正确交互。
- 制定全面涵盖所有标签的安全策略,并将权限限定到其绝对最低级别。
关键文件
SELinux for Android 随附了立即启用 SELinux 所需的一切。你只需集成最新的 Android 内核,然后整合 system/sepolicy 目录中的文件即可:
https://android.googlesource.com/kernel/common/
https://android.googlesource.com/platform/system/sepolicy/
这些文件在编译后会包含 SELinux 内核安全策略,并涵盖上游 Android 操作系统。你应该不需要直接修改 system/sepolicy 中的文件,而只需添加你自己的设备专用策略文件(位于 /device/manufacturer/device-name/sepolicy 目录中)即可。
要实现 SELinux,你必须创建或修改以下文件:
- 新的 SELinux 策略源代码 (*.te) 文件 - 位于 /device/manufacturer/device-name/sepolicy 目录中。这些文件用于定义域及其标签。在编译到单个 SELinux 内核策略文件时,新的策略文件会与现有的策略文件组合在一起。
- 更新后的 BoardConfig.mk Makefile - 位于包含 sepolicy 子目录的目录中。如果初始实现中没有 sepolicy 子目录,那么在该子目录创建之后,必须更新 BoardConfig.mk makefile,以引用该子目录。
- file_contexts - 位于 sepolicy 子目录中。该文件用于为文件分配标签,并且可供多种用户空间组件使用。在创建新策略时,请创建或更新该文件,以便为文件分配新标签。要应用新的 file_contexts,你必须重新构建文件系统映像,或对要重新添加标签的文件运行 restorecon。在升级时,对 file_contexts 所做的更改会在升级过程中自动应用于系统和用户数据分区。此外,还可以通过以下方式使这些更改在升级过程中自动应用于其他分区:在以允许读写的方式装载相应分区后,将 restorecon_recursive 调用添加到 init.board.rc 文件中。
- genfs_contexts - 位于 sepolicy 子目录中。该文件用于为不支持扩展属性的文件系统(例如,proc 或 vfat)分配标签。此配置会作为内核策略的一部分进行加载,但更改可能对核心内 inode 无效。要全面应用更改,需要重新启动设备,或卸载后重新装载文件系统。此外,通过使用 context=mount 选项,还可以为装载的特定系统文件(例如 vfat)分配特定标签。
- property_contexts - 位于 sepolicy 子目录中。该文件用于为 Android 系统属性分配标签,以便控制哪些进程可以设置这些属性。在启动期间以及 selinux.reload_policy 属性每次被设为 1 时,init 进程都会读取此配置。
- service_contexts - 位于 sepolicy 子目录中。该文件用于为 Android Binder 服务分配标签,以便控制哪些进行可以为相应服务添加(注册)和查找(查询)Binder 引用。在启动期间以及 selinux.reload_policy 属性每次被设为 1 时,servicemanager 进程都会读取此配置。
- seapp_contexts - 位于 sepolicy 子目录中。该文件用于为应用进程和 /data/data 目录分配标签。在每次应用启动时,Zygote 进程都会读取此配置;在启动期间以及 selinux.reload_policy 属性每次被设为 1 时,installd 都会读取此配置。
- mac_permissions.xml - 位于 sepolicy 子目录中。该文件用于根据应用签名和应用软件包名称(后者可选)为应用分配 seinfo 标记。然后,分配的 seinfo 标记可在 seapp_contexts 文件中用作密钥,以便为带有该 seinfo 标记的所有应用分配特定标签。在启动期间,system_server 会读取此配置。
接下来,只需在 sepolicy 子目录和各个策略文件创建之后,更新 BoardConfig.mk Makefile(位于包含 sepolicy 子目录的目录中)以引用该子目录和这些策略文件即可,如下所示。BOARD_SEPOLICY 变量及其含义记录在 system/sepolicy/README 文件中。
BOARD_SEPOLICY_DIRS += \
<root>/device/manufacturer/device-name/sepolicy
BOARD_SEPOLICY_UNION += \
genfs_contexts \
file_contexts \
sepolicy.te
设备在重新编译后会启用 SELinux。现在,你可以根据自己向 Android 操作系统添加的内容自定义自己的 SELinux 策略(如自定义中所述),也可以验证你的现有设置(如验证中所述)。
在新策略文件和 BoardConfig.mk 更新部署到位后,新策略设置会立即自动内置到最终的内核策略文件中。
用例
下面列举了一些在开发软件以及制定关联的 SELinux 策略时需要注意的具体漏洞:
符号链接 - 由于符号链接以文件形式显示,因此通常也是作为文件被读取。这可能会导致漏洞。例如,某些特权组件(例如 init)会更改某些文件的权限,有时会使之极度开放。
这样一来,攻击者便可以将这些文件替换成指向其控制的代码的符号链接,从而重写任意文件。但如果你知道自己的应用绝不会遍历符号链接,则可以通过 SELinux 来禁止你的应用遍历符号链接。
系统文件 - 以应该只有系统服务器可以修改的一系列系统文件为例。由于 netd、init 和 vold 是以 Root 身份运行的,因此它们也可以访问这些系统文件。这样一来,如果 netd 遭到入侵,它将可以入侵这些文件,并可能会入侵系统服务器本身。
借助 SELinux,你可以将这些文件标识为系统服务器数据文件。这样一来,系统服务器就是唯一对这些文件具有读写权限的域。即使 netd 遭到入侵,它也无法将域切换到系统服务器域并访问这些系统文件,就算它是以 Root 身份运行的也是如此。
应用数据 - 另一个示例是必须以 Root 身份运行但不应获得应用数据访问权限的一系列函数。这非常有用,因为可以做出广泛的声明,例如禁止与应用数据无关的特定域访问互联网。
setattr - 对于 chmod、chown 等命令,你可以标识关联域可以在哪些文件中进行 setattr 操作。这样一来,便可以禁止对这些文件之外的任何文件进行此类更改,即使以 Root 身份进行也不例外。因此,应用可以对带 app_data_files 标签的文件运行 chmod 和 chown 命令,但不能对带 shell_data_files 或 system_data_files 标签的文件运行这些命令。
详细步骤
下面详细介绍了 Android 建议你如何采用并自定义 SELinux 来保护设备:
- 在内核中启用 SELinux: CONFIG_SECURITY_SELINUX=y
- 更改 kernel_cmdline 参数,以便: BOARD_KERNEL_CMDLINE := androidboot.selinux=permissive。 这仅适用于初始制定设备策略的情况。在拥有初始引导程序策略后,请移除此参数,以便将设备恢复强制模式,否则设备将无法通过 CTS 验证。
- 以宽容模式启动系统,看看在启动时会遇到哪些拒绝事件: 在 Ubuntu 14.04 或更高版本中: adb shell su -c dmesg | grep denied | audit2allow -p out/target/product/board/root/sepolicy 在 Ubuntu 12.04 中: adb shell su -c dmesg | grep denied | audit2allow
- 评估输出。如需查看相关说明和工具,请参阅验证。
- 标识设备以及需要添加标签的其他新文件。
- 为你的对象使用现有标签或新标签。查看 *_contexts 文件,了解之前是如何为内容添加标签的,然后根据对标签含义的了解分配一个新标签。这最好是一个能够融入到策略中的现有标签,但有时需要使用新标签,并且还需要关于访问该标签的规则。
- 标识应该拥有自己的安全域的域/进程。可能需要为其中每个域/进程从头开始编写策略。例如,从 init 衍生的所有服务都应该有自己的安全域。可以通过以下命令查看保持运行的服务(不过所有服务都需要如此处理):
$ adb shell su -c ps -Z | grep init $ adb shell su -c dmesg | grep 'avc: '
- 查看 init.<device>.rc,以找出所有没有类型的服务。应提早为此类服务提供域,以避免向 init 添加规则或将 init 访问权限与其自身策略中的访问权限混淆。
- 将 BOARD_CONFIG.mk 设为使用 BOARD_SEPOLICY_* 变量。如需关于如何进行此项设置的详细信息,请参阅 system/sepolicy 中的 README。
- 检查 init.<device>.rc 和 fstab.<device> 文件,确保每一次使用“mount”都对应一个添加了适当标签的文件系统,或者指定了 context= mount 选项。
- 查看每个拒绝事件,并创建 SELinux 策略来妥善处理每个拒绝事件。请参阅自定义中的示例。
自定义
集成这一基本级别的功能并全面分析结果后,你可以添加自己的策略设置,以便涵盖自己对 Android 操作系统进行的自定义。当然,这些策略仍必须要满足 Android 兼容性计划的要求,并且不会移除默认的 SELinux 设置。
制造商不得移除现有的安全设置,否则可能会破坏 Android SELinux 实现及其管控的应用。这包括可能需要进行改进以符合策略并正常运行的第三方应用。应用必须无需进行任何修改即可继续在启用了 SELinux 的设备上正常运行。
当开始着手自定义 SELinux 时,应记得做以下事情:
- 为所有新的守护进程编写 SELinux 策略
- 尽可能使用预定义的域
- 为作为 init 服务衍生的所有进程分配域
- 在编写策略之前先熟悉相关的宏
- 向 AOSP 提交对核心策略进行的更改
不要做以下事情:
- 创建不兼容的策略
- 允许对最终用户策略进行自定义
- 允许对 MDM 策略进行自定义
- 恐吓违反策略的用户
- 添加后门程序
如需查看具体要求,请参阅 Android 兼容性定义文档中的“内核安全功能”部分。
SELinux 采用白名单方法,这意味着只能授予策略中明确允许的访问权限。由于 Android 的默认 SELinux 策略已经支持 Android 开放源代码项目,因此原始设备制造商 (OEM) 无需以任何方式修改 SELinux 设置。如果他们要自定义 SELinux 设置,则应格外谨慎,以免破坏现有应用。以下是Android官方建议的做法:
- 使用最新的 Android 内核。
- 采用最小权限原则。
- 仅针对你向 Android 添加的内容调整 SELinux 策略。默认策略能够自动适用于 Android 开放源代码项目代码库。
- 将各个软件组件拆分成多个负责执行单项任务的模块。
- 创建用于将这些任务与无关功能隔离开来的 SELinux 策略。
- 将这些策略放在 /device/manufacturer/device-name/sepolicy 目录中的 *.te 文件(te 是 SELinux 策略源代码文件使用的扩展名)内,然后使用 BOARD_SEPOLICY 变量将它们纳入到你的版本中。
- 先将新域设为宽容域。通过在相应域的 .te 文件中使用宽容声明,可以做到这一点。
- 分析结果并优化域定义。
- 当 userdebug 版本中不再出现拒绝事件时,移除宽容声明。
集成工作完成后,原始设备制造商 (OEM) 的 Android 开发过程还应包含一个确保向前兼容 SELinux 的步骤。在理想的软件开发过程中,仅当软件模型发生变化时,SELinux 策略才需要进行更改,而当实际实现发生变化时,SELinux 策略将不需要进行更改。
当设备制造商开始自定义 SELinux 时,他们应首先审核自己向 Android 添加的内容。如果他们添加了执行新功能的组件,在开启强制模式之前,他们需要先确认该组件是否符合 Android 采用的安全策略,以及原始设备制造商 (OEM) 制定的所有相关策略。
为了防止出现不必要的问题,过度宽泛和过度兼容要好于过度限制和不兼容,后者会导致设备功能损坏。不过,如果制造商进行的更改能够惠及其他人,则应将这些更改作为补丁程序提供给默认 SELinux 策略。如果相应补丁程序已应用于默认安全策略,制造商将不再需要针对每个新的 Android 版本进行此项更改。
策略声明示例
首先请注意,SELinux 基于 M4 语言,因此支持多种有助于节省时间的宏。
在以下示例中,所有域都被授予从 /dev/null 读取数据或向其写入数据的权限以及从 /dev/zero 读取数据的权限。
# Allow read / write access to /dev/null
allow domain null_device:chr_file { getattr open read ioctl lock append write};
# Allow read-only access to /dev/zero
allow domain zero_device:chr_file { getattr open read ioctl lock };
可以使用 SELinux *_file_perms 宏编写相同的声明(代码非常简短):
# Allow read / write access to /dev/null
allow domain null_device:chr_file rw_file_perms;
# Allow read-only access to /dev/zero
allow domain zero_device:chr_file r_file_perms;
可用控件
类 | 权限 |
---|---|
文件 | ioctl read write create getattr setattr lock relabelfrom relabelto append unlink link rename execute swapon quotaon mounton |
目录 | add_name remove_name reparent search rmdir open audit_access execmod |
套接字 | ioctl read write create getattr setattr lock relabelfrom relabelto append bind connect listen accept getopt setopt shutdown recvfrom sendto recv_msg send_msg name_bind |
文件系统 | mount remount unmount getattr relabelfrom relabelto transition associate quotamod quotaget |
进程 | fork transition sigchld sigkill sigstop signull signal ptrace getsched setsched getsession getpgid setpgid getcap setcap share getattr setexec setfscreate noatsecure siginh setrlimit rlimitinh dyntransition setcurrent execmem execstack execheap setkeycreate setsockcreate |
安全 | compute_av compute_create compute_member check_context load_policy compute_relabel compute_user setenforce setbool setsecparam setcheckreqprot read_policy |
功能 | chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap |
neverallow 规则
SELinux neverallow 规则用于禁止在任何情况下都不应该发生的行为。通过兼容性测试,现在各种合作伙伴设备上都会强制执行 SELinux neverallow 规则。
以下准则旨在协助制造商在自定义过程中避免与 neverallow 规则相关的错误。此处使用的规则编号与 Android 5.1 中使用的编号一致,并且会因版本而异。
规则 48:neverallow { domain -debuggerd -vold -dumpstate -system_server } self:capability sys_ptrace; 请参阅 ptrace 的帮助页面。sys_ptrace 功能用于授予对任何进程执行 ptrace 命令的权限。拥有该权限后,可以对其他进程进行广泛的控制。应该只有该规则中列出的指定系统组件享有该权限。如果需要该功能,则通常表明存在的某些内容不适用于面向用户的版本或存在不需要的功能。请移除不必要的组件。
规则 76:neverallow { domain -appdomain -dumpstate -shell -system_server -zygote } { file_type -system_file -exec_type }:file execute; 该规则旨在防止执行系统中的任意代码。具体来说就是,该规则声明仅执行 /system 中的代码,以便通过验证启动等机制实现安全保证。通常情况下,当遇到与这个 neverallow 规则相关的问题时,最好的解决办法是将违规代码移到 /system 分区。
验证
Android 强烈建议原始设备制造商全面测试其 SELinux 实现。制造商在实现 SELinux 时,应先为设备上需要测试的所有内容应用新策略。
应用新策略后,可以通过执行 getenforce 命令来确认 SELinux 在设备上的运行模式是否正确
该命令将会显示全局 SELinux 模式:强制或宽容。请注意,该命令只会显示全局 SELinux 模式。要确定每个域的 SELinux 模式,你必须查看相应的文件,或运行带有适当 (-p) 标记的最新版 sepolicy-analyze(位于 /platform/system/sepolicy/tools/ 中)。
读取拒绝事件
接下来是检查是否存在错误。错误会以事件日志的形式路由到 dmesg 和 logcat,并可在设备上从本地查看。制造商应先检查这些设备上路由到 dmesg 的 SELinux 输出并优化设置,然后再在宽容模式下公开发布,最后切换到强制模式。SELinux 日志消息中包含“avc:”,因此可以通过 grep 轻松找到。可以通过运行 cat /proc/kmsg 来获取当前的拒绝事件日志,也可以通过运行 cat /proc/last_kmsg 来获取上次启动时的拒绝事件日志。
借助这种输出,制造商可以轻松发现系统用户或组件违反 SELinux 策略的行为。然后,制造商便可以通过对相应软件和/或 SELinux 策略进行更改来防范这种恶意行为。
具体来说就是,这些日志消息会指明在强制模式下哪些进行会失败以及失败原因。示例如下:
avc: denied { connectto } for pid=2671 comm="ping" path="/dev/socket/dnsproxyd"
scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket
该输出的解读如下:
- 上方的 { connectto } 表示正在执行的操作。通过它和末尾的 tclass (unix_stream_socket),你可以大致了解正在对什么对象执行什么操作,在该示例中是某个操作方正在试图连接到 UNIX 信息流套接字。
- scontext (u:r:shell:s0) 旨在告诉你发起相应操作的环境,在该示例中是某个作为 shell 运行的操作方。
- tcontext (u:r:netd:s0) 旨在告诉你操作目标的环境,在该示例中是某个归 netd 所有的 unix_stream_socket。
- 顶部的 comm=”ping” 旨在为你提供更多提示,让你了解拒绝事件发生时正在运行的操作。在该示例中,这是一个非常实用的提示。
下面是另一个示例:
$ adb shell su root dmesg | grep 'avc: '
<5> type=1400 audit: avc: denied { read write } for pid=177
comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0
tcontext=u:object_r:kmem_device:s0 tclass=chr_file
以下是此拒绝事件的关键元素:
- 操作 - 试图进行的操作使用括号突出显示:read write 或 setenforce。
- 操作方 - scontext(来源环境)条目表示操作方,在该示例中是 rmt_storage 守护进程。
- 对象 - tcontext(目标环境)条目表示正在对哪个对象执行操作,在该示例中是 kmem。
- 结果 - tclass(目标类别)条目表示操作对象的类型,在该示例中是 chr_file(字符设备)。
切换到宽容模式
要通过 ADB 将设备的 SELinux 执行模式切换到全局宽容模式,请以根用户的身份执行以下命令:
$ adb shell su root setenforce 0
或在内核命令行中输入以下命令(在设备启动初期):
androidboot.selinux=permissive
androidboot.selinux=enforcing
使用 audit2allow
selinux/policycoreutils/audit2allow 工具可以获取 dmesg 拒绝事件并将其转换成相应的 SELinux 策略声明。因此,该工具有助于大幅加快 SELinux 开发速度。audit2allow 会作为 Android 源代码树的一部分被移植到设备上,并会在你基于源代码编译 Android 时自动进行编译。
要使用该工具,请运行以下命令:
$ adb shell su root dmesg | audit2allow -p $OUT/root/sepolicy
不过,在检查各种潜在增加项是否存在越界权限时务必要谨慎。例如,为 audit2allow 馈送之前显示的 rmt_storage 拒绝事件会导致以下建议的 SELinux 策略声明:
#============= shell ==============
allow shell kernel:security setenforce;
#============= rmt ==============
allow rmt kmem_device:chr_file { read write };
这会授予 rmt 向内核内存写入内容的权限,从而形成明显的安全漏洞。通常情况下,audit2allow 声明只是一个起点,在这之后,可能还需要更改来源域以及目标的标签,并纳入适当的宏,才能获得好的策略。有时,要解决正在检查的拒绝事件,不应对策略进行任何更改,而是应更改违规的应用。
编写 SELinux 策略
Android 开放源代码项目 (AOSP) 针对所有 Android 设备中常用的应用和服务提供了一个可靠实用的基本策略。AOSP 的贡献者会定期完善该策略。该核心策略应占设备上最终策略的 90-95%,而剩下的 5-10% 则为设备专用自定义策略。本文重点介绍了这些设备专用自定义策略、如何编写设备专用策略,以及在编写此类策略时要避免的一些陷阱。
设备启动
在编写设备专用策略时,请按顺序执行以下步骤。
在宽容模式下运行
当设备处于宽容模式时,拒绝事件会被记录下来,但不会被强制执行。宽容模式非常重要,原因有以下两个:
- 宽容模式可确保策略启用不会延误其他早期设备启动任务。
- 被强制执行的拒绝事件可能会掩盖其他拒绝事件。例如,文件访问通常会涉及目录搜索、文件打开和文件读取操作。在强制模式下,只会发生目录搜索拒绝事件。宽容模式可确保所有拒绝事件都会显示出来。 要使设备进入宽容模式,最简单的方法是通过内核命令行来实现。相应命令可以添加到设备的 BoardConfig.mk 文件中:platform/device/<vendor>/<target>/BoardConfig.mk。修改命令行之后,执行 make clean,接着执行 make bootimage,然后刷写新的启动映像。
在此之后,通过以下命令确认宽容模式:
adb getenforce
将处于全局宽容模式的时间设为两周比较合理。在解决大多数拒绝事件之后,返回到强制模式,并在出现错误时加以解决。对于仍然不断出现拒绝事件的域或仍处于密集开发阶段的服务,可以暂时使其进入宽容模式,但要尽快使其返回到强制模式。
提早采用强制模式
在强制模式下,拒绝事件会被记录下来,并且会被强制执行。最佳做法是尽早使你的设备进入强制模式。如果花时间等待创建和强制执行设备专用策略,通常会导致有问题的产品和糟糕的用户体验。在实际使用过程中,要提前足够长的时间开始参与 dogfooding,确保对功能进行全面测试。提早开始有助于确保安全问题能够在相关人员做出设计决策时被考虑在内。相反,仅根据观察到的拒绝事件来授予权限是一种不安全的做法。可以利用这段时间对设备进行安全审核,并针对不应被允许的行为提出错误。
移除或删除现有策略
之所以要在新设备上从头开始创建设备专用策略,有很多合理的理由,其中包括:
- 安全审核
- 过度宽容的策略
- 策略规模缩小
- Dead 策略
- 解决核心服务生成的拒绝事件
核心服务生成的拒绝事件通常是通过为文件添加标签来解决的。例如:
avc: denied { open } for pid=1003 comm=”mediaserver” path="/dev/kgsl-3d0”
dev="tmpfs" scontext=u:r:mediaserver:s0 tcontext=u:object_r:device:s0
tclass=chr_file permissive=1
avc: denied { read write } for pid=1003 name="kgsl-3d0" dev="tmpfs"
scontext=u:r:mediaserver:s0
tcontext=u:object_r:device:s0 tclass=chr_file permissive=1
是完全通过为 /dev/kgsl-3d0 添加适当的标签来解决的。在此示例中,tcontext 是 device。这表示默认环境,在该环境中,/dev 内的所有文件都会获得“device”标签,除非被分配了更具体的标签。直接在此处接受来自 audit2allow 的输出会导致不正确且过度宽容的规则。
要解决这种问题,可以为文件添加更具体的标签,在此示例中为 gpu_device。由于 mediaserver 在核心策略中已有访问 gpu_device 所需的必要权限,因此不再需要更多权限。
其他需要以核心策略中预定义的类型作为标签的设备专用文件:
- 块设备
- 音频设备
- 视频设备
- 传感器
- nfc
- gps_device
- /sys 中的文件
- /proc 中的文件 一般情况下,向默认标签授予权限的做法是错误的。其中许多权限都是 neverallow 规则所不允许的,但即使该规则并未明确禁止这些权限,也最好是提供具体标签。
为新服务添加标签并解决拒绝事件
通过 init 启动的服务需要在各自的 SELinux 域中运行。以下示例会将服务“foo”放入它自己的 SELinux 域中并为其授予权限。
该服务是在设备的 init.<target>.rc 文件中启动的,如下所示:
service foo /system/bin/foo
class core
a. 创建一个新域“foo” 创建包含以下内容的文件 device/<oem>/<target>/sepolicy/foo.te:
# foo service
type foo, domain;
type foo_exec, exec_type, file_type;
init_daemon_domain(foo)
这是 foo SELinux 域的初始模板,你可以根据该可执行文件执行的具体操作为该模板添加规则。
b. 为 /system/bin/foo 添加标签
将以下内容添加到 device/
/system/bin/foo u:object_r:foo_exec:s0
这可确保为该可执行文件添加适当的标签,以便 SELinux 在适当的域中运行相应服务。
c. 编译并刷写启动映像和系统映像。
d. 优化相应域的 SELinux 规则。
根据拒绝事件确定所需的权限。audit2allow 工具提供了一些实用的指南,但该工具仅适用于提供编写策略时所需的信息。切勿只是复制输出内容。
切换回强制模式
可以在宽容模式下进行问题排查,但要尽早切换回强制模式,并尽量保持该模式。