本文是旨在记录最近所学的知识,若文章内容存在不当,希望各位指正,任何问题都可留言讨论。
由于作者主要研究方向是区块链相关,所以本文全部内容只针对智能合约。
一、模糊测试
首先,模糊测试的定义:模糊测试(Fuzz Testing or Fuzzing)是一种软件测试方法,通过输入大量的随机、无效或边界情况的数据来测试目标系统的健壮性和安全性。模糊测试旨在发现系统中的潜在漏洞、错误或异常行为。
模糊测试测试的对象实际上是目标程序中的不变量(ps:不变量是目标程序中预期的条件和状态),下面我们以一个合约为例子:
在本合约中(合约简陋,便于理解即可,实际情况中无论是测试对象还是模糊测试的程序都要复杂的多),我们将send()函数视为我们向一个账户发送奖励的方法,fuzztest_send()函数视为对send()函数进行模糊测试的方法。既然是发送奖励,那自然不应该为0,所以send()函数最后返回的结果预期是大于0,即测试合约中的奖励值y预期大于0,这就是一个不变量。
我们知道模糊测试的对象是程序中的不变量以及不变量是什么之后,就很容易知道模糊测试的目的,其目的是为了测试目标程序中的不变量是否存在被打破的可能。还是以上述的合约为例子,我们知道我们测试的目标程序就是send()函数,我们的模糊测试程序会针对send()函数生成大量的测试用例,也就是生成许多随机的两个测试数据testdata1,testdata2。由于不变量是send()函数的结果大于0,即testdata1大于testdata2时满足预期,而一旦testdata1小于testdata2时则会出现非预期的结果0,即不变量y大于0被打破。
二、模糊测试与符号执行
符号执行 (Symbolic Execution)是一种程序分析技术,它可以通过分析程序来得到让特定代码区域执行的输入。顾名思义,使用符号执行分析一个程序时,该程序会使用符号值作为输入,而非一般执行程序时使用的具体值。在达到目标代码时,分析器可以得到相应的路径约束,然后通过约束求解器来得到可以触发目标代码的具体值。
符号执行与模糊测试都是软件测试中常用的方法,但二者各有所长,下面将进行模糊测试与符号执行的优劣对比:
如果将程序的的运行空间比作一个执行树的话,符号执行将程序输入符号化旨在探索更加全面的程序路径,但往往无法探测到较深的分支,其原因有二:
1.符号执行存在状态爆炸问题:当程序中存在一些使得变量数量或类型增大的情况下,符号执行所维护的符号状态可能会呈指数增加,所而导致巨大的时间和空间的开销
2.若是静态符号执行,则过于依赖约束求解器求解约束 ,采用动态符号执行,但会出现在某些条件分支处实际化符号后就可能无法在该分支上继续探索接下来的路径
而模糊测试采用随机变异生成测试用例并动态执行程序可以执行并覆盖到较深的分支,但其很难通过变异的方法生成覆盖到复杂条件分支的测试用例,导致无法突破较浅条件。其原因如下:
由于模糊测试采用随机变异生成测试用例,导致可能无法变异出复杂条件从而进入其对应代码,这是因为复杂的条件分支通常涉及多个变量和条件的组合,其可能的状态数量非常大。而随机变异的方法很难有足够的概率生成覆盖到所有可能状态的测试用例。
1 int twice(int v){
2 return 2*v;
3 }
4
5 void testme(int x, int y){
6 z = twice(y);
7 if (z == x){
8 if (x > y+10)
9 ERROR;
10 }
11 }
12
13 int main(){
14 x = sym_input();
15 y = sym_input();
16 testme(x, y);
17 return 0;
18 }
以上面这段代码为例,模糊测试的局限进行解释。我们假设无论是第7行的条件还是第8行的条件他们都有更多的条件分支(如下图所示),对于第7行,他还存在同一级别的条件2、条件3...等非常多的条件分支,在每一个这些条件分支内又存在很多个类似于第8行的条件分支(对于条件2,如条件2.1、2.2...),并且如果这些条件分支中存在许多更加复杂的条件,这样的环境就无法保证模糊测试通过随机生成的测试用例可以覆盖到每横向上的每一个条件分支,所以相比于在横向条件中模糊测试产生大量的测试用例来覆盖全部的条件分支,他在纵向的条件对每一个条件产生大量的测试用例从而更加容易达到程序树更深的层次。
三、模糊测试与单元测试
单元测试是指,对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数或者类。
单元测试是一种白盒测试,测试员通常对所测试的程序代码的内部结构以及逻辑有很好的了解,因此可以针对特定的输入和预期的输出编写测试程序来验证所测试的模块的功能。
而模糊测试是一种灰盒或黑盒测试,很多情况下