Skip to content

Commit 689c527

Browse files
committed
Ruby: Include content flow through captured variables
1 parent 667edf7 commit 689c527

File tree

7 files changed

+288
-75
lines changed

7 files changed

+288
-75
lines changed

ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -693,16 +693,30 @@ module ExprNodes {
693693

694694
override VariableAccess e;
695695

696-
final override VariableAccess getExpr() { result = ExprCfgNode.super.getExpr() }
696+
override VariableAccess getExpr() { result = ExprCfgNode.super.getExpr() }
697+
698+
/** Gets the variable that is being accessed. */
699+
Variable getVariable() { result = this.getExpr().getVariable() }
697700
}
698701

699702
/** A control-flow node that wraps a `VariableReadAccess` AST expression. */
700-
class VariableReadAccessCfgNode extends ExprCfgNode {
703+
class VariableReadAccessCfgNode extends VariableAccessCfgNode {
701704
override string getAPrimaryQlClass() { result = "VariableReadAccessCfgNode" }
702705

703706
override VariableReadAccess e;
704707

705-
final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() }
708+
override VariableReadAccess getExpr() { result = VariableAccessCfgNode.super.getExpr() }
709+
}
710+
711+
/** A control-flow node that wraps a `LocalVariableReadAccess` AST expression. */
712+
class LocalVariableReadAccessCfgNode extends VariableReadAccessCfgNode {
713+
override string getAPrimaryQlClass() { result = "LocalVariableReadAccessCfgNode" }
714+
715+
override LocalVariableReadAccess e;
716+
717+
final override LocalVariableReadAccess getExpr() { result = super.getExpr() }
718+
719+
final override LocalVariable getVariable() { result = super.getVariable() }
706720
}
707721

708722
private class InstanceVariableAccessMapping extends ExprChildMapping, InstanceVariableAccess {
@@ -728,21 +742,32 @@ module ExprNodes {
728742
}
729743

730744
/** A control-flow node that wraps a `SelfVariableAccess` AST expression. */
731-
class SelfVariableAccessCfgNode extends ExprCfgNode {
745+
class SelfVariableAccessCfgNode extends VariableAccessCfgNode {
732746
final override string getAPrimaryQlClass() { result = "SelfVariableAccessCfgNode" }
733747

734748
override SelfVariableAccessMapping e;
735749

736-
override SelfVariableAccess getExpr() { result = ExprCfgNode.super.getExpr() }
750+
override SelfVariableAccess getExpr() { result = VariableAccessCfgNode.super.getExpr() }
737751
}
738752

739753
/** A control-flow node that wraps a `VariableWriteAccess` AST expression. */
740-
class VariableWriteAccessCfgNode extends ExprCfgNode {
754+
class VariableWriteAccessCfgNode extends VariableAccessCfgNode {
741755
override string getAPrimaryQlClass() { result = "VariableWriteAccessCfgNode" }
742756

743757
override VariableWriteAccess e;
744758

745-
final override VariableWriteAccess getExpr() { result = ExprCfgNode.super.getExpr() }
759+
override VariableWriteAccess getExpr() { result = VariableAccessCfgNode.super.getExpr() }
760+
}
761+
762+
/** A control-flow node that wraps a `LocalVariableWriteAccess` AST expression. */
763+
class LocalVariableWriteAccessCfgNode extends VariableWriteAccessCfgNode {
764+
override string getAPrimaryQlClass() { result = "LocalVariableWriteAccessCfgNode" }
765+
766+
override LocalVariableWriteAccess e;
767+
768+
final override LocalVariableWriteAccess getExpr() { result = super.getExpr() }
769+
770+
final override LocalVariable getVariable() { result = super.getVariable() }
746771
}
747772

748773
/** A control-flow node that wraps a `ConstantReadAccess` AST expression. */

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

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ CfgNodes::ExprCfgNode getAPostUpdateNodeForArg(Argument arg) {
7171
not exists(getALastEvalNode(result))
7272
}
7373

74+
/**
75+
* Gets the data-flow node referencing SSA definition `def` at index `i` in basic
76+
* block `bb`. The node is either the definition itself, or a read of the
77+
* definition.
78+
*/
79+
Node getSsaRefNode(Ssa::Definition def, BasicBlock bb, int i) {
80+
def = result.(SsaDefinitionNode).getDefinition() and
81+
def.definesAt(_, bb, i)
82+
or
83+
exists(CfgNodes::ExprCfgNode e |
84+
e = result.asExpr() and
85+
e = bb.getNode(i) and
86+
e = def.getARead()
87+
)
88+
}
89+
7490
/** Provides predicates related to local data flow. */
7591
module LocalFlow {
7692
private import codeql.ruby.dataflow.internal.SsaImpl
@@ -80,15 +96,9 @@ module LocalFlow {
8096
* can reach `next`.
8197
*/
8298
private predicate localFlowSsaInput(Node nodeFrom, Ssa::Definition def, Ssa::Definition next) {
83-
exists(BasicBlock bb, int i | lastRefBeforeRedef(def, bb, i, next) |
84-
def = nodeFrom.(SsaDefinitionNode).getDefinition() and
85-
def.definesAt(_, bb, i)
86-
or
87-
exists(CfgNodes::ExprCfgNode e |
88-
e = nodeFrom.asExpr() and
89-
e = bb.getNode(i) and
90-
e.getExpr() instanceof VariableReadAccess
91-
)
99+
exists(BasicBlock bb, int i |
100+
lastRefBeforeRedef(def, bb, i, next) and
101+
nodeFrom = getSsaRefNode(def, bb, i)
92102
)
93103
}
94104

@@ -142,18 +152,15 @@ module LocalFlow {
142152
// Flow into phi node
143153
exists(Ssa::PhiNode phi |
144154
localFlowSsaInput(nodeFrom, def, phi) and
145-
phi = nodeTo.(SsaDefinitionNode).getDefinition() and
146-
def = phi.getAnInput()
155+
phi = nodeTo.(SsaDefinitionNode).getDefinition()
147156
)
157+
// or
158+
// // Flow into uncertain SSA definition
159+
// exists(SsaImpl::UncertainWriteDefinition uncertain |
160+
// localFlowSsaInput(nodeFrom, def, uncertain) and
161+
// uncertain = nodeTo.(SsaDefinitionNode).getDefinition()
162+
// )
148163
)
149-
// TODO
150-
// or
151-
// // Flow into uncertain SSA definition
152-
// exists(LocalFlow::UncertainExplicitSsaDefinition uncertain |
153-
// localFlowSsaInput(nodeFrom, def, uncertain) and
154-
// uncertain = nodeTo.(SsaDefinitionNode).getDefinition() and
155-
// def = uncertain.getPriorDefinition()
156-
// )
157164
}
158165

159166
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
@@ -995,10 +1002,8 @@ private module OutNodes {
9951002

9961003
import OutNodes
9971004

998-
predicate jumpStep(Node pred, Node succ) {
999-
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionNode).getDefinition(),
1000-
succ.(SsaDefinitionNode).getDefinition())
1001-
or
1005+
private predicate jumpStepCommon(Node pred, Node succ) {
1006+
// Flow out of a method directly via a captured variable
10021007
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionNode).getDefinition(),
10031008
succ.(SsaDefinitionNode).getDefinition())
10041009
or
@@ -1007,6 +1012,33 @@ predicate jumpStep(Node pred, Node succ) {
10071012
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred, succ)
10081013
}
10091014

1015+
predicate jumpStep(Node pred, Node succ) {
1016+
jumpStepCommon(pred, succ)
1017+
or
1018+
// Flow into a method via a captured variable
1019+
exists(Ssa::Definition def, BasicBlock bb, int i |
1020+
SsaImpl::captureFlowIn(_, def, bb, i, succ.(SsaDefinitionNode).getDefinition()) and
1021+
[pred, pred.(PostUpdateNode).getPreUpdateNode()] = getSsaRefNode(def, bb, i)
1022+
)
1023+
or
1024+
// Flow out of a method via content on a captured variable
1025+
exists(CfgNodes::ExprNodes::LocalVariableReadAccessCfgNode inner |
1026+
inner = pred.(PostUpdateNode).getPreUpdateNode().asExpr()
1027+
|
1028+
SsaImpl::captureContentFlowOut(_, inner, succ.asExpr())
1029+
or
1030+
SsaImpl::captureContentFlowOutPhi(_, inner, succ.(SsaDefinitionNode).getDefinition())
1031+
)
1032+
}
1033+
1034+
predicate jumpStepTypeTracker(Node nodeFrom, Node nodeTo) {
1035+
jumpStepCommon(nodeFrom, nodeTo)
1036+
or
1037+
// Flow into a method via a captured variable
1038+
SsaImpl::captureFlowIn(_, nodeFrom.(SsaDefinitionNode).getDefinition(), _, _,
1039+
nodeTo.(SsaDefinitionNode).getDefinition())
1040+
}
1041+
10101042
private ContentSet getKeywordContent(string name) {
10111043
exists(ConstantValue::ConstantSymbolValue key |
10121044
result.isSingleton(TKnownElementContent(key)) and

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
477477
|
478478
def.getARead() = testedNode and
479479
guardChecks(g, testedNode, branch) and
480-
SsaImpl::captureFlowIn(call, def, result) and
480+
SsaImpl::captureFlowIn(call, def, _, _, result) and
481481
guardControlsBlock(g, call.getBasicBlock(), branch) and
482482
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
483483
)
@@ -542,7 +542,7 @@ abstract deprecated class BarrierGuard extends CfgNodes::ExprCfgNode {
542542
|
543543
def.getARead() = testedNode and
544544
this.checks(testedNode, branch) and
545-
SsaImpl::captureFlowIn(call, def, result) and
545+
SsaImpl::captureFlowIn(call, def, _, _, result) and
546546
this.controlsBlock(call.getBasicBlock(), branch) and
547547
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
548548
)

0 commit comments

Comments
 (0)