Skip to content

Commit 384e7c7

Browse files
committed
Jump step for sinatra callbacks
1 parent e65d722 commit 384e7c7

File tree

4 files changed

+84
-9
lines changed

4 files changed

+84
-9
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,26 @@ module Ssa {
294294
override Location getLocation() { result = this.getBasicBlock().getLocation() }
295295
}
296296

297+
/**
298+
* An SSA definition inserted at the beginning of a scope to represent a captured `self` variable.
299+
* For example, in
300+
*
301+
* ```rb
302+
* def m(x)
303+
* x.tap do |x|
304+
* foo(x)
305+
* end
306+
* end
307+
* ```
308+
*
309+
* an entry definition for `self` is inserted at the start of the `do` block.
310+
*/
311+
class CapturedSelfDefinition extends CapturedEntryDefinition {
312+
private SelfVariable v;
313+
314+
CapturedSelfDefinition() { this.getSourceVariable() = v }
315+
}
316+
297317
/**
298318
* An SSA definition inserted at a call that may update the value of a captured
299319
* variable. For example, in

ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ private import codeql.ruby.DataFlow
55
private import codeql.ruby.Concepts
66
private import codeql.ruby.AST
77
private import codeql.ruby.dataflow.FlowSummary
8+
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
9+
private import codeql.ruby.dataflow.SSA
810

911
/** Provides modeling for the Sinatra library. */
1012
module Sinatra {
@@ -30,10 +32,9 @@ module Sinatra {
3032
}
3133

3234
private class Params extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
33-
private Route route;
34-
3535
Params() {
36-
this.asExpr().getExpr().getEnclosingCallable() = route.getBody().asCallableAstNode() and
36+
this.asExpr().getExpr().getEnclosingCallable() =
37+
[any(Route r).getBody(), any(Filter f).getBody()].asCallableAstNode() and
3738
this.getMethodName() = "params"
3839
}
3940

@@ -140,6 +141,9 @@ module Sinatra {
140141

141142
Filter() { this = app.getAModuleLevelCall(["before", "after"]) }
142143

144+
/** Gets the app which this filter belongs to. */
145+
App getApp() { result = app }
146+
143147
/**
144148
* Gets the pattern which constrains this route, if any. In the example below, the pattern is `/protected/*`.
145149
* Patterns are typically given as strings, and are interpreted by the `mustermann` gem (they are not regular expressions).
@@ -169,4 +173,50 @@ module Sinatra {
169173
class AfterFilter extends Filter {
170174
AfterFilter() { this.getMethodName() = "after" }
171175
}
176+
177+
/**
178+
* A class defining additional jump steps arising from filters.
179+
*/
180+
class FilterJumpStep extends DataFlowPrivate::AdditionalJumpStep {
181+
/**
182+
* Holds if data can flow from `pred` to `succ` via a callback chain.
183+
*/
184+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
185+
exists(BeforeFilter filter, Route route |
186+
// the filter and route belong to the same app
187+
filter.getApp() = route.getApp() and
188+
// the filter applies to all routes
189+
not filter.hasPattern() and
190+
selfPostUpdate(pred, filter.getApp(), filter.getBody().asExpr().getExpr()) and
191+
blockCapturedSelfParameterNode(succ, route.getBody().asExpr().getExpr())
192+
)
193+
}
194+
195+
/**
196+
* Holds if `n` is a post-update node for the `self` parameter of `app` in block `b`.
197+
*
198+
* In this example, `n` is the post-update node for `@foo = 1`.
199+
* ```rb
200+
* class MyApp < Sinatra::Base
201+
* before do
202+
* @foo = 1
203+
* end
204+
* end
205+
* ```
206+
*/
207+
private predicate selfPostUpdate(DataFlow::PostUpdateNode n, App app, Block b) {
208+
n.getPreUpdateNode().asExpr().getExpr() =
209+
any(SelfVariableAccess self |
210+
pragma[only_bind_into](b) = self.getEnclosingCallable() and
211+
self.getVariable().getDeclaringScope() = app.getADeclaration()
212+
)
213+
}
214+
}
215+
216+
private predicate blockCapturedSelfParameterNode(DataFlow::Node n, Block b) {
217+
exists(Ssa::CapturedSelfDefinition d |
218+
n.(DataFlowPrivate::SsaDefinitionExtNode).getDefinitionExt() = d and
219+
d.getBasicBlock().getScope() = b
220+
)
221+
}
172222
}

ruby/ql/test/library-tests/frameworks/sinatra/Flow.ql

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import PathGraph
88
import codeql.ruby.frameworks.Sinatra
99
import codeql.ruby.Concepts
1010

11-
class ParamsTaintFlowConf extends DefaultTaintFlowConf {
12-
override predicate isSource(DataFlow::Node n) { n instanceof Http::Server::RequestInputAccess }
13-
}
14-
15-
from DataFlow::PathNode source, DataFlow::PathNode sink, ParamsTaintFlowConf conf
11+
from DataFlow::PathNode source, DataFlow::PathNode sink, DefaultTaintFlowConf conf
1612
where conf.hasFlowPath(source, sink)
1713
select sink, source, sink, "$@", source, source.toString()

ruby/ql/test/library-tests/frameworks/sinatra/app.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class MyApp < Sinatra::Base
7272
end
7373

7474
get '/' do
75-
@foo = params["foo"]
75+
@foo = source "foo"
7676
erb :index, locals: {foo: @foo}
7777
end
7878

@@ -91,14 +91,23 @@ class MyApp < Sinatra::Base
9191
params['splat'] #=> 'bar/baz'
9292
end
9393

94+
get "/home" do
95+
sink @user # $ hasTaintFlow=a
96+
end
97+
9498
after do
9599
puts response.status
96100
end
97101

102+
before do
103+
@user = source "a"
104+
end
105+
98106
before '/protected/*' do
99107
authenticate!
100108
end
101109

110+
102111
after '/create/:slug' do |slug|
103112
session[:last_slug] = slug
104113
end

0 commit comments

Comments
 (0)