Skip to content

Commit 08dc6d7

Browse files
committed
Add support for flow summaries
1 parent c183e05 commit 08dc6d7

File tree

16 files changed

+1520
-86
lines changed

16 files changed

+1520
-86
lines changed

ql/lib/codeql/ruby/ast/Call.qll

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,8 @@ class Call extends Expr, TCall {
5353

5454
/** Gets a potential target of this call, if any. */
5555
final Callable getATarget() {
56-
exists(DataFlowCall c | this = c.getExpr() |
57-
result = viableCallable(c)
58-
or
59-
result = viableCallableLambda(c, _)
56+
exists(DataFlowCall c | this = c.asCall().getExpr() |
57+
TCfgScope(result) = [viableCallable(c), viableCallableLambda(c, _)]
6058
)
6159
}
6260

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/** Provides classes and predicates for defining flow summaries. */
2+
3+
import ruby
4+
import codeql.ruby.DataFlow
5+
private import internal.FlowSummaryImpl as Impl
6+
private import internal.DataFlowDispatch
7+
8+
// import all instances below
9+
private module Summaries { }
10+
11+
class SummaryComponent = Impl::Public::SummaryComponent;
12+
13+
/** Provides predicates for constructing summary components. */
14+
module SummaryComponent {
15+
private import Impl::Public::SummaryComponent as SC
16+
17+
predicate parameter = SC::parameter/1;
18+
19+
predicate argument = SC::argument/1;
20+
21+
/** Gets a summary component that represents a qualifier. */
22+
SummaryComponent qualifier() { result = argument(-1) }
23+
24+
/** Gets a summary component that represents a block argument. */
25+
SummaryComponent block() { result = argument(-2) }
26+
27+
/** Gets a summary component that represents the return value of a call. */
28+
SummaryComponent return() { result = SC::return(any(NormalReturnKind rk)) }
29+
}
30+
31+
class SummaryComponentStack = Impl::Public::SummaryComponentStack;
32+
33+
/** Provides predicates for constructing stacks of summary components. */
34+
module SummaryComponentStack {
35+
private import Impl::Public::SummaryComponentStack as SCS
36+
37+
predicate singleton = SCS::singleton/1;
38+
39+
predicate push = SCS::push/2;
40+
41+
predicate argument = SCS::argument/1;
42+
43+
/** Gets a singleton stack representing a qualifier. */
44+
SummaryComponentStack qualifier() { result = singleton(SummaryComponent::qualifier()) }
45+
46+
/** Gets a singleton stack representing a block argument. */
47+
SummaryComponentStack block() { result = singleton(SummaryComponent::block()) }
48+
49+
/** Gets a singleton stack representing the return value of a call. */
50+
SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
51+
}
52+
53+
/** A callable with a flow summary, identified by a unique string. */
54+
abstract class SummarizedCallable extends LibraryCallable {
55+
bindingset[this]
56+
SummarizedCallable() { any() }
57+
58+
/**
59+
* Holds if data may flow from `input` to `output` through this callable.
60+
*
61+
* `preservesValue` indicates whether this is a value-preserving step
62+
* or a taint-step.
63+
*
64+
* Input specifications are restricted to stacks that end with
65+
* `SummaryComponent::argument(_)`, preceded by zero or more
66+
* `SummaryComponent::return()` or `SummaryComponent::content(_)` components.
67+
*
68+
* Output specifications are restricted to stacks that end with
69+
* `SummaryComponent::return()` or `SummaryComponent::argument(_)`.
70+
*
71+
* Output stacks ending with `SummaryComponent::return()` can be preceded by zero
72+
* or more `SummaryComponent::content(_)` components.
73+
*
74+
* Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an
75+
* optional `SummaryComponent::parameter(_)` component, which in turn can be preceded
76+
* by zero or more `SummaryComponent::content(_)` components.
77+
*/
78+
pragma[nomagic]
79+
predicate propagatesFlow(
80+
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
81+
) {
82+
none()
83+
}
84+
85+
/**
86+
* Same as
87+
*
88+
* ```rb
89+
* propagatesFlow(
90+
* SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
91+
* )
92+
* ```
93+
*
94+
* but uses an external (string) representation of the input and output stacks.
95+
*/
96+
pragma[nomagic]
97+
predicate propagatesFlowExt(string input, string output, string kind) { none() }
98+
99+
/**
100+
* Holds if values stored inside `content` are cleared on objects passed as
101+
* the `i`th argument to this callable.
102+
*/
103+
pragma[nomagic]
104+
predicate clearsContent(int i, DataFlow::Content content) { none() }
105+
}
106+
107+
private class SummarizedCallableAdaptor extends Impl::Public::SummarizedCallable {
108+
private SummarizedCallable sc;
109+
110+
SummarizedCallableAdaptor() { this = TLibraryCallable(sc) }
111+
112+
final override predicate propagatesFlow(
113+
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
114+
) {
115+
sc.propagatesFlow(input, output, preservesValue)
116+
}
117+
118+
final override predicate clearsContent(int i, DataFlow::Content content) {
119+
sc.clearsContent(i, content)
120+
}
121+
}
122+
123+
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;

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

Lines changed: 151 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ private import ruby
22
private import codeql.ruby.CFG
33
private import DataFlowPrivate
44
private import codeql.ruby.typetracking.TypeTracker
5-
private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlow
65
private import codeql.ruby.ast.internal.Module
6+
private import FlowSummaryImpl as FlowSummaryImpl
7+
private import codeql.ruby.dataflow.FlowSummary
78

89
newtype TReturnKind =
910
TNormalReturnKind() or
@@ -39,83 +40,183 @@ class BreakReturnKind extends ReturnKind, TBreakReturnKind {
3940
override string toString() { result = "break" }
4041
}
4142

42-
class DataFlowCallable = CfgScope;
43+
/** A callable defined in library code, identified by a unique string. */
44+
abstract class LibraryCallable extends string {
45+
bindingset[this]
46+
LibraryCallable() { any() }
4347

44-
class DataFlowCall extends CfgNodes::ExprNodes::CallCfgNode {
45-
DataFlowCallable getEnclosingCallable() { result = this.getScope() }
48+
/** Gets a call to this library callable. */
49+
abstract Call getACall();
50+
}
4651

47-
pragma[nomagic]
48-
private predicate methodCall(DataFlow::LocalSourceNode sourceNode, string method) {
49-
exists(DataFlow::Node nodeTo |
50-
method = this.getExpr().(MethodCall).getMethodName() and
51-
nodeTo.asExpr() = this.getReceiver() and
52-
sourceNode.flowsTo(nodeTo)
53-
)
54-
}
52+
/**
53+
* A callable. This includes callables from source code, as well as callables
54+
* defined in library code.
55+
*/
56+
class DataFlowCallable extends TDataFlowCallable {
57+
/** Gets the underlying source code callable, if any. */
58+
Callable asCallable() { this = TCfgScope(result) }
5559

56-
private Block yieldCall() {
57-
this.getExpr() instanceof YieldCall and
58-
exists(BlockParameterNode node |
59-
node = trackBlock(result) and
60-
node.getMethod() = this.getExpr().getEnclosingMethod()
61-
)
62-
}
60+
/** Get the underlying library callable, if any. */
61+
LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) }
6362

64-
pragma[nomagic]
65-
private predicate superCall(Module superClass, string method) {
66-
this.getExpr() instanceof SuperCall and
67-
exists(Module tp |
68-
tp = this.getExpr().getEnclosingModule().getModule() and
69-
superClass = tp.getSuperClass() and
70-
method = this.getExpr().getEnclosingMethod().getName()
71-
)
72-
}
63+
/** Gets a textual representation of this callable. */
64+
string toString() { result = [this.asCallable().toString(), this.asLibraryCallable()] }
7365

74-
pragma[nomagic]
75-
private predicate instanceMethodCall(Module tp, string method) {
76-
exists(DataFlow::LocalSourceNode sourceNode |
77-
this.methodCall(sourceNode, method) and
78-
sourceNode = trackInstance(tp)
79-
)
80-
}
66+
/** Gets the location of this callable. */
67+
Location getLocation() { result = this.asCallable().getLocation() }
68+
}
69+
70+
/**
71+
* A call. This includes calls from source code, as well as call(back)s
72+
* inside library callables with a flow summary.
73+
*/
74+
class DataFlowCall extends TDataFlowCall {
75+
/** Gets the enclosing callable. */
76+
DataFlowCallable getEnclosingCallable() { none() }
77+
78+
/** Gets the underlying source code call, if any. */
79+
CfgNodes::ExprNodes::CallCfgNode asCall() { none() }
80+
81+
/** Gets a textual representation of this call. */
82+
string toString() { none() }
83+
84+
/** Gets the location of this call. */
85+
Location getLocation() { none() }
86+
}
87+
88+
/**
89+
* A synthesized call inside a callable with a flow summary.
90+
*
91+
* For example, in
92+
* ```rb
93+
* ints.each do |i|
94+
* puts i
95+
* end
96+
* ```
97+
*
98+
* there is a call to the block argument inside `each`.
99+
*/
100+
class SummaryCall extends DataFlowCall, TSummaryCall {
101+
private FlowSummaryImpl::Public::SummarizedCallable c;
102+
private DataFlow::Node receiver;
103+
104+
SummaryCall() { this = TSummaryCall(c, receiver) }
105+
106+
/** Gets the data flow node that this call targets. */
107+
DataFlow::Node getReceiver() { result = receiver }
108+
109+
override DataFlowCallable getEnclosingCallable() { result = c }
110+
111+
override string toString() { result = "[summary] call to " + receiver + " in " + c }
112+
113+
override Location getLocation() { result = c.getLocation() }
114+
}
115+
116+
private class NormalCall extends DataFlowCall, TNormalCall {
117+
private CfgNodes::ExprNodes::CallCfgNode c;
81118

119+
NormalCall() { this = TNormalCall(c) }
120+
121+
override CfgNodes::ExprNodes::CallCfgNode asCall() { result = c }
122+
123+
override DataFlowCallable getEnclosingCallable() { result = TCfgScope(c.getScope()) }
124+
125+
override string toString() { result = c.toString() }
126+
127+
override Location getLocation() { result = c.getLocation() }
128+
}
129+
130+
pragma[nomagic]
131+
private predicate methodCall(
132+
CfgNodes::ExprNodes::CallCfgNode call, DataFlow::LocalSourceNode sourceNode, string method
133+
) {
134+
exists(DataFlow::Node nodeTo |
135+
method = call.getExpr().(MethodCall).getMethodName() and
136+
nodeTo.asExpr() = call.getReceiver() and
137+
sourceNode.flowsTo(nodeTo)
138+
)
139+
}
140+
141+
private Block yieldCall(CfgNodes::ExprNodes::CallCfgNode call) {
142+
call.getExpr() instanceof YieldCall and
143+
exists(BlockParameterNode node |
144+
node = trackBlock(result) and
145+
node.getMethod() = call.getExpr().getEnclosingMethod()
146+
)
147+
}
148+
149+
pragma[nomagic]
150+
private predicate superCall(CfgNodes::ExprNodes::CallCfgNode call, Module superClass, string method) {
151+
call.getExpr() instanceof SuperCall and
152+
exists(Module tp |
153+
tp = call.getExpr().getEnclosingModule().getModule() and
154+
superClass = tp.getSuperClass() and
155+
method = call.getExpr().getEnclosingMethod().getName()
156+
)
157+
}
158+
159+
pragma[nomagic]
160+
private predicate instanceMethodCall(CfgNodes::ExprNodes::CallCfgNode call, Module tp, string method) {
161+
exists(DataFlow::LocalSourceNode sourceNode |
162+
methodCall(call, sourceNode, method) and
163+
sourceNode = trackInstance(tp)
164+
)
165+
}
166+
167+
cached
168+
private module Cached {
82169
cached
83-
DataFlowCallable getTarget() {
170+
newtype TDataFlowCallable =
171+
TCfgScope(CfgScope scope) or
172+
TLibraryCallable(LibraryCallable callable)
173+
174+
cached
175+
newtype TDataFlowCall =
176+
TNormalCall(CfgNodes::ExprNodes::CallCfgNode c) or
177+
TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, DataFlow::Node receiver) {
178+
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
179+
}
180+
181+
cached
182+
CfgScope getTarget(CfgNodes::ExprNodes::CallCfgNode call) {
84183
exists(string method |
85184
exists(Module tp |
86-
this.instanceMethodCall(tp, method) and
185+
instanceMethodCall(call, tp, method) and
87186
result = lookupMethod(tp, method) and
88187
if result.(Method).isPrivate()
89188
then
90189
exists(Self self |
91-
self = this.getReceiver().getExpr() and
190+
self = call.getReceiver().getExpr() and
92191
pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
93192
pragma[only_bind_out](result.getEnclosingModule().getModule())
94193
) and
95194
// For now, we restrict the scope of top-level declarations to their file.
96195
// This may remove some plausible targets, but also removes a lot of
97196
// implausible targets
98197
if result.getEnclosingModule() instanceof Toplevel
99-
then result.getFile() = this.getFile()
198+
then result.getFile() = call.getFile()
100199
else any()
101200
else any()
102201
)
103202
or
104203
exists(DataFlow::LocalSourceNode sourceNode |
105-
this.methodCall(sourceNode, method) and
204+
methodCall(call, sourceNode, method) and
106205
sourceNode = trackSingletonMethod(result, method)
107206
)
108207
)
109208
or
110209
exists(Module superClass, string method |
111-
this.superCall(superClass, method) and
210+
superCall(call, superClass, method) and
112211
result = lookupMethod(superClass, method)
113212
)
114213
or
115-
result = this.yieldCall()
214+
result = yieldCall(call)
116215
}
117216
}
118217

218+
import Cached
219+
119220
private DataFlow::LocalSourceNode trackInstance(Module tp, TypeTracker t) {
120221
t.start() and
121222
(
@@ -297,8 +398,13 @@ private DataFlow::LocalSourceNode trackModule(Module tp) {
297398

298399
/** Gets a viable run-time target for the call `call`. */
299400
DataFlowCallable viableCallable(DataFlowCall call) {
300-
result = call.getTarget() and
301-
not call.getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
401+
result = TCfgScope(getTarget(call.asCall())) and
402+
not call.asCall().getExpr() instanceof YieldCall // handled by `lambdaCreation`/`lambdaCall`
403+
or
404+
exists(LibraryCallable callable |
405+
result = TLibraryCallable(callable) and
406+
call.asCall().getExpr() = callable.getACall()
407+
)
302408
}
303409

304410
/**
@@ -307,7 +413,7 @@ DataFlowCallable viableCallable(DataFlowCall call) {
307413
* qualifier accesses a parameter of the enclosing callable `c` (including
308414
* the implicit `self` parameter).
309415
*/
310-
predicate mayBenefitFromCallContext(DataFlowCall call, Callable c) { none() }
416+
predicate mayBenefitFromCallContext(DataFlowCall call, DataFlowCallable c) { none() }
311417

312418
/**
313419
* Gets a viable dispatch target of `call` in the context `ctx`. This is

0 commit comments

Comments
 (0)