1. GN cfi配置说明
在GN(GNU Ninja)构建系统中,cfi(Control Flow Integrity)是一种安全特性,用于检测和防止控制流劫持攻击。blocklist是一个配置选项,用于指定在编译时应该被排除在cfi检查之外的函数或源文件。
- GN 编写cfi配置
ohos_shared_library("example") {
sanitize = {
cfi = true # 开启控制流完整性检测
cfi_cross_dso = true # 开启跨so调用的控制流完整性检测
integer_overflow = true # 开启整数溢出检测
boundary_sanitize = true # 开启边界检测
ubsan = true # 开启部分ubsan选项
all_ubsan = true # 开启全量ubsan选项
debug = true # 可选, 调测模式, 默认是不开启
blocklist = "./blocklist.txt" # 可选, 屏蔽名单路径
}
...
}
在这个例子中,blocklist选项被设置为"./blocklist.txt",这意味着GN将使用当前目录下名为blocklist.txt的文件来指定应该被排除在cfi检查之外的函数或源文件。
- 编写blocklist文件
blocklist.txt文件应该包含一个或多个规则,每个规则指定一个应该被排除的函数或源文件。规则的格式通常是[type] [name],其中type是fun(函数)或src(源文件),name是函数或源文件的名称。例如:
[fun]
fun: my_function
[src]
src: my_source_file.cpp
2. 问题说明
//base/customization/enterprise_device_management/services/edm_plugin新增xxx_plugin.cpp文件用于实现网络共享控制策略功能,其调用libwifi_sdk.so动态库hotspot模块新增接口GetApPoliciesMacList、UpdateApPoliciesMacList、ClearApPoliciesMacList。
当前项目中存在cfi检查,因此未配置cfi相关策略或开发针对性代码时,出现cfi check异常问题。
3. 解决方式
第一种:配置libwifi_sdk.so动态库cfi设置
ohos_shared_library("wifi_sdk") {
sanitize = {
cfi = false
boundary_sanitize = true
cfi_cross_dso = true
debug = false
blocklist = "../wifi_frameworks_sdk_block.txt"
}
…
}
配置libwifi_sdk.so动态库cfi设置项为false,不支持cfi校验
第二种:配置调用方EDM白名单
template("edm_plugin_shared_library") {
ohos_shared_library("${target_name}") {
forward_variables_from(invoker, "*")
…
sanitize = {
boundary_sanitize = true
cfi = true
cfi_cross_dso = true
debug = false
integer_overflow = true
ubsan = true
blocklist = "edm_plugin_block.txt"
}
…
}
}
#edm_plugin_block.txt
src:*xxx_plugin.cpp
src:*xxx_plugin.h
这里对xxx_plugin.cpp整体文件进行白名单屏蔽,也可以通过[type] [name]的方式对函数进行屏蔽。也可以添加[cfi]的作用域限定。
第三种:sanitizers默认配置文件增加白名单
//build/config/sanitizers/cfi_blocklist.txt
src:*xxx_plugin.cpp
src:*xxx_plugin.h
cfi存在默认blocklist.txt,对其配置白名单也可以解决问题。
第四种:
ohos_source_set("wifi_hotspot_proxy_impl") {
…
sanitize = {
cfi = true
boundary_sanitize = true
cfi_cross_dso = true
debug = false
}
sources = [
"src/wifi_hotspot_impl.cpp",
"src/wifi_hotspot_mgr_proxy.cpp",
"src/wifi_hotspot_proxy.cpp",
]
…
}
ohos_shared_library("wifi_sdk") {
branch_protector_ret = "pac_ret"
sanitize = {
cfi = true
boundary_sanitize = true
cfi_cross_dso = true
debug = false
blocklist = "../wifi_frameworks_sdk_block.txt"
}
sources = [
"c_adapter/src/wifi_c_device.cpp",
"c_adapter/src/wifi_c_event.cpp",
…
]
deps = [
":wifi_device_proxy_impl",
":wifi_hotspot_proxy_impl",
":wifi_p2p_proxy_impl",
…
]
…
}
在wifi_hotspot_proxy_impl提供的对外接口,因此在这里增加sanitize的cfi控制,貌似也能说得通。但是细心的你应该发现wifi_sdk中已经提供sanitize的cfi控制,这里的逻辑有点像“我的封臣的封臣,不是我的封臣”。这点也是wifi模块cfi控制坑人的点,如果wifi_sdk的cfi可以对wifi_hotspot_proxy_impl下的文件生效,则不会存在对cfi的研究,最终对于cfi的修改,采用的就是本方法。
4. 解决思路
从第三步提供的解决方式中,可以看出cfi的处理方式会很多,且变化较多,但万变不离其宗。上述的后几种做法,普遍采用的都是白名单思路。第四种虽然没有明确的白名单说明,但实际上依旧是白名单,他使用的白名单是wifi_sdk中cfi配置的白名单wifi_frameworks_sdk_block.txt。
#wifi_frameworks_sdk_block.txt
[cfi]
# xmlFree check failed for CFI.
type:OHOS::Wifi::*
type:OHOS::*
wifi_frameworks_sdk_block.txt这里的白名单基本上将wifi所有的内容均做了屏蔽。主动屏蔽该白名单后,wifi模块刷屏式报错。
既然说cfi的问题,万变不离其宗,那这里的宗是什么?是编译选项。
如上图所示:包含cfi配置的编译选项中,存在-fsanitize的一系列编译选项,查看自己所涉及的文件是否进行了cfi配置,可通过构造错误,查看其编译中是否携带-fsanitize的选项。这里的所有修改方式,均是通过这种方法确定的。
5. sanitize与visibility
Clang 包括许多控制流完整性(CFI)方案的实现,这些方案旨在在检测到某些形式的未定义行为时中止程序,这些行为可能允许攻击者颠覆程序的控制流。这些方案已针对性能进行了优化,开发人员放心在版本构建中启用它们。
要启用 Clang 的所有可用 CFI 防护,可以使用编译选项 -fsanitize=cfi,也可以通过更细粒度的编译选项 -fsanitize=cfi-icall,-fsanitize=cfi-vcall 等只启用部分防护。目前实施的所有防护都依赖于链接时优化(LTO);因此需要开启编译选项 -flto(使用的链接器必须支持LTO,如 lld、gold[12]),如:
clang -O2 -fvisibility=hidden -flto -fuse-ld=lld -fsanitize=cfi -fno-sanitize-trap=all -o cfi_icall cfi_icall.c
编译器在能够推断一个类的隐藏 LTO(链接时优化)可见性时,才会为一个类生成 CFI 检查。LTO可见性是类的一个属性,由编译选项 -fvisibility=default/hidden 或在源码添加相关attribute(如:__attribute__((visibility("default"))) )指定。
使用编译选项 -fsanitize=cfi-{vcall,nvcall,derived-cast,unrelated-cast} 时要求同时加上编译选项 -fvisibility=[default|internal|hidden|protected]。这是因为默认的可见性设置是-fvisibility=default,这会禁用没有可见性属性的类的CFI检查。通常我们使用编译选项-fvisibility=hidden 为类启用CFI检查。
以上内容摘自:编译器安全专题 | 控制流完整性-CSDN博客
GCC的visibility属性用来控制.so文件的符号表,也就是控制外部能不能找到符号调用,比如函数、变量、模板、类等。符号表分静态的 .symtab 和动态的 .dynsym,一个对应链接视图另一个对应执行视图。设置为 hidden 符号将不导出,即不出现在 .dynsym 当中,不能为模块外所用。
默认是可见,这也就是“default”的含义。
在编译文件中:
1. 当-fvisibility=hidden时
动态库中的函数默认是隐藏的,除非代码中显示声明为__attribute__((visibility("default"))).
2. 当-fvisibility=default时
动态库中的函数默认是可见的,除非代码中显示声明为__attribute__((visibility("hidden"))).
由上述内容可知,cfi生效的前提是visibility编译选项为hidden,反之,我们配置visibility=default时,则可以不进行cfi检查,蓝牙的对外函数就是这样做的(该条路径未经测试)。
- EDM调用点
- 蓝牙接口
- BluetoothHost类定义
- BLUETOOTH_API
6. no_saniyize(type)
在C语言中,no_sanitize(cfi)是一个编译器指令,用于告诉编译器不要对特定的函数或代码块进行控制流完整性(Control Flow Integrity,CFI)检查。CFI是一种安全机制,用于防止程序的控制流被攻击者劫持,例如通过缓冲区溢出或其他类型的内存错误。这个指令通常与编译器的-fsanitize=cfi选项一起使用,后者用于启用CFI检查。当编译器遇到no_sanitize(cfi)指令时,它会跳过对相应代码的CFI检查。
wifi对外提供的C语言函数,就是通过no_sanitize(cfi)编译器指令处理的。可参见wifi_sdk动态库对外的6个.C文件进行查阅no_sanitize(cfi)方式的C语言函数对外接口。