本文是 Juliet Test Suite v1.2 for C_Cpp - User Guide.pdf 的中文翻译,官方网站位于 https://samate.nist.gov/SARD/documentation#juliet

第一章:介绍

1.1 文件目的

本文档描述了为C/C++开发的Juliet测试套件v1.2。该测试套件由国家安全局(NSA)的安全软件中心(CAS)创建并开发,专门用于评估静态分析工具的能力。它适用于任何希望将测试用例用于自己的测试目的,或希望更深入了解测试用例创建方式的人员。

本文档解释了测试用例命名和设计背后的理念,并提供了如何使用命令行界面(CLI)编译和运行它们的指导。第8节还提供了如何评估工具结果的详细信息。

测试用例可公开下载于 http://samate.nist.gov/SRD/testsuite.php

1.2 测试用例是什么?

测试用例是可以构建的代码片段,用于研究静态分析工具。一个测试用例针对的正是一类缺陷,但其他不相关的缺陷可能偶然存在。例如,C测试用例“CWE476_NULL_Pointer_Dereference__char_01”只针对空指针解引用缺陷。除了包含目标缺陷的构造之外,每个测试用例通常还包含一个或多个执行与有缺陷构造类似功能的非缺陷构造。一小部分测试用例不包含非缺陷构造,被认为是“仅坏”测试用例(见第4.5节)。

1.3 为什么需要测试用例?

为了研究静态分析工具,CAS需要软件进行工具分析。CAS之前考虑使用“自然”或“人造”软件。自然软件是没有为测试静态分析工具而创建的软件。开源软件应用程序,如Apache Web服务器httpd.apache.org和OpenSSH套件www.openssh.com,是自然软件的例子。人造软件,在这个情况下,是包含故意缺陷并专门为测试静态分析工具而创建的软件。测试用例是人造软件的一个例子。

1.3.1 自然代码的限制

在以前的研究工作中,CAS在测试静态分析工具时使用了自然和人造代码的组合。此外,CAS遵循了国家标准与技术研究院(NIST)静态分析工具博览会(SATE),该博览会检验了静态分析工具在自然代码上的性能。

  • 从这些努力中获得的经验表明,使用自然代码通常面临特定的挑战,例如:

    • 评估工具结果以确定其正确性 - 当静态分析工具在自然代码上运行时,每个结果都需要审查以确定代码是否真的在指定位置有指定类型的缺陷(即,结果是否正确或“假阳性”)。对于大多数自然代码的结果来说,这种审查并非易事,通常无法在合理的时间内以高度的确定性确定给定结果的正确性。

    • 比较不同工具的结果 - 在自然代码上比较静态分析工具的结果很复杂,因为不同的工具以不同的方式报告结果。例如,许多缺陷涉及“源”的受污染数据和“汇”不当使用该数据的地方。一些工具可能报告源,而其他工具报告汇。有时,多个受污染数据的源都指向一个汇,这可能导致不同工具报告不同数量的结果。

    • 识别代码中没有工具发现的缺陷 - 在评估静态分析工具时,需要一个“标准”列表,列出代码中的所有缺陷,以便识别每个工具未能报告的缺陷。对于自然代码来说,创建这个“标准”很困难,特别是在识别没有任何自动化工具报告的缺陷时,因此只能通过手动代码审查来发现。

    • 评估工具在代码中未出现的构造上的性能 - 自然代码的限制在于,即使是不同项目的组合,也可能不包含CAS想要测试的所有有缺陷和无缺陷的构造。即使代码中出现的缺陷类型也可能因复杂的控制和数据流而被混淆,以至于自然代码中的缺陷即使被通常能捕获该类型缺陷的工具也会未被检测到。为了解决这个问题,CAS考虑使用“播种”方法将缺陷和非缺陷嵌入到自然代码中。最终,CAS决定创建测试用例,而不是使用“播种”,因为CAS认为使用“播种”代码研究静态分析工具将过于复杂,并且导致测试的构造少于预期。

基于这些经验和挑战,CAS决定开发人造测试用例来测试静态分析工具。使用人造代码通过允许CAS控制、识别和定位代码中包含的缺陷和非缺陷来简化工具研究。

1.3.2 测试用例的限制

尽管使用测试用例简化了静态分析工具研究,但它可能以以下两种方式限制结果的适用性:

  • 测试用例比自然代码简单 - 一些测试用例故意是正在测试的缺陷的最简单形式。即使是包含控制或数据流复杂性的测试用例也比自然代码简单,无论是在代码行数还是在分支、循环和函数调用的数量和类型上。这种简单性可能会夸大结果,即工具可能报告在测试用例中它们很少在自然、非平凡代码中报告的缺陷。

  • 测试用例中缺陷和无缺陷构造的频率可能不反映它们在自然代码中的频率 - 每个类型的缺陷在测试用例中测试一次,无论这种缺陷类型在自然代码中有多常见或罕见。因此,两个在测试用例上有相似结果的工具可能在自然代码上提供非常不同的结果,例如,如果一个工具发现常见缺陷,而另一个工具只发现罕见缺陷。即使在测试用例上表现不佳的工具也可能在自然代码上表现良好。同样,每个无缺陷构造在测试用例中也只出现一次,无论该构造在自然代码中有多常见。因此,测试用例上的假阳性率可能与工具在自然代码上的比率大不相同。

1.4 创建测试用例

大多数非类基缺陷的测试用例是使用包含缺陷的源文件和CAS创建的名为“测试用例模板引擎”的工具生成的。生成的测试用例文件包含在第一行的注释中,表明它们是生成的。

一些缺陷类型不能被CAS的自定义测试用例模板引擎生成。这些缺陷类型的测试用例是手动创建的。由于资源限制,这些测试用例被创建为只包含缺陷的最简单形式,没有添加控制或数据流复杂性。


第二章:测试用例范围

本节提供了测试用例范围的详细信息。通常,测试用例侧重于底层平台上可用的函数,而不是第三方库的使用。

尽管C和C++是不同的编程语言,但它们被视为一个单元,因为C++通常是C的超集。此外,大多数软件保证工具都支持C和C++。

尽可能地,C/C++测试用例将API调用限制在C标准库中,该库在所有平台上都可用。为了涵盖更多的问题,一些测试用例针对Windows平台(使用Windows特定的API函数)。未来,这项工作可以扩展到涵盖Windows之外的其他平台独有的API函数。不使用任何第三方C或C++库函数。

C测试用例代码针对C89标准,以便测试用例可以使用可能不支持C语言新版本的各种工具进行编译和分析。

测试用例限制了C++构造和特性的使用,仅在需要它们时使用(例如,与C++类或“new”操作符相关的测试用例)。除非针对的缺陷类型需要,否则测试用例不使用C++标准库。

2.1 测试用例选择

CAS在选择测试用例的缺陷类型时使用了几个来源:

  • 软件保证团队在CAS中的经验
  • CAS之前工具研究中使用的缺陷类型
  • 供应商关于其工具识别的缺陷类型的信息
  • MITRE通用弱点枚举(CWE)中的弱点信息

虽然每个测试用例都使用CWE标识符作为其名称的一部分,但创建测试用例并不需要特定于缺陷类型的CWE条目。为所有适当的缺陷类型创建测试用例,并使用最相关的CWE条目(可能相当通用和/或抽象)为每个测试用例命名。

2.2 测试用例统计

测试用例涵盖了2011年CWE/SANS前25个最危险软件错误中的11个。在测试用例未涵盖的14个CWE条目中,有10个是设计问题,不适合CAS测试用例的结构。其他四个不是特定于C/C++的,并且在相关的Java测试用例中得到涵盖。(见附录B,了解与前25个相关的测试用例的详细信息)。

在Juliet测试套件v1.2中为C/C++添加了新的缺陷。2012年C/C++测试用例的数量总计为61,387,而2011年为57,099。这表示增长了7.5%。表1包含了2011年和2012年测试用例的大小和范围的统计数据。

指标20112012百分比变化
CWE条目覆盖119118-0.8%
缺陷类型1,4891,6178.6%
测试用例57,09961,3877.5%
代码行数28,375,6048,679,6823.6%

表1 – 2011-2012 C/C++测试用例统计

请参见附录A,获取测试用例覆盖的CWE条目的完整列表。

2 通过CLOC(cloc.sourceforge.net)计算。未包括空行或注释行。包括主函数。

此外,Juliet测试套件v1.2中发生了以下更改:

  • 添加了六个CWE的新测试用例。
  • 删除了七个CWE的测试用例。
  • 删除了一个流变体。
  • 添加了两个流变体。
  • 22个CWE的缺陷类型数量增加或减少。
  • 一些测试用例目录被拆分成更小的子目录,以便每个子目录包含不超过1,000个测试用例文件。
  • 从多个控制流变体中删除了死代码。

有关更多详细信息,请参见附录E。


第三章:测试用例命名

如第1.2节所述,测试用例是可以构建的代码片段,针对的正是一类缺陷,通常还包含一个或多个执行与有缺陷构造类似功能的非缺陷构造。

3.1 命名方案

测试用例使用MITRE的CWE作为命名和组织的基础。测试用例力求使用与目标缺陷最具体的CWE条目。每个测试用例文件与恰好一个CWE条目相关联。

测试用例通过以下四个元素的组合唯一标识:

  • 与意图缺陷最相关的CWE条目的标识号和可能的缩写名称。
  • “功能变体”名称,指示意图缺陷比CWE条目更具体。
  • 与“流变体”相关的两位数字,指示测试用例中使用的数据和/或控制流类型。例如,流变体“01”是缺陷的最简单形式,不包含数据或控制流。
  • 测试用例中使用的编程语言。这通过测试用例文件的扩展名(“.c”、“ .cpp”或“.h”)表示。

测试用例的名称写作“C test case CWE476_NULL_Pointer_Dereference__char_01”。单文件测试用例也可以通过文件名引用。

3.2 测试用例功能变体

每个测试用例都有一个“功能变体”名称。功能变体一词也可以与“缺陷类型”同义使用。这个词或短语用于区分同一CWE条目的测试用例。它应该尽可能简短,通常只是测试用例中使用的类型或函数的名称。如果某个CWE条目只有一种缺陷类型,则该CWE条目的测试用例的功能变体名称为“basic”。

3.2.1 功能变体名称中的关键字符串

功能变体名称中可以出现一个关键字符串,以指示测试用例的特征。这个字符串被管理测试用例、构建过程和结果评估的脚本使用。由于生成大多数测试用例的软件的性质,这个字符串可能在功能变体名称中出现多次:

  • “w32” – 此字符串出现在测试用例的功能变体名称中,表示该功能变体特定于Windows操作系统。通常,这些测试用例使用在其他操作系统中不存在的“win32” API中的函数。许多C/C++测试用例将在非Windows平台上编译,但这些将不会。例如,C测试用例CWE78_OS_Command_Injection__char_listen_socket_w32_execv_41.c。

3.3 测试用例流变体

测试用例用于演示静态分析工具跟踪各种控制和数据流的能力,以便正确报告缺陷并正确忽略软件中的非缺陷。测试用例中存在的控制或数据流类型由“流变体”编号指定。具有相同流变体编号(但CWE条目或“功能变体”不同)的测试用例使用相同类型的控制或数据流。

流变体为“01”的测试用例是缺陷的最简单形式,不包含附加的控制或数据流复杂性。这组测试用例称为“基线”测试用例。

流变体编号不是“01”的测试用例称为“更复杂”的测试用例。流变体从“02”到“22”(包括)覆盖各种类型的控制流构造,称为“控制流”测试用例。流变体为“31”或更大的测试用例覆盖各种类型的数据流构造,称为“数据流”测试用例。22和31之间的间隙留给未来扩展。

并非所有缺陷类型都有每个流变体的测试用例。这是因为并非所有缺陷类型:

  • 涉及“数据”,因此不能用于数据流测试用例。
  • 可以放置在控制或数据流中,因为缺陷固有于C++类(仅可能为基线测试用例)。
  • 可以通过CAS的当前版本的自定义测试用例模板引擎生成,因此仅为这些缺陷类型创建基线(“01”流变体)测试用例。将来,可能会为这些缺陷类型创建更复杂的测试用例,无论是手动创建还是通过使用增强版本的引擎。
  • 支持与所有控制和数据流的兼容性,可能导致测试用例无法编译或正常工作。这些问题中的一些是不可避免的,因为问题固有于缺陷类型和流变体的组合。其他兼容性问题涉及当前测试用例模板引擎的限制。未来版本的测试引擎可能包含其他组合。

测试用例中使用的流变体在附录C中详细列出。

3.4 测试用例文件

测试用例文件是与恰好一个测试用例相关联的文件(与通常被多个测试用例使用的测试用例支持文件不同)。单个测试用例由一个或多个测试用例文件组成。以下是测试用例及其相关文件名的示例:

C测试用例CWE476_NULL_Pointer_Dereference__char_01由一个文件组成:

  • CWE476_NULL_Pointer_Dereference__char_01.c

C测试用例CWE476_NULL_Pointer_Dereference__char_51由两个文件组成:

  • CWE476_NULL_Pointer_Dereference__char_51a.c
  • CWE476_NULL_Pointer_Dereference__char_51b.c

C测试用例CWE476_NULL_Pointer_Dereference__char_54由五个文件组成:

  • CWE476_NULL_Pointer_Dereference__char_54a.c
  • CWE476_NULL_Pointer_Dereference__char_54b.c
  • CWE476_NULL_Pointer_Dereference__char_54c.c
  • CWE476_NULL_Pointer_Dereference__char_54d.c
  • CWE476_NULL_Pointer_Dereference__char_54e.c

C++测试用例CWE563_Unused_Variable__unused_class_member_value_01由两个文件组成:

  • CWE563_Unused_Variable__unused_class_member_value_01_bad.cpp
  • CWE563_Unused_Variable__unused_class_member_value_01_good1.cpp

测试用例并不完全自包含。它们依赖于其他称为测试用例支持文件的文件,这些文件在第5节中描述。

3.4.1 测试用例文件名

测试用例文件的命名由以下部分按顺序组成:

部分描述可选/强制
“CWE” 字符串文字强制
CWE ID与此测试用例相关的CWE条目的数字标识符,例如“36”
“_” 字符串文字强制
缩写CWE条目名称CWE条目名称的缩写版本,单词之间用下划线连接,例如“Absolute_Path_Traversal”
“__” (两个下划线)字符串文字强制
功能变体名称描述此缺陷的特定变体的单词或短语,例如“fromConsole”。此项在第3.2节中进一步描述。
“_” 字符串文字强制
流变体描述测试用例复杂性类型的两位整数值,例如“01”、“02”或“61”。此项在第3.3节中进一步描述。
子文件标识符标识此文件在由多个文件组成的测试用例中的字符串,例如“a”、“b”、“_bad”、“_good1”。此项在第3.4.2节中进一步描述。
“.” 字符串文字强制
语言标识符/文件扩展名字符串文字“c”、“cpp”或“.h”

表2 – 测试用例文件名组成部分

例如,考虑一个用于评估工具发现整数溢出的能力的测试用例。此测试用例使用“fscanf”函数从控制台读取输入并添加两个数字。此测试用例是这种缺陷的最简单形式,包含在一个文件中:

CWE条目ID:190 缩写CWE条目名称:“Integer_Overflow” 功能变体:“char_fscanf_add” 流变体:01 语言:C

测试用例将包含在名为:

CWE190_Integer_Overflow__char_fscanf_add_01.c的文件中。

3.4.2 子文件标识符

大多数缺陷的较简单形式可以包含在单个源代码文件中,但有些测试用例由多个文件组成。每个文件使用不同类型的字符串来标识测试用例中的每个文件。

  • 一些C++缺陷固有于类,需要为有缺陷和无缺陷构造分别使用不同的文件。在这种情况下,缺陷将在以字符串“_bad”标识的文件中(例如“CWE401_Memory_Leak__destructor_01_bad.cpp”),无缺陷将在以字符串“_good1”标识的文件中(例如“CWE401_Memory_Leak__destructor_01_good1.cpp”)。第4.2节包含有关基于类的缺陷的更多信息。
  • 一些数据流测试用例涉及不同源代码文件中函数之间的数据流。在这些测试用例中,测试用例将“开始”于以字符串“a”标识的文件(例如“CWE476_NULL_Pointer_Dereference__char_54a.c”)。“a”文件中的函数将调用“b”文件中的函数,这些函数可能调用“c”文件中的函数,等等。
  • 一些数据流测试用例涉及虚拟函数调用之间的数据流。在这些测试用例的C++版本中,使用头文件(.h)定义虚拟函数,实现在单独的源(.cpp)文件中。
  • 一些数据流测试用例涉及类构造函数和析构函数之间的数据流。在这些测试用例中,使用头文件(.h)定义构造函数和析构函数,实现在单独的源(.cpp)文件中。

第四章:测试用例设计

4.1 非类基缺陷测试用例

大多数测试用例覆盖的缺陷可以包含在任意函数中(非类基缺陷)。然而,一些缺陷,称为类基缺陷,是C++类定义固有的,必须在测试用例设计中以不同的方式处理。例如:

C++测试用例 CWE416_Use_After_Free__operator_equals_01 (在这个测试用例中,未能定义operator=可能导致程序崩溃,因为可能会使用已经释放的内存。)

虚函数、构造函数/析构函数和仅坏测试用例是独特的。虚拟函数和构造函数/析构函数测试用例需要多个文件,而仅坏测试用例只用于测试缺陷,而不是像其他所有测试用例那样测试缺陷和非缺陷。

所有C/C++测试用例还在主文件中定义了一个“main”函数。当一次编译多个测试用例时,不使用这个主函数。但是,它可以在构建单个测试用例时使用,例如,用于开发人员测试或用于创建用于测试二进制分析工具的二进制文件。

在C/C++测试用例中,预处理器宏INCLUDEMAIN必须在编译时定义,以便在编译中包含这个主函数。

4.1.1 必需函数

针对不是C++类固有的缺陷的测试用例必须定义坏函数和好函数。(注意:一些测试用例被认为是仅坏的,不包含好函数的实现。见第4.3节以了解更多关于这些测试用例的详细信息。)

对于使用多个文件的测试用例,以下函数在“a”子文件中定义(例如,CWE78_OS_Command_Injection__wchar_t_connect_socket_execl_51a.c)。测试用例的“主文件”是多文件测试用例中的“a”子文件的通用术语,或者是单文件测试用例中的唯一文件。

4.1.1.1 主要坏函数

每个测试用例在主文件中包含一个主要坏函数。在许多较简单的测试用例中,这个函数包含有缺陷的构造,但在其他测试用例中,这个函数调用其他包含缺陷的“汇”或“辅助”函数(“汇”和“辅助”函数在后面的部分中描述)。

主要坏函数:

  • 对于C,是以测试用例名称后跟字符串“_bad”命名的,例如“CWE78_OS_Command_Injection__char_connect_socket_execl_01_bad()”。
  • 对于C++,是以bad()命名的,并位于测试用例独有的命名空间中。该函数不是C++类的一部分。
  • 不接受任何参数,也没有返回值。

主要坏函数的名称匹配以下正则表达式: ^(CWE.*_)?bad$

4.1.1.2 主要好函数

每个测试用例在主文件中(与主要坏函数相同的文件)包含一个主要好函数。这个好函数中唯一的代码是对每个次要好函数(在下一节中描述)的调用。然而,一些仅坏测试用例包含空的好函数。这个函数不包含任何无缺陷的构造。

主要好函数:

  • 对于C,这个函数是以测试用例名称后跟字符串“_good”命名的,例如“CWE78_OS_Command_Injection__char_connect_socket_execl_01_good()”。
  • 对于C++,这个函数是以good()命名的,并位于测试用例独有的命名空间中。该函数不是C++类的一部分。
  • 不接受任何参数,也没有返回值。

主要好函数的名称匹配以下正则表达式: ^(CWE.*_)?good$

4.1.1.3 次要好函数(们)

非类基测试用例还在主文件中包含一个或多个次要好函数。然而,一些仅坏测试用例不包括任何次要好函数。在许多较简单的测试用例中,这些次要好函数包含实际的无缺陷构造。在其他测试用例中,这些函数将调用包含无缺陷构造的“汇”或“辅助”函数。次要好函数的数量取决于测试用例的缺陷类型以及存在多少类似于该缺陷的无缺陷构造。许多测试用例只有一个次要好函数,但其他测试用例可能有更多。

有三种命名约定用于次要好函数:

  • goodG2B, goodG2B1, goodG2B2, goodG2B3, 等。 - 这些名称在数据流测试用例中使用,当一个好的源传递安全数据给潜在的坏汇。
  • goodB2G, goodB2G1, goodB2G2, goodB2G3, 等。 - 这些名称在数据流测试用例中使用,当一个坏源传递不安全或潜在不安全数据给好汇。
  • good1, good2, good3, 等。 - 这是这些函数的“默认”或“通用”名称,当上述条件不适用时使用。

次要好函数的名称匹配以下正则表达式: ^good(\d+|G2B\d*|B2G\d*)$

注意:重要的是这个正则表达式不与之前定义的好函数正则表达式重叠,以便不匹配主要好函数。

次要好函数具有与主要坏函数和主要好函数相同的参数和返回类型。此外,次要好函数具有以下特点:

  • 在C和C++测试用例中,次要好函数是静态作用域的。因此,它们只在该源代码文件中可访问,这防止了名称冲突。
  • 在C++测试用例中,次要好函数位于测试用例独有的命名空间中。这些函数不是C++类的一部分。

4.1.2 可选函数

除了必需函数外,测试用例可能还会定义“辅助”、“源”和/或“汇”函数,如下所述。

4.1.2.1 辅助函数

当即使最简单的缺陷形式也不能包含在单个函数中时(在测试用例设计的约束内),测试用例中会使用辅助函数。用于创建数据流模式的函数(“源”和“汇”函数)在更复杂的测试用例中不被视为“辅助”函数,因为它们不是缺陷构造的一部分。

需要辅助函数的测试用例示例包括:

  • 涉及可变参数函数的测试用例,例如C测试用例CWE134_Uncontrolled_Format_String__char_console_vprintf_01。
  • 未使用参数的测试用例,例如C测试用例CWE563_Unused_Variable__unused_parameter_variable_01。

以下是对辅助函数的进一步描述:

  • 辅助函数始终特定于坏函数或好函数。坏辅助和好辅助函数可能包含不同的代码或完全相同的代码(使用单独的函数可以轻松地将工具结果评估为“真阳性”或“假阳性”)。
  • 坏代码的辅助函数命名为“helperBad”。
  • 理想情况下,辅助函数应该特定于单个次要好函数,并命名为“helperGood1”或“helperGoodG2B”。这种命名在手动创建的测试用例中使用,但不幸的是,当前的测试用例模板引擎不支持。在生成的测试用例中,使用了一个名为“helperGood”的通用函数。
  • 在C测试用例中,辅助函数尽可能是静态作用域的。
  • 在C++测试用例中,辅助函数位于测试用例的命名空间中,并且尽可能是静态作用域的。
  • 在多文件测试用例中,辅助函数可能在主文件或其他非主文件中。
  • 在使用可变参数函数的测试用例中,例如CWE134_Uncontrolled_Format_String__char_console_vprintf_01,辅助函数命名为“badVaSink”、“goodG2BVaSink”和“goodB2GVaSink”。在控制流测试用例中,例如CWE134_Uncontrolled_Format_String__char_console_vprintf_02,辅助函数命名为“badVaSinkB”、“goodB2G1VaSinkG”、“goodB2G2VaSinkG”、“goodG2B1VaSinkB”等。尽管它们的名字,这些“VaSink”函数被认为是“辅助”函数而不是“汇”函数,因为它们即使在最简单的缺陷形式中也是需要的。

辅助函数的名称将匹配以下正则表达式: ^(CWE.+)?(helperBad|badVaSink[BG]?)$ ^(CWE.+)?((helperGood(G2B|B2G)?\d*)|(good(G2B|B2G)?\d*VaSink[BG]?))$

4.1.2.2 源和汇函数

包含数据流的测试用例使用“源”和“汇”函数,这些函数彼此调用或从主要坏或好函数调用。每个源或汇函数特定于测试用例的坏函数或确切的一个次要好函数。

以下是对源和汇函数的进一步描述:

  • 坏源和汇函数通常命名为“BadSource”和“BadSink”。
  • 好源函数通常命名为“goodG2BSource”、“goodG2B1Source”、“goodB2GSource”、“goodB2G2Source”等。
  • 好汇函数通常命名为“goodG2BSink”、“goodG2B1Sink”、“goodB2GSink”、“goodB2G2Sink”等。
  • 在C测试用例中,源和汇函数尽可能是静态作用域的。如果该函数从另一个文件调用,则测试用例名称会添加到函数名称的开头。非静态源和汇函数名称的示例包括“CWE134_Uncontrolled_Format_String__char_console_printf_61b_badSource”和“CWE134_Uncontrolled_Format_String__char_console_printf_51b_goodG2BSink”。
  • 在C++中,源和汇函数在该测试用例的命名空间中定义,并且尽可能是静态作用域的。当一个测试用例中使用多个源或多个汇函数时,它们被命名为“badSink_b”、“badSink_c”等和“goodG2BSink_b”、“goodG2BSink_c”等。
  • 在多文件测试用例中,源和汇函数可能定义在主文件或其他非主文件中。

源和汇函数的名称将匹配以下正则表达式: ^(CWE.+)badSource([a-z])?$ ^(CWE.+)badSink([a-z])?$ ^(CWE.+)good(G2B\d*|B2G\d*)?Source([a-z])?$ ^(CWE.+)good(G2B\d*|B2G\d*)?Sink([a-z])?$

4.2 类基缺陷测试用例

C++类基缺陷(即,影响整个类而不仅仅是语句或代码块的缺陷)的测试用例设计略有不同,因为坏和好构造不能包含在任意函数中。这些测试用例使用单独的文件中的单独类。

类基缺陷的坏文件

在类基缺陷的测试用例中,坏文件:

  • 文件名以_bad结尾(在扩展名之前)。例如,“CWE401_Memory_Leak__destructor_01_bad.cpp”。
  • 包含一个必需的坏函数,其签名类似于非类基缺陷测试用例中的坏函数。这个函数使用这个测试用例的坏类来行使正在测试的缺陷。
    • 在C++中,这个函数位于测试用例的命名空间中,但文件中的类之外。
  • 如果文件中只有一个类,则命名为“BadClass”。如果缺陷需要基类和派生类,则这些类在同一文件中,分别命名为“BadBaseClass”和“BadDerivedClass”。
  • 包含一个main函数,调用坏函数。像非类基缺陷测试用例中的main函数一样,这个函数仅用于测试或构建测试用例的单独二进制文件。

类基缺陷的好文件

在类基缺陷的测试用例中,好文件:

  • 文件名以“_good1”结尾(在扩展名之前)。例如,“CWE401_Memory_Leak__destructor_01_good1.cpp”。未来版本的测试用例可能包括包含“_good2”、“_good3”等名称的其他好文件。
  • 包含一个必需的主要好函数,其签名类似于非类基缺陷测试用例中的“good”函数。像非类基缺陷测试用例中的主要好函数一样,这个函数只调用此文件中的次要好函数。
    • 在C++中,这个函数位于测试用例的命名空间中,但文件中的类之外。
  • 包含至少一个必需的次要好函数,命名为“good1”以匹配文件名(目前,只使用“good1”函数名称,但未来版本的测试用例可能使用函数“good2”、“good3”等)。这个次要好函数的签名类似于非类基缺陷测试用例中的次要好函数。这个次要好函数使用此文件中的类来行使正在测试的无缺陷构造。
    • 在C++中,“good1”函数是静态作用域的,并位于测试用例的命名空间中,但文件中的类之外。
  • 在C++中,如果文件中只有一个类,则命名为“GoodClass”。如果需要基类和派生类来行使无缺陷构造,则这些类在同一文件中,分别命名为“GoodBaseClass”和“GoodDerivedClass”。
  • 包含一个main函数,调用主要好函数。像非类基缺陷测试用例中的main函数一样,这个函数仅用于测试或构建测试用例的单独二进制文件。

4.3 虚拟函数测试用例

一些测试用例,如使用流变体81和82的测试用例,使用虚拟函数。为了将这些类型的测试用例适应测试用例套件,它们的设计与传统的测试用例略有不同。

C++虚拟函数数据流测试用例包含五个文件:

  1. 头文件 - 此文件定义基类,并在基类中声明一个名为“action”的函数,作为一个纯虚函数。它还定义了坏和好的类,并在这些类中声明“action”函数,这些函数将实现基类中的“action”函数。此文件的扩展名是标准的“.h”。
  2. 根文件 - 此文件包含坏和好函数的实现。文件名包含字母‘a’作为子文件标识符,是一个C++源文件。
  3. 坏实现文件 - 此文件实现坏类的“action”函数,包含字符串“bad”作为子文件标识符,是一个C++源文件。
  4. GoodG2B实现文件 - 此文件实现一个好类的“action”函数,该好类使用一个坏汇。根文件确保使用好源与这个坏汇一起使用。文件名包含字符串“goodG2B”作为子文件标识符,是一个C++源文件。
  5. GoodB2G实现文件 - 此文件实现一个好类的“action”函数,该好类使用一个好的汇。根文件确保使用坏源与这个好汇一起使用。文件名包含字符串“goodB2G”作为子文件标识符,是一个C++源文件。

例如,CWE191_Integer_Underflow__unsigned_int_rand_sub_81测试用例的文件如下:

  • CWE191_Integer_Underflow__unsigned_int_rand_sub_81.h
  • CWE191_Integer_Underflow__unsigned_int_rand_sub_81a.cpp
  • CWE191_Integer_Underflow__unsigned_int_rand_sub_81_bad.cpp
  • CWE191_Integer_Underflow__unsigned_int_rand_sub_81_goodG2B.cpp
  • CWE191_Integer_Underflow__unsigned_int_rand_sub_81_goodB2G.cpp

4.4 构造函数/析构函数测试用例

一些测试用例,如使用流变体83和84的测试用例,包含在类的构造函数和析构函数之间的数据流。包含数据流“源”的代码包含在构造函数中,包含数据流“汇”的代码包含在析构函数中。像第4.3节中描述的虚拟函数测试用例一样,这些类型的测试用例设计得与传统的测试用例略有不同,以便它们能够适应测试用例套件。

C++构造函数/析构函数数据流测试用例包含五个文件:

  1. 头文件 - 此文件定义坏和好的类。这些类每个都包含一个单一的构造函数和析构函数。此文件的扩展名是标准的“.h”。
  2. 根文件 - 此文件包含坏和好函数的实现。这些函数创建将调用在以下实现文件中描述的构造函数/析构函数的对象。文件名包含字母“a”作为子文件标识符,是一个C++源文件。
  3. 坏实现文件 - 此文件实现坏类的构造函数和析构函数,包含字符串“bad”作为子文件标识符,是一个C++源文件。
  4. GoodG2B实现文件 - 此文件实现一个好类的构造函数和析构函数,该好类使用一个坏汇。好源实现包含在构造函数中,坏汇实现包含在析构函数中。文件名包含字符串“goodG2B”作为子文件标识符,是一个C++源文件。
  5. GoodB2G实现文件 - 此文件实现一个好类的构造函数和析构函数,该好类使用一个好的汇。坏源实现包含在构造函数中,好汇实现包含在析构函数中。文件名包含字符串“goodB2G”作为子文件标识符,是一个C++源文件。

4.5 仅坏测试用例

在测试用例设计过程中,我们发现在少数情况下无法生成一个正确的无缺陷构造来修复正在测试的缺陷。因此,有一小部分测试用例被认为是“仅坏”的,即它们只包含有缺陷的构造。

仅坏测试用例与其他测试用例的不同之处在于:

  • 所有仅坏测试用例都是非类基的。
  • 没有仅坏测试用例包含数据流。

仅坏测试用例遵循与非类基测试用例相同的命名方案。需要注意的是,这些测试用例应该从任何试图确定静态分析工具报告的假阳性数量的分析中排除。这些测试用例的列表出现在附录D中。


第五章:测试用例支持文件

如第3.4节所述,测试用例不是自包含的。每个测试用例至少需要一个通用的测试用例支持文件。还有一些特定于CWE条目的测试用例支持文件,适用于相关的测试用例。此外,还提供了包含主函数的支持文件来执行测试用例。

以下各节描述了每种测试用例支持文件的目的和内容。

5.1 通用支持文件

每个测试用例都需要一个或多个通用支持文件,这些文件位于~testcasesupport目录中。

标准测试用例头文件:

  • std_testcase.h - 此头文件包含在每个C/C++测试用例源代码文件中,包含多个变量和宏定义。它还包括其他头文件,如“std_testcase_io.h”和系统头文件“stdio.h”,这样它们就不需要被每个测试用例包含。 输入/输出相关的支持文件:

  • io.c - 此文件包含多个函数的定义,这些函数被测试用例源代码文件用来向控制台打印各种类型。例如,printLine()用于向控制台打印字符数组。测试用例使用此文件中的函数而不是直接调用控制台输出函数,以防止静态分析工具意外报告“不当日志记录”或“可能的数据泄露”。此文件还包含多个全局变量的定义,这些变量被控制流测试用例使用。

  • std_testcase_io.h - 此头文件声明了在io.c中定义的函数和变量。它没有被命名为io.h,因为Windows中有一个同名的系统头文件。

线程相关的支持文件:

  • std_thread.c - 此文件包含多个线程相关函数的实现,这些函数被测试用例源代码文件使用。
  • std_thread.h - 此头文件用于定义std_thread.c中的函数。

5.2 CWE条目特定支持文件

除了通用支持文件外,测试用例可能会使用特定于多个测试用例的CWE条目的支持文件。当存在这些文件时,它们将位于CWE条目的目录中,并且名称与测试用例文件的预期模式不匹配。

5.3 主函数支持文件

用于测试单个CWE条目测试用例文件也是支持的。这些文件称为main.cpp和testcases.h,是自动生成的,并包含在每个CWE条目中(例如,在~testcases\CWE15_External_Control_of_System_or_Configuration_Setting目录中)。它们可以用来测试CWE条目目录中的所有测试用例。

从C/C++的Juliet测试套件v1.2开始,一些CWE条目被拆分到多个子目录中,由于文件数量庞大。每个子目录限制最多包含1,000个测试用例文件,并包含一个main.cpp文件和一个testcases.h文件。这些文件可以用来编译和测试子目录中的所有测试用例。

在~testcasesupport目录中,每个文件都有一个“主”版本,用于一次运行所有测试用例。

每个文件描述如下:

  • main.cpp - 此文件是自动生成的,包含一个“main”函数,该函数调用每个测试用例的主要“good”函数,然后调用每个测试用例的主要“bad”函数。此文件可以使用预处理器宏OMITBAD或OMITGOOD编译,以省略对坏函数或好函数的调用。
  • testcases.h - 此头文件是自动生成的,包含测试用例中坏函数和好函数的声明,以便main.cpp文件可以调用它们。此头文件只包含在main.cpp中。

大多数测试用例应该可以在非Windows操作系统上编译。包含字符串“w32”的Windows特定测试用例文件名。如果每个CWE条目的目录(或目录)包含将在Linux上编译的测试用例,则包括以下支持文件:

  • main_linux.cpp - 此文件是自动生成的,包含一个“main”函数,该函数调用每个非Windows特定测试用例的主要“good”函数,然后是主要“bad”函数。此文件可以使用预处理器宏OMITBAD或OMITGOOD编译,以省略对“bad”或“good”函数的调用。
  • Makefile - 一个标准makefile,当由实用程序“make”执行时,将编译CWE目录中的所有非Windows特定测试用例。

3 测试用例专门针对Windows平台设计。所有测试用例已在Windows上测试并成功编译。


第六章:构建测试用例

6.1 构建先决条件

此分发中包含的所有文件需要在以下环境中使用(开发和测试使用的版本如下所示):

  • Microsoft Windows平台(Windows 7)
  • Microsoft Visual Studio(2010 Professional)
  • Python for Windows(版本3.2.3)

当前发布的测试用例针对Microsoft Windows平台;然而,许多测试用例将在非Windows平台上工作。Windows特定的测试用例文件包含字符串“w32”在它们的名称中。

尽管上述版本用于开发和测试测试用例,但其他版本也可能有效。

6.2 编译测试用例

有两种方式可以编译这些测试用例:作为一个包含所有测试用例的单一编译可执行文件;或者作为每个包含单个CWE条目的单独可执行文件。

请注意,一些CWE条目的测试用例被拆分到多个子目录中。使用每个子目录中的main.cpp和testcases.h编译代码将只编译该文件夹中的测试用例,而不是CWE条目的所有测试用例。另外,由于文件数量和测试用例中包含的代码行数,一些静态分析工具可能无法分析单个编译的可执行文件。

6.2.1 编译所有测试用例为一个可执行文件

要编译一个名为“testcases.exe”的单个(大)可执行文件,请运行位于顶层目录的文件“compile_all.bat”。此批处理文件必须在设置Visual Studio特定环境变量的情况下运行,这最容易通过运行“Visual Studio命令提示符”来完成。此批处理文件可以作为分析所有测试用例的基础,具体请参考所使用工具的文档中的说明。

非Windows特定的测试用例可以编译成一个名为“all-testcases”的单个(大)可执行文件,方法是运行“make”并针对顶层目录中的“Makefile_all”。

  • 24 -

6.2.2 按CWE条目编译测试用例

测试用例也可以编译,以便为每个CWE条目生成单独的可执行文件,有一些例外。这是通过在该CWE条目的目录中运行批处理文件来完成的(例如,通过运行~testcases\CWE476_NULL_Pointer_Dereference目录中的“CWE476.bat”来创建文件“CWE476.exe”)。

为了自动化每个CWE条目目录中单独测试用例的编译过程,可以执行“run_analysis_example_tool.py” Python脚本(也在“Visual Studio命令提示符”中)。此脚本将前往每个CWE条目目录并运行批处理文件以编译这些测试用例。此脚本也可以作为自动化执行每个CWE条目测试用例分析的脚本的基础。脚本中的注释提供了如何完成此操作的示例。

通过在CWE目录中运行“make”,也可以编译给定CWE的测试用例。请注意,有几个CWE目录不包含make文件,因为这些CWE的所有测试用例都是Windows特定的。

6.2.2.1 包含子目录的CWE条目

由于一些CWE条目的测试用例文件数量庞大,这些CWE的测试用例文件被拆分到每个目录不超过1,000个测试用例文件的子目录中。例如,CWE 590的测试用例被拆分为以下子目录:

  • ~testcases\CWE590_Free_Memory_Not_on_Heap\s01
  • ~testcases\CWE590_Free_Memory_Not_on_Heap\s02
  • ~testcases\CWE590_Free_Memory_Not_on_Heap\s03
  • ~testcases\CWE590_Free_Memory_Not_on_Heap\s04
  • ~testcases\CWE590_Free_Memory_Not_on_Heap\s05

每个子目录包含一个批处理文件,可以用来编译该目录中的所有测试用例文件(例如,“CWE590_s01.bat”)。批处理文件和生成的可执行文件都包含CWE编号和子目录编号在它们的文件名中。例如,“CWE590_s01.bat”可以执行以编译位于~testcases\CWE590_Free_Memory_Not_on_Heap\s01目录中的测试用例为“CWE590_s01.exe”。

请注意,给定功能变体的所有流变体将出现在同一子目录中。

6.3 编译单个测试用例

尽管测试用例通常以集合的形式编译和分析,但测试用例的设计使得每个测试用例都可以单独编译和执行。运行单个测试用例在测试用例开发期间非常有用,但也可以用于单独分析测试用例。

6.3.1 构建和运行测试用例

在测试用例中,存在一个主函数,其中包含对测试用例主要好函数的调用,随后是对坏函数的调用。编译测试用例时使用预处理器宏INCLUDEMAIN来包含主函数。还可以使用预处理器宏OMITBAD和OMITGOOD来省略编译中的缺陷或非缺陷构造。省略测试用例的某些部分允许编译一个仅包含缺陷或仅包含非缺陷的二进制文件,这在测试二进制分析工具时可能会很有用。

以下示例命令将使用Visual Studio命令行编译器将单个文件测试用例编译为文件testcase.exe。此命令应在“Visual Studio命令提示符”中运行,位于测试用例.zip文件解压的目录中。

1
cl /Itestcasesupport /DINCLUDEMAIN /Fetestcase.exe testcasesupport\io.c testcasesupport\std_thread.c testcases\CWE78_OS_Command_Injection\s02\CWE78_OS_Command_Injection__char_console_system_01.c

以下示例命令将编译多个文件测试用例为文件testcase.exe。

1
cl /Itestcasesupport /DINCLUDEMAIN /Fetestcase.exe testcasesupport\io.c testcasesupport\std_thread.c testcases\CWE78_OS_Command_Injection\s02\CWE78_OS_Command_Injection__char_console_system_54*.c

在这两种情况下,这将生成一个可执行文件testcase.exe。


第七章:更新构建文件

测试用例分发包中包含的脚本可用于在对要分析的测试用例集进行更改时更新测试用例构建文件。使用分发的测试用例或在对现有测试用例文件进行编辑后,不需要使用这些脚本。如果从测试用例集中删除了新测试用例,或者添加了新测试用例,则需要谨慎遵循测试用例设计,以防止在这些脚本、编译或工具结果分析中出现错误。

7.1 更新C/C++构建文件

C/C++测试用例归档包含三个脚本,可用于在对要分析的测试用例集进行更改时更新构建文件。

  • create_single_batch_file.py - 运行此脚本将更新文件“compile_all.bat”,可以运行该文件以将所有测试用例编译为一个可执行文件。此脚本还会编辑源代码和头文件,以确保成功编译此批处理文件所需。
  • create_single_Makefile.py - 运行此脚本将更新文件“Makefile_all”,可以通过“make”编译所有非Windows特定测试用例为一个可执行文件。此脚本还会编辑源代码和头文件,以确保成功编译此makefile所需。
  • create_per_cwe_files.py - 运行此脚本将更新批处理文件和非Windows特定测试用例的makefile(如果存在),以编译每个CWE条目的测试用例为单独的可执行文件。此脚本还会编辑源代码和头文件,以确保成功编译这些批处理文件所需。

第八章:工具分析

测试用例的设计使得静态分析工具的结果可以轻松评估。本节描述了在测试用例上运行静态分析工具时所期望的结果。

8.0 译者阅读补充

  1. 真阳性 (True Positive, TP)
    预测为阳性且实际为阳性的样本。也就是说,模型正确地预测出了阳性案例。
    例子:预测病人患病且实际患病。

  2. 假阴性 (False Negative, FN)
    预测为阴性,但实际为阳性。也就是说,模型没有识别出真实的阳性案例。
    例子:预测病人健康,但实际上病人患病。

  3. 假阳性 (False Positive, FP)
    预测为阳性,但实际为阴性。也就是说,模型错误地预测为阳性。
    例子:预测病人患病,但实际上病人健康。

  4. 真阴性 (True Negative, TN)
    预测为阴性且实际为阴性的样本。也就是说,模型正确地预测出了阴性案例。
    例子:预测病人健康且实际健康。

8.1 真阳性与假阴性

当静态分析工具在测试用例上运行时,一个期望的结果是工具报告一个目标类型的缺陷。该报告的缺陷应位于名称中包含“bad”的函数(例如bad()、badSink()或CWE476_NULL_Pointer_Dereference__char_41_bad())中,或者在文件名中包含“bad”的类的实现中(例如“CWE401_Memory_Leak__destructor_01_bad.cpp”)。这种类型的正确报告被视为“真阳性”。

在某些情况下,工具可能会在坏函数或类中多次报告测试用例中的缺陷。例如,工具可能报告多个稍微不同的缺陷类型,或者在其他情况下,工具可能在不同的位置报告缺陷。有时,工具可能在同一位置报告两个结果,类型完全相同(有时具有不同的调用堆栈或其他不同的元数据)。

如果工具未在测试用例的坏函数或类中报告目标类型的缺陷,则该工具在测试用例上的结果被视为“假阴性”。

8.2 假阳性与真阴性

在测试用例上运行工具时,另一个期望的结果是工具不报告任何位于名称中包含“good”的函数或文件名中包含“good”的类中的目标类型缺陷。错误地报告好函数中的目标缺陷类型被称为“假阳性”。

如第4.1.1.3节所述,每个非类基测试用例都有一个或多个次要好函数,包含无缺陷的构造。当测试用例有多个次要函数时,测试用例用户可能希望确定工具在哪些次要好函数中报告了假阳性,以及在哪些次要好函数中工具没有假阳性(即,工具有“真阴性”)。

在许多测试用例中,可以通过检查工具报告的结果所在的函数名称来确定这一点。源函数和汇函数可以与它们被调用的次要好函数关联(例如,函数goodB2GSource或goodB2GSink可以与次要好函数goodB2G关联)。

不幸的是,CAS的测试用例模板引擎的限制使得无法将所有假阳性结果明确地与测试用例中的次要好函数关联。具体而言,如第4.1.2.1节所述,好的辅助函数并不特定于测试用例中的次要好函数。因此,在一个测试用例中,如果有多个次要好函数,并且在一个好的辅助函数中报告了一个或多个假阳性,则无法轻松地将假阳性与次要好函数关联,因此也无法确定真阴性。

8.3 无关缺陷报告

工具还可能报告与测试用例中的目标缺陷类型无关的缺陷。有两种情况可能发生这种情况:

  • 这些缺陷报告可能正确地指出测试用例中存在的非目标类型缺陷。这种类型的缺陷被称为“附带缺陷”。测试用例的开发者试图最小化附带缺陷,并用包含字符串“INCIDENTAL”的注释标记不可避免的附带缺陷。然而,许多未注释的附带缺陷仍然存在,因此用户在研究工具报告的非目标缺陷类型时,不应轻易得出结论。
  • 缺陷报告可能指示测试用例中不存在的缺陷。这种类型的缺陷报告被称为“无关假阳性”,因为它们是错误的缺陷报告(假阳性),而且与测试用例旨在测试的缺陷类型无关。

非目标类型的缺陷报告通常无法以自动或简单的方式进行正确或错误的分类。它们可能由在大量测试用例中重复的常见代码构造触发(由于用于创建测试用例的自动生成过程)。出于这些原因,这些缺陷报告在研究静态分析工具时通常被忽略。