控制流完整性 (CFI) 是一种安全机制,它不允许更改已编译二进制文件的原始控制流图,因而执行此类变得异常困难。

在 Android 9 中,我们在更多组件以及内核中启用了 LLVM 的 CFI 实现。系统 CFI 默认处于启用状态,但内核 CFI 需要您手动启用。

LLVM 的 CFI 需要使用链接时优化 (LTO) 进行编译。LTO 会一直保留对象文件的 LLVM 位码表示法直至链接时,以便编译器更好地推断可以执行哪些优化。启用 LTO 可缩减最终二进制文件的大小并提高性能,但会增加编译时间。在 Android 上进行测试时,结合使用 LTO 和 CFI 对代码大小和性能开销的影响微乎其微;在少数情况下,这两者都会有所改善。

如需了解有关 CFI 以及如何处理其他前向控制检查的更多技术详情,请参阅 LLVM 设计文档。

实现

Android 通用内核 4.9 和 4.14 版本中提供对内核 CFI 的支持。如果您的内核基于 4.9 或 4.14 版本且使用 Clang 进行构建,那么您可以启用它。如需启用 kCFI,您需要复制相关补丁程序并更新内核配置文件。

复制 kCFI 补丁程序

将以下更改添加到您的内核:

启用 kCFI

复制相关更改后,您需要在内核配置文件中启用 kCFI,例如 /kernel/PROJECT/+/BRANCH/arch/arm64/configs/PROJECT_defconfig。

如需启用 kCFI,请添加以下行:

CONFIG_LTO_CLANG=y

CONFIG_CFI_CLANG=y

问题排查

启用 kCFI 后,修正其驱动程序可能存在的任何类型不匹配错误。通过不兼容的函数指针间接调用函数将导致 CFI 故障。当检测到 CFI 故障时,内核会输出一条警告,其中包括被调用的函数和导致故障的堆栈轨迹。您可以通过确保函数指针始终与调用的函数属于同一类型来修正此问题。

如需协助调试 CFI 故障,请启用 CONFIG_CFI_PERMISSIVE,它会输出警告(而不会导致内核崩溃)。切勿在正式版中使用宽容模式。

验证

目前,没有专门针对 CFI 的 CTS 测试。不过,您可以确保无论是否启用 CFI,均能通过 CTS 测试,从而确认 CFI 不会影响设备。