Skip to content

Commit 8aa337a

Browse files
committed
Initial taint-tracking library
1 parent 88fb3c7 commit 8aa337a

10 files changed

Lines changed: 205 additions & 17 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Provides classes for performing local (intra-procedural) and
3+
* global (inter-procedural) taint-tracking analyses.
4+
*/
5+
module TaintTracking {
6+
import codeql_ruby.dataflow.internal.tainttracking1.TaintTrackingImpl
7+
}

ql/src/codeql_ruby/controlflow/CfgNodes.qll

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,21 +208,33 @@ module ExprNodes {
208208
AssignExprCfgNode() { this.getExpr() instanceof AssignExpr }
209209
}
210210

211-
private class BinaryOperationExprChildMapping extends ExprChildMapping, BinaryOperation {
211+
private class OperationExprChildMapping extends ExprChildMapping, Operation {
212212
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
213213
}
214214

215+
/** A control-flow node that wraps an `Operation` AST expression. */
216+
class OperationCfgNode extends ExprCfgNode {
217+
override OperationExprChildMapping e;
218+
219+
override Operation getExpr() { result = super.getExpr() }
220+
221+
/** Gets an operand of this operation. */
222+
final ExprCfgNode getAnOperand() { e.hasCfgChild(e.getAnOperand(), this, result) }
223+
}
224+
215225
/** A control-flow node that wraps a `BinaryOperation` AST expression. */
216-
class BinaryOperationCfgNode extends ExprCfgNode {
217-
override BinaryOperationExprChildMapping e;
226+
class BinaryOperationCfgNode extends OperationCfgNode {
227+
private BinaryOperation bo;
228+
229+
BinaryOperationCfgNode() { e = bo }
218230

219-
final override BinaryOperation getExpr() { result = ExprCfgNode.super.getExpr() }
231+
final override BinaryOperation getExpr() { result = super.getExpr() }
220232

221233
/** Gets the left operand of this binary operation. */
222-
final ExprCfgNode getLeftOperand() { e.hasCfgChild(e.getLeftOperand(), this, result) }
234+
final ExprCfgNode getLeftOperand() { e.hasCfgChild(bo.getLeftOperand(), this, result) }
223235

224236
/** Gets the right operand of this binary operation. */
225-
final ExprCfgNode getRightOperand() { e.hasCfgChild(e.getRightOperand(), this, result) }
237+
final ExprCfgNode getRightOperand() { e.hasCfgChild(bo.getRightOperand(), this, result) }
226238
}
227239

228240
private class BlockArgumentChildMapping extends ExprChildMapping, BlockArgument {

ql/src/codeql_ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -544,15 +544,6 @@ predicate isImmutableOrUnobservable(Node n) { none() }
544544
*/
545545
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() }
546546

547-
/**
548-
* A guard that validates some expression.
549-
*/
550-
class BarrierGuard extends Expr {
551-
BarrierGuard() { none() }
552-
553-
Node getAGuardedNode() { none() }
554-
}
555-
556547
newtype LambdaCallKind =
557548
TYieldCallKind() or
558549
TLambdaCallKind()

ql/src/codeql_ruby/dataflow/internal/DataFlowPublic.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,12 @@ class Content extends TContent {
136136
/** Gets the location of this content. */
137137
Location getLocation() { none() }
138138
}
139+
140+
/**
141+
* A guard that validates some expression.
142+
*/
143+
class BarrierGuard extends CfgNodes::ExprCfgNode {
144+
BarrierGuard() { none() }
145+
146+
Node getAGuardedNode() { none() }
147+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
private import ruby
2+
private import TaintTrackingPublic
3+
private import codeql_ruby.CFG
4+
private import codeql_ruby.DataFlow
5+
6+
/**
7+
* Holds if `node` should be a sanitizer in all global taint flow configurations
8+
* but not in local taint.
9+
*/
10+
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
11+
12+
/**
13+
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included
14+
* in all global taint flow configurations.
15+
*/
16+
cached
17+
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
18+
exists(CfgNodes::ExprNodes::OperationCfgNode op |
19+
op = nodeTo.asExpr() and
20+
op.getAnOperand() = nodeFrom.asExpr() and
21+
not op.getExpr() instanceof AssignExpr
22+
)
23+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
private import ruby
2+
private import TaintTrackingPrivate
3+
private import codeql_ruby.CFG
4+
private import codeql_ruby.DataFlow
5+
6+
/**
7+
* Holds if taint propagates from `source` to `sink` in zero or more local
8+
* (intra-procedural) steps.
9+
*/
10+
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
11+
12+
/**
13+
* Holds if taint can flow from `e1` to `e2` in zero or more
14+
* local (intra-procedural) steps.
15+
*/
16+
predicate localExprTaint(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
17+
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
18+
}
19+
20+
predicate localTaintStep = defaultAdditionalTaintStep/2;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Provides an implementation of global (interprocedural) taint tracking.
3+
* This file re-exports the local (intraprocedural) taint-tracking analysis
4+
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
5+
* exposed through the `Configuration` class. For some languages, this file
6+
* exists in several identical copies, allowing queries to use multiple
7+
* `Configuration` classes that depend on each other without introducing
8+
* mutual recursion among those configurations.
9+
*/
10+
11+
import TaintTrackingParameter::Public
12+
private import TaintTrackingParameter::Private
13+
14+
/**
15+
* A configuration of interprocedural taint tracking analysis. This defines
16+
* sources, sinks, and any other configurable aspect of the analysis. Each
17+
* use of the taint tracking library must define its own unique extension of
18+
* this abstract class.
19+
*
20+
* A taint-tracking configuration is a special data flow configuration
21+
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
22+
* necessarily preserve values but are still relevant from a taint tracking
23+
* perspective. (For example, string concatenation, where one of the operands
24+
* is tainted.)
25+
*
26+
* To create a configuration, extend this class with a subclass whose
27+
* characteristic predicate is a unique singleton string. For example, write
28+
*
29+
* ```ql
30+
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
31+
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
32+
* // Override `isSource` and `isSink`.
33+
* // Optionally override `isSanitizer`.
34+
* // Optionally override `isSanitizerIn`.
35+
* // Optionally override `isSanitizerOut`.
36+
* // Optionally override `isSanitizerGuard`.
37+
* // Optionally override `isAdditionalTaintStep`.
38+
* }
39+
* ```
40+
*
41+
* Then, to query whether there is flow between some `source` and `sink`,
42+
* write
43+
*
44+
* ```ql
45+
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
46+
* ```
47+
*
48+
* Multiple configurations can coexist, but it is unsupported to depend on
49+
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
50+
* overridden predicates that define sources, sinks, or additional steps.
51+
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
52+
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
53+
*/
54+
abstract class Configuration extends DataFlow::Configuration {
55+
bindingset[this]
56+
Configuration() { any() }
57+
58+
/**
59+
* Holds if `source` is a relevant taint source.
60+
*
61+
* The smaller this predicate is, the faster `hasFlow()` will converge.
62+
*/
63+
// overridden to provide taint-tracking specific qldoc
64+
abstract override predicate isSource(DataFlow::Node source);
65+
66+
/**
67+
* Holds if `sink` is a relevant taint sink.
68+
*
69+
* The smaller this predicate is, the faster `hasFlow()` will converge.
70+
*/
71+
// overridden to provide taint-tracking specific qldoc
72+
abstract override predicate isSink(DataFlow::Node sink);
73+
74+
/** Holds if the node `node` is a taint sanitizer. */
75+
predicate isSanitizer(DataFlow::Node node) { none() }
76+
77+
final override predicate isBarrier(DataFlow::Node node) {
78+
isSanitizer(node) or
79+
defaultTaintSanitizer(node)
80+
}
81+
82+
/** Holds if taint propagation into `node` is prohibited. */
83+
predicate isSanitizerIn(DataFlow::Node node) { none() }
84+
85+
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
86+
87+
/** Holds if taint propagation out of `node` is prohibited. */
88+
predicate isSanitizerOut(DataFlow::Node node) { none() }
89+
90+
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
91+
92+
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
93+
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
94+
95+
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
96+
97+
/**
98+
* Holds if the additional taint propagation step from `node1` to `node2`
99+
* must be taken into account in the analysis.
100+
*/
101+
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
102+
103+
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
104+
isAdditionalTaintStep(node1, node2) or
105+
defaultAdditionalTaintStep(node1, node2)
106+
}
107+
108+
/**
109+
* Holds if taint may flow from `source` to `sink` for this configuration.
110+
*/
111+
// overridden to provide taint-tracking specific qldoc
112+
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
113+
super.hasFlow(source, sink)
114+
}
115+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import codeql_ruby.dataflow.internal.TaintTrackingPublic as Public
2+
3+
module Private {
4+
import codeql_ruby.DataFlow::DataFlow as DataFlow
5+
import codeql_ruby.dataflow.internal.TaintTrackingPrivate
6+
}

ql/src/queries/security/cwe-798/HardcodedCredentials.ql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
import ruby
1515
import codeql_ruby.DataFlow
1616
import DataFlow::PathGraph
17-
private import codeql_ruby.controlflow.CfgNodes
17+
import codeql_ruby.TaintTracking
18+
import codeql_ruby.controlflow.CfgNodes
1819

1920
bindingset[char, fraction]
2021
predicate fewer_characters_than(StringLiteral str, string char, float fraction) {

scripts/identical-files.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111
"codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll",
1212
"ql/src/codeql_ruby/dataflow/internal/DataFlowImpl.qll"
1313
],
14+
"TaintTracking": [
15+
"codeql/csharp/ql/src/semmle/code/csharp/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
16+
"ql/src/codeql_ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll"
17+
],
1418
"TypeTracker": [
1519
"codeql/python/ql/src/experimental/typetracking/TypeTracker.qll",
1620
"ql/src/codeql_ruby/typetracking/TypeTracker.qll"
1721
]
18-
}
22+
}

0 commit comments

Comments
 (0)