CodeQL学习

1.数据流分析

1.1.简介

data-flow-analysis

CodeQL数据流lib通过对程序或函数的数据流图进行建模来实现对其的数据流分析。数据流边表示2个程序元素之间的数据流。比如 x || y 表达式中存在 x --> (x || y)y --> (x || y) 的数据流边。

同时数据流也分为局部数据流(local data flow)和全局数据流(global data flow):

  • 本地数据流只考虑函数内的数据流而忽略函数之间以及通过对象属性的数据流。

  • 全局数据流都会考虑。

污点追踪会向数据流图中引入额外的边,这些边并不精确地对应于value flow,而是对运行时的某个值是否可以从另一个值派生进行建模,例如通过字符串操作。

计算精确和完整的数据流图存在以下挑战:

  • 对于无法接触到源代码的标准库函数,计算通过这些函数的数据流不可行。

  • 有些行为要到运行时才能确定,这意味着数据流lib必须采取额外的步骤来找到潜在的调用目标。

  • 变量之间的别名可能导致一个写入操作可能更改多个指针指向的值(需要指针分析)。

  • 数据流图可能很大,因此计算可能非常耗时。

CodeQL实现了2种数据流lib计算数据流图:

  • 本地数据流:只需要考虑函数内的数据流。对于许多查询来说,它通常足够快速、高效和精确,并且通常可以计算CodeQL数据库中所有函数的本地数据流。

  • 全局数据流:通常考虑程序内的所有数据流。通常比计算本地数据流更耗时,因此应优化查询语句以寻找更具体的source和sink点。

在标准库中,CodeQL对常规数据流污点跟踪进行了区分。常规数据流库用于分析信息流,其中每个步骤都保留数据值。

在QL中,污点跟踪通过一些步骤来扩展数据流分析。在这些步骤中,数据值不一定会被保留,但潜在的不安全对象仍然会被传播。这些流步骤在污点跟踪库中使用谓词进行建模,如果污点在节点之间传播,则谓词会保持不变。

可进一步参考exploring-data-flow-with-path-queries

1.2.分析C/C++数据流

关于C/C++基本分析可以参考codeql-for-cpp。这里只写数据流分析。

1.2.1本地数据流

本地数据流库位于模块 DataFlow 中,它定义了类 Node,表示数据可以流经的任何元素。Node 分为表达式节点(ExprNode)和参数节点(ParameterNode)。可以使用成员谓词 asExprasParameter 在数据流节点和表达式/参数之间进行映射。或者使用谓词 exprNodeparameterNode

class Node {
  /** Gets the expression corresponding to this node, if any. */
  Expr asExpr() { ... }

  /** Gets the parameter corresponding to this node, if any. */
  Parameter asParameter() { ... }
  
  /**
 * Gets the node corresponding to expression `e`.
 */
  ExprNode exprNode(Expr e) { ... }

  /**
 * Gets the node corresponding to the value of parameter `p` at function entry.
 */
  ParameterNode parameterNode(Parameter p) { ... }
  
  ...
}

谓词 localFlowStep(Node nodeFrom, Node nodeTo) 在存在从 nodeFromnodeTo 的直接数据流边(immediate data flow edge)时成立。谓词可以递归应用(使用 +* 运算符,这里运算符的语义分别是正则表达式中至少1次和至少0次,不是算数运算符),也可以通过预定义的递归谓词localFlow 应用,localFlow 等效于 localFlowStep* (即调用1次或多次 localFlowStep)。

表达式 DataFlow::localFlow(DataFlow::parameterNode(source), DataFlow::exprNode(sink)) 可以通过0步或多步找出从形参 source 到表达式 sink 的数据流。

1.2.2.本地污点追踪

本地污点跟踪通过包括non-value-preserving步骤来扩展本地数据流。比如:

int i = tainted_user_input();
some_big_struct *array = malloc(i * sizeof(some_big_struct));

这种情况下,malloc 的实参被污染了。

本地污点追踪lib位于 TaintTracking 模块中。与本地数据流一样,如果存在从节点 nodeFrom 到节点 nodeTo 的直接污点传播边 ,则谓词 localTaintStep(DataFlow:Node nodeFrom,DataFlow:Node nodeTo) 成立 。谓词可以递归应用(使用 +* 运算符),也可以通过预定义的递归谓词 localTaint 应用,localTaint 等效于 localTaintStep*

TaintTracking::localTaint(DataFlow::parameterNode(source), DataFlow::exprNode(sink)) 可以通过0步或多步找出从形参 source 到表达式 sink 的污点流。

1.2.3.示例1

下面查询语句可以找出传递到 fopen 中的文件名,即 fopen 调用的第1个实参。查询语句的大致意思是找到函数调用 fc 并且 fc 调用的是 fopen,返回 fc 第1个参数。

import cpp

from Function fopen, FunctionCall fc
where fopen.hasGlobalName("fopen")
  and fc.getTarget() = fopen
select fc.getArgument(0)

不过,这只会给出参数中的表达式,而不是可能传递给它的值。因此,可以使用本地数据流来查找流入实参的所有表达式。查询语句的大致意思是找到调用 fopen 的函数调用 fc 并找到所有流入 fc 第1个参数的本地数据流,返回数据流的 source 点。

import cpp
import semmle.code.cpp.dataflow.DataFlow

from Function fopen, FunctionCall fc, Expr src
where fopen.hasGlobalName("fopen")
  and fc.getTarget() = fopen
  and DataFlow::localFlow(DataFlow::exprNode(src), DataFlow::exprNode(fc.getArgument(0)))
select src

接着继续优化查询,查找当前函数是否有一个形参流入了 fopen 的参数

import cpp
import semmle.code.cpp.dataflow.DataFlow

from Function fopen, FunctionCall fc, Parameter p
where fopen.hasGlobalName("fopen")
  and fc.getTarget() = fopen
  and DataFlow::localFlow(DataFlow::parameterNode(p), DataFlow::exprNode(fc.getArgument(0)))
select p

下面查询语句查找格式化函数中格式化参数没有硬编码的情况。

  • DataFlow::localFlow(source, sink) and source.asExpr() instanceof StringLiteral and sink.asExpr() = formatString 意思是 source 是字符串常量,也就是该数据流不属于污点分析。
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.commons.Printf

from FormattingFunction format, FunctionCall call, Expr formatString
where call.getTarget() = format
  and call.getArgument(format.getFormatParameterIndex()) = formatString
  and not exists(DataFlow::Node source, DataFlow::Node sink |
    DataFlow::localFlow(source, sink) and
    source.asExpr() instanceof StringLiteral and
    sink.asExpr() = formatString
  )
select call, "Argument to " + format.getQualifiedName() + " isn't hard-coded."

1.2.4.全局数据流

全局数据流跟踪整个程序中的数据流,因此比本地数据流更强大。然而,全局数据流不如本地数据流精确,并且分析通常需要更多的时间和内存来执行。

全局数据流使用需要继承类 DataFlow::Configuration

import semmle.code.cpp.dataflow.DataFlow

class MyDataFlowConfiguration extends DataFlow::Configuration {
  MyDataFlowConfiguration() { this = "MyDataFlowConfiguration" }

  override predicate isSource(DataFlow::Node source) {
    ...
  }

  override predicate isSink(DataFlow::Node sink) {
    ...
  }
}

from MyDataFlowConfiguration dataflow, DataFlow::Node source, DataFlow::Node sink
where dataflow.hasFlow(source, sink)
select source, "Data flow to $@.", sink, sink.toString()
  • isSource 定义source点

  • isSink 定义sink点

  • isBarrier 可选,限制数据流

  • isBarrierGuard 可选,同样限制数据流

  • isAdditionalFlowStep 可选,添加额外flow step

  • hasFlow(DataFlow::Node source, DataFlow::Node sink) 用来进行 sourcesink 的全局数据流分析。

1.2.5.全局污点追踪

import semmle.code.cpp.dataflow.TaintTracking

class MyTaintTrackingConfiguration extends TaintTracking::Configuration {
  MyTaintTrackingConfiguration() { this = "MyTaintTrackingConfiguration" }

  override predicate isSource(DataFlow::Node source) {
    ...
  }

  override predicate isSink(DataFlow::Node sink) {
    ...
  }
}
  • isSource 定义source点

  • isSink 定义sink点

  • isSanitizer 可选,限制污点流

  • isSanitizerGuard 可选,同样限制数据流

  • isAdditionalTaintStep 可选,添加额外flow step

  • hasFlow(DataFlow::Node source, DataFlow::Node sink) 用来进行 sourcesink 的全局数据流分析。

1.2.6.示例2

以下数据流配置跟踪从环境变量到在类Unix环境中打开文件的数据流

  • 定义source点为 getenv 函数调用返回值。

  • 定义sink点为 fopen 函数第1个参数。

import semmle.code.cpp.dataflow.DataFlow

class EnvironmentToFileConfiguration extends DataFlow::Configuration {
  EnvironmentToFileConfiguration() { this = "EnvironmentToFileConfiguration" }

  override predicate isSource(DataFlow::Node source) {
    exists (Function getenv |
      source.asExpr().(FunctionCall).getTarget() = getenv and
      getenv.hasGlobalName("getenv")
    )
  }

  override predicate isSink(DataFlow::Node sink) {
    exists (FunctionCall fc |
      sink.asExpr() = fc.getArgument(0) and
      fc.getTarget().hasGlobalName("fopen")
    )
  }
}

from Expr getenv, Expr fopen, EnvironmentToFileConfiguration config
where config.hasFlow(DataFlow::exprNode(getenv), DataFlow::exprNode(fopen))
select fopen, "This 'fopen' uses data from $@.",
  getenv, "call to 'getenv'"

以下污点分析查询跟踪从 ntohl 的调用到数组索引操作的数据。它使用Guards库来识别已进行边界检查的表达式,并定义 isSanitizer 以防止污染通过它们传播。它还使用 isAdditionalTaintStep 将流从循环边界添加到循环索引。

  • source点为 ntohl 函数调用,该函数将一个无符号长整形数从网络字节顺序转换为主机字节顺序。

  • sink点为数组索引。

import cpp
import semmle.code.cpp.controlflow.Guards
import semmle.code.cpp.dataflow.TaintTracking

class NetworkToBufferSizeConfiguration extends TaintTracking::Configuration {
  NetworkToBufferSizeConfiguration() { this = "NetworkToBufferSizeConfiguration" }

  override predicate isSource(DataFlow::Node node) {
    node.asExpr().(FunctionCall).getTarget().hasGlobalName("ntohl")
  }

  override predicate isSink(DataFlow::Node node) {
    exists(ArrayExpr ae | node.asExpr() = ae.getArrayOffset())
  }

  override predicate isAdditionalTaintStep(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)
    )
  }

  override predicate isSanitizer(DataFlow::Node node) {
    exists(GuardCondition gc, Variable v |
      gc.getAChild*() = v.getAnAccess() and
      node.asExpr() = v.getAnAccess() and
      gc.controls(node.asExpr().getBasicBlock(), _)
    )
  }
}

from DataFlow::Node ntohl, DataFlow::Node offset, NetworkToBufferSizeConfiguration conf
where conf.hasFlow(ntohl, offset)
select offset, "This array offset may be influenced by $@.", ntohl,
  "converted data from the network"

2.Query

query编写可参考codeql-query-help

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值