编写 SELinux 策略

Android 开源项目 (AOSP) 为所有 Android 设备通用的应用和服务提供了坚实的基础策略。AOSP 的贡献者会定期改进此策略。核心策略预计约占设备上最终策略的 90–95%,而设备特定的自定义设置则占剩余的 5–10%。本文重点介绍这些设备特定的自定义设置、如何编写设备特定的策略以及沿途需要避免的一些陷阱。

设备启动

在编写设备特定策略时,请遵循以下步骤。

在宽容模式下运行

当设备处于宽容模式时,拒绝会被记录但不会强制执行。宽容模式非常重要,原因有二:

  • 宽容模式可确保策略启动不会延迟其他早期的设备启动任务。
  • 强制执行的拒绝可能会掩盖其他拒绝。例如,文件访问通常需要目录搜索、文件打开,然后是文件读取。在强制执行模式下,只会发生目录搜索拒绝。宽容模式可确保看到所有拒绝。

将设备置于宽容模式的最简单方法是使用内核命令行。这可以添加到设备的 BoardConfig.mk 文件中:platform/device/<vendor>/<target>/BoardConfig.mk。修改命令行后,执行 make clean,然后执行 make bootimage,并刷写新的启动映像。

之后,使用以下命令确认宽容模式:

adb shell getenforce

在全球宽容模式下运行两周是合理的时间量。在解决大多数拒绝后,切换回强制执行模式,并在出现错误时解决这些错误。仍在产生拒绝的网域或仍在大量开发的服务可以暂时置于宽容模式,但应尽快将其切换回强制执行模式。

尽早强制执行

在强制执行模式下,拒绝既会被记录也会被强制执行。最佳实践是尽早使您的设备进入强制执行模式。等待创建和强制执行设备特定策略通常会导致产品存在漏洞且用户体验不佳。尽早开始参与内部测试,并确保在真实世界使用中对功能进行全面测试覆盖。尽早开始可确保安全问题为设计决策提供信息。相反,仅根据观察到的拒绝授予权限是不安全的方法。利用这段时间对设备执行安全审核,并针对不应允许的行为提交错误。

移除或删除现有策略

在新设备上从头开始创建设备特定策略有很多充分的理由,其中包括:

解决核心服务的拒绝

核心服务生成的拒绝通常通过文件标记来解决。例如:

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 来解决。在此示例中,tcontextdevice。这表示默认上下文,其中 /dev 中的所有内容都将收到“ device”标签,除非分配了更具体的标签。仅接受来自 audit2allow 的输出在此处将导致不正确且过度宽容的规则。

要解决此类问题,请为文件提供更具体的标签,在本例中为 gpu_device。由于核心策略中 mediaserver 已具有访问 gpu_device 所需的权限,因此无需其他权限。

其他应使用核心策略中预定义的类型标记的设备特定文件:

一般来说,向默认标签授予权限是错误的。许多此类权限被 neverallow 规则所禁止,但即使未明确禁止,最佳实践也是提供特定标签。

标记新服务并解决拒绝

Init 启动的服务需要在其自己的 SELinux 网域中运行。以下示例将服务“foo”置于其自己的 SELinux 网域中,并授予其权限。

该服务在设备的 init.device.rc 文件中启动,如下所示:

service foo /system/bin/foo
    class core
  1. 创建新网域“foo”

    创建文件 device/manufacturer/device-name/sepolicy/foo.te,内容如下:

    # foo service
    type foo, domain;
    type foo_exec, exec_type, file_type;
    
    init_daemon_domain(foo)
    

    这是 foo SELinux 网域的初始模板,您可以在其中根据该可执行文件执行的特定操作添加规则。

  2. 标记 /system/bin/foo

    将以下内容添加到 device/manufacturer/device-name/sepolicy/file_contexts

    /system/bin/foo   u:object_r:foo_exec:s0
    

    这可确保正确标记可执行文件,以便 SELinux 在正确的网域中运行服务。

  3. 构建并刷写启动和系统映像。
  4. 优化网域的 SELinux 规则。

    使用拒绝来确定所需的权限。audit2allow 工具提供了良好的指导原则,但仅使用它来为策略编写提供信息。不要只是复制输出。

切换回强制执行模式

在宽容模式下进行问题排查是可以的,但应尽早切换回强制执行模式,并尽量保持在该模式下。

常见错误

以下是针对编写设备特定策略时发生的常见错误的一些解决方案。

过度使用否定

以下示例规则就像锁上前门但打开窗户一样:

allow { domain -untrusted_app } scary_debug_device:chr_file rw_file_perms

意图很明确:除了第三方应用之外的所有人都可以访问调试设备。

该规则在几个方面存在缺陷。untrusted_app 的排除项很容易绕过,因为所有应用都可以选择在 isolated_app 网域中运行服务。同样,如果将第三方应用的新网域添加到 AOSP,它们也可以访问 scary_debug_device。该规则过于宽容。大多数网域都无法从访问此调试工具中获益。该规则应编写为仅允许需要访问的网域。

生产环境中的调试功能

调试功能不应出现在生产版本中,其策略也不应出现。

最简单的替代方法是仅在 eng/userdebug 版本上禁用 SELinux 时才允许调试功能,例如 adb rootadb shell setenforce 0

另一种安全的替代方法是将调试权限括在 userdebug_or_eng 语句中。

策略大小激增

Characterizing SEAndroid Policies in the Wild 描述了设备策略自定义设置增长的令人担忧的趋势。设备特定的策略应占设备上运行的总体策略的 5–10%。20%+ 范围内的自定义设置几乎肯定包含过度特权的网域和无效策略。

不必要的大型策略

  • 对内存造成双重打击,因为策略位于 ramdisk 中,并且还会加载到内核内存中。
  • 通过需要更大的启动映像来浪费磁盘空间。
  • 影响运行时策略查找时间。

以下示例显示了两个设备,其中制造商特定的策略分别占设备上策略的 50% 和 40%。如下所示,策略的重写在没有功能损失的情况下产生了实质性的安全改进。(AOSP 设备 Shamu 和 Flounder 也包含在内以进行比较。)

Figure 1: Comparison of device-specific policy size after security audit.

图 1. 安全审核后设备特定策略大小的比较。

在这两种情况下,策略的大小和权限数量都大幅减少。策略大小的减小几乎完全归因于移除了不必要的权限,其中许多权限可能是由 audit2allow 生成并被随意添加到策略中的规则。无效网域也是这两个设备的问题。

授予 dac_override 功能

dac_override 拒绝表示违规进程正在尝试访问具有不正确的 unix 用户/组/世界权限的文件。正确的解决方案几乎永远不是授予 dac_override 权限。而是更改文件或进程的 unix 权限。少数网域(例如 initvoldinstalld)确实需要覆盖 unix 文件权限以访问其他进程文件的能力。有关更深入的解释,请参阅 Dan Walsh 的博客