Skip to content

Commit b4c2942

Browse files
committed
Make barrier guards more specific
Following examples from the other libraries, this change introduces a member predicate `checks(CfgNode expr, boolean branch)` to `BarrierGuard`, which holds if the guard validates `expr` for a particular value of `branch`, which represents the value of the condition in the guard. For example, in the following guard... if foo == "foo" do_something foo else do_something_else foo end ...the variable `foo` is validated when the condition `foo == "foo"` is true. We also introduce the concept that a guard "controls" a code block based on the value of `branch`. In the example above, the "then" branch of the if statement is controlled when `branch` is true. The else branch is not controlled because `foo` can take (almost) any value in that branch. Based on these concepts, we define a guarded node to be a read of a validated variable in a controlled block. In the above example, the `foo` in `do_something foo` is guarded, but the `foo` in `do_something_else foo` is not.
1 parent a62aa2b commit b4c2942

File tree

5 files changed

+73
-4
lines changed

5 files changed

+73
-4
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class StringConstCompare extends DataFlow::BarrierGuard,
3232
)
3333
}
3434

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

3838
/**
@@ -64,5 +64,5 @@ class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
6464
)
6565
}
6666

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

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

Lines changed: 46 additions & 2 deletions
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
@@ -158,9 +159,52 @@ class Content extends TContent {
158159
Location getLocation() { none() }
159160
}
160161

162+
/**
163+
* A node that controls whether other nodes are evaluated.
164+
*/
165+
class GuardNode extends CfgNodes::ExprCfgNode {
166+
ConditionBlock conditionBlock;
167+
168+
GuardNode() { this = conditionBlock.getLastNode() }
169+
170+
/** Holds if this guard controls block `b` upon evaluating to `branch`. */
171+
predicate controlsBlock(BasicBlock bb, boolean branch) {
172+
exists(SuccessorTypes::BooleanSuccessor s | s.getValue() = branch |
173+
conditionBlock.controls(bb, s)
174+
)
175+
}
176+
}
177+
161178
/**
162179
* A guard that validates some expression.
180+
*
181+
* To use this in a configuration, extend the class and provide a
182+
* characteristic predicate precisely specifying the guard, and override
183+
* `checks` to specify what is being validated and in which branch.
184+
*
185+
* It is important that all extending classes in scope are disjoint.
163186
*/
164-
abstract class BarrierGuard extends CfgNodes::ExprCfgNode {
165-
Node getAGuardedNode() { none() }
187+
abstract class BarrierGuard extends GuardNode {
188+
/**
189+
* Holds if this guard validates `expr` upon evaluating to `branch`.
190+
* For example, the following code validates `foo` when the condition
191+
* `foo == "foo"` is true.
192+
* ```ruby
193+
* if foo == "foo"
194+
* do_something
195+
* else
196+
* do_something_else
197+
* end
198+
* ```
199+
*/
200+
abstract predicate checks(CfgNode expr, boolean branch);
201+
202+
final Node getAGuardedNode() {
203+
exists(boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def |
204+
def.getARead() = testedNode and
205+
def.getARead() = result.asExpr() and
206+
this.checks(testedNode, branch) and
207+
this.controlsBlock(result.asExpr().getBasicBlock(), branch)
208+
)
209+
}
166210
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:15:4:17 | 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:15:10:17 | foo | barrier-guards.rb:9:21:9:23 | foo | true |
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
foo = "foo"
2+
3+
if foo == "foo"
4+
do_true_1 foo
5+
else
6+
do_false_1 foo
7+
end
8+
9+
if ["foo"].include?(foo)
10+
do_true_2 foo
11+
else
12+
do_false_2 foo
13+
end
14+
15+
do_default foo
16+

0 commit comments

Comments
 (0)