Skip to content

Commit 020a049

Browse files
authored
Merge pull request #15103 from hvitved/ruby/simple-pattern-flow
Ruby: Model simple pattern matching as value steps instead of taint steps
2 parents 2eda592 + 25a676a commit 020a049

File tree

5 files changed

+44
-11
lines changed

5 files changed

+44
-11
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,8 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig {
663663
// We exclude steps into type checked variables. For those, we instead rely on the
664664
// type being checked against
665665
localFlowStep(nodeFrom, nodeTo, summary) and
666-
not hasAdjacentTypeCheckedReads(nodeTo)
666+
not hasAdjacentTypeCheckedReads(nodeTo) and
667+
not asModulePattern(nodeTo, _)
667668
}
668669

669670
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,40 @@ module LocalFlow {
143143
SsaImpl::adjacentReadPairExt(def, nodeFrom.asExpr(), nodeTo.asExpr())
144144
}
145145

146+
/**
147+
* Holds if SSA definition `def` assigns `value` to the underlying variable.
148+
*
149+
* This is either a direct assignment, `x = value`, or an assignment via
150+
* simple pattern matching
151+
*
152+
* ```rb
153+
* case value
154+
* in Foo => x then ...
155+
* in y => then ...
156+
* end
157+
* ```
158+
*/
159+
predicate ssaDefAssigns(Ssa::WriteDefinition def, CfgNodes::ExprCfgNode value) {
160+
def.assigns(value)
161+
or
162+
exists(CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::AstCfgNode pattern |
163+
case.getValue() = value and
164+
pattern = case.getBranch(_).(CfgNodes::ExprNodes::InClauseCfgNode).getPattern()
165+
|
166+
def.getWriteAccess() = pattern
167+
or
168+
def.getWriteAccess() = pattern.(CfgNodes::ExprNodes::AsPatternCfgNode).getVariableAccess()
169+
)
170+
}
171+
146172
/**
147173
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
148174
* SSA definition `def`.
149175
*/
150176
pragma[nomagic]
151177
predicate localSsaFlowStep(SsaImpl::DefinitionExt def, Node nodeFrom, Node nodeTo) {
152178
// Flow from assignment into SSA definition
153-
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
179+
ssaDefAssigns(def, nodeFrom.asExpr()) and
154180
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
155181
or
156182
// Flow from SSA definition to first read
@@ -273,7 +299,7 @@ module VariableCapture {
273299
or
274300
exists(Ssa::Definition def |
275301
def.getARead() = e2 and
276-
def.getAnUltimateDefinition().(Ssa::WriteDefinition).assigns(e1)
302+
LocalFlow::ssaDefAssigns(def.getAnUltimateDefinition(), e1)
277303
)
278304
}
279305

@@ -574,8 +600,7 @@ private module Cached {
574600

575601
/** Holds if `n` wraps an SSA definition without ingoing flow. */
576602
private predicate entrySsaDefinition(SsaDefinitionExtNode n) {
577-
n.getDefinitionExt() =
578-
any(SsaImpl::WriteDefinition def | not def.(Ssa::WriteDefinition).assigns(_))
603+
n.getDefinitionExt() = any(SsaImpl::WriteDefinition def | not LocalFlow::ssaDefAssigns(def, _))
579604
}
580605

581606
pragma[nomagic]

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,16 @@ private module Cached {
7979
cached
8080
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
8181
// value of `case` expression into variables in patterns
82-
exists(CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprNodes::InClauseCfgNode clause |
83-
nodeFrom.asExpr() = case.getValue() and
82+
exists(
83+
CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprCfgNode value,
84+
CfgNodes::ExprNodes::InClauseCfgNode clause, Ssa::Definition def
85+
|
86+
nodeFrom.asExpr() = value and
87+
value = case.getValue() and
8488
clause = case.getBranch(_) and
85-
nodeTo.(SsaDefinitionExtNode).getDefinitionExt().(Ssa::Definition).getControlFlowNode() =
86-
variablesInPattern(clause.getPattern())
89+
def = nodeTo.(SsaDefinitionExtNode).getDefinitionExt() and
90+
def.getControlFlowNode() = variablesInPattern(clause.getPattern()) and
91+
not LocalFlow::ssaDefAssigns(def, value)
8792
)
8893
or
8994
// operation involving `nodeFrom`

ruby/ql/test/library-tests/dataflow/local/DataflowStep.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2528,6 +2528,8 @@
25282528
| local_dataflow.rb:78:12:78:20 | [post] self | local_dataflow.rb:85:22:85:28 | self |
25292529
| local_dataflow.rb:78:12:78:20 | [post] self | local_dataflow.rb:86:28:86:34 | self |
25302530
| local_dataflow.rb:78:12:78:20 | [post] self | local_dataflow.rb:87:20:87:26 | self |
2531+
| local_dataflow.rb:78:12:78:20 | call to source | local_dataflow.rb:79:13:79:13 | b |
2532+
| local_dataflow.rb:78:12:78:20 | call to source | local_dataflow.rb:80:8:80:8 | a |
25312533
| local_dataflow.rb:78:12:78:20 | self | local_dataflow.rb:79:20:79:26 | self |
25322534
| local_dataflow.rb:78:12:78:20 | self | local_dataflow.rb:80:24:80:30 | self |
25332535
| local_dataflow.rb:78:12:78:20 | self | local_dataflow.rb:82:7:82:13 | self |

ruby/ql/test/library-tests/dataflow/local/local_dataflow.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ def test_case x
7676
end
7777

7878
z = case source(1)
79-
in 5 => b then sink(b) # $ hasTaintFlow=1
80-
in a if a > 0 then sink(a) # $ hasTaintFlow=1
79+
in 5 => b then sink(b) # $ hasValueFlow=1
80+
in a if a > 0 then sink(a) # $ hasValueFlow=1
8181
in [c, *d, e ] then [
8282
sink(c), # $ hasTaintFlow=1
8383
sink(d), # $ hasTaintFlow=1

0 commit comments

Comments
 (0)