注意
此处描述的数据流库可从 CodeQL 2.12.5 开始使用。随着 CodeQL 2.13.0 的发布,该库使用新的模块化 API 进行数据流。有关库的先前版本的信息,请参阅 分析 C 和 C++ 中的数据流,有关新模块化 API 以及如何将任何现有查询迁移到更新的数据流库的信息,请参阅 CodeQL 查询编写的新数据流 API。
分析 C 和 C++ 中的数据流(新)¶
您可以使用数据流分析来跟踪可能存在恶意或不安全数据流,这些数据流会导致代码库中的漏洞。
关于数据流¶
数据流分析计算变量在程序中的各个点可能具有的值,确定这些值如何在程序中传播以及它们在何处使用。在 CodeQL 中,您可以对本地数据流和全局数据流进行建模。有关数据流建模的更一般介绍,请参阅“关于数据流分析。”
本地数据流¶
本地数据流是单个函数内的数据流。本地数据流通常比全局数据流更容易、更快且更精确,并且对于许多查询来说已经足够了。
使用本地数据流¶
本地数据流库位于模块 DataFlow
中,该模块定义了表示数据可以流经的任何元素的类 Node
。 Node
分为表达式节点 (ExprNode
, IndirectExprNode
) 和参数节点 (ParameterNode
, IndirectParameterNode
)。间接节点表示经过固定数量的指针解引用后的表达式或参数。
可以使用成员谓词 asExpr
、asIndirectExpr
和 asParameter
在数据流节点和表达式或参数之间进行映射。
class Node {
/**
* Gets the expression corresponding to this node, if any.
*/
Expr asExpr() { ... }
/**
* Gets the expression corresponding to a node that is obtained after dereferencing
* the expression `index` times, if any.
*/
Expr asIndirectExpr(int index) { ... }
/**
* Gets the parameter corresponding to this node, if any.
*/
Parameter asParameter() { ... }
/**
* Gets the parameter corresponding to a node that is obtained after dereferencing
* the parameter `index` times.
*/
Parameter asParameter(int index) { ... }
...
}
谓词 localFlowStep(Node nodeFrom, Node nodeTo)
如果从节点 nodeFrom
到节点 nodeTo
存在立即数据流边,则为真。该谓词可以递归地应用(使用 +
和 *
运算符),或者通过预定义的递归谓词 localFlow
应用,该谓词等效于 localFlowStep*
。
例如,可以按以下方式查找从参数 source
到表达式 sink
在零个或多个本地步骤中的流,其中 nodeFrom
和 nodeTo
的类型为 DataFlow::Node
nodeFrom.asParameter() = source and
nodeTo.asExpr() = sink and
DataFlow::localFlow(nodeFrom, nodeTo)
使用本地污点跟踪¶
本地污点跟踪通过包括非值保留流步骤来扩展本地数据流。例如
int i = tainted_user_input();
some_big_struct *array = malloc(i * sizeof(some_big_struct));
在这种情况下,malloc
的参数被污染了。
本地污点跟踪库位于模块 TaintTracking
中。与本地数据流一样,谓词 localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo)
如果从节点 nodeFrom
到节点 nodeTo
存在立即污点传播边,则为真。该谓词可以递归地应用(使用 +
和 *
运算符),或者通过预定义的递归谓词 localTaint
应用,该谓词等效于 localTaintStep*
。
例如,可以按以下方式查找从参数 source
到表达式 sink
在零个或多个本地步骤中的污点传播,其中 nodeFrom
和 nodeTo
的类型为 DataFlow::Node
nodeFrom.asParameter() = source and
nodeTo.asExpr() = sink and
TaintTracking::localTaint(nodeFrom, nodeTo)
示例¶
以下查询查找传递给 fopen
的文件名
import cpp
from Function fopen, FunctionCall fc
where
fopen.hasGlobalName("fopen") and
fc.getTarget() = fopen
select fc.getArgument(0)
但是,这只会给出参数中的表达式,而不是可能传递给它的值。相反,我们可以使用本地数据流来查找流入参数的所有表达式,其中我们使用 asIndirectExpr(1)
。这是因为我们感兴趣的是传递给 fopen 的字符串的值,而不是指向它的指针
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
from Function fopen, FunctionCall fc, Expr src, DataFlow::Node source, DataFlow::Node sink
where
fopen.hasGlobalName("fopen") and
fc.getTarget() = fopen and
source.asIndirectExpr(1) = src and
sink.asIndirectExpr(1) = fc.getArgument(0) and
DataFlow::localFlow(source, sink)
select src
然后,我们可以更改源,例如使用函数的参数。以下查询查找在打开文件时使用参数的位置
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
from Function fopen, FunctionCall fc, Parameter p, DataFlow::Node source, DataFlow::Node sink
where
fopen.hasGlobalName("fopen") and
fc.getTarget() = fopen and
source.asParameter(1) = p and
sink.asIndirectExpr(1) = fc.getArgument(0) and
DataFlow::localFlow(source, sink)
select p
以下示例查找格式化函数的调用,其中格式字符串不是硬编码的。
import semmle.code.cpp.dataflow.new.DataFlow
import semmle.code.cpp.commons.Printf
from FormattingFunction format, FunctionCall call, Expr formatString, DataFlow::Node sink
where
call.getTarget() = format and
call.getArgument(format.getFormatParameterIndex()) = formatString and
sink.asIndirectExpr(1) = formatString and
not exists(DataFlow::Node source |
DataFlow::localFlow(source, sink) and
source.asIndirectExpr(1) instanceof StringLiteral
)
select call, "Argument to " + format.getQualifiedName() + " isn't hard-coded."
全局数据流¶
全局数据流跟踪整个程序中的数据流,因此比本地数据流更强大。但是,全局数据流不如本地数据流精确,并且分析通常需要更长的时间和更多的内存来执行。
注意
您可以通过创建路径查询来在 CodeQL 中对数据流路径进行建模。要在用于 VS Code 的 CodeQL 中查看路径查询生成的数据流路径,您需要确保它具有正确的元数据和
select
子句。有关更多信息,请参阅 创建路径查询。
使用全局数据流¶
全局数据流库通过实现签名 DataFlow::ConfigSig
并应用模块 DataFlow::Global<ConfigSig>
来使用,如下所示
import semmle.code.cpp.dataflow.new.DataFlow
module MyFlowConfiguration implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
...
}
predicate isSink(DataFlow::Node sink) {
...
}
}
module MyFlow = DataFlow::Global<MyFlowConfiguration>;
以下谓词在配置中定义
isSource
—定义数据可能流出的位置isSink
—定义数据可能流入的位置isBarrier
—可选,限制数据流isAdditionalFlowStep
—可选,添加其他流步骤
数据流分析是使用谓词 flow(DataFlow::Node source, DataFlow::Node sink)
执行的
from DataFlow::Node source, DataFlow::Node sink
where MyFlow::flow(source, sink)
select source, "Data flow to $@.", sink, sink.toString()
使用全局污点跟踪¶
全局污点跟踪对于全局数据流就像本地污点跟踪对于本地数据流一样。也就是说,全局污点跟踪通过额外的非值保留步骤来扩展全局数据流。全局污点跟踪库通过将模块 TaintTracking::Global<ConfigSig>
应用于您的配置而不是 DataFlow::Global<ConfigSig>
来使用,如下所示
import semmle.code.cpp.dataflow.new.TaintTracking
module MyFlowConfiguration implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
...
}
predicate isSink(DataFlow::Node sink) {
...
}
}
module MyFlow = TaintTracking::Global<MyFlowConfiguration>;
生成的模块与从 DataFlow::Global<ConfigSig>
获得的模块具有相同的签名。
示例¶
以下数据流配置跟踪从环境变量到在类 Unix 环境中打开文件的流
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
module EnvironmentToFileConfiguration implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(Function getenv |
source.asIndirectExpr(1).(FunctionCall).getTarget() = getenv and
getenv.hasGlobalName("getenv")
)
}
predicate isSink(DataFlow::Node sink) {
exists(FunctionCall fc |
sink.asIndirectExpr(1) = fc.getArgument(0) and
fc.getTarget().hasGlobalName("fopen")
)
}
}
module EnvironmentToFileFlow = DataFlow::Global<EnvironmentToFileConfiguration>;
from
Expr getenv, Expr fopen, DataFlow::Node source, DataFlow::Node sink
where
source.asIndirectExpr(1) = getenv and
sink.asIndirectExpr(1) = fopen and
EnvironmentToFileFlow::flow(source, sink)
select fopen, "This 'fopen' uses data from $@.", getenv, "call to 'getenv'"
以下污点跟踪配置跟踪从对 ntohl
的调用到数组索引操作的数据。它使用 Guards
库来识别已进行边界检查的表达式,并定义 isBarrier
以阻止污点通过它们传播。它还使用 isAdditionalFlowStep
来添加从循环界限到循环索引的流。
import cpp
import semmle.code.cpp.controlflow.Guards
import semmle.code.cpp.dataflow.new.TaintTracking
module NetworkToBufferSizeConfiguration implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
node.asExpr().(FunctionCall).getTarget().hasGlobalName("ntohl")
}
predicate isSink(DataFlow::Node node) {
exists(ArrayExpr ae | node.asExpr() = ae.getArrayOffset())
}
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(Loop loop, LoopCounter lc |
loop = lc.getALoop() and
loop.getControllingExpr().(RelationalOperation).getGreaterOperand() = pred.asExpr()
|
succ.asExpr() = lc.getVariableAccessInLoop(loop)
)
}
predicate isBarrier(DataFlow::Node node) {
exists(GuardCondition gc, Variable v |
gc.getAChild*() = v.getAnAccess() and
node.asExpr() = v.getAnAccess() and
gc.controls(node.asExpr().getBasicBlock(), _) and
not exists(Loop loop | loop.getControllingExpr() = gc)
)
}
}
module NetworkToBufferSizeFlow = TaintTracking::Global<NetworkToBufferSizeConfiguration>;
from DataFlow::Node ntohl, DataFlow::Node offset
where NetworkToBufferSizeFlow::flow(ntohl, offset)
select offset, "This array offset may be influenced by $@.", ntohl,
"converted data from the network"
答案¶
练习 1¶
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
from StringLiteral sl, FunctionCall fc, DataFlow::Node source, DataFlow::Node sink
where
fc.getTarget().hasName("gethostbyname") and
source.asIndirectExpr(1) = sl and
sink.asIndirectExpr(1) = fc.getArgument(0) and
DataFlow::localFlow(source, sink)
select sl, fc
练习 2¶
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
module LiteralToGethostbynameConfiguration implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asIndirectExpr(1) instanceof StringLiteral
}
predicate isSink(DataFlow::Node sink) {
exists(FunctionCall fc |
sink.asIndirectExpr(1) = fc.getArgument(0) and
fc.getTarget().hasName("gethostbyname")
)
}
}
module LiteralToGethostbynameFlow = DataFlow::Global<LiteralToGethostbynameConfiguration>;
from
StringLiteral sl, FunctionCall fc, DataFlow::Node source, DataFlow::Node sink
where
source.asIndirectExpr(1) = sl and
sink.asIndirectExpr(1) = fc.getArgument(0) and
LiteralToGethostbynameFlow::flow(source, sink)
select sl, fc
练习 3¶
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
class GetenvSource extends DataFlow::Node {
GetenvSource() { this.asIndirectExpr(1).(FunctionCall).getTarget().hasGlobalName("getenv") }
}
练习 4¶
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
class GetenvSource extends DataFlow::Node {
GetenvSource() { this.asIndirectExpr(1).(FunctionCall).getTarget().hasGlobalName("getenv") }
}
module GetenvToGethostbynameConfiguration implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof GetenvSource }
predicate isSink(DataFlow::Node sink) {
exists(FunctionCall fc |
sink.asIndirectExpr(1) = fc.getArgument(0) and
fc.getTarget().hasName("gethostbyname")
)
}
}
module GetenvToGethostbynameFlow = DataFlow::Global<GetenvToGethostbynameConfiguration>;
from
Expr getenv, FunctionCall fc, DataFlow::Node source, DataFlow::Node sink
where
source.asIndirectExpr(1) = getenv and
sink.asIndirectExpr(1) = fc.getArgument(0) and
GetenvToGethostbynameFlow::flow(source, sink)
select getenv, fc