2026-10-21 20:16:16
CFI安全特性在Android的应用
CFI(Control Flow Integrity)安全特性在Android系统中得到了广泛应用,主要用于保护程序的控制流调用不被攻击。以下是对CFI在Android中应用的具体分析,包括编译器和运行时(runtime)所做的事情。
一、编译器的作用
生成Metadata信息:
Clang编译器在编译过程中会生成一些metadata信息,这些信息用于支持CFI的各种检查。例如,当进行类型转换或函数调用时,编译器会插入检查指令来验证目标地址的合法性。
这些metadata信息包括类型元数据(type metadata),它们描述了程序中各个类型的信息,如类的继承关系、虚函数表等。
类型检查:
Clang支持多种CFI检查选项,如-fsanitize=cfi-cast-strict、-fsanitize=cfi-derived-cast等,这些选项会在编译时插入相应的检查指令。
例如,当进行基类到派生类的转换时,编译器会检查转换是否合法,即目标对象是否确实是指定派生类的实例。这通过比较对象的虚函数表指针(vptr)和metadata中的信息来实现。
IR层面的处理:
在中间表示(IR)层面,编译器会产生与CFI相关的metadata数据和llvm.type.test测试。
这些数据包括bit vector等,用于在运行时进行类型检查。
二、运行时(runtime)的作用
跨DSO检查:
在Android系统中,程序可能由多个动态共享对象(DSO)组成。为了支持跨DSO的CFI检查,引入了__cfi_check函数。
当进行跨DSO的函数调用时,runtime会调用__cfi_check函数来验证目标地址的合法性。这个函数会根据CallSiteTypeId(调用点类型ID)和目标地址的类型信息来进行检查。
CFI Shadow内存分配:
为了快速查找一个库的__cfi_check函数地址,Android系统引入了CFI Shadow机制。
CFI Shadow是一个特殊的内存区域,用于存储每个DSO的__cfi_check函数地址。这样,在运行时可以快速地根据目标地址找到对应的__cfi_check函数进行调用。
CFI Shadow的初始化流程包括在so加载、main函数调用之前以及so卸载时进行的操作。
报错处理:
当CFI检查失败时,runtime会调用cfi_check_fail函数来生成错误信息。
这些错误信息可以帮助开发者定位问题所在,并采取相应的修复措施。
三、具体实现细节
Metadata的生成与降低:
Clang编译器在编译过程中会生成类型元数据,并通过一系列的处理步骤将这些元数据转换为可在运行时使用的格式。
这些处理步骤包括metadata的降低(lowering)等,最终生成可在LLVM IR层面使用的bit vector等数据。
__cfi_check函数的实现:
__cfi_check函数是一个大的switch语句,用于根据CallSiteTypeId来判断目标地址的类型信息是否合法。
这个函数会遍历所有已知的类型信息,并与CallSiteTypeId进行比较,如果匹配则允许调用,否则报错。
CFI Shadow的初始化与使用:
CFI Shadow的初始化流程包括在so加载时分配内存、设置权限等操作。
在运行时,通过查找CFI Shadow可以快速定位到目标DSO的__cfi_check函数地址,并进行相应的检查。
四、图片展示
以下是一些与CFI相关的图片展示,用于更直观地理解CFI在Android中的应用:





综上所述,CFI安全特性在Android系统中通过编译器和运行时的紧密配合,实现了对程序控制流调用的有效保护。这种保护机制有助于提高Android系统的安全性,防止恶意攻击者利用控制流劫持等漏洞进行攻击。