Skip to content

Commit 12723f0

Browse files
authored
Merge pull request #288 from github/hmac-barrier-guard-checks
Make barrier guards more specific
2 parents f4e2c30 + 4763312 commit 12723f0

File tree

8 files changed

+113
-13
lines changed

8 files changed

+113
-13
lines changed

ql/lib/codeql/ruby/dataflow/BarrierGuards.qll

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,26 @@ private import codeql.ruby.CFG
2020
class StringConstCompare extends DataFlow::BarrierGuard,
2121
CfgNodes::ExprNodes::ComparisonOperationCfgNode {
2222
private CfgNode checkedNode;
23+
// The value of the condition that results in the node being validated.
24+
private boolean checkedBranch;
2325

2426
StringConstCompare() {
2527
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
26-
this.getExpr() instanceof EqExpr or
27-
this.getExpr() instanceof CaseEqExpr
28+
this.getExpr() instanceof EqExpr and checkedBranch = true
29+
or
30+
this.getExpr() instanceof CaseEqExpr and checkedBranch = true
31+
or
32+
this.getExpr() instanceof NEExpr and checkedBranch = false
2833
|
2934
this.getLeftOperand() = strLitNode and this.getRightOperand() = checkedNode
3035
or
3136
this.getLeftOperand() = checkedNode and this.getRightOperand() = strLitNode
3237
)
3338
}
3439

35-
override DataFlow::Node getAGuardedNode() { result.asExpr() = checkedNode }
40+
override predicate checks(CfgNode expr, boolean branch) {
41+
expr = checkedNode and branch = checkedBranch
42+
}
3643
}
3744

3845
/**
@@ -64,5 +71,5 @@ class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
6471
)
6572
}
6673

67-
override DataFlow::Node getAGuardedNode() { result.asExpr() = checkedNode }
74+
override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true }
6875
}

ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ private import DataFlowDispatch
33
private import DataFlowPrivate
44
private import codeql.ruby.CFG
55
private import codeql.ruby.typetracking.TypeTracker
6+
private import codeql.ruby.dataflow.SSA
67

78
/**
89
* An element, viewed as a node in a data flow graph. Either an expression
@@ -160,7 +161,45 @@ class Content extends TContent {
160161

161162
/**
162163
* A guard that validates some expression.
164+
*
165+
* To use this in a configuration, extend the class and provide a
166+
* characteristic predicate precisely specifying the guard, and override
167+
* `checks` to specify what is being validated and in which branch.
168+
*
169+
* It is important that all extending classes in scope are disjoint.
163170
*/
164171
abstract class BarrierGuard extends CfgNodes::ExprCfgNode {
165-
Node getAGuardedNode() { none() }
172+
private ConditionBlock conditionBlock;
173+
174+
BarrierGuard() { this = conditionBlock.getLastNode() }
175+
176+
/** Holds if this guard controls block `b` upon evaluating to `branch`. */
177+
private predicate controlsBlock(BasicBlock bb, boolean branch) {
178+
exists(SuccessorTypes::BooleanSuccessor s | s.getValue() = branch |
179+
conditionBlock.controls(bb, s)
180+
)
181+
}
182+
183+
/**
184+
* Holds if this guard validates `expr` upon evaluating to `branch`.
185+
* For example, the following code validates `foo` when the condition
186+
* `foo == "foo"` is true.
187+
* ```ruby
188+
* if foo == "foo"
189+
* do_something
190+
* else
191+
* do_something_else
192+
* end
193+
* ```
194+
*/
195+
abstract predicate checks(CfgNode expr, boolean branch);
196+
197+
final Node getAGuardedNode() {
198+
exists(boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def |
199+
def.getARead() = testedNode and
200+
def.getARead() = result.asExpr() and
201+
this.checks(testedNode, branch) and
202+
this.controlsBlock(result.asExpr().getBasicBlock(), branch)
203+
)
204+
}
166205
}

ql/lib/codeql/ruby/regexp/PolynomialReDoSCustomizations.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ module PolynomialReDoS {
124124
)
125125
}
126126

127-
override DataFlow::Node getAGuardedNode() { result = input }
127+
override predicate checks(CfgNode node, boolean branch) {
128+
node = input.asExpr() and branch = true
129+
}
128130
}
129131
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | barrier-guards.rb:3:4:3:6 | foo | true |
2+
| barrier-guards.rb:9:4:9:24 | call to include? | barrier-guards.rb:10:5:10:7 | foo | barrier-guards.rb:9:21:9:23 | foo | true |
3+
| barrier-guards.rb:15:4:15:15 | ... != ... | barrier-guards.rb:18:5:18:7 | foo | barrier-guards.rb:15:4:15:6 | foo | false |
4+
| barrier-guards.rb:21:8:21:19 | ... == ... | barrier-guards.rb:24:5:24:7 | foo | barrier-guards.rb:21:8:21:10 | foo | true |
5+
| barrier-guards.rb:27:8:27:19 | ... != ... | barrier-guards.rb:28:5:28:7 | foo | barrier-guards.rb:27:8:27:10 | foo | false |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import codeql.ruby.dataflow.internal.DataFlowPublic
2+
import codeql.ruby.dataflow.BarrierGuards
3+
import codeql.ruby.controlflow.CfgNodes
4+
5+
from BarrierGuard g, boolean branch, ExprCfgNode expr
6+
where g.checks(expr, branch)
7+
select g, g.getAGuardedNode(), expr, branch
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
foo = "foo"
2+
3+
if foo == "foo"
4+
foo
5+
else
6+
foo
7+
end
8+
9+
if ["foo"].include?(foo)
10+
foo
11+
else
12+
foo
13+
end
14+
15+
if foo != "foo"
16+
foo
17+
else
18+
foo
19+
end
20+
21+
unless foo == "foo"
22+
foo
23+
else
24+
foo
25+
end
26+
27+
unless foo != "foo"
28+
foo
29+
else
30+
foo
31+
end
32+
33+
foo

ql/test/query-tests/security/cwe-089/ActiveRecordInjection.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,15 @@ def some_other_request_handler
7171
def safe_paths
7272
dir = params[:order]
7373
# GOOD: barrier guard prevents taint flow
74-
dir = "DESC" unless dir == "ASC"
75-
User.order("name #{dir}")
74+
if dir == "ASC"
75+
User.order("name #{dir}")
76+
else
77+
dir = "DESC"
78+
User.order("name #{dir}")
79+
end
80+
# TODO: a more idiomatic form of this guard is the following:
81+
# dir = "DESC" unless dir == "ASC"
82+
# but our taint tracking can't (yet) handle that properly
7683

7784
name = params[:user_name]
7885
# GOOD: barrier guard prevents taint flow

ql/test/query-tests/security/cwe-089/SqlInjection.expected

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ edges
1212
| ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:56:38:56:50 | ...[...] : |
1313
| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | ActiveRecordInjection.rb:8:31:8:34 | pass : |
1414
| ActiveRecordInjection.rb:62:10:62:15 | call to params : | ActiveRecordInjection.rb:68:21:68:33 | ... + ... |
15-
| ActiveRecordInjection.rb:94:22:94:27 | call to params : | ActiveRecordInjection.rb:94:22:94:45 | ...[...] : |
16-
| ActiveRecordInjection.rb:94:22:94:45 | ...[...] : | ActiveRecordInjection.rb:20:23:20:31 | condition : |
15+
| ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:101:22:101:45 | ...[...] : |
16+
| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | ActiveRecordInjection.rb:20:23:20:31 | condition : |
1717
nodes
1818
| ActiveRecordInjection.rb:8:25:8:28 | name : | semmle.label | name : |
1919
| ActiveRecordInjection.rb:8:31:8:34 | pass : | semmle.label | pass : |
@@ -36,12 +36,12 @@ nodes
3636
| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | semmle.label | ...[...] : |
3737
| ActiveRecordInjection.rb:62:10:62:15 | call to params : | semmle.label | call to params : |
3838
| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | semmle.label | ... + ... |
39-
| ActiveRecordInjection.rb:94:22:94:27 | call to params : | semmle.label | call to params : |
40-
| ActiveRecordInjection.rb:94:22:94:45 | ...[...] : | semmle.label | ...[...] : |
39+
| ActiveRecordInjection.rb:101:22:101:27 | call to params : | semmle.label | call to params : |
40+
| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | semmle.label | ...[...] : |
4141
#select
4242
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:23:56:28 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:23:56:28 | call to params | a user-provided value |
4343
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:38:56:43 | call to params | a user-provided value |
44-
| ActiveRecordInjection.rb:23:17:23:25 | condition | ActiveRecordInjection.rb:94:22:94:27 | call to params : | ActiveRecordInjection.rb:23:17:23:25 | condition | This SQL query depends on $@. | ActiveRecordInjection.rb:94:22:94:27 | call to params | a user-provided value |
44+
| ActiveRecordInjection.rb:23:17:23:25 | condition | ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:23:17:23:25 | condition | This SQL query depends on $@. | ActiveRecordInjection.rb:101:22:101:27 | call to params | a user-provided value |
4545
| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | ActiveRecordInjection.rb:35:30:35:35 | call to params : | ActiveRecordInjection.rb:35:30:35:44 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:35:30:35:35 | call to params | a user-provided value |
4646
| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | ActiveRecordInjection.rb:39:30:39:35 | call to params : | ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:39:30:39:35 | call to params | a user-provided value |
4747
| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | ActiveRecordInjection.rb:43:32:43:37 | call to params : | ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:43:32:43:37 | call to params | a user-provided value |

0 commit comments

Comments
 (0)