控制流完整性 (CFI) 是一种安全机制,可阻止对已编译二进制文件的原始控制流图进行更改,从而大大增加执行此类攻击的难度。
在 Android 9 中,我们在更多组件以及内核中启用了 LLVM 的 CFI 实现。 系统 CFI 默认处于启用状态,但您需要启用内核 CFI。
LLVM 的 CFI 需要使用链接时优化 (LTO) 进行编译。LTO 会保留对象文件的 LLVM 位代码表示形式,直到链接时,这使编译器能够更好地推断可以执行哪些优化。启用 LTO 会减小最终二进制文件的大小并提高性能,但会增加编译时间。在 Android 上的测试中,LTO 和 CFI 的组合对代码大小和性能的影响可忽略不计;在少数情况下,两者都有所改进。
有关 CFI 以及如何处理其他前向控制检查的更多技术详细信息,请参阅 LLVM 设计文档。
实现
kCFI 补丁位于所有受支持的 Android 内核版本中。CONFIG_CFI_CLANG
选项启用 kCFI,并且在 GKI 中默认设置。
问题排查
启用后,解决可能与其驱动程序存在的任何类型不匹配错误。通过不兼容的函数指针进行的间接函数调用会触发 CFI。检测到 CFI 故障时,内核会打印出警告,其中包含被调用的函数以及导致故障的堆栈跟踪。通过确保函数指针始终与被调用的函数类型相同来纠正此问题。
为了帮助调试 CFI 故障,请启用 CONFIG_CFI_PERMISSIVE
,这将打印出警告而不是导致内核崩溃。宽容模式不得在生产环境中使用。
验证
目前,没有专门针对 CFI 的 CTS 测试。相反,请确保 CTS 测试在启用和禁用 CFI 的情况下都能通过,以验证 CFI 不会影响设备。