众所周知,高级静态分析工具擅长发现程序中的一般缺陷。借助自定义的属性检查器,最终用户还可以扩展它们,以查找特定应用程序特有的特定于域的错误。
先进的静态分析工具使用复杂的全程序、路径敏感技术来发现深层语义问题,包括安全缺陷和安全漏洞。这些工具识别的几个缺陷可以在CWE/SANS前25个最危险的编程错误中找到,其中列出了为了降低安全风险而要避免的最重要的错误。
具体来说,开箱即用的高级静态分析工具可以找到通用编程缺陷。来自静态分析工具的典型通用报告可能会显示警告,例如未初始化的变量、缓冲区溢出、无法访问的条件和无法访问的调用等;但是,并非所有安全漏洞或安全错误都是由一般问题引起的。许多缺陷是特定应用所独有的。
这些工具的一个经常被低估的方面是它们是可扩展的,因此通常也可以对其进行配置或编程以查找违反特定于域的规则的情况。因此,如果开发团队有自己的内部规则来使用专有API,或者要求程序员使用特定的习惯用语,那么通常可以编写一个检查器来表示违反这些规则。因此,程序员通常只需很少的编程工作,就可以显着增加他们从工具中获得的价值。
一个常见的用例是当发生错误并触发漫长而痛苦的诊断和调试期时。找到缺陷后,明智的做法是首先在代码中查找重复错误的其他位置,然后采取措施检测错误是否再次发生。对现有代码和新代码运行自定义检查可能是实现此目的的有效、低成本方法。这些工具的工作原理以及如何自定义它们的检查器是使用 CodeSonar 中的代码片段描述的,尽管这些原则也可以与其他高级静态分析工具一起使用。
高级静态分析的工作原理
要了解扩展的不同编写方式,首先要了解什么是静态分析,以及静态分析工具如何在后台工作。
静态分析工具的工作方式与编译器非常相似。他们将源代码作为输入,然后对其进行解析并转换为中间表示 (IR)。高级静态分析工具架构的框图如图 1 所示。编译器将使用 IR 生成目标代码,而静态分析工具保留 IR,而检查器通常通过遍历或查询 IR 来实现,查找指示缺陷的特定属性或模式。
图1:高级静态分析工具的架构
高级工具从复杂的符号执行技术中获得强大功能,这些技术通过控制流图探索路径。这些算法跟踪程序的抽象状态,并知道如何使用该状态来排除对不可行路径的考虑。
特定于域的属性的检查器可以访问这些表示形式,并以各种方式利用分析算法。
自定义检查器
最终用户如何创作自定义属性检查器?这在很大程度上取决于属性的性质,因此有几种机制可用:
可以通过向配置文件添加指令来扩展现有检查器。
用户可以向其代码添加注释,以指示分析查找某些属性。如果用户不希望干扰源代码,则可以以面向方面的方式在侧面完成这些注释。
API 允许用户访问所有中间表示形式。通常,使用访问者模式,允许扩展利用分析已在执行的遍历。
让我们仔细看看这些机制。
配置文件
这些先进的静态分析工具实现了数十个检查器。通常,用户需要一个与内置检查器略有不同的检查器,并且其中许多检查器都设计为可扩展的。一类检查器查找禁止使用的函数。例如,众所周知,C 库函数 get 是不安全的。检查器由一个阶段实现,该阶段查找对函数名称的引用,然后将它们与一组正则表达式进行匹配。通过向配置文件添加行来向此集合添加其他正则表达式是一件简单的事情。
代码注释
编写检查器的第二种方法是向代码添加注释。
例如,假设有一个名为 foo 的内部函数采用单个整数参数,并且当此参数为 -1 时引入了潜在的安全漏洞。可以通过向foo主体添加一些代码来实现对这种情况的检查,如下所示:
void foo(int x)
{
#ifdef __CSURF__
csonar_trigger(x, “==”, -1,
“Dangerous call to foo()”);
#endif __CSURF__
…
}
#ifdef构造可确保常规编译器看不到此新代码。但是,当该工具分析此代码时,会看到对csonar_trigger的调用。因此,此调用从未实际执行,而是由工具解释为对基础分析引擎的指令。如果分析得出结论,可能满足触发条件,则它将发出带有给定消息的警告。
大多数程序员不喜欢用这样的注释来混淆他们的代码,所以有一种替代方法来实现这种检查,允许它写在单独的文件中。当 foo 的源代码不可用时,例如当它位于第三方库中时,此方法也适用。为此,检查器的作者将编写一个替换函数,如下所示:
void csonar_replace_foo(int x)
{
csonar_trigger(x, “==”, -1,
“Dangerous call to foo()”);
foo();
}
当分析看到csonar_replace_foo的定义时,它会将代码中的所有 foo 调用(csonar_replace_foo 内部的调用除外)视为对 csonar_replace_foo 的调用。
此触发器习惯用法适用于检查时态属性,尤其是函数调用序列。假设有一条规则说,在 foo 执行时,永远不应该调用 bar。可以按如下方式实施检查:
static int foo_is_executing = 0;
void csonar_replace_foo(int x) {
foo_is_executing = 1;
foo(x);
foo_is_executing = 0;
}
void csonar_replace_bar(void) {
csonar_trigger(foo_is_executing,
“==”, 1,
“Call to bar from foo”);
bar();
}
请注意,全局状态变量用于记录 foo 是否处于活动状态。在输入 foo 之前,它被设置为 1,然后在 foo 返回后重置为零。然后在 bar 中的触发器中检查此变量,如果设置为 1,将发出警告。
前面的示例演示如何检查全局属性。相同的机制可用于编写对单个对象的属性的检查。可以将属性附加到对象以跟踪其状态。
这种方法允许用户几乎像编写动态检查一样编写静态检查。这种检查的 API 很小,语言是 C,所以有一个温和的学习曲线。这种简单性具有欺骗性:该技术可用于实现相当复杂的检查。
用于中间表示的 API
实现自定义检查的最终方法是使用提供对基础表示形式 (IR) 的访问权限的 API。一家公司使用此 API 查找用于处理硬件错误的自定义习惯用法的冲突。
此 API 也可用于其他程序分析任务。例如,一家医疗设备公司主要使用一种工具,不仅识别其代码中的潜在任务问题,而且还发出允许他们交互式探索堆栈配置属性的信息。其他应用程序包括收集代码的自定义指标和生成程序可视化工具的输入。
可以使用访问者模式编写许多检查 - 该函数作为给定类型的每个 IR 元素的回调调用。可以为控制流图和语法树中的文件、标识符、子程序和节点定义访问者。访问者的接口允许与构造进行模式匹配。
CodeSonar具有一种特殊的访问者类型,在控制流图的路径探索期间调用该访问者。这允许检查器作者编写复杂的检查,以利用该工具的内置路径敏感程序分析功能。这种检查器的一个关键方面是它使用分析部分,消除了对不可行路径的探索,这会自动降低误报结果的概率。
提高软件质量
先进的静态分析工具已成为软件开发人员必不可少的工具,因为它们能够发现严重的安全和安全缺陷。创作自定义检查器是提高这些工具有效性的低成本方法。
审核编辑:郭婷
-
API
+关注
关注
2文章
1481浏览量
61776 -
编译器
+关注
关注
1文章
1617浏览量
49026
发布评论请先 登录
相关推荐
评论