从典型工具看 SAST 工具的可扩展性实现
可扩展性,是 SAST 工具非常重要的评价指标。一款工具自身提供的检查能力,正常都无法满足一家企业的全部需求,因此必然就涉及到规则和能力的扩展。本文从几种典型的 SAST 工具(Coverity、Fortify、SonarQube、Klocwork、CodeQL)的扩展方式入手,分析典型的 SAST 工具的扩展方式,并分析什么样的扩展能力更有价值。
通过对部分典型的 SAST 工具进行梳理,SAST 的主要的扩展方式有如下五种:
基于 SDK 扩展,主要用于规则开发
基于 DSL 扩展,主要用于规则开发,函数行为描述
基于代码模型扩展,主要用于第三方库行为描述,和基于 SDK 有些类似
基于配置文件扩展,主要用于规则开发和第三方库行为描述
基于 XPath 扩展,主要用于结构化规则开发
下面将对上面的 5 种类型的 SAST 工具的扩展方式进行介绍。
1. 基于 SDK 的扩展方式
基于 SDK 的扩展方式,主要就是对外暴露 SAST 工具原生的检查引擎的 API,让外部的检查工具通过调用这些 API,从而实现能力的扩展。当前支持该方式的检查工具有:Coverity、Klocwork、SonarQube、Clang Static Analyzer 等。下面将对部分工具的 SDK 扩展规则方式进行介绍。
1.1 基于 SDK 的扩展方式示例
其实这一部分,我最想拿 Coverity 来举例,但是我在公开资料上,没有搜索到相关的资料,担心构成信息安全风险,所以这里选择 Klocwork 和 SonarQube 来举例。
1.1.1 Klocwork Path 规则的扩展示例[1]
如下,我们直接举一个 Klocwork 的 Path 自定义规则的例子的代码(参考资料中的源代码)来说明:
关于上面 Klocwork Path 规则的三个类,已经加了简单的注释。我们可以看到,Klocwork 在创建自定义规则时,调用了原生的 SDK,例如:在设置 Source 和 Sink 时,使用了统一的父类 Trigger,并都使用了 extract 方法,在执行分析任务时,使用的检查引擎 SDK 是 SourceSinkAnalyzerPtr,通过 getConditionalSourceSinkChecker 方法创建。
1.1.2 SonarQube AST 规则的扩展示例[2]
如下,我们直接举一个 SonarQube 创建 Java 自定义规则的例子的代码(参考资料中的源代码)来说明:
如上,是一个检查在 Java 代码中,是否有使用 Annotation 的规则,如果有使用,报告警。上面的规则,调用了 SonarQube 提供的 SDK,对源码的 AST 进行遍历,从而分析得到是否有使用 Annotation 的代码。
1.2 基于 SDK 的扩展方式实现
我们在上一节中,分别采用 SonarQube 和 Klocwork 介绍了两类规则的开发:
结构化规则的开发,在 SonarQube 中,通过调用原生引擎提供的 SDK,采用 Visitor 遍历的方式,检查问题场景;
数据流规则的开发,在 Klocwork Path 规则中,调用了基于污点分析的数据流引擎 SourceSinkAnalyzer,并分别定义 Source 和 Sink 来进行检查。
如果 SAST 工具支持其他的检查引擎,例如基于状态机的检查等,可以相应地曝露相关的 API 接口,在自定义规则中,可以调用相关的 API 完成规则开发。
这里,SDK 的调用,并完成规则开发并不难,主要的难点在于完成的规则如何调起来。这里我们介绍两类调用方式:
自定义规则作为一个单独的可执行文件执行,例如 Klocwork 和 Coverity 就是这样的实现机制,这种机制实现起来也比较容易;
自定义规则以插件的方式加载到执行引擎中,和内置规则一起加载使用。例如 SonarQube 和 Clang Static Analyzer 就是这样的实现机制。
1.3 基于 SDK 的扩展方式优缺点分析
那么,这样的实现方式有什么优缺点呢?我觉得优缺点都非常明显。
主要的优点有:
可以充分利用原生 SDK 的能力,实现更加灵活的定制;
使用原生的开发语言,能够实现更加深层次的复杂的规则开发。
主要的缺点有:
规则定制有一定的准入门槛,比如用户如果要开发 Klocwork Path 规则,需要会 C++ 开发;
规则 SDK 确定好后就无法轻易变动,否则容易造成向下不兼容。
2. 基于 DSL 的扩展方式
DSL 就是针对 SAST 工具,专门设计一种简化的检查语言。DSL 是实现难度相对比较复杂的一种扩展方式,这里简单介绍两个 Fortify 的结构化规则 和 CodeQL 的 DSL(业界标杆),Coverity 大概在 19 年左右推出 CodeXM 的 DSL(这里不用 Coverity 举例,但是其实实现的能力都类似)。
2.1 基于 DSL 的扩展方式示例
2.1.1 Fortify 结构化自定义规则举例[3]
下面是一个 Fortify 的自定义规则示例。
如上,真正的 DSL 部分是上面的第 10-22 行,实际上,这种一个针对 C/C++ 语言开发的自定义规则,表达的含义是检查某个“=”运算,这个“=”右边,应该是个函数调用,函数名是 “GetPrivateData”,“=”左边应该是个对某个变量的域的访问,并且这个变量的类型是 Response。
2.1.2 CodeQL 自定义规则举例
下面介绍一个 CodeQL 的自定义规则的例子:
上面的这个例子,是 CodeQL 首页上面的一个例子。是一个数据流规则的告警,要求从 source 到 sink 可达,并且 sink 是 UnsafeDeserializationSink。
CodeQL 提供了我当前知道的,最丰富的 DSL 定义的 API。
2.2 基于 DSL 的扩展方式实现
一般来说,基于 DSL 的扩展方式,对面向结构化的代码检查更有用处。因为如果是面向数据流的规则,很多时候通过配置方式就可以实现,而且更加简单直观。
如果要实现一种 DSL,是一种非常复杂的功能,在这里的篇幅,是无法完全介绍清楚的。下面简单介绍两个大的方向(后面有机会,单独介绍 DSL 的扩展实现):
设计一种规则扩展的语言,使用 Antlr4 等解析规则,生成对应的语法树,然后在引擎中开发代码,通过匹配节点的方式,解释执行,最后报告警;
采用业界经典的 DSL 设计框架,例如 Kotlin,设计适用于 SAST 工具的 DSL,这样就不是解释执行,而是直接运行了,这种方式在当前商用工具中使用比较少,我之前写过一个简单的 demo[4],大家可以参考(我有想过把 xml 舍弃掉,做成一个更好看的 DSL 来着,就是一直没有时间)。
不建议大家考虑自己实现一个 DSL 还支持把这个 DSL 编译成可执行文件去执行,工作量太大了,如果选择把这个 DSL 编译成 class 文件,然后再解释执行,完全可以选择 Kotlin DSL。
2.3 基于 DSL 扩展方式优缺点分析
基于 DSL 的扩展方式,优缺点也非常明显:
主要的优点有:
对于一个 SAST 工具中支持的所有语言,提供一个有限支持的 DSL,可以简化规则开发难度,提高用户体验;
用户不需要去学习一门高级语言;
让你的 SAST 工具看起来就高大上了起来。
主要的缺点有:
DSL 是一种有限支持的规则开发方式,检查能力跟对外暴露的接口相关,支持的语法特性有限,因此一般来说,基于 DSL 只能进行有限的规则能力支持,误报和漏报相对于基于 SDK 会较高,并且部分规则场景可能都没办法支持。
3. 基于代码模型的扩展方式
这一部分内容,在 Coverity 中有看到类似。但是其机制并不复杂,如果要做相关的开发,并不是很难。
这里的模型,其实就是一种第三方库的摘要信息。通过写代码的方式,调用 Coverity 的原语,从而对函数的行为加以定义,这就是这类扩展方式。
下面介绍一下 Coverity 中这一类扩展的例子。
3.1 Coverity 模型扩展举例
如下面的例子:
在代码中,my_free 函数,在其中给了一个实现,是通过宏定义的,宏名是 __coverity_free__,该宏的调用会告诉 Coverity,如果代码中调用了 my_free 函数,就相当于释放了该函数的第一个参数指针,就不会有内存泄漏。基于该配置,可以避免出现相关的误报。
上面的 __coverity_free__ 在 Coverity 中被称为原语(primitive),Coverity 中,提供了非常非常多的类似的原语。
在 Coverity 中,原语的类型非常多,可以分为:
资源管理相关的原语
安全相关的原语
并发相关的原语
3.2 模型扩展方式实现
前面提到过,模型扩展方式实现相对比较简单,但是也要看实现机制。目前看来,主要有两类实现途径(我们使用 Coverity 中的原语的概念):
通过 SAST 工具内建模型原语,然后将写好的模型文件(即调用了原语的 C/C++源码)和源码一起编译,并且链接在一起,自然模型中的函数相关的实现就会取代原来的实现,从而可以直接进行进一步分析;
通过 SAST 工具内建模型原语,然后将写好的模型文件(即调用了原语的 C/C++源码)单独编译,生成摘要信息,然后在分析中使用摘要。
实际上,Coverity 采用的是第 2 种方式,但是我们讲,摘要并不好建立,函数摘要没有银弹,有可能每一条规则、每一种代码结构可能都需要单独建立摘要。
4. 基于配置文件的扩展方式
基于配置文件的 SAST 工具扩展方式,在 SAST 工具中应用非常常见。下面我们介绍两种配置文件的方式,分别对应了规则相关的配置扩展和第三方库的函数行为的配置扩展。
4.1 基于配置文件的扩展方式示例
4.1.1 SonarQube 的配置文件的扩展示例(面向规则扩展)[5]
SonarQube 可以通过配置文件,给特定的规则增加 Source、Sink、Passthrough 等节点,可以通过 JSON 配置,Fortify 也有类似的机制,但是是通过 XML 格式配置的,并且配置的是 TaintFlag 相关的传递信息,整体类似。
下面是 SonarQube 官方的一个示例:
为了缩小篇幅,删掉了一部分,原生的配置,可以参考下面的链接。
如上,配置文件的信息,可以对 Source、Sink、Passthrough、Sanitizer 等进行配置,从而实现规则的误报优化,或者减少漏报。实现规则扩展。
4.1.2 Klocwork kb 文件的配置扩展示例(面向第三方库扩展)[6]
Klocwork 的 KB 文件,是对第三方库的函数行为进行建模的一种配置方式,并不是直接针对规则的配置,其目的和上一节介绍的 Coverity 的模型类似。比如对于下面的代码(该例子是从上面引用中摘下来的):
比如对于上面的代码,如果不希望在第 6 行报内存泄漏的问题,可以在 kb 文件中,对 custom_alloc 添加如下的配置:
该配置,告诉 Klocwork 忽略掉 custom_alloc 的 ALLOC 的记录(record)。Klocwork 支持非常多的记录类型(record kind,比如前面的 ALLOC 就是一种 record kind)。
这种建模方式,跟 Coverity 的模型建模方式,目的类似,但是没有 Coverity 灵活,能实现的功能不如 Coverity 丰富,但是会比 Coverity 简单一点儿。
4.2 基于配置文件的扩展方式的优缺点
一般来说,基于配置文件的扩展方式,实现比较简单,大部分的 SAST 工具,支持的第一步,一般就是基于配置的扩展,只是看配置文件中配置的丰富程度各有不同。
基于配置文件的扩展方式的优点:
配置简单清晰,容易理解
方便阅读,直观简单
基于配置文件的扩展的缺点:
能力略显不足,无法实现复杂的配置
并不适合结构化规则
5. 基于 XPath 的扩展方式
基于 XPath 的规则扩展方式,就是通过 XPath 的方式,对 AST 进行遍历的一种方式,也只适用于 AST,不适用于其他场景。
5.1 基于 XPath 的扩展方式示例
PMD 就支持基于 XPath 的规则,大家可以看看,这里举一个 Klocwork KAST 的规则扩展的例子。
5.1.1 Klocwork KAST 规则举例[7]
比如针对如下代码:
我们希望找到所有的 static 函数,只需要写如下表达式,创建 KAST 规则即可:
5.2 基于 XPath 的规则扩展方式的优缺点
基于 XPath 的规则的扩展方式非常简单,如我们之前提到的,XPath 的扩展方式只适合 AST 遍历,使用受限,实现上,大家可以参考 PMD 的实现。
主要的优点有:
结构紧凑
逻辑比较清晰
主要的缺点有:
只支持结构化规则
XPath 原生在部分属性函数支持上比较弱,如果要支持需要进行更多的额外开发
6. 总结
本文介绍了部分 SAST 工具的扩展方式。从实现上面来说,可以采用多种扩展方式的组合。这里,笔者基于个人经验,给如下建议:
对外提供的工具版本,建议支持 基于 DSL 的扩展 + 模型扩展
基于 SDK 的方式,可以作为专业服务进行提供
配置文件的方式能够提供的功能比较弱,但是实现容易,可以作为初始版本提供的能力,再慢慢迭代
基于 XPath 的方式,在商用工具中并不多见,不建议采用
当然,上面是个人建议,仅供参考。
参考
[1] Klocwork Path 规则 API 参考文档(https://help.klocwork.com/2023/pdfs/Klocwork_C_Cxx_PATH_API_Reference.pdf)
[2] SonarQube Java 规则开发举例(https://github.com/SonarSource/sonar-java/blob/master/docs/java-custom-rules-example)
[3] fortify-structural-rules-guide(https://github.com/ryohare/fortify-structural-rules-guide)
[4] java-sca-ast-dsl(https://github.com/maijun-sec/java-sca-ast-dsl)
[5] Security engine custom configuration(https://docs.sonarsource.com/sonarqube/latest/analyzing-source-code/security-engine-custom-configuration/)
[6] Walk-through 3: Tuning with knowledge base records(https://help.klocwork.com/2022/en-us/concepts/walkthrough3tuningwithknowledgebaserecords.htm)
[7] Find all static functions(https://help.klocwork.com/current/en-us/concepts/findallstaticfunctions.htm)
版权声明: 本文为 InfoQ 作者【maijun】的原创文章。
原文链接:【http://xie.infoq.cn/article/7b0c613d2afbd29d1916eea73】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论