
为了抵御上面的代码复用攻击,加州大学和微软公司于2005年提出了控制流完整性(Control-Flow-Integrity, CFI)的防御机制。Control-Flow-Integrity (CFI) 是一种确保软件必须在先前确定的控制流图上执行的安全策略。其核心思想就是在函数在发生不确定的跳转时,验证跳转的合法性。
CFI分为Forward Edges CFI和Backward Edges CFI。前者是在间接调用前验证控制流,而后者是在函数返回时验证返回地址是否属于调用者。下面罗列了Linux下相关实现,如下:

目前还有硬件实现的Backward CFI
* Intel CET 基于硬件的只读影子调用栈
* ARM V8.3a Pointer Authentication(“signed return address”)
struct sanitizer
我们通过分析一些常见的内核漏洞POC,发现这些POC对控制流的修改都集中在几种结构体内置函数指针的修改上。而上面的CFI的方案需要对所有代码进行插桩验证控制流,这样势必会带来明显的性能下降问题。所以我们提出了struct-sanitizer(struct_san)这种新的控制流完整性检测机制。
struct_san与上面的CFI方案相比,struct_san在对结构体指针的验证要比已有的CFI技术更严苛。当前主流的CFI技术主要是验证函数指针的类型,而struct_san在此基础上还要验证此函数指针是否还属于当前结构体实例。struct_san还可以做到非全量插桩,以减少一些非不必要的性能损耗。
实现原理
struct_san工作原理如下:

struct san 通过对在结构体里的函数调用前加入校验函数__sanitizer_struct_guard__(),来验证此函数指针是否属于当前结构体实例,如果验证合法则继续运行下面的间接调用函数,否则抛出ud2。
使用方法
struct_san为了避免非全量插桩,新增一个GNU Attributes __attribute__ ((sanitize_struct)) 。
使用方法是在想要保护的结构体类型声明处和调用此结构体的函数指针的函数前加入此关键字,例如想要保护内核中的pipe_buf_release()代码中的pipe_buf_operations->release()函数。
1.在结构体类型声明时加入此关键字

在类型声明完成以后,struct_san会将此类型的所有结构体实例保存到.sanitize_struct段内。
2.在需要保护的函数中也要加入上面的关键字。例如在pipe_buf_release()函数的声明和定义处加关键字,加入关键字后会在调用pipe_buf_operations->release()前插入校验函函数__sanitizer_struct_guard__()
下面是插桩前后在gcc的gimple IR中的不同表示:

插桩前

插桩后
检测算法
struct_san目前只在内核中完成了相关实现。其算法是在内核中开辟一个128M大小shadow memory用来保存结构体和结构指针的对应关系。__sanitizer_struct_guard__()在调用时会检测传入的struct和函数指针是否在shadow memory中,如果不在则抛出一个ud2异常,否则返回函数指针。实现方案如下:

这个算法参考了AddressSanitizer的实现,兼顾了效果和效率。
效果
以漏洞CVE-2021-22555的攻击代码为例,在启用struct_san的情况下,CFI阻断了攻击代码的执行,起到了有效的防御。

开源地址
我们对struct_san进行了开源,期望和业界一起探讨CFI技术的改进。后续我们也会推出一些其它的漏洞缓解技术。
https://github.com/YunDingLab/struct_sanitizer