Skip to content

Commit fdf1cd3

Browse files
committed
Data flow: Add a synthetic return node
1 parent 1d12159 commit fdf1cd3

File tree

6 files changed

+92
-25
lines changed

6 files changed

+92
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
436436
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
437437
*/
438438
predicate exprNodeReturnedFrom(DataFlow::ExprNode e, Callable c) {
439-
exists(ReturnNode r |
439+
exists(ReturningNode r |
440440
r.getEnclosingCallable().asCallable() = c and
441441
(
442442
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or

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

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ module LocalFlow {
4848

4949
/**
5050
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
51-
* SSA definition `def.
51+
* SSA definition `def`.
5252
*/
5353
predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
5454
// Flow from parameter into SSA definition
@@ -114,6 +114,12 @@ private module Cached {
114114
newtype TNode =
115115
TExprNode(CfgNodes::ExprCfgNode n) or
116116
TReturningNode(CfgNodes::ReturningCfgNode n) or
117+
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
118+
exists(ReturningNode ret |
119+
ret.(NodeImpl).getCfgScope() = scope and
120+
ret.getKind() = kind
121+
)
122+
} or
117123
TSsaDefinitionNode(Ssa::Definition def) or
118124
TNormalParameterNode(Parameter p) { not p instanceof BlockParameter } or
119125
TSelfParameterNode(MethodBase m) or
@@ -140,14 +146,12 @@ private module Cached {
140146
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode;
141147

142148
/**
143-
* This is the local flow predicate that is used as a building block in global
144-
* data flow. It excludes SSA flow through instance fields, as flow through fields
145-
* is handled by the global data-flow library, but includes various other steps
146-
* that are only relevant for global flow.
149+
* This is the local flow predicate that is shared between local data flow
150+
* and global data flow.
147151
*/
148152
cached
149-
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
150-
exists(Ssa::Definition def | LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo))
153+
predicate simpleLocalFlowStepCommon(Node nodeFrom, Node nodeTo) {
154+
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
151155
or
152156
nodeTo.(ParameterNode).getParameter().(OptionalParameter).getDefaultValue() =
153157
nodeFrom.asExpr().getExpr()
@@ -186,12 +190,39 @@ private module Cached {
186190
) and
187191
nodeFrom.asExpr() = for.getValue()
188192
)
193+
}
194+
195+
/**
196+
* This is the local flow predicate that is used as a building block in global
197+
* data flow. It excludes SSA flow through instance fields, as flow through fields
198+
* is handled by the global data-flow library, but includes various other steps
199+
* that are only relevant for global flow.
200+
*/
201+
cached
202+
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
203+
simpleLocalFlowStepCommon(nodeFrom, nodeTo)
204+
or
205+
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
189206
or
190207
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
191208
}
192209

193210
cached
194-
predicate isLocalSourceNode(Node n) { not simpleLocalFlowStep+(any(ExprNode e), n) }
211+
predicate isLocalSourceNode(Node n) {
212+
n instanceof ParameterNode
213+
or
214+
// This case should not be needed once we have proper use-use flow
215+
// for `self`. At that point, the `self`s returned by `trackInstance`
216+
// in `DataFlowDispatch.qll` should refer to the post-update node,
217+
// and we can remove this case.
218+
n instanceof SelfArgumentNode
219+
or
220+
not simpleLocalFlowStepCommon+(any(Node e |
221+
e instanceof ExprNode
222+
or
223+
e instanceof ParameterNode
224+
), n)
225+
}
195226

196227
cached
197228
newtype TContent = TTodoContent() // stub
@@ -208,6 +239,8 @@ predicate nodeIsHidden(Node n) {
208239
n instanceof SummaryNode
209240
or
210241
n instanceof SummaryParameterNode
242+
or
243+
n instanceof SynthReturnNode
211244
}
212245

213246
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -234,7 +267,7 @@ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
234267
* `ControlFlow::Node`s.
235268
*/
236269
class ReturningStatementNode extends NodeImpl, TReturningNode {
237-
private CfgNodes::ReturningCfgNode n;
270+
CfgNodes::ReturningCfgNode n;
238271

239272
ReturningStatementNode() { this = TReturningNode(n) }
240273

@@ -436,6 +469,12 @@ private module ArgumentNodes {
436469

437470
import ArgumentNodes
438471

472+
/** A data-flow node that represents a value syntactically returned by a callable. */
473+
abstract class ReturningNode extends Node {
474+
/** Gets the kind of this return node. */
475+
abstract ReturnKind getKind();
476+
}
477+
439478
/** A data-flow node that represents a value returned by a callable. */
440479
abstract class ReturnNode extends Node {
441480
/** Gets the kind of this return node. */
@@ -463,11 +502,9 @@ private module ReturnNodes {
463502
* A data-flow node that represents an expression returned by a callable,
464503
* either using an explict `return` statement or as the expression of a method body.
465504
*/
466-
class ExplicitReturnNode extends ReturnNode, ReturningStatementNode {
467-
private CfgNodes::ReturningCfgNode n;
468-
505+
class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
469506
ExplicitReturnNode() {
470-
isValid(this.getReturningNode()) and
507+
isValid(n) and
471508
n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
472509
n.getScope() instanceof Callable
473510
}
@@ -479,7 +516,7 @@ private module ReturnNodes {
479516
}
480517
}
481518

482-
class ExprReturnNode extends ReturnNode, ExprNode {
519+
class ExprReturnNode extends ReturningNode, ExprNode {
483520
ExprReturnNode() {
484521
this.getExprNode().getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
485522
this.(NodeImpl).getCfgScope() instanceof Callable
@@ -488,6 +525,34 @@ private module ReturnNodes {
488525
override ReturnKind getKind() { result instanceof NormalReturnKind }
489526
}
490527

528+
/**
529+
* A synthetic data-flow node for joining flow from different syntactic
530+
* returns into a single node.
531+
*
532+
* This node only exists to avoid computing the product of a large fan-in
533+
* with a large fan-out.
534+
*/
535+
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
536+
private CfgScope scope;
537+
private ReturnKind kind;
538+
539+
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
540+
541+
/** Get a syntactic return node that flows into this synthetic node. */
542+
ReturningNode getAnInput() {
543+
result.(NodeImpl).getCfgScope() = scope and
544+
result.getKind() = kind
545+
}
546+
547+
override ReturnKind getKind() { result = kind }
548+
549+
override CfgScope getCfgScope() { result = scope }
550+
551+
override Location getLocationImpl() { result = scope.getLocation() }
552+
553+
override string toStringImpl() { result = "return " + kind + " in " + scope }
554+
}
555+
491556
private class SummaryReturnNode extends SummaryNode, ReturnNode {
492557
private ReturnKind rk;
493558

@@ -631,7 +696,7 @@ private import PostUpdateNodes
631696

632697
/** A node that performs a type cast. */
633698
class CastNode extends Node {
634-
CastNode() { none() }
699+
CastNode() { this instanceof ReturningNode }
635700
}
636701

637702
class DataFlowExpr = CfgNodes::ExprCfgNode;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ predicate hasLocalSource(Node sink, Node source) {
120120
or
121121
exists(Node mid |
122122
hasLocalSource(mid, source) and
123-
simpleLocalFlowStep(mid, sink)
123+
simpleLocalFlowStepCommon(mid, sink)
124124
)
125125
}
126126

@@ -137,7 +137,7 @@ ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
137137
* (intra-procedural) step.
138138
*/
139139
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
140-
simpleLocalFlowStep(nodeFrom, nodeTo)
140+
simpleLocalFlowStepCommon(nodeFrom, nodeTo)
141141
or
142142
// Simple flow through library code is included in the exposed local
143143
// step relation, even though flow is technically inter-procedural

ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,18 @@ predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::Param
4545
* recursion (or, at best, terrible performance), since identifying calls to library
4646
* methods is done using API graphs (which uses type tracking).
4747
*/
48-
predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) {
48+
predicate returnStep(Node nodeFrom, Node nodeTo) {
4949
exists(ExprNodes::CallCfgNode call |
50+
nodeFrom instanceof DataFlowPrivate::ReturnNode and
5051
nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and
5152
nodeTo.asExpr().getNode() = call.getNode()
5253
)
54+
or
55+
// In normal data-flow, this will be a local flow step. But for type tracking
56+
// we model it as a returning flow step, in order to avoid computing a potential
57+
// self-cross product of all calls to a function that returns one of its parameters
58+
// (only to later filter that flow out using `TypeTracker::append`).
59+
nodeTo.(DataFlowPrivate::SynthReturnNode).getAnInput() = nodeFrom
5360
}
5461

5562
/**
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import ruby
22
import codeql.ruby.dataflow.internal.DataFlowPrivate
33

4-
select any(ReturnNode node)
4+
select any(ReturningNode node)

ql/test/query-tests/security/cwe-601/UrlRedirect.expected

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ edges
33
| UrlRedirect.rb:14:17:14:22 | call to params : | UrlRedirect.rb:14:17:14:43 | call to fetch |
44
| UrlRedirect.rb:19:17:19:22 | call to params : | UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash |
55
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:24:17:24:37 | call to filter_params |
6-
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : |
76
| UrlRedirect.rb:34:20:34:25 | call to params : | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" |
8-
| UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : |
97
nodes
108
| UrlRedirect.rb:4:17:4:22 | call to params | semmle.label | call to params |
119
| UrlRedirect.rb:9:17:9:22 | call to params : | semmle.label | call to params : |
@@ -18,10 +16,7 @@ nodes
1816
| UrlRedirect.rb:24:31:24:36 | call to params : | semmle.label | call to params : |
1917
| UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | semmle.label | "#{...}/foo" |
2018
| UrlRedirect.rb:34:20:34:25 | call to params : | semmle.label | call to params : |
21-
| UrlRedirect.rb:56:21:56:32 | input_params : | semmle.label | input_params : |
22-
| UrlRedirect.rb:57:5:57:29 | call to permit : | semmle.label | call to permit : |
2319
subpaths
24-
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : | UrlRedirect.rb:24:17:24:37 | call to filter_params : |
2520
#select
2621
| UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | Untrusted URL redirection due to $@. | UrlRedirect.rb:4:17:4:22 | call to params | a user-provided value |
2722
| UrlRedirect.rb:9:17:9:28 | ...[...] | UrlRedirect.rb:9:17:9:22 | call to params : | UrlRedirect.rb:9:17:9:28 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:9:17:9:22 | call to params | a user-provided value |

0 commit comments

Comments
 (0)