使用部分流调试数据流查询¶
注意
此处描述的新的模块化数据流 API 自 CodeQL 2.13.0 起与之前的库一起提供。有关库更改方式以及如何将任何现有查询迁移到模块化 API 的信息,请参阅 CodeQL 查询编写的新的数据流 API.
如果数据流查询没有生成您预期看到的结果,则可以使用部分流来调试问题。
在 CodeQL 中,您可以使用 数据流分析 来计算变量在程序中的不同位置可能持有的值。典型的數據流查詢如下所示
module MyConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof MySource }
predicate isSink(DataFlow::Node node) { node instanceof MySink }
}
module MyFlow = TaintTracking::Global<MyConfig>;
from MyFlow::PathNode source, MyFlow::PathNode sink
where MyFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Sink is reached from $@.", source.getNode(), "here"
通过不使用 路径解释,可以稍微简化相同的查询
from DataFlow::Node source, DataFlow::Node sink
where MyFlow::flow(source, sink)
select sink, "Sink is reached from $@.", source.getNode(), "here"
如果您编写的數據流查詢没有生成您期望它生成的结果,则您的查询可能存在问题。您可以尝试按照以下步骤调试潜在问题。
检查源和接收器¶
首先,您应该确保源和接收器定义包含您期望的内容。如果源或接收器为空,则永远不会有任何数据流。检查此方法的最简单方法是使用 CodeQL for VS Code 中的快速评估。选择文本 node instanceof MySource
,右键单击并选择“CodeQL: 快速评估”。这将评估突出显示的文本,在本例中表示源集。有关更多信息,请参阅 GitHub 文档中的 运行 CodeQL 查询。
如果源和接收器定义看起来都很好,那么我们需要寻找缺少的流步骤。
fieldFlowBranchLimit
¶
数据流配置包含一个名为 fieldFlowBranchLimit
的参数。如果将值设置得太高,您可能会遇到性能下降,但如果设置得太低,您可能会错过结果。在调试数据流时,尝试将 fieldFlowBranchLimit
设置为一个较高的值,并查看您的查询是否生成了更多结果。例如,尝试将以下内容添加到您的配置中
int fieldFlowBranchLimit() { result = 5000 }
如果没有结果并且性能仍然可用,那么最好在进行进一步调试时将此值设置为一个较高的值。
部分流¶
下一个简单的步骤可能是将接收器定义更改为 any()
。这意味着我们将获得大量流向从源可到达的所有位置。虽然这种方法在某些情况下可能有效,但您可能会发现它会生成如此多的结果,以至于很难探索这些发现。它还会严重影响查询性能。更重要的是,您可能甚至无法看到所有部分流路径。这是因为数据流库竭尽全力修剪不可能的路径,并且由于字段存储和读取必须沿路径均匀匹配,因此我们永远不会看到经过存储但无法到达相应读取的路径。这使得很难看到流实际上在哪里停止。
为了避免这些问题,数据流库提供了一种用于探索部分流的机制,该机制试图处理这些警告。这是 MyFlow::FlowExplorationFwd<explorationLimit/0>::partialFlow
谓词
/**
* Holds if there is a partial data flow path from `source` to `node`. The
* approximate distance between `node` and the closest source is `dist` and
* is restricted to be less than or equal to `explorationLimit()`. This
* predicate completely disregards sink definitions.
*
* This predicate is intended for dataflow exploration and debugging and may
* perform poorly if the number of sources is too big and/or the exploration
* limit is set too high without using barriers.
*
* To use this in a `path-problem` query, import the module `PartialPathGraph`.
*/
predicate partialFlow(PartialPathNode source, PartialPathNode node, int dist) {
还有一个 MyFlow::FlowExplorationRev<explorationLimit/0>::partialFlow
用于从接收器向后探索流。
要访问这些谓词,您必须使用探索限制实例化 MyFlow::FlowExplorationFwd<>
模块(或对于反向流,实例化 MyFlow::FlowExplorationRev<>
模块)。例如
int explorationLimit() { result = 5 }
module MyPartialFlow = MyFlow::FlowExplorationFwd<explorationLimit/0>;
这定义了 partialFlow
返回结果的探索半径。
将单个源作为流探索的起点很有用。最简单的方法是在 isSource
谓词中添加一个临时限制。
要快速评估部分流,最简单的方法通常是向查询中添加一个专门用于快速评估的谓词(右键单击谓词名称,然后选择“CodeQL: 快速评估”)。一个好的起点是像这样
predicate adhocPartialFlow(Callable c, MyPartialFlow::PartialPathNode n, DataFlow::Node src, int dist) {
exists(MyPartialFlow::PartialPathNode source |
MyPartialFlow::partialFlow(source, n, dist) and
src = source.getNode() and
c = n.getNode().getEnclosingCallable()
)
}
如果您专注于单个源,那么 src
列是多余的。您当然也可以根据 n
添加其他感兴趣的列,但至少包括封闭的可调用项和到源的距离通常是推荐的,因为它们是用于对结果进行排序以更好地检查结果的有用列。
如果看到大量部分流结果,您可以通过以下两种方式进行聚焦
- 如果流沿着预期的路径走得很远,这会导致大量无趣的流包含在探索半径中。为了减少无趣的流的数量,您可以用沿路径出现的合适
node
替换源定义,并从该点重新启动部分流探索。 - 可以巧妙地使用障碍来切断无趣的流路径。这在调试时也会减少要探索的部分流结果的数量。