Android 上使用 SELinux
作者:Aayush Gupta (theimpulson) & Nolen Johnson (npjohnson),撰写于 2021 年 2 月 25 日
词汇表
- AOSP:Android 开源项目(Android Open Source Project)的缩写。
- AVC:访问向量缓存(Access Vector Cache)的缩写,SELinux 用于存储其关于访问控制决定的缓存。
- comm:Communication(通信)的缩写。
- CTS:兼容性测试套件(Compatibility Test Suite),谷歌为希望发布 GMS 的设备提供的测试套件。
- LSM:Linux 安全模块(Linux Security Modules)的缩写,Linux 的一部分框架,允许支持各种安全实现。
- MAC:强制访问控制(Mandatory Access Control)的缩写,一种访问控制类型,操作系统通过它约束启动者执行所讨论操作的能力。
- macros:宏,当被调用时扩展为一组指令的单个指令。
- NSA:美国国家安全局(National Security Agency of the United States of America)的缩写。
- regex:正则表达式(Regular Expression)的缩写。
- scontext:源上下文(Source Context)的缩写,也称为域(Domain)。
- SELinux:安全增强型 Linux(Security-Enhanced Linux)的缩写。
- SELinux policy:SELinux 使用的策略,指定一组权限。
- SoC:片上系统(System on Chip)的缩写。
- tclass:目标类(Target Class)的缩写。
- tcontext:目标上下文(Target Context)的缩写。
什么是 SELinux?
SELinux 是 Linux 内核的可选功能,提供支持以实施访问控制安全策略来强制执行 MAC。它基于 LSM 框架。
SELinux 的历史
SELinux 最初由NSA开发,旨在展示 MAC 的价值以及如何将其应用于 Linux。它于 2003 年 8 月合并到 Linux 2.6 中。Red Hat 和 McAfee Corp. 是 SELinux 开发的一些重要贡献者。之后,由 NSA 领导了一个名为Android 安全增强(SE)的独立项目,旨在将 SELinux 集成到 Android 中。该项目使 SELinux 成为 Android 的核心组成部分。它在 Android 4.3 中默认以 Permissive(宽容)模式引入,在 Android 4.4 中可选为 Enforcing(强制执行)模式,并且在 Android 5.0 及更高版本中,Google 的 CTS 要求必须为 Enforcing 模式。
SELinux 如何工作?
SELinux 可以以 2 种模式运行,即 Enforcing(强制执行) 和 Permissive(宽容)。默认模式为 Enforcing。
-
在 Enforcing 模式下,SELinux 主动强制执行给定的策略,该策略指定允许的内容(一般权限)。如果启动者想要执行操作,SELinux 将检查已安装的策略是否允许这样做,如果允许,则允许请求的操作发生。如果被拒绝,它将被记录在内核日志缓冲区中,以及 Android 上的
logcat
中。 -
在 Permissive 模式下,SELinux 仅记录已安装策略中明确不允许的操作,以及这些操作的启动者。
以下是在 Android logcat
中打印的 SELinux 拒绝示例
avc: denied { write } for comm="[email protected]" name="tp_double_tap" dev="proc" ino=4026533160 scontext=u:r:hal_power_default:s0 tcontext=u:object_r:proc:s0 tclass=file permissive=0
Permissive 模式主要由开发人员在新设备启动的早期阶段使用。这允许开发人员在开发的初始阶段节省时间,通过记录不同进程、服务、固件等所需的所有策略,并在项目达到稳定状态后一起解决它们。
SELinux 策略简述
SELinux 策略是一组规则(权限),声明哪些启动者可以执行哪种类型的操作。如果进程想要执行的特定操作在已安装的策略中未明确允许,SELinux 将拒绝它。因此,在生产设备上,SELinux 策略中拥有一套完整的规则以避免由于 SELinux 拒绝而导致的崩溃/错误至关重要。话虽如此,SELinux 也必须尽可能严格。编写 SELinux 策略时要记住的一个好的经验法则是:“如果没坏,就不要修它”。
默认情况下,Android 为 AOSP 平台特有的组件提供 SELinux 策略。您可以在 AOSP 的 platform/system/sepolicy 存储库中找到这些策略。修改 AOSP 并添加额外功能的下游供应商必须编写自己的 SELinux 策略。例如,高通在 CAF(Code Aurora Forum)的 device/qcom/sepolicy 存储库中为其 SoC 设备提供 sepolicy。LineageOS 在 LineageOS GitHub 组织托管的 device/lineage/sepolicy 存储库中为开发人员提供了针对其在 AOSP 上的添加/功能的 sepolicy。
所有这些不同的 SELinux 策略规则被编译在一起,以生成特定于设备分区的 SELinux 策略。例如,特定于系统分区的 SELinux 策略规则将最终出现在系统镜像中,特定于供应商分区的规则将最终出现在供应商镜像中,等等。当 Android 系统启动时,这些特定于设备分区的策略被编译在一起成为一个单一的 SELinux 策略,这是 SELinux 审核进程所依据的最终策略。
如何编写 SELinux 策略?
如前所述,SELinux 策略只是一组规则。编写 SELinux 策略包括从标记相关服务/应用程序到编写允许上述服务启动操作或访问其他节点/文件等的规则。此外,即使设备处于 Enforcing 模式,也可以强制进程以 Permissive 模式运行,尽管应避免这种做法。如您所见,编写 SELinux 策略可以轻松地分解成许多较小的部分,从而使其更易于管理。
在编写 SELinux 策略时,会使用很多规则和标签类型。但是,鉴于本文的范围,我们将仅讨论那些经常出现且基本的规则和标签类型。设备维护人员在编写特定于设备的 SELinux 策略规则时经常使用这些语句。
第一步是标记启动者,如果尚未完成。这确保了授予的权限不是针对通用目标,而是针对更受限制的特定目标。这导致特定的启动者仅具有他们执行特定操作所需的那些权限。这些标签存在于名为 file_contexts 的单个文件中。
专业提示:如果您需要检查文件的现有标签(例如,在 stock ROM 上进行比较),只需运行 ls -alZ /path/to/file
即可。
标记启动者(非应用程序)
蓝图是:/path/to/initiator u:object_r:context_name_you_want:s0
标记 NFC 服务的示例规则如下:/(vendor|system/vendor)/bin/hw/android\.hardware\.nfc@1\.2-service\.sec u:object_r:hal_nfc_default_exec:s0
您应该使用正则表达式来标记启动者。例如,请参阅 platform/system/sepolicy/private/file_contexts。
标记启动者(Android 应用)
蓝图是:user=user_of_app seinfo=info name=name_of_app domain=scontext_to_assign type=type_of_file
为了提供一个示例,让我们考虑 Android 应用程序 IWlanService.apk,其包名为 vendor.qti.iwlan。此应用程序通常被称为 QCOM 堆栈内的 qtidataservices 应用程序,这也反映在 SELinux 规则中。
使用完全限定的包名标记 qtidataservices 的示例规则如下:user=radio seinfo=platform name=vendor.qti.iwlan domain=qtidataservices_app type=radio_data_file
如果应用程序在其 AndroidManifest.xml 内的 <application>
定义中具有 android:process="name"
条目,您可以使用像 apktool
这样的工具提取它,您可以直接使用 name
作为 app_name
。
使用 process
标签标记 qtidataservices 应用程序的示例规则如下:user=radio seinfo=platform name=.qtidataservices domain=qtidataservices_app type=radio_data_file
所有应用程序的标签都放入一个名为 seapp_contexts 的文件中,格式根据分区的要求。有关其他示例和格式要求,请参阅 platform/system/sepolicy/private/seapp_contexts。
标记文件系统
genfscon 是一种标签类型,用于为不支持任何其他类型的标签语句的文件系统分配上下文。蓝图是:genfscon filesystem_name partial_path filesystem_context
标记 /proc/hwmodel
的示例规则如下:genfscon proc /hwmodel u:object_r:proc_fih:s0
所有 genfscon 语句都放入一个名为 genfs_contexts 的文件中。例如,请检查 platform/system/sepolicy/private/genfs_contexts。
标记属性
属性是字符串,可用于控制特定功能的行为或通告设备功能。这些属性位于 .prop
文件中,这些文件在系统启动时由 init
解析。这些属性还需要关于将访问它们的启动者的有效 SELinux 上下文。
蓝图是:property_name u:object_r:property_type:s0
与相机属性相关的拒绝示例日志
avc: denied { set } for property=camera.tunning.live pid=756 uid=1047 gid=1005 scontext=u:r:hal_camera_default:s0 tcontext=u:object_r:default_prop:s0 tclass=property_service permissive=0
这应该通过将此特定属性标记为 camera_prop
来解决,因为它与相机相关,例如 camera.tunning.live u:object_r:camera_prop:s0
所有属性标签都放入一个名为 property_contexts 的文件中。例如,请检查 platform/system/sepolicy/main/private/property_contexts。
请注意,与用于授予或抑制权限的其他语句不同,服务、进程、应用程序、文件系统的标签不以 ;
结尾。
一旦启动者有了标签,就可以根据需要授予其执行所需操作的权限。
允许权限
如果您想允许启动者执行某些操作的权限,可以使用 allow
语句。这用于授予权限。蓝图是:allow scontext tcontext:tclass permission;
关于此特定案例的示例
avc: denied { read write } for pid=4565 comm="init.qcom.post_" name="read_ahead_kb" dev="sysfs" ino=52742 scontext=u:r:qti_init_shell:s0 tcontext=u:object_r:sysfs_dm:s0 tclass=file
给定的权限将是:allow qti_init_shell sysfs_dm:file { read write };
抑制拒绝
如果您的日志包含一些您想要隐藏/抑制的拒绝,您可以使用 dontaudit
语句。蓝图是:dontaudit scontext tcontext:tclass permission;
关于此特定案例的示例
avc: denied { read } for comm="thermal-engine" name="kgsl" dev="sysfs" ino=29020 scontext=u:r:thermal-engine:s0 tcontext=u:object_r:sysfs:s0 tclass=dir permissive=0
抑制此日志的规则将是:dontaudit thermal-engine sysfs:dir read;
AOSP 建议将关于特定启动者的所有规则(权限、拒绝、日志抑制、Permissive 模式)保存在 .te 格式的单独文件中,并以其 scontext 作为名称。例如,关于 scontext 为 hal_power_default 的启动者的所有规则都将存储在名为 hal_power_default.te 的文件中。例如,请检查 platform/system/sepolicy/public/vold.te。
neverallow 的概念
neverallow 是一项总括规则,用于标记不得生成的特定规则。生成一词意味着它是编译时操作,而不是运行时操作。因此,如果您将特定规则标记为 neverallow 并在另一个规则中授予关于同一规则的权限,编译器将触发错误,并且编译将失败。
示例规则:neverallow my_gallery my_secret_passwords:{ dir file } { read write open };
在 Android 中,您可以在本文前面链接的 system/sepolicy 存储库中找到 neverallow。Android 将一些规则标记为 neverallow,这些规则可能会削弱系统安全性。例如,系统上的每个文件都有一种称为 system_data_file 的类型,现在假设您有一个启动者想要 { read write } 来访问类型为 system_data_file 的特定文件。现在,如果您授予它这样的权限,这意味着您允许启动者读取和写入系统上的每个文件(因为系统中的每个文件都具有相同的文件类型)。这会显着削弱安全性。因此,应将其标记为 neverallow。解决此问题的方法是将文件标记为不同的、更具体的上下文,然后授予启动者所需的权限。值得注意的是,如果您使用的是旧设备,例如 msm8996(UM-Family)之前的任何 QCOM 芯片组,Lineage 的 device/qcom/sepolicy-legacy 分支会忽略 neverallow,因为旧设备的专有二进制文件无法遵守日益严格的 Android neverallow。
虽然默认情况下,许多规则已被标记为 neverallow,但并非所有可能的例外情况都已涵盖。这取决于开发人员仔细授予权限,并牢记所有可能的场景。有关参考,请检查 platform/system/sepolicy/public/vold.te。
宏
默认情况下,在编写 SELinux 策略时可以使用多个宏。这些宏不仅使编写 SELinux 策略更容易,而且还建议用于各种原因,例如向特定启动者授予大量权限,向启动者授予特定任务的特定权限集等。它们减少了开发人员必须编写的代码量,为特定用例对一组特定规则进行分组,并使其他开发人员更容易阅读,以及许多其他好处。
在使用宏编写 SELinux 策略以允许权限的示例是
示例 1:使用的宏:r_file_perms
不使用宏: allow hal_power_default sysfs:file { read open watch lock };
使用宏: allow hal_power_default sysfs:file r_file_perms;
示例 2:使用的宏:r_dir_file
不使用宏: allow ueventd firmware_file:dir { open search };
和 allow ueventd firmware_file:file { read getattr open };
使用宏: r_dir_file(ueventd, firmware_file)
请注意,使用宏如何缩短代码量并仍然授予必要的权限。您可以在 AOSP 的 platform/system/sepolicy 存储库中存在的 global_macros 和 te_macros 中查看可用的宏。
我的 sepolicy 应该放在哪里?
自从 Project Treble 出现以来,sepolicy 已从 boot 镜像中的一个文件移动到与这些策略相关的分区上的各种文件。例如,在 device/lineage/sepolicy 中,“common”文件夹下,我们有 system、vendor、public、private 和 dynamic 文件夹。
- system:存储任何基于系统的模块的 sepolicy
- vendor:存储任何基于供应商的模块的 sepolicy
- public:存储系统和供应商都可以访问的 sepolicy
- private:存储系统模块可以访问的平台特定 sepolicy
- dynamic:存储将包含在 vendor 镜像中的 sepolicy,如果目标设备构建 (/system)/vendor inline,否则最终会出现在 system 镜像中,例如我们自己的 Trust HAL
Stock SELinux 策略作为参考
每个 stock 固件/ROM 在特定分区镜像内都有 SELinux 策略。策略的常规位置是 partition_name/etc/selinux/
。这意味着如果您正在查找与系统镜像相关的 SELinux 策略,它将在 /system/etc/selinux
中可用,对于供应商镜像,它将在 /vendor/etc/selinux
中,依此类推。请记住,分区路径因设备而异。开发人员在为其特定设备编写 SELinux 策略时,可以使用此 stock 策略作为参考。这不仅有助于加快编写规则的任务,还可以作为参考,了解应授予哪些规则,不应授予哪些规则,如何解决特定案例等。
一些有用的工具
有一些有用的工具可以帮助使用 SELinux 和编写 SELinux 策略。其中一些是
chcon
:帮助更改目标的 SELinux 上下文。audit2allow
:生成包含 allow 和 dontaudit 规则的 SELinux 策略。不涵盖 neverallow 异常。audit2allow.perl
托管在 OpenDarwin-CVS/SEDarwin,是一个脚本,与现代版本不同,它不需要外部依赖项,并且可以在任何平台上运行。
restorecon
:恢复给定目标的默认 SELinux 上下文。sepolicy-inject
:将 allow 规则注入到二进制 SELinux 内核策略中。
有用的参考资料
以下是用于本文开发期间 SELinux 研究的一些资源链接。欢迎查看它们以获取有关 SELinux 的更多信息。