Initial taint-tracking library

This commit is contained in:
Tom Hvitved
2021-06-14 14:19:34 +02:00
parent 88fb3c7097
commit 8aa337ab01
10 changed files with 206 additions and 18 deletions

View File

@@ -0,0 +1,7 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
module TaintTracking {
import codeql_ruby.dataflow.internal.tainttracking1.TaintTrackingImpl
}

View File

@@ -208,21 +208,33 @@ module ExprNodes {
AssignExprCfgNode() { this.getExpr() instanceof AssignExpr }
}
private class BinaryOperationExprChildMapping extends ExprChildMapping, BinaryOperation {
private class OperationExprChildMapping extends ExprChildMapping, Operation {
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
}
/** A control-flow node that wraps a `BinaryOperation` AST expression. */
class BinaryOperationCfgNode extends ExprCfgNode {
override BinaryOperationExprChildMapping e;
/** A control-flow node that wraps an `Operation` AST expression. */
class OperationCfgNode extends ExprCfgNode {
override OperationExprChildMapping e;
final override BinaryOperation getExpr() { result = ExprCfgNode.super.getExpr() }
override Operation getExpr() { result = super.getExpr() }
/** Gets an operand of this operation. */
final ExprCfgNode getAnOperand() { e.hasCfgChild(e.getAnOperand(), this, result) }
}
/** A control-flow node that wraps a `BinaryOperation` AST expression. */
class BinaryOperationCfgNode extends OperationCfgNode {
private BinaryOperation bo;
BinaryOperationCfgNode() { e = bo }
final override BinaryOperation getExpr() { result = super.getExpr() }
/** Gets the left operand of this binary operation. */
final ExprCfgNode getLeftOperand() { e.hasCfgChild(e.getLeftOperand(), this, result) }
final ExprCfgNode getLeftOperand() { e.hasCfgChild(bo.getLeftOperand(), this, result) }
/** Gets the right operand of this binary operation. */
final ExprCfgNode getRightOperand() { e.hasCfgChild(e.getRightOperand(), this, result) }
final ExprCfgNode getRightOperand() { e.hasCfgChild(bo.getRightOperand(), this, result) }
}
private class BlockArgumentChildMapping extends ExprChildMapping, BlockArgument {

View File

@@ -544,15 +544,6 @@ predicate isImmutableOrUnobservable(Node n) { none() }
*/
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() }
/**
* A guard that validates some expression.
*/
class BarrierGuard extends Expr {
BarrierGuard() { none() }
Node getAGuardedNode() { none() }
}
newtype LambdaCallKind =
TYieldCallKind() or
TLambdaCallKind()

View File

@@ -136,3 +136,12 @@ class Content extends TContent {
/** Gets the location of this content. */
Location getLocation() { none() }
}
/**
* A guard that validates some expression.
*/
class BarrierGuard extends CfgNodes::ExprCfgNode {
BarrierGuard() { none() }
Node getAGuardedNode() { none() }
}

View File

@@ -0,0 +1,23 @@
private import ruby
private import TaintTrackingPublic
private import codeql_ruby.CFG
private import codeql_ruby.DataFlow
/**
* Holds if `node` should be a sanitizer in all global taint flow configurations
* but not in local taint.
*/
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
/**
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included
* in all global taint flow configurations.
*/
cached
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
not op.getExpr() instanceof AssignExpr
)
}

View File

@@ -0,0 +1,20 @@
private import ruby
private import TaintTrackingPrivate
private import codeql_ruby.CFG
private import codeql_ruby.DataFlow
/**
* Holds if taint propagates from `source` to `sink` in zero or more local
* (intra-procedural) steps.
*/
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
/**
* Holds if taint can flow from `e1` to `e2` in zero or more
* local (intra-procedural) steps.
*/
predicate localExprTaint(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
}
predicate localTaintStep = defaultAdditionalTaintStep/2;

View File

@@ -0,0 +1,115 @@
/**
* Provides an implementation of global (interprocedural) taint tracking.
* This file re-exports the local (intraprocedural) taint-tracking analysis
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
* exposed through the `Configuration` class. For some languages, this file
* exists in several identical copies, allowing queries to use multiple
* `Configuration` classes that depend on each other without introducing
* mutual recursion among those configurations.
*/
import TaintTrackingParameter::Public
private import TaintTrackingParameter::Private
/**
* A configuration of interprocedural taint tracking analysis. This defines
* sources, sinks, and any other configurable aspect of the analysis. Each
* use of the taint tracking library must define its own unique extension of
* this abstract class.
*
* A taint-tracking configuration is a special data flow configuration
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
* necessarily preserve values but are still relevant from a taint tracking
* perspective. (For example, string concatenation, where one of the operands
* is tainted.)
*
* To create a configuration, extend this class with a subclass whose
* characteristic predicate is a unique singleton string. For example, write
*
* ```ql
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
* // Override `isSource` and `isSink`.
* // Optionally override `isSanitizer`.
* // Optionally override `isSanitizerIn`.
* // Optionally override `isSanitizerOut`.
* // Optionally override `isSanitizerGuard`.
* // Optionally override `isAdditionalTaintStep`.
* }
* ```
*
* Then, to query whether there is flow between some `source` and `sink`,
* write
*
* ```ql
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
* ```
*
* Multiple configurations can coexist, but it is unsupported to depend on
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
* overridden predicates that define sources, sinks, or additional steps.
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
*/
abstract class Configuration extends DataFlow::Configuration {
bindingset[this]
Configuration() { any() }
/**
* Holds if `source` is a relevant taint source.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSource(DataFlow::Node source);
/**
* Holds if `sink` is a relevant taint sink.
*
* The smaller this predicate is, the faster `hasFlow()` will converge.
*/
// overridden to provide taint-tracking specific qldoc
abstract override predicate isSink(DataFlow::Node sink);
/** Holds if the node `node` is a taint sanitizer. */
predicate isSanitizer(DataFlow::Node node) { none() }
final override predicate isBarrier(DataFlow::Node node) {
isSanitizer(node) or
defaultTaintSanitizer(node)
}
/** Holds if taint propagation into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
/** Holds if taint propagation out of `node` is prohibited. */
predicate isSanitizerOut(DataFlow::Node node) { none() }
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
/**
* Holds if the additional taint propagation step from `node1` to `node2`
* must be taken into account in the analysis.
*/
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
isAdditionalTaintStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
/**
* Holds if taint may flow from `source` to `sink` for this configuration.
*/
// overridden to provide taint-tracking specific qldoc
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
super.hasFlow(source, sink)
}
}

View File

@@ -0,0 +1,6 @@
import codeql_ruby.dataflow.internal.TaintTrackingPublic as Public
module Private {
import codeql_ruby.DataFlow::DataFlow as DataFlow
import codeql_ruby.dataflow.internal.TaintTrackingPrivate
}

View File

@@ -14,7 +14,8 @@
import ruby
import codeql_ruby.DataFlow
import DataFlow::PathGraph
private import codeql_ruby.controlflow.CfgNodes
import codeql_ruby.TaintTracking
import codeql_ruby.controlflow.CfgNodes
bindingset[char, fraction]
predicate fewer_characters_than(StringLiteral str, string char, float fraction) {

View File

@@ -11,8 +11,12 @@
"codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll",
"ql/src/codeql_ruby/dataflow/internal/DataFlowImpl.qll"
],
"TaintTracking": [
"codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
"ql/src/codeql_ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll"
],
"TypeTracker": [
"codeql/python/ql/src/experimental/typetracking/TypeTracker.qll",
"ql/src/codeql_ruby/typetracking/TypeTracker.qll"
]
}
}