静态分析学习笔记01:静态分析初探

249 阅读6分钟

最近在看南京大学的软件分析课程, 开个合集来记录一下

1.程序语言(PL)的研究领域

PL的研究领域主要可以分为理论,环境和应用三大类, 此处的理论主要是包括程序语言设计、类型系统的相关问题,环境一般指代的是编译器、运行时系统的设计(支持程序运行的环境),而应用则是针对已有的程序提出以一系列方法,验证、分析程序,静态分析就是其中的一个例子。 image.png

2. 程序语言的分类

我们可以将编程语言分为三类范式

  1. 命令式编程语言:将逻辑拆解,每一条指令对应一个操作(JAVA/C),这类编程范式更加注重程序的具体步骤
  2. 函数式编程语言 :用数学的方式来看待程序,将程序本体视为数学计算的过程这类编程范式更加注重程序的的结果,与逻辑式语言相通
  3. 逻辑式语言:声明式语言(PROLOG、sql)这类编程范式更加注重程序的的结果

3.静态分析的必要性:

本质上是在程序运行之前就对程序的可靠性、安全性进行测定

  1. 程序的可靠性(空指针定义、内存泄露)
  2. 程序的安全性(注入攻击 、个人信息的泄露)
  3. 编译优化 ( 无用代码优化、循环展开)
  4. 程序理解:让计算机理解程序的结构,例如IDE中的调用关系、类型推断。

4.静态分析的目的与边界

静态分析 的本质是用一个程序P自动的分析另外一个程序P1、考察这个程序是否符合一些要求,这一切的考察必须建立P1尚未运行的情况下。

完备的静态分析需要满足 Sound和Complete,sound可以简单理解为一种更加严格的规范 、宁可错报、不可漏报,而complete则是宁可漏报,不可错报

image.png

静态分析本身具有不完备性,莱斯定理确定了静态分析本身是无法针对一段程序给出确定的答案,换言之静态分析无法

以下是莱斯定理的原话

我们可以将任意程序看成从输入到输出上的函数(一个输入输出对的集合),该函数描述了函数的行为。对于该函数/集合的任何 non-trivia 的属性,都不存在可以检查该属性的通用算法

简单来说,莱斯定理给出的结论为:一门主流的编程语言,其如何与运行时相关的特性的是无法给出准确静态分析

所以在实际的静态分析当中,往往会采用妥协的方式来进行解决,一般的妥协方式有两种

  1. 妥协soundness(Compromise soundness):简单来说是将判断准则放宽,产生一定的漏报(false negative)
  2. 妥协completeness(Compromise completeness):将判断准则收紧 ,产生一定的误报(false positive)

在目前的静态分析中,主要是以妥协completeness为主。

5. 稳健性(soundness)的必要性

这边结合一个例子来看,这里一共有两条数据流,我们如果此处只分析蓝色这一条数据流(妥协soundness),那么我们就可以得出此处的类型转换是安全的类型转换(safe Cast),但是这里此处如果程序触发了绿色数据流,这就可以发现这个类型转换是一个不安全的类型转换。故此在进行静态分析的时候如果采取妥协soundness的方式,往往会产生错误的结果

image.png

6. 从静态分析来分析程序(一个简单的例子)

考虑以下程序

if (input)
 x=1
else 
 x=0;

那么从静态分析的角度来说,程序等于多少?

我们考虑以下4个答案

  1. x=1 or x=0

  2. x=-1 or x=1

  3. when input is true x=1

    when input is false x=0

  4. x=0,x=1,x=2

此时答案1、答案3和答案4是对的,在静态分析中,我们需要一个sound的答案,其中答案3缺少了x=0这个选项,其妥协了soundness,故此是错误的。这里要注意不要使用动态的思维来分析程序

其中值得留意的是,答案1和答案3还可以折射出另外两个特性,其中答案3会 比答案1更加精确(precise),且开销更大(expensive)、因为其需要维护上下文的信息。

总括而言,静态分析的目标是在确保稳健性(soundness)的前提下来,尽可能地平衡准确性和速度

7. 静态分析步骤

    静态分析的步骤主要可以分为抽象近似两个步骤

    这里有一个比较简单的例子

    我们需要找出程序中所有的符号(+,-,0),该程序语言中不可以使用负值索引

7.1 抽象:

这里我们将具体的值从具体值域映射到符号域,这里是一个满射,这么做的一大好处是尽可能压缩状态空间,加速分析速度

image.png

7.2 近似

实际上近似有两个环节,定义转换函数(transfer function)与控制流(control flows)

  1. 首先我们定义转换函数,这里我们需要根据分析目标和程序每一个语句的语义来定义转换规则,具体我们可以看下图,在这个例子中,我们的语义=运算规则=转换规则(长图警告)。

image.png

这里我们需要留意的点有两个,首先是正数+负数的情况,在这种情况下,得出的数值结果并不明确,我们需要将其设置为unknow的类型

image.png

而对于未定义数/0的情况我们需要给出未定情况

image.png 此时我们就可以对程序进行初步分析了

image.png

此处我们可以发现总计三个错误,

  1. c=a/b所带来的除0错误

  2. p=arr[y]所带来的索引错误(这里规定的语法规则中不可以使用负数索引)

  3. q=arr[a]所带来的索引错误(索引的值可正可负,根据soundness的准则需要归类我错误)

  4. 控制流分析

image.png

左边代码段的控制流图如右所示,此处我们需要对每个变量映射在符号域的符号结合语义规则(7.2的长图)进行分析,这里我们需要在每一个数据交汇处进行分析,最终得到这一个代码段最终变量的在符号域的值 image.png

值得留意的是, 控制流分析在实际的应用场景下由于控制流过于庞大,往往并无法遍历每一个分支,这也就是控制流分析中近似思想的应用。