为 C 和 C++ 自定义库模型¶
您可以对控制任何框架或库中数据流的方法和可调用项进行建模。这对于自定义框架或标准 CodeQL 库不支持的利基库特别有用。
Beta 版通知 - 不稳定的 API
使用数据扩展进行的库自定义目前处于测试阶段,可能会有所变化。
此格式在测试阶段可能会出现重大更改。
关于本文¶
本文包含有关如何在数据扩展文件中为 C 和 C++ 依赖项定义自定义源、接收器和流摘要模型的参考材料。
关于数据扩展¶
您可以通过在数据扩展文件中定义代码的 C 和 C++ 依赖项的模型(摘要、接收器和源)来自定义分析。每个模型都定义了库或框架的一个或多个元素(例如可调用项)的行为。当您运行数据流分析时,这些模型将扩展数据流分析跟踪的潜在源和接收器,并提高结果的准确性。
许多安全查询会搜索从不受信任的输入源到代表漏洞的接收器的路径。这被称为污点跟踪。每个源都是数据流分析的起点,用于跟踪受污染的数据,每个接收器都是终点。
污点跟踪查询还需要了解数据如何在未包含在源代码中的元素中流动。这些元素被建模为摘要。摘要模型使查询能够综合依赖代码中未存储在存储库中的元素的流行为。
用于在扩展文件中定义元素的语法¶
每个元素的模型都是使用数据扩展定义的,其中每个元组构成一个模型。用于扩展 CodeQL 附带的标准 CPP 查询的数据扩展文件是一个 YAML 文件,形式如下
extensions:
- addsTo:
pack: codeql/cpp-all
extensible: <name of extensible predicate>
data:
- <tuple1>
- <tuple2>
- ...
每个 YAML 文件可能包含一个或多个顶级扩展。
addsTo
定义 CodeQL 包名称和扩展注入的扩展谓词。data
定义一个或多个元组行,这些元组行作为值注入扩展谓词中。列数及其类型必须与扩展谓词的定义匹配。
数据扩展使用联合语义,这意味着将单个扩展谓词的所有扩展的元组组合在一起,删除重复项,并将所有剩余的元组通过引用扩展谓词进行查询。
用于在 C 和 C++ 中创建自定义模型的扩展谓词¶
用于 CPP 分析的 CodeQL 库公开了以下扩展谓词
sourceModel(namespace, type, subtypes, name, signature, ext, output, kind, provenance)
。这用于对潜在受污染数据的来源进行建模。使用此谓词定义的源的kind
决定了它们关联的威胁模型。不同的威胁模型可用于自定义分析中使用的源。有关更多信息,请参阅“威胁模型”。sinkModel(namespace, type, subtypes, name, signature, ext, input, kind, provenance)
。这用于对受污染数据可能以使代码易受攻击的方式使用的位置(接收器)进行建模。summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind, provenance)
。这用于对元素的流进行建模。
扩展谓词使用数据扩展文件中定义的模型进行填充。
自定义模型定义示例¶
本节中的示例取自 GitHub 发布的标准 CodeQL CPP 查询包。它们演示了如何添加元组以扩展标准查询使用的扩展谓词。
示例:来自 boost::asio
命名空间的污点源¶
此示例显示了 CPP 查询包如何将 read_until
函数的返回值建模为 remote
源。
boost::asio::read_until(socket, recv_buffer, '\0', error);
我们需要通过更新数据扩展文件向 sourceModel
(namespace, type, subtypes, name, signature, ext, output, kind, provenance) 扩展谓词添加一个元组。
extensions:
- addsTo:
pack: codeql/cpp-all
extensible: sourceModel
data:
- ["boost::asio", "", False, "read_until", "", "", "Argument[*1]", "remote", "manual"]
由于我们要添加一个新的源,因此需要向 sourceModel
扩展谓词添加一个元组。前五个值标识要建模为源的可调用项(在本例中为自由函数)。
- 第一个值
"boost::asio"
是命名空间名称。 - 第二个值
""
是包含方法的类型(类)的名称。因为我们正在对自由函数进行建模,所以类型为空。 - 第三个值
False
是一个标志,指示接收器是否也适用于该方法的所有重写。对于自由函数,这应该是False
。 - 第四个值
"read_until"
是函数名称。 - 第五个值是函数输入类型签名,它可用于缩小具有相同名称的函数之间的范围。在本例中,我们希望模型包含
boost::asio
中名为read_until
的所有函数。
第六个值应保留为空,并且不在本文档范围之内。其余值用于定义输出规范、kind
和 provenance
(来源)的源。
- 第七个值
"Argument[*1]"
是输出规范,在本例中,这意味着接收器是传递给函数的第二个参数 (Argument[1]
) 的第一个间接引用(或指向的值,*
)。 - 第八个值
"remote"
是源的类型。源类型用于定义源在其中处于作用域的威胁模型。remote
适用于许多安全相关查询,因为它表示不受信任数据的远程源。有关更多信息,请参阅“威胁模型”。 - 第九个值
"manual"
是源的来源,用于标识源模型的来源。
示例:boost::asio
命名空间中的污点接收器¶
此示例显示了 CPP 查询包如何将 boost::asio::write
函数的第二个参数建模为远程流接收器。远程流接收器是将数据通过网络传输到其他机器的位置,例如,“敏感信息的明文传输” (cpp/cleartext-transmission) 查询使用它。
boost::asio::write(socket, send_buffer, error);
我们需要通过更新数据扩展文件向 sinkModel
(namespace, type, subtypes, name, signature, ext, input, kind, provenance) 扩展谓词添加一个元组。
extensions:
- addsTo:
pack: codeql/cpp-all
extensible: sinkModel
data:
- ["boost::asio", "", False, "write", "", "", "Argument[*1]", "remote-sink", "manual"]
由于我们要添加一个新的接收器,因此需要向 sinkModel
扩展谓词添加一个元组。前五个值标识要建模为接收器的可调用项(在本例中为自由函数)。
- 第一个值
"boost::asio"
是命名空间名称。 - 第二个值
""
是包含方法的类型(类)的名称。因为我们正在对自由函数进行建模,所以类型为空。 - 第三个值
False
是一个标志,指示接收器是否也适用于该方法的所有重写。对于自由函数,这应该是False
。 - 第四个值
"write"
是函数名称。 - 第五个值是函数输入类型签名,它可用于缩小具有相同名称的函数之间的范围。在本例中,我们希望模型包含
boost::asio
中名为write
的所有函数。
第六个值应保留为空,并且不在本文档范围之内。其余值用于定义输出规范、kind
和 provenance
(来源)的接收器。
- 第七个值
"Argument[*1]"
是输出规范,在本例中,这意味着接收器是传递给函数的第二个参数 (Argument[1]
) 的第一个间接引用(或指向的值,*
)。 - 第八个值
"remote-sink"
是接收器的类型。接收器类型用于定义接收器在其中处于作用域的查询。 - 第九个值
"manual"
是接收器的来源,用于标识接收器模型的来源。
示例:添加通过 boost::asio::buffer
方法的流¶
此示例展示了 CPP 查询包模型在简单情况下如何通过函数流动。
boost::asio::write(socket, boost::asio::buffer(send_str), error);
我们需要通过更新数据扩展文件,将元组添加到 summaryModel
(命名空间、类型、子类型、名称、签名、扩展、输入、输出、类型、来源)可扩展谓词中。
extensions:
- addsTo:
pack: codeql/cpp-all
extensible: summaryModel
data:
- ["boost::asio", "", False, "buffer", "", "", "Argument[*0]", "ReturnValue", "taint", "manual"]
由于我们正在添加通过函数的流,因此需要将元组添加到 summaryModel
可扩展谓词中。
前五个值标识要建模为摘要的可调用对象(在本例中为自由函数)。
- 第一个值
"boost::asio"
是命名空间名称。 - 第二个值
""
是包含方法的类型(类)的名称。因为我们正在对自由函数进行建模,所以类型为空。 - 第三个值
False
是一个标志,指示接收器是否也适用于该方法的所有重写。对于自由函数,这应该是False
。 - 第四个值
"buffer"
是函数名称。 - 第五个值是函数输入类型签名,可用于缩小具有相同名称的函数范围。在本例中,我们希望模型包括
boost::asio
中名为buffer
的所有函数。
第六个值应留空,并且在本文档中不包含。其余值用于定义输入和输出规范、kind
以及摘要的 provenance
(来源)。
- 第七个值是输入规范(数据流入的地方)。
Argument[*0]
指定第一个参数 (Argument[0]
) 传递给函数的第一个间接寻址(或指向的值,*
)。 - 第八个值
"ReturnValue"
是输出规范(数据流出的地方),在本例中是返回值。 - 第九个值
"taint"
是流的类型。taint
表示污点通过调用传播。 - 第十个值
"manual"
是摘要的来源,用于标识摘要模型的来源。
威胁模型¶
注意
威胁模型目前处于测试阶段,可能会发生变化。在测试期间,威胁模型仅受 Java 和 C# 分析支持。
威胁模型是数据流源的命名类,可以独立启用或禁用。威胁模型允许您控制要视为不安全的数据流源集。例如,一个代码库可能只将远程 HTTP 请求视为污点,而另一个代码库可能还将来自本地文件的数据视为不安全。您可以使用威胁模型来确保在 CodeQL 分析中使用相关污点源。
sourceModel
的 kind
属性确定源关联的威胁模型。主要有两类
remote
代表来自网络的请求和响应。local
代表来自本地文件 (file
)、命令行参数 (commandargs
)、数据库读取 (database
) 和环境变量 (environment
) 的数据。
运行 CodeQL 分析时,默认情况下会包含 remote
威胁模型。在使用 CodeQL CLI 和 GitHub 代码扫描时,您可以根据需要选择性地包含其他威胁模型。有关更多信息,请参阅 使用 CodeQL 查询分析您的代码 和 自定义您的代码扫描高级设置。