内核控制流完整性

控制流完整性 (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 后,修正其驱动程序可能存在的任何类型不匹配错误。通过不兼容的函数指针调用 indirect 函数,将导致 CFI 故障。当检测到 CFI 故障时,内核会输出一条警告,其中包括被调用的函数和导致故障的堆栈轨迹。您可以通过确保函数指针始终与调用的函数属于同一类型来修正此问题。

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

验证

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