一、SVF介绍
参考
1.SVF设计
介绍:SVF框架是基于LLVM所写的,首先用clang将程序源码编译成bit code文件,然后使用LLVM Gold插件把bitcode文件整合到一个bc文件;指针分析——接着进行过程间的指针分析来生成指向信息;数值流构建——基于指向信息,构建内存SSA形式,这样就能识别顶层和地址变量的def-use关系链;应用——生成的数值流信息可用于检测数据泄露和空指针引用,也可以提高数值流分析和指针分析的精确度。
指针分析:指针分析的实现分为三个组件,分别是Graph,Rules和Solver。Graph从LLVM IR搜集抽象信息,确定对哪一部分进行指针分析;Rules定义了如何从语句中获得指向信息,例如graph中的约束;Solver确定约束的处理顺序。SVF提供了一个简洁可复用的接口,用户可以随意组合这三个组件来实现自己的指针分析。
数值流构建:基于获得的指向信息,我们实现了一个轻型的Mod-Ref Analysis,以寻找每个变量的过程间引用和被修改的副作用。给定Mod-Ref结果和指向信息,每个store/load/callsite的间接使用和定义都使用别名进行注释,每个别名都表示一组可以间接访问的抽象内存对象。注意,Open64和GCC都采用过程内的内存SSA,它是在过程内计算的,所有非局部变量都在一个单独的别名集中;相反,SVF提供了Mem Region Partitioning内存区域划分模块,允许用户定义自己的内存区域划分策略,以影响别名集的形成方式,这样在分析大型程序时可灵活地权衡可扩展性和精度。(总之,优点是能够划分模块进行分析)。
2.应用场景
(1)Source-Sink Analysis: 如检测内存泄露漏洞,检查内存分配是否最终走到释放点,给定顶层和地址变量的数值流,SABER[36,37]/SPARROW[24]能检测到泄露漏洞,通过SVF框架还能检测double-free、文件open-close错误、污点数据使用错误。
(2)指针分析: 能提高指针分析的可扩展性和准确性,例如SELFS[44],能基于数值流信息对部分程序区域实施选择流敏感指针分析;FSAM[34]是基于SVF的,能对多线程c程序进行线程交错分析,以进行稀疏流敏感指针分析。
(3)加速动态分析:动态分析,通过插桩监视程序执行行为,带来了运行时的开销。可以采用静态数值流信息来引导实施选择性插桩,这样就能消除一些不必要的插桩,降低运行开销。例如USHER[43]使用过程间数值流分析来识别多余的操作,移除该处的插桩,还能用于检测其他漏洞如空指针引用和缓冲区溢出[42];还可以与符号执行[10]和动态数据流测试[16]相结合,以更快生成更有意义的测试用例。
(4)程序调试与理解:SVF还能用于软件调试和程序理解[18,21,40],可以通过只追踪相关的数值流来寻找引发错误行为的语句,不必分析不相关的语句;可扩展和精确的过程间数值流分析也对软件可视化有帮助(code map[20])。
二、技术细节
参考,包含SVF的内存模型、指针分析、值流构建的工作原理,可以参见this doxygen manual获取更多技术细节。
1.内存模型
SVF分析LLVM bitcode并为指针分析创建内部符号表信息,符号分为两类,ValSym
表示寄存器LLVM Value,高级指针;ObjSym
表示抽象内存对象,地址变量指针。有些对象需特殊标记,BlackHole
表示静态无法确定的符号,例如llvm中的UndefValue;ConstantObj
表示常量对象。
(1)抽象内存对象
抽象内存对象(MemObj
,属于全局、栈、堆对象)在内存分配点产生,用ObjTypeInfo
记录该对象的类型信息。如果对象中含结构类型(StInfo
),则用域信息来表示(FieldInfo
,包含偏移和类型),这样就能将嵌套的结构平坦化。
(2) 程序赋值图Program Assignment Graph (PAG)
注意:结构中的字段,称为域。
SVF将LLVM指令转化为图表示——PAG,每个节点(PAGNode)表示一个指针,每条边(PAGEdge)表示两个指针间的约束。
- PAGNode:主要分为两种,
ValPN
表示指针,ObjPN
表示抽象对象。ObjPN
的子类GepObjPN
,表示一个聚合对象中的域;ValPN
的子类GepValPN
,表示一个引入的虚拟节点,用于在处理外部库调用时实现域敏感(如memcpy,指向结构中某个字段的指针不会显式出现在指令中)。RetNode
表示过程返回,VarargNode
表示过程的可变参数。
- PAGEdge:分为以下类别。
AddrPE
:在内存分配点(ValPN
<--ObjPN
)CopyPE
:在PHINode
、CastInst
或SelectInst
(ValPN
<--ValPN
)StorePE
:在StoreInst
(ValPN
<--ValPN
)LoadPE
:在LoadInst
(ValPN
<--ValPN
)GepPE
:在GetElementPtrInst
(ValPN
<--ValPN
)CallPE
:从实参到形参(ValPN
<--ValPN
)RetPE
:从RetNode
返回到调用点(ValPN
<--ValPN
)-
PAGBuilder:PAGBuilder通过处理以下LLVM指令和表达式来生成PAG。LLVM中其他指令未做处理,但其他指令都能转换为基本的edge类型。
AllocaInst
PHINode
StoreInst
LoadInst
GetElementPtrInst
CallInst
InvokeInst
ReturnInst
CastInst
SelectInst
IntToPtrInst
ExtractValueInst
ConstantExpr
GlobalValue
(3)约束图 Constraint Graph
约束图(ConstraintGraph
)是PAG的副本,用于基于包含的指针分析。PAG是SVF所有分析的基础图,PAG的边是固定的,但ConstraintGraph
的边可以在求解约束时改变。
2. 指针分析
PointerAnalysis是所有实现的基类。为了实现不同的指针分析算法,用户可以根据不同的指向数据结构(PTData
)选择不同的实现。例如,流敏感和流不敏感的指针分析可以选择BVDataPTAImpl
作为基类,它使用位向量作为其核心数据结构,根据指向集将指针映射到其位向量。上下文和路径敏感分析,需要对每个指针添加上下文或路径条件作为限制。实现时,可以选用CondPTAImpl
,CondPTAImpl
将条件变量映射到其指向集。
每个指针分析实现都是对图进行操作,用求解器来求解约束。例如,Andersen分析,是利用solver从基于包含关系的约束图(ConsG
)上,推导出传递闭合规则。类似地,流敏感分析,是利用points-to propagation solver
从稀疏值流图(VFG)推导,其中要遵循流敏感的强/弱更新规则。
3. 值流构建
(1)内存SSA
顶层变量(寄存器)的def-use链在LLVM IR中很容易获取。load和store可以间接访问地址变量。def-use链的构建步骤:
-
- 执行指针分析以生成顶层指针(寄存器)的指向信息;
-
- load指令
p = *q
标注为函数μ(o)
(LoadMU
),o表示q所有可能指向的变量,μ(o)
表示load处对o的使用;store指令*p = q
标注为函数o =χ(o)
(StoreCHI
),o表示p所有可能指向的变量,o =χ(o)
表示store处对o的定义和使用def-use;call指令标注为μ(o)
(CallMU
)和o =χ(o)
(CallCHI
),以记录变量o的过程间使用和定义def-use;函数entry和函数exit标注为o =χ(o)
(EntryCHI
)和μ(o)
(RetMU
),来表示非局部变量o也即参数的传递和返回;PHI指令标注为o = mssaPhi(o)
(MSSAPHI
),在控制流汇合点整合多个对象o的定义。
- load指令
-
- 所有地址加载变量(load和store指令)转换为SSA形式,
μ(o)
看作是对对象o的use,o = χ(o)
看作是对象o的use和def。
- 所有地址加载变量(load和store指令)转换为SSA形式,
(2)稀疏值流图 Sparse Value-Flow Graph(SVFG)
给定一个程序(已经过SSA转换且带有μ和χ函数标注),通过连接每个SSA变量的def-use来构造SVFG。过程间稀疏值流图(SVFG)是有向图,它捕获顶层指针和地址获取(address-taken)对象的 def-use 链。
-
SVFGNode
:- A statement (A PAGEdge)
- AddrPE
- CopyPE
- GepPE
- LoadPE
- StorePE
- A memory region definition
- FormalIN // EntryCHI
- FormalOut // RetMU
- ActualIn // CallMU
- ActualOut // CallCHI
- MSSAPHI // MSSAPHI
- A parameter
- FormalParm // 形参 Formal Parameter
- ActualParm // 实参 Actual Parameter
- FormalRet // 函数返回变量 Procedure Return Variable
- ActualRet // 调用点返回变量 CallSite Return Variable
- A statement (A PAGEdge)
-
SVFGEdge
:表示变量从def到use的值流依赖。
顶层指针的直接值流
地址取address-taken对象的间接值流
SVFG 优化:使SVFG变得更紧凑,可以移除以下node——
ActualParm
、ActualIn
、FormalRet
、FormalOut
。
三、环境搭建
前4步,安装llvm和clang。
1.下载 LLVM-7.0.0, clang-7.0.0
2.解压
tar xf llvm-7.0.0.src.tar.xz
tar xf cfe-7.0.0.src.tar.xz
mv cfe-7.0.0.src llvm-7.0.0.src/tools/clang
3.创建目标build目录并make
mkdir llvm-7.0.0.obj
cd llvm-7.0.0.obj
cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../llvm-7.0.0.src (or add "-DCMAKE_BUILD_TYPE:STRING=Release" for release version)
make -j8 #换 make V=1
4.添加LLVM和Clang的路径
export LLVM_SRC=/home/john/Desktop/kernel/llvm-7.0.0.src #路径要修改
export LLVM_OBJ=/home/john/Desktop/kernel/llvm-7.0.0.obj #路径要修改
export LLVM_DIR=/home/john/Desktop/kernel/llvm-7.0.0.obj #路径要修改
export PATH=$LLVM_DIR/bin:$PATH
#建议添加永久环境变量
$ sudo nano /etc/profile
#NODEPATH
export CLANG_PATH=/home/john/Desktop/kernel/llvm-7.0.0.obj/bin
export PATH=$PATH:$CLANG_PATH
安装SVF。
5.下载SVF源码
git clone https://github.com/SVF-tools/SVF.git SVF
6.用cmake构建SVF (也可以使用 build.sh 去构建release或debug版本的SVF)
cd SVF
mkdir Release-build
cd Release-build
cmake ../
make -j4
构建Debug版本:
cd SVF
mkdir Debug-build
cd Debug-build
cmake -D CMAKE_BUILD_TYPE:STRING=Debug ../
make -j4
7.添加SVF路径 ($SVF_HOME是本机的源码目录)
export PATH=$SVF_HOME/Release-build/bin:$PATH #路径要修改
#建议添加永久环境变量
$ sudo nano /etc/profile
export SVF_HOME=/home/john/Desktop/kernel/SVF #路径要修改
export PATH=$PATH:$SVF_HOME/Release-build/bin
source /etc/profile
8.编译单个c程序为LLVM bitcode文件,或者用llvm-link
编译和链接多个文件。若要编译复杂的真实工程则需要使用 LLVM gold plugin。 安装步骤见 this 。
clang -c -emit-llvm -g example.c -o example.bc
clang -c -emit-llvm -g example1.c -o example1.bc
clang -c -emit-llvm -g example2.c -o example2.bc
llvm-link example1.bc example2.bc
9.设置环境变量
cd $SVF_HOME
. ./setup.sh
#若安装debug版本,则以下命令
cd $SVF_HOME
. ./setup.sh debug
四、使用方法
参考
1.全程序分析
运行Andersen指针分析
wpa -ander example.bc
运行Andersen指针分析并生成全程序数值流图
wpa -ander -svfg example.bc
运行流敏感指针分析
wpa -fspta example.bc
输出格式是 dot format,可以用 zgrviewer等工具展示。
#zgrviewer安装步骤
#1.下载 http://sourceforge.net/projects/zvtm/
$ svn co https://svn.code.sf.net/p/zvtm/code zvtm
#2.安装java虚拟机http://java.sun.com/
#3.安装GraphViz——http://www.graphviz.org/download/
#4.用run.sh即可运行ZGRViewer
$ java -jar target/zgrviewer-0.9.0.jar
2. Dump Graph
dump CallGraph
wpa -ander -dump-callgraph example.bc
wpa -fspta -dump-callgraph example.bc
dump PAG (program assignment graph)程序赋值图
wpa -ander -dump-pag example.bc
Dump Constraint Graph
wpa -ander -dump-consG example.bc
Dump Value-Flow Graph
wpa -ander -svfg -dump-svfg example.bc
Dump Memory SSA
wpa -ander -svfg -dump-mssa example.bc
3.打印数据
wp a -ander -stat example.bc
wpa -fspta -stat example.bc
打印指向结果
wpa -ander -print-pts example.bc
wpa -fspta -print-pts example.bc
参考资料
主页:http://svf-tools.github.io/SVF/
github主页:https://github.com/SVF-tools/SVF
环境搭建:https://github.com/SVF-tools/SVF/wiki/Setup-Guide-(CMake)
论文链接:https://yuleisui.github.io/publications/cc16.pdf