group网址,Pinpoint曾经开源,不过目前已经商业化,因此参考资料只能找到paper,不过其design还是很有参考意义的。相比Fastcheck和SVF的阶段化设计(即先进行全程序指针分析 -> 全程序value-flow分析 -> 全程序bug检测),Pinpoint采用的整体性设计在分析时采用整体性设计原则进行按需分析,因此能减少很多不必要的性能开销。
1.Introduction
下图示例反映了阶段化设计工具(SVF和Fastcheck)的设计原则,首先进行全程序指针分析然后构建全程序的SVFG(图b),然后进行过程间bug检测。图b反映出了部分的SVFG(这里ptr
为2级指针,这里SVFG只展示1级指针变量之间的value-flow关系),包括 a
, b
, c
, d
, e
5个变量与 f
的value-flow关系以及每个value-flow对应的路径条件。而随后报出的use-after-free bug report涉及的value-flow包括: c --> free(c)
, c --> f
, f --> print(*f)
,可以看到 a
, b
, c
, d
都没涉及到。如果在指针分析和VFG构建的时候能忽略这4个变量将节省很多性能开销。
下图则反映了Pinpoint的idea,包括: 1.识别side-effect、2.对原始程序进行语义等价转换、3. local points-to Analysis、4.Local SEG构建、5.过程间bug分析。
首先Pinpoint会对原始代码进行一些语义等价转换,以将原始程序的side-effect暴露在parameter和return value(也就是function interface处),side-effect主要指的是在当前函数引用和修改了非局部类型(比如全局变量内存、解引用指向其它函数局部变量的指针、堆内存的访问等)。
示例中, bar
函数的 q
参数带来的side-effect包括:∗q != 0
的load操作、*q = c
和 *q = b
的store操作,这几个操作都访问了非 bar
函数的局部内存。 语义转换后 ∗q
会直接作为函数参数传递并通过返回值 Y
返回。bar
转换后函数签名换了,foo
内相应代码也跟着一起转换。
随后,Pinpoint会分别对 foo
,qux
,bar
进行Local指针分析而不是像Fastcheck和SVF那样考虑3个函数的调用关系进行全程序指针分析。指针分析后为每个函数单独构建SVFG(这里表示为symbolic expression graph SEG)。比如为了 foo
第12行的 f
构建数据依赖关系,首先需要获取 *ptr
的local point-to set
{
(
L
,
θ
1
)
,
(
M
,
¬
θ
1
)
}
\{(L, \theta_1 ), (M, \neg \theta_1 )\}
{(L,θ1),(M,¬θ1)}(表示分别在条件
θ
1
\theta_1
θ1 和
¬
θ
1
\neg \theta_1
¬θ1 下指向 L
和 M
),这里只会进行函数内分析。
随后在bug检测阶段,use-after-free value-flow包括 ⟨free(c), c, Y , return Y , L, f , print(*f)⟩
。整个过程分析的value-flow只包括这个。
2.Holistic Design in Pinpoint
作者将程序抽象为下面形式,似乎不包括局部变量。 r ← c a l l f ( v 1 , v 2 , ⋅ ⋅ ⋅ ) r \leftarrow call f(v_1,v_2, · · · ) r←callf(v1,v2,⋅⋅⋅) 中 r r r 被称为callee的receiver。
2.1.Decomposing the Cost of Data Dependence Analysis
主要介绍Local Point-To Analysis和Local Value Flow Analysis的原则。
首先用一个近似path-sensitive的方式对每个function进行local point-to analysis,作者观察到70%的path condition是可以满足的,因此用昂贵的SMT solver来求解会引入太多性能开销。作者观察到90%的不可满足的路径条件形式都比较简单,比如 a ∧ ¬ a a \land \neg a a∧¬a。因此在这一步,路径条件的构造按下面方式执行。
local point-to analysis分析后就是进行local value-flow分析。如果 *q
和 *u
存在别名关系,那么 p = *q
和 *u = w
中 w
和 p
就存在数据依赖关系。但是 q
和 u
可能指向非局部(non-local)内存。作者这里主要讨论由函数调用引起的 q
和 u
可能指向上层caller内存的问题。传统的summary-based方法将访问非本地内存位置的 store
和 load
语句记录为side-effect或MOD/REF摘要,然后在上层caller的每个当前函数callsite克隆和实例化summary。由于程序中存在大量的 store
和 load
语句,side-effect summary的大小会迅速膨胀,成为scalability的一个重大障碍。
作者这里参考IFDS/IDE-based方法构建一个connectors来表示每个function的输入-输出side-effect。下图中 X
和 Y
就表示 bar
函数的connector,每个input/output connector表示由 store
或者 load
进行的内存区域读/写。在callsite,会构建callsite connector表示实参和返回值的receivers,比如 foo
函数的 K
和 L
表示 bar(ptr, K)
的connector,其中 K
连接 X
而 Y
连接 L
。
实际上作者的connector主要包括2部分:
-
1.Aux formal parameter:一个局部变量,代表通过指针表达式 ∗ ( p , k ∈ N + ) ∗(p, k \in N+) ∗(p,k∈N+) 引用的非本地内存位置,其中 p p p 是一个形参。
-
2.Aux return value:性质和Aux formal parameter类似,属于返回值。
connector插入规则如下:
2.2.Symbolic Expression Graph
symbolic expression graph (SEG) 是一种新形式的SVFG。它的特性包括:
-
1.它紧凑且精确地编码了所有条件和无条件的数据依赖关系,以及控制依赖关系。
-
2.它使得可以方便地查询“有效路径条件”,从而全面支持路径敏感分析。
-
3.它是为每个函数单独构建的,不仅节省了时间成本,还使高效的组合分析成为可能。
这里 bar
函数的SEG如下图所示,每个图结点可以表示一个语句或者是单目/双目运算符。图有数据依赖和控制依赖(虚线)表示。
2.3.Global Value Flow Analysis for Vulnerability Detection
跨函数分析需要解决两个问题:
-
1.如何在连接来自不同函数的value-flow时实现路径和上下文敏感性。
-
2.另一个问题是如何重用分析结果以避免重复计算,从而提高效率。
2.3.1.Tackle 1: Demand-Driven Path- and Context-Sensitive Value Flow Analysis
这一步解决的问题包括:
-
1.给定一个local value flow path π \pi π,对应local路径条件包括 P C ( π ) PC(\pi) PC(π)。这个路径条件不包括caller和callee的,损失了些精度,需要recover。
-
2.计算任意global value flow path的路径条件。
作者用 P C ( ⋅ ) P R PC(·)_P^R PC(⋅)PR 表示 P C ( ⋅ ) PC(·) PC(⋅) 中缺失的条件, P P P 和 R R R 分别表示当前对应的形参和返回值。
下图为从 foo
的形参 a
到 output(*c, *a)
的value-flow path的路径条件的计算。
2.3.2.Tackle 2:Compositional Approach to Bug Detection
分析一个function需要query 两类callee信息:1.return变量 v v v 的数据依赖 D D ( v @ s ) ∅ P DD(v@s)^P_\empty DD(v@s)∅P。2.某一个value-flow path π \pi π 对应的路径条件 P C ( π ) ∅ P PC(\pi)^P_\empty PC(π)∅P。对此,Pinpoint分别生成return-value(RV) summary以及value-flow(VF) summary。
作者定义下面几种value-flow summary:
-
VF1 summarizes a value-flow path from a function parameter to a return value;
-
VF2 summarizes a value-flow path from a source to return value;
-
VF3 summarizes a value-flow path from a function parameter to a source;
-
VF4 summarizes a value-flow path from a function parameter to a sink;
VF1 确定在callsite处的实参是否会流回同一调用点处的返回值receiver。因此,当在路径搜索过程中遇到实参时,VF1 决定是否应该从返回值接收者开始继续搜索。VF2 和 VF3 分别确定在callsite之后,返回值receiver和实参是否会被污染。它们有助于决定在分析函数时是否应该从返回值receiver或实参开始路径搜索。VF4 确定在callsite处的实参是否会流向callee中的某个sink点。如果在路径搜索过程中到达实参,并且被调用函数具有VF4 summary,则在callee中可能会存在bug。
在检测bug时遇到函数调用会直接query summary而不是对应的SEG进行分析。
3.Evaluation(部分)
Pinpoint主要与SVF等工具进行比较,主要的比较标准是性能优化以及查找bug的准确率。
3.1.Scalability
下面两张图分别反映了程序变大时Pinpoint和SVF的时间和内存开销对比,红色虚线为Pinpoint。
3.2.Bug detection
SVF和Pinpoint生成的report作者通过人工验证(SVF的report比较多作者随机sample后进行人工验证)