Statically Discovering High-Order Taint Style Vulnerabilities in OS Kernels
- 1.Introduction
- 2.Overview
- 3.Static Analysis Design
- 4.Vulnerability Discovery And Warning Group
- 4.1.Vulnerability Detectors
- 5.Implementation
- 6.Evaluation
- 7.Limitation And Discussion
- 参考文献
作者来自University of California Riverside - Security Lab,同一团队其它系统安全方面的工作还包括:静态分析工具UBITech、Fuzz工具SyzDescribe。
1.Introduction
这篇paper可以说是对Dr.Checker在检测高阶漏洞时的改进。所谓高阶漏洞(high-order taint style vulnerabilities),值得是用户控制的输入(即taint source)在经过多次entry function调用后,通过复杂的控制/数据流传播到敏感操作(即taint sink)导致的漏洞。例如,一个entry function A()
将用户提供的参数复制到一个全局变量 G
,而后该全局变量 G
在另一个enrty function B()
中被用作数组索引,而没有经过检查,导致了越界访问。
高阶的阶(order)指的是触发漏洞所需的入口函数调用的数量。与污点传播仅限于单个entry function调用(即一阶漏洞)相比,在有状态的软件(例如Linux内核)中经常出现的高阶漏洞更加难以发现。高阶漏洞检测面临2个challenge:
-
1.性能:检测工具需要高效地枚举cross-entry的污点传播路径。直观地说,由于多个entry function可以以任意顺序被调用,分析算法需要遍历许多可能的排列,并在不同排列中重复分析同一个entry function,这可能会增大分析开销。
-
2.精度:cross entry场景下的taint flow通常由多个entry function的taint flow拼接,因此不精确的taint flow可能会累积从而造成更多误报。
因此作者提出了SUTURE,其解决上述challenge的思路包括:
-
1.性能问题:首先独立分析每个entry function(仅分析一次),并为其局部和全局变量的taint behavior创建一个抽象摘要,然后在漏洞发现阶段,通过查询这些摘要按需构建高阶污点传播路径。这使得高效的高阶污点传播路径枚举成为可能。
-
2.精度问题:整合了许多创新和/或实用的功能来提高其精度。这些功能包括通过流敏感分析进行机会路径敏感分析,处理模糊的全局内存更新等。还包括在indirect-call分析中应用精度更高的分析算法。
作者在不同Android内核版本(谷歌、三星、华为)中的驱动模块上对SUTURE进行了评估。结果显示,我们的工具能够发现先前未知的高阶污点漏洞。到目前为止,SUTURE报告了79个真正的tru positive warning group,其中19个得到了开发人员的确认,包括一个被谷歌评定为高危漏洞。SUTURE的误报率相对来说也可以接受(51.23%),并且在性能方面也表现良好(例如,在30小时内并发分析目标内核的所有37个模块)。
2.Overview
2.1.Motivating Example
图1为一个高阶漏洞示例,其中 entry0
、entry1
、entry2
为3个不同的驱动程序entry function,其中连续调用 entry0
、entry1
会触发2阶漏洞。触发过程如下:
-
首先,需要调用
entry0()
,并传入cmd=0
,这使得用户提供的user_input
流入全局变量d.b[0]
。 -
然后,需要调用另一个入口函数
entry1()
,该函数随后调用了bar()
。bar()
函数获取了d.b[0]
的值,该值之前由entry0()
中用户输入设置,并且其地址与*(p+4)
存在别名关系。 -
接下来,在
bar()
函数中,对d.b[0]
的值进行了一个溢出导致的加法操作(位于line 22)。
这个漏洞最tricky的特征是,原始用户输入到最终的溢出位置的污点传播过程涉及到全局变量 d
,使其成为高阶(更具体地说,是2阶)taint-style漏洞。
检测高阶漏洞时,需要考虑不同 entry function之前的排列组合,比如这个示例中2阶污点分析需要遍历9种组合 (entry0 --> entry0
、entry0 --> entry1
…),实际情况可能更复杂,同时污点分析的一些错误也更容易累积。潜在错误来源如下:
-
路径不敏感性:在line 3
cmd
非零的情况下,entry0()
可以将user_input
传播到其调用者foo()
中的d.b[1]
(line 13),然而实际上在line 6和line 12存在冲突的路径条件(cmd != 0
和n == 0
),这是不可能的。这样的路径不敏感最终可能导致一个误报,即在调用了entry0()
后,entry2()
的line 27可能导致溢出。 -
跨函数分析以及指针运算:为了发现line 22可能的溢出,静态分析算法必须能够准确解析指针算术并弄清楚
*(p+4)
是d.b[0]
的别名,否则就会产生误报。此外,为了弄清楚bar
函数参数p
的别名是d
,分析还需要进行跨过程的分析。 -
索引不敏感(index-insensitivity):如果不区分
d.b[0]
和d.b[1]
,那么分析调用序列entry0(cmd=0, ...) -> entry2()
line 27可能会出现误报。如果与路径不敏感性结合在一起,那么在调用序列entry0(cmd=1, ...) -> entry2
之后,line 27可能会有更多的误报,因为整个数组a
可能被过度污染,而不仅仅是a[0]
。此外,由于d
包含d.b
作为嵌套数组,分析还需要处理这样的嵌套结构。 -
分析算法还必须正确识别
entry1()
中line 18清除了d.b[0]
的taint tag,因此在第一次调用之后的其他entry1()
调用将不再触发line 22的溢出。这需要一种cross entry的流敏感性。
2.2.Workflow
SUTURE的workflow如图2所示包括3个stage:
-
1.Input:SUTURE的输入包括目标驱动程序的llvm bitcode以及一个指定了entry function信息(例如,函数名称、用户可控制的参数)的配置文件。对于图1中的示例,配置文件中将列出
entry0()
、entry1()
和entry2()
作为三个入口函数,其中entry0()
的user_input
参数被指定为用户可控制的输入。 -
2.Static Taint Analysis:接下来,SUTURE将进行静态污点分析,并为每个entry function生成独立的摘要,其中包括entry function对应的的所有local taint flow。在这个阶段,用户输入和全局变量都被视为污点源,同时记录了entry function中每个sink变量的local taint flow。在图1中,作者在左下角的方框中展示了三个entry function的local taint flow。值得注意的是,SUTURE以一种无关顺序的方式仅对每个entry function进行一次分析,例如,
entry0()
可以在entry1()
之前或之后进行分析,避免了在不同的调用序列中重复分析相同entry function的昂贵成本,然而,SUTURE仍然能够发现后面详细介绍的高阶漏洞。(注:这里的local taint flow并不是单个function内,依旧是跨函数的,只是在单个entry function的scope内,不同entry function对应的local taint flow主要依靠全局变量连接) -
3.Vulnerability Detection:在这个阶段,SUTURE尝试针对各个cross entry function进行漏洞检测。对于找到的bug-inducing statement,SUTURE将分析是否存在从用户输入到bug-inducing statement的cross entry taint flow。在motivating example中,
entry0()
和entry1()
的local taint flow被组合在一起形成最终漏洞的高阶taint flow。 -
4.Output:对于每个告警,SUTURE输出与告警类型和完整的cross entry taint flow等相关的信息,SUTURE还计算每个告警的阶数。对于motivating example,SUTURE最终触发一个有效的告警,其调用序列显示在右下角(一个二阶漏洞)为
entry0 --> entry1
,同时避免了误报。
3.Static Analysis Design
3.1.Positioning
静态污点分析的目标是为每个entry function构建一个污点摘要,为了实现这个目标,SUTURE独立分析每个entry function并记录其taint fact。其静态污点分析沿用了Dr.Checker的大部分,包括指针分析和污点分析的tranlation function,Dr. Checker以自上而下的方式对每个entry function进行了完备的过程遍历,对于每个访问的LLVM IR,进行别名和污点分析,更新与IR中涉及的变量相关联的point to和taint信息。基本上,Dr. Checker的静态分析是上下文敏感、流敏感和字段敏感的。然而,所有这些敏感性都是部分或有限的,这在SUTURE中需要进行处理。此外,与Dr. Checker相比,SUTURE对静态分析还有许多额外的要求。
3.2.Definitions
-
Def 0-entry function ϵ \epsilon ϵ:内核模块的入口函数充当模块接口的一部分,因此在同一模块内它没有任何调用者,并且应当由用户或其他模块(例如驱动程序的顶层
ioctl()
函数)直接调用。 -
Def 1-taint source S S S: S S S 包括入口函数的由用户提供的参数( U U U,比如图1
entry0
的user_input
)以及所有全局变量或内存区域(作者称之为全局内存, G G G,比如图1的d
)。注意, G G G 包括显式定义的全局变量(例如,全局整数)以及从它们可达的变量,例如,包含指向堆内存的指针字段的全局结构体变量。因此 S = U ∪ G S = U \cup G S=U∪G -
Def 2-calling context △ \triangle △: △ = [ i 0 , i 1 , . . . , i 2 n ] \triangle = [i_0, i_1, ..., i_{2n}] △=[i0,i1,...,i2n] 是一个指令序列,偶数下标的指令表示一个caller的entry指令,而奇数下标表示call指令(例如, i 2 i_2 i2 是call指令 i 1 i_1 i1 的target),序列总是以当前执行函数的入口指令结束,因此其长度始终是奇数。
-
Def 3-Instruction Location(简称InstLoc): I = ( i , △ ) I = (i, \triangle) I=(i,△) 表示一个静态指令以及其对应的context,用来进行上下文敏感的分析。
-
Def 4-taint flow τ \tau τ: τ = [ I 0 , . . . , I n ] \tau = [I_0, ..., I_n] τ=[I0,...,In] 为一个InstLoc序列。
-
Def 5-order:order表示taint flow的阶数, o r d e r ( τ ) = ∣ { k ∣ I k ∈ τ , I k + 1 ∈ τ , ¬ r e a c h ( I k , I k + 1 ) } ∣ order(\tau) = |\{k |I_k \in \tau, I_{k+1} \in \tau, \neg reach(I_k , I_{k+1})\}| order(τ)=∣{k∣Ik∈τ,Ik+1∈τ,¬reach(Ik,Ik+1)}∣, ¬ r e a c h ( I k , I k + 1 ) \neg reach(I_k , I_{k+1}) ¬reach(Ik,Ik+1) 表示taint flow上存在相邻的InstLoc在ICFG上不可达,也就是 I k + 1 I_{k+1} Ik+1 是另一个entry function的第一个InstLoc。
-
Def 6-entry function ϵ \epsilon ϵ 对应的local taint flow集合为 L T ϵ LT_{\epsilon} LTϵ,这也是entry function的污点摘要。
3.3.Summary-Based High-Order Taint Flow Construction
3.3.1.Taint Summary Generation
entry function
ϵ
\epsilon
ϵ 的污点摘要为其污点集合
L
T
ϵ
LT_{\epsilon}
LTϵ。SUTURE通过sink变量组织
L
T
ϵ
LT_{\epsilon}
LTϵ 中的local taint flow - 每个sink变量与一组到达它的local taint flow相关联(将
L
T
ϵ
LT_{\epsilon}
LTϵ 组织成一个 Map[SinkVariable, TaintFlowSet]
),而source变量可以从对应taint flow的taint tag中获取。这使得通过sink进行taint flow的快速查询成为可能,以及用于构建高阶污点流的连接操作。
3.3.2.High-Order Taint Flow Construction
在cross entry taint flow分析时涉及连接两个local taint flow,从原理来说相对简单。然而,为了正确进行连接,下面讨论两个重要的考虑因素。
1.Global Memory Matching
只有当一个taint flow的sink匹配另一个taint flow的source时,两个local taint flow才能被连接。由于高阶taint flow通过全局变量中继,问题就变成了如何匹配一个taint flow中的全局变量污点与另一个流中使用的全局变量。
考虑图3中的示例,e0()
和 e1()
分别将 G.p
(全局变量中的指针字段)指向动态分配的堆对象 obj0
或静态全局变量 F
,而 e2()
和 e3()
直接访问由 G.p
指向的任何对象。在这种情况下,SUTURE必须正确“猜测”4个入口函数中访问的对象之间的关系,以便连接local taint flow。例如,e0()
和 e1()
明显访问不同的对象实例,因此尽管 u0
在 e0()
中流向 G.p->X
,并且看似相同的 G.p->X
在 e1()
中流向 a
,但 u0
无法流入 a
,因为
τ
0
\tau_0
τ0 中的 obj0
(堆对象)无法匹配
τ
1
\tau_1
τ1 中的 F
。然而,由于 e2()
和 e3()
可以访问 obj0
或 F
(取决于 e0()
或 e1()
调用的先后顺序),我们应该允许它们的local taint flow连接到彼此,或者连接到 e0()
和 e1()
中的local taint flow。例如,
τ
3
\tau_3
τ3 可以连接
τ
1
\tau_1
τ1,因为 obj2
可能是 F
。类似地,
τ
3
\tau_3
τ3 也可以连接
τ
4
\tau_4
τ4,因为 obj2
和 obj3
可能相同。
总结一下,当对象在local taint flow中被访问但未定义时(例如,
obj2
和obj3
),SUTURE不一定知道使用了哪些对象(例如,可能是 obj0
或 F
),因此SUTURE创建一个占位的虚拟对象。然而,当连接其它entry function的local taint flow时,这些虚拟对象通过路径匹配进行实例化的(例如,通过G.p
,可以访问 obj2
和 F
,那么 obj2
可以用 F
实例化)。
2.Taint Overwrite
另一个值得讨论的问题是,并非所有的local taint flow都应该被保留用于flow连接。例如,全局变量在 entry function ϵ \epsilon ϵ调用的中间可能被标记为污点,但后来又被重新赋值清除污点标记。同样,它可能在函数中的不同点被不同的来源污染。因此,SUTURE过滤掉那些其后会被覆盖的taint flow。这可能妨碍SUTURE发现一些taint style的并发错误,其中一个taint flow中全局变量的中间污点值可能对另一个可见。这部分作者放到future work处理。
3.4.Opportunistic Path Sensitivity
静态分析可能因为不知道冲突路径约束而遵循不可行路径,导致不准确性和低效性。传统的解决方案是采用路径敏感性,但全路径敏感的分析成本过高。因此,作者提出了“机会路径敏感性分析”的概念,旨在在可能的情况下利用路径敏感性,同时避免高昂的成本。作者的设计基于两个重要观察:
-
1.内核中的许多路径约束都比较简单,收集并对它们进行求解将剪除大量不可行的路径,无需再收集更多复杂路径约束条件。
-
2.有可能将某种形式的路径敏感分析嵌入到流敏感分析的工作流中。
基于上述观察,作者的想法是在流敏感分析期间机会地收集路径约束,即只收集以下简单形式的约束:
v
o
p
C
v op C
vopC,其中 v
是一个变量,C
是一个常数(例如,一个整数),而
o
p
∈
{
=
=
,
>
,
<
,
≥
,
≤
}
op \in \{ ==, \gt, \lt, \ge, \le \}
op∈{==,>,<,≥,≤}。具体而言,每当流敏感分析进入一个条件分支时,SUTURE收集相应的约束,如果它满足这样一个简单的形式。每当分支合并时,SUTURE移除这些约束。乍一看,如果我们以这种方式嵌入流敏感分析,就不允许进行路径敏感性分析,因为在合并点SUTURE失去了对各个分支的约束。
然而,作者注意到在一个分支内部,可能会出现额外的条件语句(过程内或过程间),这使得有可能利用机会主义路径敏感性来修剪不可行的路径。作者在图4中展示了一个实际例子。msm_lsm_ioctl()
在line 3调用了 msm_lsm_ioctl_shared()
,在一个特定的 switch case
中,cmd
限制为 SNDRV_LSM_REG_SND_MODEL_V2
,相同的 cmd
被传递给callee function,并在line 8再次用作 switch
的条件,由于其值已经在调用点(line 3)被约束,实际上在这个调用上下文中,被调用者只有一个有效的 switch case
(line 9)。机会主义路径敏感性分析可以收集 cmd
在line 3的等式约束,并将其传播到caller。这使得我们能够通过对冲突约束进行过滤,在同时提高准确性和效率的情况下剔除17个中的16个不可行的 switch case
。
3.5.Multi-Source Multi-Sink Pairing
作者在研究中识别到的静态分析的一个独特挑战是多source多sink匹配问题。如果不正确处理,point-to和taint fact可能会爆炸,导致大量的误报。图5用一个具体的例子说明了这个问题。当开始分析函数 start_endpoints()
时,参数 subs
是一个指针,根据先前的静态分析结果,它指向 snd_usb_substream
类的两个变量。因此,在line2的赋值的左侧可以是两个变量之一(即 snd_usb_endpoint
的 data_subs
字段,即实例0或1),而右侧的subs指向 snd_usb_substream
的实例0或1。在这种情况下,分析赋值的常见方法(许多流行的静态分析工具如Dr. Checker和SVF中使用的方法)是all-to-all(例如,snd_usb_endpoint 0
的 data_subs
字段将指向 snd_usb_substream 0
和1
,这是因为每个语句的分析都是独立运行的)。然而,显然在实际程序执行中,data_subs
只能指回自己的父 snd_usb_substream
实例(例如,0指向0,1指向1)。
为了解决这个问题,作者的key observation是在前面提到的场景中,赋值的两侧实际上共享相同的source of multiplicity(例如,line 2的左侧 ep0->data_subs
有两个可能的位置,因为 ep0
可以指向两个结构体实例,这同样是因为 subs
在line 1的原因,右侧的原因也是相同的),因此,只要在运行时唯一的source“折叠”为众多可能性之一,赋值的两侧也会“折叠”。
根据这一观察,对于每个LLVM IR,它都可以作为“source of multiplicity”,例如,phi
指令可以将来自不同路径的多个指向/污点记录聚合到其接收变量中,SUTURE为每个单独的结果记录分配一个唯一的标签
<
I
R
,
i
>
<IR, i>
<IR,i>,该标签也将传播到所有派生记录。例如,在图5中,指针 ep0
是从 subs
派生出来的,后者的两个 snd_usb_substream
0和1的指向记录分别被继承到ep0的两个 snd_usb_endpoint
0和1的记录中。通过匹配这些标签,当多对多赋值发生时(例如,line 2),SUTURE可以精确地将source和sink配对起来,如果它们共享相同的source of multiplicity,这样在图5中的 2∗2 更新会变成两个 1∗1 的更新。
3.6.Other Improvements in SUTURE
3.6.1.Memory SSA based Analysis
Dr.Checker没解决的一个问题是缺乏SSA(静态单赋值)。虽然事实上LLVM IR的top-level variable已经是SSA形式,比如line 6和line 9的 r += c
和 r -= c
的结果会放在两个不同的top-level variable %add
和 %sub
中。但对于结构体变量的字段访问(line 7和line 10对 d.a
的访问)依旧不是SSA。会导致流不敏感和上下文不敏感性。
为了解决这个问题,SUTURE实现了即时(on-the-fly)SSA分析。具体而言,SUTURE在每个内存单元的指向/污点更新后附加一个 InstLoc,以表示更新发生的位置(例如,在图6中的一个 store
指令后附上 foo
及相关context)。基于这样的信息,SUTURE可以正确地确定哪些指向/污点记录应该传播到相同内存单元的某个使用点。
3.6.2.Index Sensitivity
作者基于下面原则实现索引敏感性:
-
(1) 如果使用常量索引读取数组元素(例如,
v = a[2]
),SUTURE返回与确切索引相关的指向/污点fact;如果索引是一个变量(例如,v = a[i]
),SUTURE保守地合并所有数组元素的fact并返回它们。 -
(2) 如果使用常量索引写入数组元素(例如,
a[2] = v
),SUTURE对确切的索引执行强更新(即,新fact覆盖旧fact);如果索引是一个变量(例如,a[i] = v
),SUTURE保守地弱更新每个数组元素(即,新fact与旧fact共存)。
3.6.3.General Language Feature Support
1.嵌套结构体
嵌套结构体(即,一个结构体嵌入到另一个结构体的字段中)是一种广泛使用的语言特性,Dr. Checker只区分父结构中的顶层字段,而不区分嵌套结构中的字段,这可能导致over-taint等问题。
SUTURE通过在访问嵌套字段时递归创建新的抽象内存对象来解决这个问题,同时保持新对象与父对象之间的关系,这样,SUTURE支持任意层次的嵌套结构。作者还精心设计了在指向和污点分析中处理LLVM IR的逻辑,以考虑嵌套结构,例如,SUTURE处理 GEP
指令的所有索引而不仅仅是前两个。
2.指针运算
众所周知,C语言家族中的指针运算经常会增加静态分析难度,因为在算术计算过程中很难精确跟踪指针位置,例如,通常,LLVM IR通过在 GEP
指令中使用结构体指针直接指定字段2来访问结构的第二个字段,然而,在某些情况下(例如,优化),可以通过直接从指向第5个字段的指针中减去偏移量(在第2个和第5个字段之间)来访问该字段。为了处理这种情况,SUTURE记录了每个结构的详细布局(例如,每个字段的大小和偏移量,以字节为单位),并根据指针类型(例如,指针转换感知,如(char*)p-1
和 (int32*)p-1
是不同的)。值得注意的是,SUTURE指针运算处理具有字节级的准确性,而且作者总是尝试对齐到字段边界,尽管很少见,这可能导致一些不准确性(例如,指向字段中间的指针,或位级指针算术),作者将在未来的工作处理这些情况。
3.6.4.Kernel Code Pattern Handling
为了更好地支持内核代码的分析,SUTURE还处理了一些特殊的内核代码模式,其中最重要的是普遍存在的间接调用。Dr. Checker使用纯类型匹配的方法来解析间接调用的目标,尽管这是一种常见且标准的解决方案,但可能导致许多误报。为了进一步提高准确性,SUTURE采用类似于PeX的方法,利用内核编码范例的领域知识(结构体层次),并通过匹配函数指针的父结构体和字段ID来解析间接调用的目标。
3.6.5.Multi-Tag Taint Analysis
为了构建高阶污点流,SUTURE必须能够区分多个taint source,例如,在图1中,我们必须确切地知道 entry1()
的local taint flow源自特定的全局变量 d.b[0]
,以便将其连接到 entry0()
的local taint flow。因此,SUTURE不仅维护了基于二进制的污点状态(tainted / untainted),还为taint source中的每个变量关联了一个唯一的污点标签,该标签也将传播到由相关源受污染的所有变量,从而使SUTURE能够轻松地查询每个taint flow的source变量。
4.Vulnerability Discovery And Warning Group
在给每个entry function ϵ \epsilon ϵ 生成taint摘要(local taint flow集合 L T ϵ LT_{\epsilon} LTϵ),SUTURE随即进行高阶污点漏洞分析并生成相应报告。这个过程包括两个步骤:
-
(1) 确定可能触发漏洞的指令(例如,算术指令可能导致整数溢出),这是通过各种漏洞检测规则完成的。对于每个被识别的指令,SUTURE通过判断是否涉及的变量受到了用户输入的污染(可以通过高阶local taint flow)来确认漏洞的存在;
-
(2) 为确认的漏洞发出和分组警告。
4.1.Vulnerability Detectors
作者沿用了Dr.Checker的4种漏洞检测规则,如表1所示
4.2.On-Demand Query of Taint Summaries
传统漏洞检测方式先考虑所有entry function的排列组合来构建高阶taint flow summary,但这开销过大。相反,SUTURE按需以逆向方式构建高阶taint flow。它以taint flow的sink变量为输入,以相关联的从用户输入开始到sink变量的taint flow为输出。SUTURE首先通过查看其local taint,在每个单独的entry function上应用漏洞检测器(首先假设全局变量为taint)。如果生成了警告,SUTURE检查其是否受到用户输入影响。
值得注意的是,尽管SUTURE能够通过递归执行逆向查询来发现任意阶的漏洞,但实际上很难发现真正高阶的漏洞(如果超过4阶,则很可能是误报)。事实上,实验显示,大多数漏洞都是2阶的。因此,当查询花费的时间过长时,SUTURE将停止搜索高于4阶的污点流。
4.3.Warning Grouping
在告警审查过程中作者观察到的一个重要发现是,许多告警在初始污点传播阶段通常共享相同的“前缀”,而在最终的告警位置或污点sink点上有轻微的差异。例如,a
是一个整数溢出语句 c = a + b
中的被标记为污点变量,而 c
,它受到污点 a
影响,然后立即被用作循环上界,这种情况下会为溢出和循环上界分别生成两个告警。显然,与其逐个审查这两个警告,更好的方法是首先检查从原始用户输入到 a
的共同前缀轨迹,如果没有问题,然后再查看不同的sink点(通常在彼此附近)。
SUTURE从数据流的角度将相似的警告分组在一起,而不考虑表1中列出的警告类型(即,不同类型的告警可以放在同一组中)。更具体地说,如果两个告警满足以下条件,它们将被分为同一组:
-
1.它们的告警位置位于同一函数
f
中; -
2.它们的污点传播追踪从函数
f
的入口开始共享相同的轨迹。
5.Implementation
-
LLVM Version:与Dr.Checker基于LLVM 3.8实现相比,SUTURE基于LLVM 9.0实现。
-
Driver Module and Entry Function Identification:这一步大部分与Dr.Checker采用一样的策略。不过作者依旧做了一些调整,包括:
- (1)除了通过关键字搜索在内核配置文件中识别的模块之外,还查看了内核源代码树,以覆盖任何缺失的模块;
- (2)更新了Dr. Checker的入口识别脚本中一些过时的内核结构定义,并覆盖了一些遗漏的
ioctl()
函数。
-
False Alarms Filtering:作者使用一些简单但可靠的启发式方法来过滤掉一些明显的误报。具体而言
-
(1)如果污点传播涉及模运算,且模数是一个小整数(当前阈值为 <= 64),不进行污点传播。
-
(2)如果污点传播涉及逻辑“与”操作,且与运算位数有限(当前阈值为6),不进行污点传播。
-
6.Evaluation
Dataset:
SUTURE的漏洞检测性能如表4所示,总共生成1603个告警组,其中79个经过手动验证确认为true positive。
SUTURE的false alarm来源包括:
- 1.污点变量约束的缺失:
mtk_session_set_mode()
的参数session_mode
是用户可控的,然后被用于索引line 6的数组mode_tb
。因此,SUTURE 确定整个检索到的数组元素mode_tb[session_mode]
是被标记为污点。然而,session_mode
在line 2进行了上界检查,数组mode_tb
也是预定义的,因此用户实际上不能真正控制所获得的数组元素的内容。因此,从mode_tb[session_mode]
继续进行污点传播随后导致了误报。到目前为止,约束丢失是 SUTURE 产生误报的最常见原因,也是许多其他基于静态分析的错误检测工作的原因。
-
2.递归数据结构:递归数据结构(例如链表)是静态分析的另一个困难之处,因为在静态情况下很难区分它们包含的元素。为了保守起见,SUTURE 不区分链表中的元素,而这在内核中是广泛使用的。比如,taint flow的sink点来源于链表第0个元素,另一个taint flow source点来源于链表第1个元素,这2者本身是无法连接,但SUTURE还是错误地连接了。
-
3.不可行的路径:SUTURE依旧有可能分析了不可行路径,原因包括:(1).SUTURE的间接分析调用出现误报以及在路径敏感分析中未能识别冲突的约束条件。不过这对误报的影响不大。
7.Limitation And Discussion
-
Soundness:SUTURE 的静态分析并不完备(例如,没有固定点循环分析,调用堆栈深度有限)。尽管作者牺牲了完备性以获得类似于 Dr. Checker 中的更好性能和准确性,但这可能导致漏报,无法发现潜在的漏洞。提高完备性的一种可能方式是在构建每个entry function的taint摘要时采用自底向上的静态分析风格(即在调用者之前分析和总结被调用者),这通过提高效率(例如,不会反复分析相同的被调用者)减轻了由于调用堆栈深度受限的问题。作者将这留待将来的工作。
-
Recursive Data Structure Handling:SUTURE 在递归数据结构(例如,链表)中不区分元素的设计选择是为了保守性和简化访问路径(以便于缓解涉及递归结构的全局对象匹配的复杂性,例如,从一个链表元素到另一个链表元素可能存在大量访问路径)。然而,这种设计选择在处理递归数据结构时也在很大程度上导致了误报。
-
Path Constraints Reasoning:SUTURE考虑了简单路径约束以过滤出不可行路径之外,并未考虑与可行taint flow相关的路径约束,导致大部分误报。在先前的工作中存在针对这个问题的解决方案包括Sys和UBITech利用有限规模的符号执行验证发现的漏洞,而KINT则仔细研究了整数溢出漏洞的特定约束。