编写 SELinux 政策

Android 开源项目 (AOSP) 针对所有 Android 设备中常用的应用和服务提供了一个可靠实用的基本政策。AOSP 的贡献者会定期完善该政策。核心政策应占设备上最终政策的 90-95%,而剩下的 5-10% 则为设备专用自定义政策。本文重点介绍这些设备专用自定义政策、编写设备专用政策的方法,以及在编写此类政策时要避免的一些陷阱。

设备启动

在编写设备专用政策时,请按照下列步骤操作。

在宽容模式下运行

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

  • 宽容模式可确保政策启用不会延误其他早期设备启动任务。
  • 被强制执行的拒绝事件可能会掩盖其他拒绝事件。例如,文件访问通常会涉及目录搜索、文件打开和文件读取操作。在强制模式下,只会发生目录搜索拒绝事件。宽容模式可确保所有拒绝事件都会显示出来。

如需让设备进入宽容模式,最简单的方法是使用内核命令行来实现。相应命令可以添加到设备的 BoardConfig.mk 文件中:platform/device/<vendor>/<target>/BoardConfig.mk。修改命令行之后,执行 make clean,接着执行 make bootimage,然后刷写新的启动映像。

在此之后,通过以下命令确认宽容模式:

adb shell getenforce

将处于全局宽容模式的时间设为两周比较合理。在解决大多数拒绝事件之后,返回到强制模式,并在出现错误时加以解决。对于仍然不断出现拒绝事件的域或仍处于密集开发阶段的服务,可以暂时使其进入宽容模式,但要尽快使其返回到强制模式。

提早采用强制模式

在强制模式下,拒绝事件会被记录下来,并且会被强制执行。最佳做法是尽早使您的设备进入强制模式。如果花时间等待创建和强制执行设备专用政策,通常会导致有问题的产品和糟糕的用户体验。要提前足够长的时间开始参与 dogfooding,确保对实际使用中涉及的功能进行全面测试。提早开始有助于确保安全问题能够在相关人员做出设计决策时被考虑在内。相反,仅根据观察到的拒绝事件来授予权限是一种不安全的做法。可以利用这段时间对设备进行安全审核,并针对不应被允许的行为提交 bug 报告。

移除或删除现有政策

之所以要在新设备上从头开始创建设备专用政策,有很多合理的理由,其中包括:

解决核心服务生成的拒绝事件

核心服务生成的拒绝事件通常是通过为文件添加标签来解决的。例如:

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。该规则过于宽容。对于大多数域来说,能够访问该调试工具并不能使它们获益。该规则应编写为仅允许需要访问该调试工具的域。

正式版中的调试功能

调试功能及其政策不应存在于正式版 build 中。

最简单的替代方案是,仅当 eng/userdebug build 中停用了 SELinux 时,才允许使用调试功能,例如 adb rootadb shell setenforce 0

另一种安全的替代方案是在 userdebug_or_eng 声明中包含调试权限。

政策规模扩张

描述市场上现有设备中的 SEAndroid 政策的特征介绍了一个令人关注的设备政策自定义发展趋势。设备专用政策应占设备上运行的所有政策的 5-10%。如果自定义政策所占的比例超过 20%,则几乎肯定会包含超特权域和不再有效的政策。

过大的政策:

  • 由于此类政策位于 ramdisk 中,并且还会加载到内核内存中,因此会占据两倍的内存。
  • 需要较大的启动映像,浪费磁盘空间。
  • 影响运行时政策查询次数。

以下示例显示了制造商专用政策分别占设备上政策 50% 和 40% 的两种设备。重写政策大幅提高了安全性,而且功能方面没有任何损失,如下所示。(AOSP 设备 Shamu 和 Flounder 也包含在了该示例中,以便进行比较。)

图 1:安全审核后的设备专用政策规模对比。

图 1. 安全审核后的设备专用政策规模对比。

在两种设备中,政策的规模和权限数量都大大减小了。政策规模的减小几乎完全是因为移除了不必要的权限,其中许多权限可能是由 audit2allow 生成且被随意添加到政策中的规则。对这两种设备来说,不再有效的域也是一个问题。

授予 dac_override 权能

dac_override 拒绝事件意味着违规进程正在尝试使用错误的 unix user/group/world 权限访问某个文件。正确的解决方案几乎从不授予 dac_override 权限,而是更改相应文件或进程的 unix 权限。有些域(例如 initvoldinstalld)确实需要能够替换 unix 文件权限才能访问其他进程的文件。如需查看更深入的讲解,请访问 Dan Walsh 的博客