Semmle QL:TaintTracking hasFlow() 污染其参数的来源问题

Semmle QL: TaintTracking hasFlow() Problem with Sources that taint their Arguments

我想对使用用户输入污染其参数的函数进行 TaintTracking。示例:

fgets(buf, sizeof(buf), stdin); // buf is tainted
[...]
n = strlen(buf); // tainted argument to strlen
[...]
memcpy(somewhere, buf, n) // tainted call to memcpy

Semmle 应该能够通过如下所示的查询发现这一点(仅以 fgets->strlen 为例)。我从 SecurityOptions 借用代码:

import cpp
import semmle.code.cpp.dataflow.TaintTracking

class IsTaintedArg extends string {
  IsTaintedArg() { this = "IsTaintedArg" }
  predicate userInputArgument(FunctionCall functionCall, int arg) {
    exists(string fname |
      functionCall.getTarget().hasGlobalName(fname) and
      exists(functionCall.getArgument(arg)) and (fname = "fgets" and arg = 0) // argument 0 of fgets is tainted
    )
  }

  predicate isUserInput(Expr expr, string cause) {
    exists(FunctionCall fc, int i |
      this.userInputArgument(fc, i) and
      expr = fc.getArgument(i) and
      cause = fc.getTarget().getName()
    )
  }
}

class TaintedFormatConfig extends TaintTracking::Configuration {
  TaintedFormatConfig() { this = "TaintedFormatConfig" }
  override predicate isSource(DataFlow::Node source) {
    exists (IsTaintedArg opts |
      opts.isUserInput(source.asExpr(), _)
    )
  }
  override predicate isSink(DataFlow::Node sink) { 
    exists (FunctionCall fc | sink.asExpr() = fc.getArgument(0) and fc.getTarget().hasName("strlen")) // give me all calls that land in strlen's first argument
  }
}

from TaintedFormatConfig cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select sink, source

但它看起来并不正常。

然而,当我只查询 cfg.isSource()cfg.isSink() 时,source 和 sink 都被识别了。但是 hasFlow() 仍然 returns 什么都没有 - 尽管路径肯定存在。

有谁知道我在上面的查询中可能做错了什么?

缺少的位在 isSource 中,其中污点跟踪从第 0 个参数开始到 fgets。使用 asExpr 将描述从该参数 fgets 的流程。我们想要的是 out of fgets 通过该参数的流程。您可以通过将 asExpr 替换为 asDefiningArgument.

来获得它

Here 是您查询结果的 link,我在 isSource 中同时使用了 asExprasDefiningArgument。这意味着如果您将来扩展 isUserInput,它的表达式将被视为来源,既作为它们的值又作为输出参数。

新版查询有8个结果,有些只看source和sink会比较难理解,因为它们可以在不同的文件中。我制作了一个清理版本的查询

  • 生成源和汇之间的路径解释 (@kind path-problem),
  • 删除了辅助谓词的 IsTaintedArg class 包装,
  • 删除了一些未使用的参数并进行了检查,并且
  • 将调用添加到 asDefiningArgument

这是完整的查询:

/**
 * @kind path-problem
 * @id taint-to-strlen
 */
import cpp
import semmle.code.cpp.dataflow.DataFlow
import DataFlow::PathGraph

predicate userInputArgument(FunctionCall functionCall, int arg) {
  functionCall.getTarget().hasGlobalName("fgets") and
  arg = 0 // argument 0 of fgets is tainted
}

predicate isUserInput(Expr expr) {
  exists(FunctionCall fc, int i |
    userInputArgument(fc, i) and
    expr = fc.getArgument(i)
  )
}

class TaintedFormatConfig extends DataFlow::Configuration {
  TaintedFormatConfig() { this = "TaintedFormatConfig" }
  override predicate isSource(DataFlow::Node source) {
    isUserInput(source.asExpr())
    or
    isUserInput(source.asDefiningArgument())
  }
  override predicate isSink(DataFlow::Node sink) { 
    exists (FunctionCall fc | sink.asExpr() = fc.getArgument(0) and fc.getTarget().hasName("strlen"))
  }
}

from TaintedFormatConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "Taint from fgets call in " + source.getNode().getFunction().getFile().getBaseName()

您可以在 https://lgtm.com/query/4800800615370766111/ 查看结果。