Fuzz方法在SPDK iSCSI的应用实例

uzz 简介

本文目的是介绍如何使用fuzz方法写出测试代码。首先了解一下fuzz的概念。

1

Fuzz Test 是什么

中文可翻译为模糊测试。就是用大量的测试用例一个一个试,尽可能多的找出有可能出问题的地方。第一个模糊测试工具,最初由Barton Miller于1989年在威斯康星大学开发。模糊测试是一种软件测试技术,它是一种安全测试。

在模糊测试中,用随机坏数据(也称做 fuzz)攻击一个程序,然后等待观察哪里遭到了破坏。模糊测试的技巧在于,它是不符合逻辑的。自动模糊测试不去猜测哪个数据会导致破坏(就像人工测试员那样),而是将尽可能多的杂乱数据投入程序中。经过这个测试验证过的失败模式通常对程序员来说是个彻底的震憾,因为任何按逻辑思考的人可能都不会想到这种失败。

2

为什么要进行模糊测试?

  • 可能发现最严重的安全故障或缺陷。

  • 当与Black Box测试、Beta测试和其他调试方法一起使用时,模糊测试可以提供更有效的结果。

  • 模糊测试用于检查软件的漏洞。这是非常划算的测试技术。

  • 模糊测试是黑盒测试技术之一。模糊测试是黑客用来发现系统漏洞的最常见方法之一。 

3

模糊测试能检测到的错误类型

  • 断言失败和内存泄漏

    此方法广泛用于大型应用程序,其中的错误会影响内存的安全性,这是一个严重的漏洞。

  • 输入无效

    在模糊测试中,模糊器用于生成无效输入,用于测试错误处理例程,这对于不控制其输入的软件很重要。简单的模糊测试可以被称为自动化负面测试(Negative Testing)的一种方法。

  • 正确的错误

    模糊测试也可用于检测某些类型的“正确性”错误。如数据库损坏,搜索结果不佳等。

4

模糊测试实现过程

1) 找到一份待测试的可执行文件代码;

2) 生成大量的测试用例(Fuzzed数据)找到输入点,然后把随机数据丢进去;

3) 执行文件;

4) 观察破坏了什么;

5) 记录缺陷。

5

模糊测试的优缺点

好处

  • 模糊测试改进了软件安全测试。

  • 在模糊测试中发现的错误有时很严重,包括崩溃、内存泄漏、未处理的异常等。

  • 如果由于时间和资源的限制,测试人员可能没有注意到的一些错误,那么在模糊测试中会发现。

缺点

  • 仅靠模糊测试无法保证整体安全。

  • 模糊测试在处理不会导致程序崩溃的安全威胁方面效果较差,例如某些病毒、蠕虫、特洛伊木马等。

  • 模糊测试只能检测简单的故障或威胁。

  • 要有效地执行,需要大量时间。

  • 使用随机输入设置边界值条件是非常有问题的,但现在使用基于用户输入的确定性算法,大多数测试人员解决了这个问题。

SPDK iSCSI应用实例

1

被测对象

确定需要进行fuzz测试的文件代码。这里以spdk的lib/iscsi.c为被测对象。我们编写的fuzz app代码为spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c。

2

了解iSCSI流程

iSCSI结构简介

iSCSI使用Client/Server模型。Target端即磁盘阵列或其他装有磁盘的主机。通过iSCSI Target工具将磁盘空间映射到网络上,initiator端就可以寻找发现并使用该磁盘。

Figure 1 给出了iSCSI结构里不同部分之间的关系。Figure 2 给出了iSCSI中数据传输的简单流程。

Figure 1:iSCSI结构

Figure 2:数据传输的简单流程

PDU(Protocol Data Units)是iSCSI交换数据最基本的单位,格式见Figure 3。

Figure 3:iSCSI PDU的格式

其中,基本报文头BHS(Basic Header Segment)的格式见Figure 4。

Figure 4:BHS的格式

接下来,我们要用大量的fuzz输入用例,来模拟填充PDU的主要数据结构,这里重点是填充BHS,以及其中的操作码(opcode)。各个操作码的意义见Figure5。

代码里对BHS操作码的定义:

1.  enum iscsi_op {  

2.      /* Initiator opcodes */  

3.      ISCSI_OP_NOPOUT         = 0x00,  

4.      ISCSI_OP_SCSI           = 0x01,  

5.      ISCSI_OP_TASK           = 0x02,  

6.      ISCSI_OP_LOGIN          = 0x03,  

7.      ISCSI_OP_TEXT           = 0x04,  

8.      ISCSI_OP_SCSI_DATAOUT   = 0x05,  

9.      ISCSI_OP_LOGOUT         = 0x06,  

10.    ISCSI_OP_SNACK          = 0x10,  

11.    ISCSI_OP_VENDOR_1C      = 0x1c,  

12.    ISCSI_OP_VENDOR_1D      = 0x1d,  

13.    ISCSI_OP_VENDOR_1E      = 0x1e,  

14.  

15.    /* Target opcodes */  

16.    ISCSI_OP_NOPIN          = 0x20,  

17.    ISCSI_OP_SCSI_RSP       = 0x21,  

18.    ISCSI_OP_TASK_RSP       = 0x22,  

19.    ISCSI_OP_LOGIN_RSP      = 0x23,  

20.    ISCSI_OP_TEXT_RSP       = 0x24,  

21.    ISCSI_OP_SCSI_DATAIN    = 0x25,  

22.    ISCSI_OP_LOGOUT_RSP     = 0x26,  

23.    ISCSI_OP_R2T            = 0x31,  

24.    ISCSI_OP_ASYNC          = 0x32,  

25.    ISCSI_OP_VENDOR_3C      = 0x3c,  

26.    ISCSI_OP_VENDOR_3D      = 0x3d,  

27.    ISCSI_OP_VENDOR_3E      = 0x3e,  

28.    ISCSI_OP_REJECT         = 0x3f,  

29.};  

Figure 5:操作码的意义

3

生成fuzz数据

iSCSI主要的数据处理流程包括iscsi_pdu_hdr_handle( )和iscsi_pdu_payload_handle( )这两个函数。

iscsi_pdu_hdr_handle( )代码如下:

1.  static int  

2.  iscsi_pdu_hdr_handle(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu)  

3.  {  

4.    

5.      if (opcode == ISCSI_OP_LOGIN) {  

6.          return iscsi_pdu_hdr_op_login(conn, pdu);  

7.      }  

8.    

9.      switch (opcode) {  

10.    case ISCSI_OP_NOPOUT:  

11.        rc = iscsi_pdu_hdr_op_nopout(conn, pdu);  

12.    case ISCSI_OP_SCSI:  

13.        rc = iscsi_pdu_hdr_op_scsi(conn, pdu);  

14.    case ISCSI_OP_TASK:  

15.        rc = iscsi_pdu_hdr_op_task(conn, pdu);  

16.    case ISCSI_OP_TEXT:  

17.        rc = iscsi_pdu_hdr_op_text(conn, pdu);  

18.    case ISCSI_OP_LOGOUT:  

19.        rc = iscsi_pdu_hdr_op_logout(conn, pdu);  

20.    case ISCSI_OP_SCSI_DATAOUT:  

21.        rc = iscsi_pdu_hdr_op_data(conn, pdu);  

22.    case ISCSI_OP_SNACK:  

23.        rc = iscsi_pdu_hdr_op_snack(conn, pdu);  

24.    default:  

25.        return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR);  

26.    }  

Fuzz的任务,就是来填充包含opcode的整个BHS结构体。

1.  struct iscsi_bhs {  

2.      uint8_t opcode      : 6

3.      uint8_t immediate   : 1;  

4.      uint8_t reserved    : 1;  

5.      uint8_t flags;  

6.      uint8_t rsv[2];  

7.      uint8_t total_ahs_len;  

8.      uint8_t data_segment_len[3];  

9.      uint64_t lun;  

10.    uint32_t itt;  

11.    uint32_t ttt;  

12.    uint32_t stat_sn;  

13.    uint32_t exp_stat_sn;  

14.    uint32_t max_stat_sn;  

15.    uint8_t res3[12];  

16. };  

Request消息,根据自身BHS的opcode来进入iscsi_pdu_hdr_handle( )不同的处理分支。

填充BHS的函数是prep_iscsi_pdu_bhs_opcode_cmd(),

1.  static void  

2. prep_iscsi_pdu_bhs_opcode_cmd(struct fuzz_iscsi_dev_ctx *dev_ctx, struct fuzz_iscsi_io_ctx *io_ctx)  

3.  {  

4.      io_ctx->iov_ctx.iov_req.iov_len = sizeof(struct iscsi_bhs);  

5.      fuzz_fill_random_bytes((char *)io_ctx->req.bhs, sizeof(struct iscsi_bhs),  

6.                     &dev_ctx->random_seed);  

7.  }  

其中函数fuzz_fill_random_bytes( ),用来生成随机数据,填充BHS各个字段。

1.  static void  

2.  fuzz_fill_random_bytes(char *character_repr, size_t len, unsigned int *rand_seed)  

3.  {  

4.      size_t i;  

5.    

6.      for (i = 0; i < len; i++) {  

7.          character_repr[i] = rand_r(rand_seed) % UINT8_MAX;  //生成随机数据

8.      }  

9.  } 

不仅要填充bhs,还需要填充适量的PDU的数据结构。

1.  struct spdk_iscsi_pdu {  

2.      struct iscsi_bhs bhs;  

3.      struct spdk_mobj *mobj;  

4.      bool is_rejected;  

5.      uint8_t *data_buf;  

6.      uint8_t *data;  

7.      uint8_t header_digest[ISCSI_DIGEST_LEN];  

8.      uint8_t data_digest[ISCSI_DIGEST_LEN];  

9.      size_t data_segment_len;  

10.......  

因为包处理是以PDU为对象的,缺少参数会导致意外的错误,第一步验证条件就被挡住了。所以,为了能深入尽可能多的分支,需要填充一些基本的PDU参数。

例如这样,

1.  req_pdu->writev_offset = 0;  

2.  req_pdu->hdigest_valid_bytes = 0;  

3.  req_pdu->ahs_valid_bytes = 0;  

4.  req_pdu->data_buf_len = 0;  

同样是为了能深入尽可能多的分支,还需要重新指定一些基本的BHS参数。并且,需要对login这一特殊的的PDU进行单独地处理。

1.  if (opcode == ISCSI_OP_LOGIN) {  

2.      return iscsi_pdu_hdr_op_login(conn, pdu);  

3.  }  

4.  req_pdu->bhs.immediate = 1;  

5.  req_pdu->bhs.reserved = 0;  

6.  req_pdu->bhs_valid_bytes = ISCSI_BHS_LEN;  

7.  req_pdu->bhs.total_ahs_len = 0;  

8.  req_pdu->bhs.stat_sn = 0;  

4

执行结果

最开始的时候,initiator端发送的第一个包是login request,用以跟target端建立connection。成功后,从第二个开始就可以都是随机包了。如果没有建立connection的话,target端不会处理任何一个来自initiator的PDU包。

Received payload_handle response opcode from Target is 0x23.(这是target回过来的第一个response包,LOGIN_RSP)

无效包的处理,target端返回REJECT包。

例如:发出请求0x1e(无效包),返回响应0x3f(REJECT包)

Random request bhs.opcode of Initiator is 0x1e.(随机生成的无效包)

Dumping this request bhs contents now.

"bhs": {

  "opcode": 30, (0x1e十进制)

  "immediate": 1,

  "reserved": 0,

  "total_ahs_len":0,

 "data_segment_len": "AAAA",

  "itt":1658750280,

  "exp_stat_sn":4117834500

}

Sent an invalid opcode PDU.(这是一个非法包)

Received rejected hdr_handle response opcode(0x3f) from Target.

Received payload_handle response opcode from Target is 0x3f.(REJECT包)

有效包的处理,target端返回相应的response包。

例如:发出请求0x2(TASK),返回响应0x24(TASK_RSP)

Random request bhs.opcode of Initiator is 0x4.(随机生成的TASK包)

Dumping this request bhs contents now.

"bhs": {

  "opcode": 4,

  "immediate": 1,

  "reserved": 0,

  "total_ahs_len":0,

 "data_segment_len": "AAAA",

  "itt": 0,

  "exp_stat_sn":4138086897

}

Sent a valid opcode PDU.(这是一个合法包)

Received hdr_handle response opcode from Target is 0x24. (target返回正确的response)

Received payload_handle response opcode from Target is 0x24.

可以看出,结果是符合预期的。这里测试时间设定30秒。完成时,fuzz app模拟的initiator端一共向target端发送了17447个合法随机包,161259个非法随机包。

Fuzzing completed. Shutting down the fuzz application.

device 0x1efc200 stats: Sent 17447 valid opcode PDUs, 161259invalid opcode PDUs.

具体执行参数,请参考shell脚本spdk/test/iscsi_tgt/fuzz/fuzz.sh。

5

通过fuzz发现的一个issue

例如在iscsi_reject()函数里,要把pdu->ahs指向的数据拷贝到data + data_len的地址。原先的代码没有考虑到多条路径过来的验证越界问题。即如果(4 * total_ahs_len)大于ISCSI_AHS_LEN时,源数据长度超过目标缓冲区长度,返回地址乱了,会导致Segmentation Fault的错误。

Fuzz随机生成了一个比较狂野的bhs.total_ahs_len的值,超过了ISCSI_AHS_LEN的范围,暴露了这个问题。代码见Figure 6,绿底是修改后的。

Figure 6:lib/iscsi.c

写在最后,如果仅仅用fuzz来模拟bhs->opcode,作用会非常有限。Iscsi.c代码里iscsi_pdu_hdr_handle( )函数的switch (opcode)部分代码已经对各种opcode做了处理。但是用fuzz来模拟填充各种BHS结构体的字段内容,就能进入更多的其他代码分支,就能发现更多潜在的问题。同样地,可以再扩大fuzz的范围,来模拟填充更多PDU结构体的字段,这样会覆盖更多的代码分支。

原文链接:https://mp.weixin.qq.com/s/3O79dePb0gQnLmAH4WHq8Q

学习更多dpdk视频
DPDK 学习资料、教学视频和学习路线图 :https://space.bilibili.com/1600631218
Dpdk/网络协议栈/ vpp /OvS/DDos/NFV/虚拟化/高性能专家 上课地址: https://ke.qq.com/course/5066203?flowToken=1043799
DPDK开发学习资料、教学视频和学习路线图分享有需要的可以自行添加学习交流q 君羊909332607备注(XMG) 获取
 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340