Dr.Checker-内核驱动静态分析工具

作者来自University of California Santa Barbara - SecLab,在系统安全方面的工作还包括:DiFuseKaronte

1.Introduction

这篇工作提出了Dr.Checker静态分析工具,用于检测Linux内核驱动程序中的错误。

作者的motivation包括:

  • 内核驱动程序长期以来一直被认为具有重大安全风险,因为它们具有特殊访问权限且由第三方开发代码质量较低。

  • 用于检测这些驱动程序中错误的工具在数量上和效果上都严重不足。

  • 内核驱动程序中的指针密集型(即函数和参数经常作为指针传递)代码给静态分析带来了一些挑战,而且这些驱动程序与硬件紧密耦合,使得动态分析在大多数情况下不可行(例如处理硬件外设)。

研究表明,在linux kernel中由驱动程序导致的错误比其它核心代码多7倍 [ 2 ] ^{[2]} [2],并且Windows XP中的崩溃有85%是由于驱动程序引起的 [ 3 ] ^{[3]} [3]。这些惊人的数字被认为是由于驱动程序的整体代码质量较低,这些代码提供驱动程序的第三方编写。

在2011年,Palix等人的一项研究 [ 4 ] ^{[4]} [4] 分析了Linux内核,结果显示尽管驱动程序仍然是包含最多bug的部分,这很可能是因为驱动程序占据了内核总代码量的57%,但驱动程序的错误率已不再是最高。最近对主线Linux内核提交信息的分析发现,在过去一年中,针对Linux仓库CVE补丁中有28%涉及到了内核驱动程序(自2005年以来占比为19%),这一结果与先前的研究 [ 5 ] ^{[5]} [5] 相吻合。

同时,移动领域近年来出现了大量新设备以及相应的新型驱动程序。然而,这些驱动程序缺乏足够的关注,它们对设备安全性可能带来的潜在威胁也并未被忽视。近期的研究甚至表明,在Android内核 [ 6 ] ^{[6]} [6] 中报告的bug中,高达85%来源于移动内核驱动程序。尽管如此,目前尚未出现针对此类驱动程序的大规模分析研究。

因此作者提出了Dr.Checker工具,通过将分析范围限制在内核中最容易出错的部分(即驱动程序部分),以及在极少数情况下牺牲准确性以确保技术既可扩展又精确,Dr.Checker克服了许多静态分析固有的局限性。

Dr.Checker是一个模块化的工具,其设计使得开发人员能轻松扩展分析算法(例如指针分析或污点传播分析)和bug检测器(如整数溢出检测或内存损坏检测)。该工具基于常用的程序分析技术构建,能够执行流敏感、上下文敏感以及字段敏感的指针和污点分析。

Dr.Checker为了提高精确度而待分析的内核驱动代码进行了假设,从而牺牲了部分可靠性。但依旧能够大规模分析驱动代码中各类常见错误。在分析9个流行移动设备内核共310万行代码(LOC)的过程中,Dr.Checker正确报告了3,973个缺陷,并发现了158个之前未被发现的bug。

总的来说,Dr.Checker依旧是一个传统的IFDS工具,只是针对kernel环境进行了一些策略上的调整。

2.Motivation

下图展示了一个由Dr.Checker发现的real-world bug。在这个bug中,用户空间中的一个被污染的变量 argp 通过 copy_from_user 函数复制到内核空间的结构体变量 flow_p。该结构体中的一个 cnt 字段随后被用于计算另一个驱动程序数组 buf 的所需内存大小,具体表现为将该字段值乘以 struct bst_traffic_flow_prop 结构体的大小,这一步操作存在整数溢出的风险。

如果发生整数溢出,实际分配的缓冲区大小将会远小于实际需要的数据量。在这种情况下,如果仅是溢出本身,可能不会造成特别严重的问题。然而,关键在于,buf_len 最终被用作决定要在缓冲区中复制多少数据(即 adjust_traffic_flow_by_pkg(buf, flow_p.cnt) ),这最终导致了内存破坏。由于 buf 长度不足,多余的数据会溢出缓冲区范围,从而引发潜在的安全漏洞或程序崩溃。Dr.Checker能够检测到这类复杂、隐蔽且可能导致严重后果的编程错误。

在这里插入图片描述

简单静态分析算法在检测该bug时面临下面挑战:

  • 1.复杂数据流问题:该bug源于受污染数据(argp)通过多个usage传播至危险函数中。这要求进行流敏感分析才能识别,因为普通静态分析技术可能无法追踪这种数据依赖关系。

  • 2.字段级精确度挑战:整数溢出并非由整个 flow_p 变量引起,而是 flow_p 的特定字段 cnt 导致。非字段敏感的分析工具会对此类问题过拟合,并错误地认为flow_p 是引发bug的原因。

  • 3.上下文相关调用处理:内存损坏实际出现在另一个函数 adjust_traffic_flow_by_pkg 中,意味着分析工具需要以上下文敏感的方式来处理过程间调用,以便准确报告污染数据的源头。

综上所述,要精确检测和此bug,需要采用一种同时具备流敏感性上下文敏感性字段敏感性的分析方法。此外,这个bug存在于一款流行移动设备的驱动程序中,表明它成功规避了代码审计专家以及其它bug检测工具的审查。

3.Analysis Design

Dr.Checker的流程如图1所示,采用遍历的方式对代码进行分析。遍历的过程中调用analysis client进行分析(指针分析、污点分析)。所有analysis client共享一个global state,并会根据分析结果修改global state。修改global state之后,系统会调用漏洞检测器进行漏洞检测。

请添加图片描述
在分析过程中,作者对kernel code作出以下假设:

  • 1.假设主流linux core都完美地遵循相应编程规范。同时Dr.Checker不会对kernel API call进行过程间展开分析。

  • 2.只针对循环结构执行到达-定义(reach-def)分析所需的遍历次数。这种策略可能会导致指针分析不够可靠(unsound)。

  • 3.对于循环结构中的每个call指令,仅遍历一次。这样做的目的是为了避免创建过多的context,并最大限度地减少误报,尽管这可能导致分析不够精确。

3.1.Terminology and Definitions

这里常用的术语就不列出来了。

  • Def 3.4 entry function: ϵ \epsilon ϵ 是至少有1个参数是污点变量的函数(ioctl 函数)。

  • Def 3.5 context: △ \triangle 是一个有序列表,包含了从入口函数开始的所有调用点(例如栈上的函数调用)。这个列表详细表示了为了到达给定函数所需经过的一系列函数调用及其在代码中的位置顺序。假设 △ = { ϵ , c 1 , c 2 , . . . } \triangle = \{\epsilon, c_1, c_2, ...\} ={ϵ,c1,c2,...},其中 ϵ \epsilon ϵ 代表入口函数, c 1 c_1 c1 是从入口函数内部进行的第一次调用。对于所有 i > 1 i \gt 1 i>1 的情况, c i c_i ci 是在与前一个调用指令( c i − 1 c_{i-1} ci1)相关的函数内执行的调用指令。

  • Def 3.6 global taint trace map: τ \tau τ 包含了关于在分析过程中遇到的受污染的变量(值)的信息。它将特定的变量(值)映射到一系列指令(I),这些指令在执行之后导致了该变量(值)变为污点状态。

τ : { v → { I 1 , I 2 , I 3 , …   } if TAINTED v → ∅ otherwise \tau: \left\{ \begin{array}{ll} v \rightarrow \{I_1, I_2, I_3, \dots\} & \text{if TAINTED} \\ v \rightarrow \empty & \text{otherwise} \end{array} \right. τ:{v{I1,I2,I3,}vif TAINTEDotherwise

  • Def 3.7 alias object: α ^ = { ρ , t } \hat\alpha = \{\rho, t\} α^={ρ,t},由一个offset map ρ \rho ρ 和一个local taint map t t t 组成。 ρ : n → { α ^ 1 , α ^ 2 , α ^ 3 , . . . } \rho : n \rightarrow \{ \hat\alpha_1, \hat\alpha_2, \hat\alpha_3, ... \} ρ:n{α^1,α^2,α^3,...} 将当前object的offset n n n 映射成可能存在别名关系的其它object, t : n → { I 1 , I 2 , I 3 , . . . } t : n \rightarrow \{I_1, I_2, I_3, ... \} t:n{I1,I2,I3,...} 将当前object的offset n n n 映射成对应的导致其被污点化的指令序列。在分析时:

    • 函数局部变量(或栈上位置):对于每一个局部变量,Dr.Check都为其维护一个alias object。这样可以追踪每个局部变量在程序执行过程中的变化和与其他变量的关联。

    • 动态分配变量(或堆上位置):这些是指在程序堆上动态分配的内存位置,例如通过 mallocget page 等函数获取的内存区域。对于每一次内存分配操作点,Dr.Checker维护一个对应的alias object,以便跟踪从该分配点开始的所有内存块。

    • 全局变量:每个全局变量都会被赋予一个唯一的alias object。

栈和堆上的存储位置都是上下文相关的(也就是说,当一个函数在不同上下文中被多次调用时,会创建不同的alias object)。此外,由于采用了上下文传播技术,堆上的存储位置还表现出调用点敏感性(即对于给定的某个上下文,在每个内存分配函数的调用点上,都会为该上下文创建一个独立的alias object)。

  • Def 3.8 pointer map: ϕ \phi ϕ,将一个变量(值)与该变量可能指向的所有可能位置关联起来,这些位置通过包含alias object及其在相应对象内的偏移量的元组集合来表示。 ϕ : v → { ( n 1 ​ , α ^ 1 ​ ) , ( n 1 ​ , α ^ 2 ​ ) , ( n 2 ​ , α ^ 3 ​ ) , … } \phi : v \rightarrow \{(n_1​,\hat\alpha_1​), (n_1​,\hat\alpha_2​), (n_2​,\hat\alpha_3​),…\} ϕ:v{(n1,α^1),(n1,α^2),(n2,α^3),}。例如,考虑以下指令:val1 = &info->dirmap,其中 info 表示栈上的一个结构体,其成员变量 dirmap 偏移量为8。这条指令执行后,值(val1)将指向别名对象 info 内的偏移量8(即 ϕ ( v a l 1 ) = { ( 8 , i n f o ) } \phi(val_1) = \{(8, info)\} ϕ(val1)={(8,info)})。

  • Def 3.9 global state: S = { ϕ c , τ c } S = \{\phi_c, \tau_c\} S={ϕc,τc},包含了所有函数在每个上下文下的所有计算信息。其中, ϕ c : △ → ϕ \phi_c : \triangle → \phi ϕc:ϕ 是映射每个上下文到相应pointer map的映射,而 τ c : △ → τ \tau_c : \triangle → \tau τc:τ 则是映射每个上下文到相应taint trace的映射。

3.2.Soundy Driver Traversal (SDT)

SDT部分采用了IFDS框架实现。

在大多数现有的静态分析技术中,它们通常会在执行bug检测之前进行抽象分析直至达到fix point。然而,当运行多个不同的分析时,由于不同抽象分析的精度可能存在差异,按照这种模式进行可能会出现问题。若在所有分析完成后处理结果,则会将所有分析的精度限制在最不精确的分析级别。为了避免这种情况,并确保所有分析模块的精度最大化,Dr.Checker从入口点开始对驱动程序进行了流敏感和上下文敏感的遍历。特定分析模块(例如污点传播分析和指针分析)在这个框架中被实现为analysis client,在遍历代码的过程中,会根据相应的context和当前global state调用这些客户端。这样的设计使得所有的analysis client能够在需要的时候互相利用彼此的结果,且不会损失精度。此外,这也使Dr.Checker能够对底层的所有analysis client仅进行一次程序遍历。

值得注意的是,不同的analysis client可能需要遍历CFG不同次数来到达固定点。例如,指针分析可能相比污点分析需要更多遍历循环次数。这意味着Dr.Checker必须按照最多遍历次数进行。

为了保证这一特性,Dr.Checker使用了reach-def的迭代次数作为遍历次数。这可以确保所有能直接到达指令的写操作都会被覆盖到。这意味着指针分析可能无法收敛,因为它相比reach-def分析可能需要更多的迭代次数。但在最坏情况下,指针分析可能会无限增长,导致所有内容指向所有内容。因此,为了确保收敛性和实现上的可行性,Dr.Checker在一定程度上牺牲了精确性。

3.2.1.Loop

在处理循环时,Dr.Checker必须确保遍历循环足够多次,以确保所有变量的所有可能赋值都得到了充分的分析。因此,Dr.Checker需要计算reach-def分析在循环上达到固定点所需的迭代次数,并随后在该循环内的所有基本块上执行相应次数的迭代。

需要注意的是,对于标准的reach-def分析而言,要在循环上收敛所需的迭代次数有一个上限,即循环中最长的use-def链(即从变量被赋值到其被使用的指令之间的最长时间跨度)。这种直觉背后的原因是,在最坏情况下,循环中的每条指令都有可能依赖于use-def链中的变量,这意味着它们的潜在值可能会在每次循环迭代中更新。然而,这种情况最多只能发生与指令数量相等的次数,因为每个指令中变量的赋值操作至多只能发生一次。

请添加图片描述

3.2.2.Function Call

如果函数调用是直接调用,并且目标函数位于正在分析的代码范围之内(即它是驱动程序的一部分),那么将会使用新的上下文( △ n e w \triangle_{new} new,应该是在当前context下添加一个call指令)对其进行遍历。同时,state会更新新的point-to map ρ n e w \rho_{new} ρnew 和新的local taint map τ n e w \tau_{new} τnew,这些信息包含了函数参数以及全局变量的信息。

对于间接函数调用(即通过指针调用的函数),Dr.Checker采用基于function type match方法进行求解。也就是说,给定一个函数指针类型 a = (rettype)(arg1Type, arg2Type,..),Dr.Checker会查找同一驱动程序中所有函数前面匹配的address-taken function(例如 void *ptr = &fnfn 为address-taken function)。这一过程在通过 resolve_call 实现。

每个调用站点或调用指令在每个上下文中只会被分析一次。对于递归函数,Dr.Checker并未专门设置处理程序,因为在内核驱动中很少使用递归功能。

请添加图片描述

3.2.3.Complete Analysis

SDTraversal的算法如下图所示,首先,通过拓扑排序函数的CFG,得到一个有序的强连通分量(SCC)列表。接下来,对于每个SCC,根据其是否为循环结构进行不同的处理。每一个SCC都在basic-block级别上进行遍历,在此过程中,basic-block内的每条指令都会连同context和global state一起提供给所有可能的analysis client分析(例如污点传播分析和指针分析)。

client能够在global state中收集并维护任何所需的信息,并且这些信息能够立即对其它client可见,从而实现不同分析模块之间的信息共享和利用。通过这样的设计,确保了在整个遍历过程中,所有分析模块都能够以最高精度、最有效的方式进行工作,并充分利用其他模块计算出的结果。

请添加图片描述

在一个驱动程序的入口点 ϵ \epsilon ϵ,Dr.Checker首先创建一个初始状态: S s t a r t = { ϕ s t a r t , ∅ } S_{start} = \{ \phi_{start}, \empty \} Sstart={ϕstart,},其中 ϕ s t a r t \phi_{start} ϕstart 包含所有全局变量的point-to map。接着,遍历该驱动的所有 .init 函数(即负责驱动初始化的函数),这些函数通常会初始化大部分全局对象。随后将由此得到的初始化状态( S i n i t S_{init} Sinit)与任何受污点影响的参数的taint map合并( S i n i t = S i n i t ∪ τ i n i t S_{init} = S_{init} \cup \tau_{init} Sinit=Sinitτinit)。关于如何确定这些受污染的参数,作者在第5.3节进行详细描述。

最后,在这个初始化后的状态下调用 S D T r a v e r s a l ( S i n i t , { e } , ϵ ) SDTraversal(S_{init}, \{e\}, \epsilon) SDTraversal(Sinit,{e},ϵ),从而开始对驱动程序进行分析。

analysis client在遍历CFG的过程中通过transfer function进行分析,作者针对下面5种指令设置transfer function:

  • Alloca 指令:v = alloca typename 在栈上分配一个 typename类型的变量,并将location分配给 v(例如,%1 = alloca i32)。

  • BinOp 操作:v = op op1, op2op1op2 应用二元操作 op,并将结果分配给 v(例如,%1 = add val, 4)。op 包括flow-merging的phi指令。

  • Load 操作:v = load typename op 从由操作数 op 表示的地址加载类型 typename 的内容到变量 v 中(例如,%tmp1 = load i32* %tmp)。

  • Store 操作:store typename v, op 将值 v 表示的类型 typename 的内容存储到由 op 表示的地址中(例如,store i8 %frombool1, %y.addr)。

  • GetElementPtr 操作:表示结构体和数组的访问的指令,一种简化的表示方式是 v = getelementptr typename ob, off,这将从类型为 typename 的对象 ob 中索引为 off 的值存储在 v中(例如,%val = getelementptr %struct.point %mypoint,0)。

3.3.pointer analysis

指针分析的结果是一个由 (value, Set[Object]) 组成的列表,其中 value 表示变量, Set[Object] 表示有别名关系的变量。

针对上述5种指令指针分析client的transfer function如下,其中 δ \delta δ 对应的是当前的context,可以看到和Andersen算法差别不大。主要差别在于对binary运算指令 v = o p 1    o p    o p 2 v = op_1 \; op \; op_2 v=op1opop2 也进行了建模。同样只有在 alloca指令会引入新的object ( m a p p t ( v ) ← ( 0 , l o c x ) map_{pt}(v) \leftarrow (0, loc_x) mappt(v)(0,locx))。

请添加图片描述

除了上述5种指令,Dr.Checker还考虑下面两种情况:

  • Dynamic allocation:为了处理动态分配,Dr.Checker维护一个用于在堆上分配内存的内核函数列表(例如,kmallockmem_cache_allocget_free_page)。对于这些函数的每个调用点,Dr.Checker为其创建一个唯一的alias object。因此,每个dynamic allocation指令在一个给定context △ = { ϵ , c 1 , c 2 , . . . } \triangle = \{\epsilon, c_1, c_2, ...\} ={ϵ,c1,c2,...} 下都有一个单独的alias location。例如,如果在循环的基本块中调用了 kmalloc,只会为其创建一个alias location。

  • Internal kernel functions:除了少数几个内核API函数(analysis client可以轻松处理其影响,例如 memcpystrcpymemset)之外,作者忽略所有其他内核API和核心内核函数。例如,如果 call 指令的调用目标是 i2c_master_send,Dr.Checker不会深入分析这个调用。与其他研究不同,Dr.Checker假设所有对这些函数的使用都是有效的,因为作者只关心分析更容易出错的驱动程序代码。因此,Dr.Checker不会跟踪任何调用进入核心内核代码的函数。虽然由于这一点Dr.Checker可能会错过一些指针信息,再次牺牲了完备性,但这个假设允许Dr.Checker在驱动程序内部更精确地进行分析并提高分析的可扩展性。

3.4.Taint Analysis

污点分析client的transfer function如下:

请添加图片描述
在Dr.Checker的污点分析算法中污点的源头是entry function的参数,同时 copy_from_usersimple write to buffer 等都被视为污点源,并对这些函数的目标操作数的alias location中的所有字段进行污点标记。与指针分析类似,除了一些例外情况(例如 memcpy)外,Dr.Checker不对任何核心内核函数调用进行污点传播分析。Dr.Checker污点分析中的sink点依赖于特定漏洞检测器,因为每个检测器都有自己的污点策略。如果任何受被污点标记违反指定的策略(例如,如果一个被污点标记的值用作 memcpy中的长度),这些检测器将发出警告。

4.Vulnerability Detectors

Dr.Checker定义了以下漏洞检测器:

  • 1.Improper Tainted-Data Use Detector (ITDUD):ITDUD检查是否在高危函数中使用被污点标记的数据(高危函数即,strc*strt*sscanfkstrto 和简单的 strto 系列函数)。Listing2是通过ITDUD检测到的先前未知的缓冲区溢出的示例。

请添加图片描述

  • 2.Tainted Arithmetic Detector (TAD):TAD检查在可能导致算数运算溢出或下溢的操作中使用的受污染数据(例如,addsubmul)。Listing3是TAD检测到的一个零日漏洞的示例。

请添加图片描述

  • 3.Invalid Cast Detector (ICD):ICD检查是否发生了到不同大小的对象之间的强制类型转换。

  • 4.Tainted Loop Bound Detector (TLBD):TLBD检查循环边界条件的数据是否被污点标记。这些漏洞可能导致拒绝服务甚至是任意内存写入。Listing3中的示例展示了一个相关real-wrold漏洞,该漏洞也触发了TAD。

  • 5.Tainted Pointer Dereference Detector (TPDD):检测被污点标记的指针。

  • 6.Tainted Size Detector (TSD):TSD检查任何 copy_to_copy_from_ 函数中的 size 相关参数被污点标记的情况。这类漏洞可能导致信息泄漏或缓冲区溢出,因为受污染的大小参数可用于控制复制的字节数。

  • 7.Uninit Leak Detector (ULD):ULD跟踪被初始化的对象,并在任何用户空间copy function(例如,copy_to_user)的 src 指针可能指向任何未初始化对象时发出警告。它还检测带有填充的结构体变量,如果相应的对象上没有调用 memsetkzalloc,则会发出警告,因为这可能导致信息泄漏。以下是此检测器检测到的一个先前未知的漏洞的示例,如Listing4所示。

请添加图片描述

  • 8.Global Variable Race Detector (GVRD):GVRD检查在没有互斥锁的情况下访问的全局变量。由于内核是可重入的,未经同步地访问全局变量可能导致竞争条件,从而可能引发检查时用时漏洞(TOCTOU)。

5.Implementation

Dr.Checker基于LLVM 3.8实现,为1个LLVM pass。输入包括:一个bitcode文件、一个entry function name和一个entry function type。然后,它开始运行SDTraversal,利用各种分析引擎和漏洞检测器。根据entry function type,Dr.Checker会在调用SDTraversal之前对入口函数的某些参数进行污点标记。

由于Dr.Checker的分析操作建立在LLVM bitcode上,Dr.Checker必须首先识别并编译给定内核的所有驱动程序的bitcode(第5.1节)。类似地,Dr.Checker必须识别这些驱动程序中的所有入口点(第5.2节),以便将它们传递给SDTraversal分析。

5.1.Identifying Vendor Drivers

首先需要从内核代码中区分驱动程序源代码文件核心内核代码。不幸的是,在各种kernel source tree中,没有标准的用于存放驱动程序代码的文件位置。更有甚者,一些驱动程序源文件名称省略了供应商的版权信息,有些供应商甚至直接修改了现有的源代码以实现他们自己的功能。

因此,作者采用下面步骤识别source tree中供应商驱动程序的位置。

  • 首先,作者与mainline sources进行 diff 分析,并将那些文件与引用供应商的配置选项进行比较,以搜索包含供应商名称的文件名。幸运的是,每个供应商都有一个code-name在所有的选项和大多数文件中都使用(例如,高通的配置选项包含字符串 MSM,联发科是 MTK,华为可以是 HISIHUAWEI),这有助于Dr.Checker识别各种供应商的选项和文件名。我们为所有供应商执行此操作,并保存相对于source-tree的驱动程序位置。

  • 确定了驱动程序文件位置后,作者使用clang将它们编译成32位和64位的ARM bitcode文件。这需要对clang进行一些非常规的修改,因为Linux内核使用了许多(GCC)选项,而这些选项在clang中不受支持(例如,各种Android供应商使用的 -fno-var-tracking-assignments-Wno-unused-but-set-variable 选项)。作者还向clang添加了额外的编译器选项(例如,-target)以协助分析。实际上,使用LLVM编译Linux内核仍然需要大量努力。

  • 最后,对于每个驱动程序,作者使用 llvm-link 将所有相关的供应商文件链接成一个单独的bitcode文件,从而为每个驱动程序生成一个自包含的bitcode文件。

5.2.Driver Entry Points

Linux内核驱动程序与用户空间程序进行交互的方式有三种,分别是文件(file)[20]、属性(attribute)和套接字(socket)。

1.文件操作

文件操作是与用户空间进行交互的最常见方式。在这种情况下,驱动程序在已知目录下(例如,/dev/sys/proc)新建一个文件,用于通信。在初始化过程中,驱动程序通过初始化相关结构体变量的函数指针来指定各种操作要调用的函数,该结构体将用于处理特定的操作(例如,readwriteioctl)。用于初始化的结构体对于每种驱动程序类型都可能不同。

实际上,在Android内核中至少有86种不同类型的结构(例如,struct snd_pcm_opsstruct file_operationsstruct watchdog_ops)。更糟糕的是,入口函数在这些结构的每个不同偏移处可能存在。例如,ioctl 函数指针在 struct snd_pcm_ops 中位于字段2,而在 struct file_operations 中位于字段8。即使对于相同的结构体,不同的内核可能以不同的方式实现相关字段,这导致了每个内核的入口函数位置不同。例如,在联发科的mt8163内核上,struct file_operationsioctl 函数在字段11处,而在华为上,它出现在结构的字段9处。

为了自动化处理这些不规则性,作者使用了c2xml来解析每个内核的头文件,并找到这些结构体中可能的入口函数字段(例如,readwrite)的偏移。随后,对于给定的驱动程序的bitcode文件,Dr.Checker找到被初始化的不同文件操作结构体,并识别用于初始化不同entry function的函数。

2.属性操作

属性操作通常由驱动程序公开,用于读取或写入该驱动程序的某些属性。读取或写入的数据的最大大小限制为内存中的单个页面。

3.套接字操作

套接字操作由驱动程序以套接字文件的形式公开,通常是UNIX套接字,用于通过各种套接字操作(例如,sendrecvioctl)与用户空间进行通信。

还有其他一些驱动程序,在其中内核实现了一个main wrapper function,该函数在调用相应的驱动程序函数之前对用户参数进行初始验证并部分清理它们。V4L2 Framework中的一个例子可以看到这种情况,该框架用于视频驱动程序。在Dr.Checker的实现中,作者只考虑可以通过wrapper function video_ioctl2 由用户空间调用的结构体 struct v4l2_ioctl_ops

5.3.Tainting Entry Point Arguments

入口点参数可以包含被污点标记的数据(即,参数由用户空间直接传递且未经检查)或间接受污染的数据(即,参数指向包含受污染数据的内核位置)。所有受污染的入口点函数可以分为六个类别,表1中显示了它们的参数代表的污点数据类型。

请添加图片描述

直接被污点标记的数据的一个示例为Listing6。在这个代码片段中,tc_client_ioctl 是一个 ioctl 入口函数,因此参数2(arg)是直接被污点标记的。因此,语句 char c = (char *)arg 将引用被污点标记的数据,因此Dr.Checker会报出warning。另外,在iris_s_ext_ctrls 中,参数2(ctrl)是一个 V4Ioctl,并且是间接受污染的。因此,对 (ctrl->controls[0]).string 的解引用是安全的,但它会使数据受到污点标记。

请添加图片描述

6.Limitation

由于Dr.Checker的soundy特性,它不能发现所有驱动程序中的所有漏洞。具体而言,它会错过以下类型的漏洞:

  • 1.State dependent bugs:由于Dr.Checker是一个无状态系统,它独立处理每个entry point(即,污点数据不在多个入口点之间传播)。因此,Dr.Checker将错过因多个入口点之间的交互而发生的错误。比如CVE-2016-2068

  • 2.Improper API usage:Dr.Checker假设所有内核API函数是安全且正确使用的。由于不正确的内核API使用而导致的漏洞将被Dr.Checker错过。然而,其他工具(例如APISan)已经开发出来,用于找到这些特定类型的漏洞,并可以用来补充Dr.Checker。

  • 3.Non-input-validation bugs:Dr.Checker专门针对输入验证漏洞。因此,无法检测到非输入验证漏洞(例如,侧信道或访问控制漏洞)。

7.Evaluation

为了评估Dr.Checker的有效性,作者对以下9个流行的移动设备内核及其相关驱动程序进行了分析(总共437个驱动)。这些设备中的内核驱动程序涵盖小到31行代码的组件到240000行复杂的代码块,平均每个驱动程序为7,000行代码。总体而言,这些驱动程序包含超过310万行代码。然而,许多内核重复使用相同的代码,这可能导致分析相同的入口点两次并夸大分析结果。因此,作者根据其底座芯片组对各种内核进行了分组,并仅基于这些分组报告结果:

  • Mediatek

    • Amazon Echo (5.5.0.3)

    • Amazon Fire HD8 (6th Generation, 5.3.2.1)

    • HTC One Hima (3.10.61-g5f0fe7e)

    • Sony Xperia XA (33.2.A.3.123)

  • Qualcomm

    • HTC Desire A56 (a56uhl-3.4.0)

    • LG K8 ACG (AS375)

    • ASUS Zenfone 2 Laser (ZE550KL / MR5-21.40.1220.1794)

  • Huawei

    • Huawei Venus P9 Lite (2016-03-29)
  • Samsung

    • Samsung Galaxy S7 Edge (SM-G935F NN)

作者同时还比对了其它4个静态分析工具flawfinderRATscppcheck、Sparse。

5.1.Dr.Checker

Dr.Checker报告的所有警告的总结结果如表4所示。在这张表中,如果report和trace实际上是真实的(例如,一个被污染的变量被危险函数使用),那么这就是一个true-positive warning。所有这些report都由作者手动验证,标记为bug的那些已被确认为关键的零日漏洞,作者已经向相关供应商披露。事实上,158个被识别的零日漏洞中,其中7个已经被分配了CVE标识符。其中,Sparse正确识别了1个,flawfinder正确识别了3个,RATs识别了与flawfinder相同的1个,而cppcheck则未能识别任何一个。这些漏洞从简单的数据泄漏到在内核中执行任意代码。

请添加图片描述

5.2.Soundy Analysis

Dr.Checker总共分析了1207个入口点,其中90%的入口点花费的时间不到100秒。这要得益于作者的soundy假设。具体来说,不分析核心内核函数以及不等待循环收敛到一个固定点加快了分析速度。这里作者评估了这些假设对Dr.Checker的精度(即实用性)和运行时间(即可扩展性)的影响。

这项分析是通过从每个代码库(即华为、高通、联发科和三星)随机选择25个入口点,从而得到100个随机选择的驱动程序入口点来完成的。然后,作者去除了Dr.Checker的两个 soundy 假设,进行了一次“sound”分析,再次运行了分析流程。

请添加图片描述

Kernel Function

作者假设所有内核函数都正确实现没有bug,这个假设对于Dr.Checker的有效性至关重要,有两个原因:

  • 分析所有核心内核代码导致的状态爆炸使得分析算法计算上变得不可行。

  • 使用LLVM对ARM Linux内核进行编译仍然是一个正在进行的项目,因此需要大量的工程工作。

事实上,作者使用 LLVM 进行了对这100个随机选择的入口点的尽力编译,其中为每个入口点创建了一个包含所有所需内核API函数的链接好的bitcode文件,除了 LLVM 无法编译的那些函数。作者使用这些已编译的API 函数运行了“sound”分析,并评估了所有循环,直到指针分析和污点分析达到固定点,并将超时窗口增加到每个入口点4个小时。即使可能缺少内核API函数定义,这100个入口点中只有18个在4小时内完成。表5的第一行(Sound)显示了这18个入口点的时间分布。此外,这18个入口点产生了63个警告,并总共花费了52分钟进行评估,而使用soundy分析则只有9个警告和不到1分钟的评估时间。

Fixed-point Loop Analysis

由于无法真正评估一个真正sound的分析算法,作者还单独评估了第二个假设(即使用reach-def循环分析而不是固定点分析)以检查其对Dr.Checker的影响。在这个实验中,作者忽略了内核 API 函数(即假设正确的实现),但在相同的100个入口点上评估了所有循环,直到它们达到固定点为止。在这种情况下,所有的入口点都在4个小时的超时窗口内成功地被分析。表5中的第二行(No API)显示了这些入口点的评估时间分布。请注意,这种方法平均需要比Dr.Checker方法多3倍的时间来分析一个入口点。同样,soundy分析得到的warning要少得多,210个,而通过这种方法产生的警告有474个。

可以在表5中观察到,该表显示忽略内核API函数是Dr.Checker实现可扩展性的主要因素。因为几乎所有的内核驱动程序本身都是作为内核模块编写的,这些模块很小(在分析的内核中平均有7.3K行代码)且自包含。

8.Discussion

原则上,Dr.Checker可以应用于任何模块化且具有明确定义入口点的代码库(例如,ImageMagick)。尽管技术是可移植的,但要应用DR. CHECKER,需要执行以下步骤:

  • 1.识别模块的源文件,并将它们编译并链接成1个bitcode文件。

  • 2.识别entry function name。

  • 3.确定这些函数的参数的污点状态。

作者在其网站上提供了关于如何在实际应用中执行这些步骤的更详细的文档。

不过这里作者用到了IFDS框架的flow- & field- & context-sensitive指针分析。将其用sparse value-flow analysis替代会不会更高效是个未知问题。

参考文献

[1].Machiry A , Spensky C , Corina J ,et al.DR. Checker: a soundy analysis for linux kernel drivers[J]. 2017.

[2].CHOU, A., YANG, J., CHELF, B., HALLEM, S., AND ENGLER, D. An empirical study of operating systems errors. In Proceedings of the 2001 ACM Symposium on Operating Systems Principles (New York, NY, USA, 2001), SOSP ’01, ACM, pp. 73–88

[3].SWIFT, M. M., BERSHAD, B. N., AND LEVY, H. M. Improving the reliability of commodity operating systems. In Proceedings of the 2003 ACM Symposium on Operating Systems Principles (New York, NY, USA, 2003), SOSP ’03, ACM, pp. 207–222

[4].PALIX, N., THOMAS, G., SAHA, S., CALV `ES, C., LAWALL, J.,AND MULLER, G. Faults in linux: Ten years later. In Proceedings of the 2011 International Conference on Architectural Support for Programming Languages and Operating Systems (New York, NY, USA, 2011), ASPLOS’11, ACM, pp. 305–318

[5].CHEN, H., MAO, Y., WANG, X., ZHOU, D., ZELDOVICH, N., AND KAASHOEK, M. F. Linux kernel vulnerabilities: State-of-the-art defenses and open problems. In Proceedings of the 2011 Asia-Pacific Workshop on Systems (New York, NY, USA, 2011),APSys ’11, ACM, pp. 5:1–5:5

[6].STOEP, J. V. Android: protecting the kernel. Linux Securit Summit (August 2016).

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
优化这一段sql:SELECT user_worker.Title AS worker, COUNT(CASE WHEN (CASE WHEN bl.Checker = 0 THEN '' WHEN bl.Checker > 0 AND bl.Audited = -1 THEN 'NG' WHEN bl.Checker > 0 AND bl.Audited = 1 THEN 'OK' END) ='' THEN 1 END) as nullstr, COUNT(CASE WHEN (CASE WHEN bl.Checker = 0 THEN '' WHEN bl.Checker > 0 AND bl.Audited = -1 THEN 'NG' WHEN bl.Checker > 0 AND bl.Audited = 1 THEN 'OK' END) = 'OK' THEN 1 END) as okstr, COUNT(CASE WHEN (CASE WHEN bl.Checker = 0 THEN '' WHEN bl.Checker > 0 AND bl.Audited = -1 THEN 'NG' WHEN bl.Checker > 0 AND bl.Audited = 1 THEN 'OK' END) = 'NG' THEN 1 END) as NGstr, (COUNT(CASE WHEN (CASE WHEN bl.Checker = 0 THEN '' WHEN bl.Checker > 0 AND bl.Audited = -1 THEN 'NG' WHEN bl.Checker > 0 AND bl.Audited = 1 THEN 'OK' END) = 'OK' THEN 1 END) +COUNT(CASE WHEN (CASE WHEN bl.Checker = 0 THEN '' WHEN bl.Checker > 0 AND bl.Audited = -1 THEN 'NG' WHEN bl.Checker > 0 AND bl.Audited = 1 THEN 'OK' END) ='' THEN 1 END) )as totalOKStr FROM [bdWorkOrderList] bl LEFT JOIN [bdWorkOrder] bd ON bl.OrderID = bd.ID LEFT JOIN [reportMJWXJL] rwx ON bl.OrderID = rwx.OrderID LEFT JOIN [reportMJQHLHJL] rqh ON bl.OrderID = rqh.OrderID LEFT JOIN user_worker ON user_worker.ID = bl.Worker WHERE bd.IsDeleted = 0 AND bl.StepName LIKE 'Maker%' AND bl.StepName IN ('Maker修模', 'maker切换料号', 'Maker模具保养') AND DATEPART(MONTH, bl.StartTime) = DATEPART(MONTH, GETDATE()) and DATEPART(YEAR, bl.StartTime) = DATEPART(YEAR, GETDATE()) GROUP BY user_worker.Title)
最新发布
07-15

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值