一、前 言
二、工作原理
三、安装指南
四、CSA Driver 使用
五、CSA 源码浅析
六、CSA 控制流分析
七、CSA CallGraph
八、CSA 数据流分析
九、总 结
静态分析技术因其在学术研究和工业应用中的广泛用途而备受关注。尽管静态分析技术已经取得了长足的进步,但现有的静态分析框架仍然存在易用性方面的挑战。本文主要介绍了 Clang Static Analyzer(CSA)的使用方法,对部分检查器的源代码进行了浅析,并探讨了其设计思想。希望通过本文,能够帮助希望深入了解静态分析技术的同学更好地掌握相关知识。
CSA 分析器的设计灵感来自几篇基础性的研究论文 ([1], [2])。
分析器基本上是一个源代码模拟器,它能跟踪可能的执行路径。程序的状态由状态(ProgramStateRef)进行封装。在 CSA 术语中对应ExplodedNode
。
分析器通过对分支进行推理,找出多条路径,然后对状态进行分叉。在真分支上,假设该分支的条件为真,而在假分支上,假设该分支的条件为假。这种“假设”会对程序的值产生约束,这些约束被记录在程序状态(ProgramState)中并由约束管理器管理。如果假设分支的条件会导致约束条件无法满足,那么该分支将被视为不可行,该路径将被删除,这就是路径敏感性。CSA 通过缓存节点来减少指数级爆炸,如果新节点的状态和程序点与现有节点相同,路径就会被“缓存”,我们只需重新使用现有节点即可。
更详细的工作原理可以参考 CSA README.md [3]。
总而言之,Clang Static Analyzer(CSA) 是基于 Clang AST(编译器前端) 实现的源代码静态符号执行工具。支持分析 C,C++,和 Objective-C 。基于静态符号执行技术实现了路径敏感技术,也支持过程间分析。工具被集成进 llvm-project,有长期的维护和发展。如果你对符号执行比较熟悉,会注意到其与 KLEE,Angr 的诸多相似之处。
clang is all you need。
-
通过包管理工具安装因平台而异,这里以 ubuntu 为例。
-
根据源码编译 LLVM 项目,编译细节可以参考 Getting Started with the LLVM System ([4], [5])。
由于 CSA 工具被集成进 clang
中, 并不是一个非常“独立”的工具,使用起来会有一些麻烦。当然 CSA 相关项目也提供了一些相对独立的工具如 clang-check
, CodeCheck
,但是从结果来看与直接使用 clang
是差不多的。一般来说,使用 CSA 需要 driver 来加载参数来决定是否开启某些 checker,是否开启 z3 进行约束求解,是否导出报告等。由于参数实在之多,不可能一一列出,这里只介绍部分。
-
查看与前端 analyze 相关的参数:
期望输出:
-
查看当前 clang 有哪些 checker:
或者
期望输出:
为例便于使用 CSA ,我们需要用 driver 来配置 CSA 的参数,对单个或多个源代码进行分析,自己写或者利用现成的工具。
以分析如下代码为例,说明 CSA 是如何进行过程间分析,并熟悉相关参数:
-
源代码:
-
导出 ast:
-
导出
externalDefMap.txt
:
-
替换
externalDefMap.txt
一些路径问题:
-
使用 CSA 进行过程间分析:
-
报告可以通过参数来指定,下图是一个报告的例子:
下面以 TaintTesterChecker.cpp
为例说明 CSA checker 的一般结构与实现。
-
TaintTesterChecker.cpp
可以从path-to-llvm-project/clang/lib/StaticAnalyzer/Checkers
目录下找到,这里直接引用它的内容。
-
这个 checker 继承了
public Checker<check::PostStmt<Expr>>
,表示在 CSA 进行分析的过程中,它会在每个 Stmt 执行后调用该 checker。如果在该StateRef
被污染了,就会产生路径敏感的报告。
-
最后还需要在
CheckerMananger
中中注册相应的 checker。
下面对 MallocChecker 进行简单的分析,希望读者也能有一些收获,关于 malloc free pair 和 new delete pair 等相关的实现问题。
MallocChecker
也在上文提及的目录中,CSA checkers
一般都在此实现。
静态分析工具 CSA 的核心就是建模,阅读 MallocChecker
后很明显能了解到 CSA 的开发者是以何种方式思考,希望能够描述出什么样的 malloc 与 free 是合法与非法的,以此类推,建立程序语言与形式化方法之间的桥梁。
MallocChecker
通过 AllocationFamilyKind
标识内存分配/释放的家族,总共有以下几类,对每一个家族有不同的处理,这一点比较好理解。
分配/释放的内存处于某种状态,可以理解为在有限自动机上的状态转换。每一种都有注释,可以详细阅读一番。其中 Relinquished
还是比较有趣的,在统计引用的过程中表示引用已经被“偷走”,不必再对其 free
或者减引用,否则有可能导致 UAF。
关于程序建模,以 Malloc
的建模为例,程序中的注释表示得还算清楚。分配内存需要考虑分配内存的函数 AllocationFamily Family
是什么,需要分配的大小 const Expr *SizeEx
,是否需要初始化 Sval Init
,以及内存分配函数的上下文 CallEvent
和 CheckerContext
。
还需要考虑 Malloc
的返回值 RetVal
,是否分配成功,应该分配怎么样的内存,什么样的情况下需要被污染,什么样的情况下报错,这就需要结合 FreeMemAux
考虑。
Malloc
只要分配一块可被追踪的内存,而 Free
需要考虑的就很多了。
实际上,FreeMemAux
写的并不是那么完美,首先本身指针分析就不是非常的准确,比如调用有关 Free 的函数指针,就不一定能被检测出来。另外 CSA 本身也有一些问题,迭代的次数也是有限的,全局变量初始化也不一定能被分析到,而且 StateRef 也意味着单线程的分析,得到的报告很可能因为路径不可行而成为 FP,因为 CSA 在分析过程中有太多假设了,这些误报理论上应该通过程序的其他信息/规则来得到或者说优化。
在种种对入参进行检查之后,才到检查漏洞的相关代码。关于 Free alloca
分配内存,甚至后面还有合法性的检查(如 Double free
检查),与内存的建模关系非常大。
现在来考虑一下如何在 CSA 中实现最基础的静态分析算法(流不敏感)。可以参考下文摘录的部分代码(比较经典的 worklist)。
从 MallocChecker
一些 FIXME,TODO 中可以看出 CSA 还存在一些“奇怪”的 bug,如 Ownership
可能会因为存储发生变化。
对应于源码中的定义: llvm-project/clang/Analysis/CFG.h
基于 AST 构建的 CFG。
对应于源码中的定义: llvm-project/clang/Analysis/CallGraph.h
基于 AST 构建的 CallGraph。
对应于源码中的定义: llvm-project/clang/Analysis/FlowSensitive/DataflowAnalysis.h
基本思想是通过控制流图(CFG)的边缘传播有关程序的事实,直到到达固定点。[6] 受限于静态分析对路径的执行次数上限,(不能太多次展开循环),同时需要对数据进行一个合理的估计。
可以看出,CFG,DFG,CG 等关键的图都能基于 AST 构建出来,与时下流行的基于 DSL 的静态分析并驾齐驱,相比于 CodeQL 等 DSL 工具。虽然 CSA 在规则编写上会有一些繁琐,但是能客制化更复杂,功能更强的规则以检测代码缺陷。另外,当前静态分析仍然受制于最基础的指向分析(Point-To Analysis),过程间分析的能力,随着静态分析等理论/工具的发展,能检测到的代码缺陷也会变多。但是代码不会因此变得没有漏洞,只是漏洞可能会更深。
2. A memory model for static analysis of C programs, Z Xu, T Kremenek, and J Zhang
4. Getting Started with the LLVM System
【版权说明】
本作品著作权归R0g3rTh4t所有
未经作者同意,不得转载
@天工实验室🧑💻@Vidar-Team,主攻静态分析,模糊测试
原文始发于微信公众号(奇安信天工实验室):探索Clang Static Analyzer:使用方法与源码解读
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论