Skip to content

Commit 071517c

Browse files
committed
Ruby: Clean up Sinatra modeling
1 parent bfe42a6 commit 071517c

File tree

4 files changed

+179
-47
lines changed

4 files changed

+179
-47
lines changed

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

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,37 @@ private import codeql.ruby.dataflow.SSA
1010

1111
/** Provides modeling for the Sinatra library. */
1212
module Sinatra {
13+
/**
14+
* A Sinatra application.
15+
*
16+
* ```rb
17+
* class MyApp < Sinatra::Base
18+
* get "/" do
19+
* erb :home
20+
* end
21+
* end
22+
* ```
23+
*/
1324
class App extends DataFlow::ClassNode {
1425
App() { this = DataFlow::getConstant("Sinatra").getConstant("Base").getADescendentModule() }
1526

27+
/**
28+
* Gets a route defined in this application.
29+
*/
1630
Route getARoute() { result.getApp() = this }
1731
}
1832

33+
/**
34+
* A Sinatra route handler. HTTP requests with a matching method and path will
35+
* be handled by the block. For example, the following route will handle `GET`
36+
* requests with path `/`.
37+
*
38+
* ```rb
39+
* get "/" do
40+
* erb :home
41+
* end
42+
* ```
43+
*/
1944
class Route extends DataFlow::CallNode {
2045
private App app;
2146

@@ -26,11 +51,20 @@ module Sinatra {
2651
])
2752
}
2853

54+
/**
55+
* Gets the application that defines this route.
56+
*/
2957
App getApp() { result = app }
3058

59+
/**
60+
* Gets the body of this route.
61+
*/
3162
DataFlow::BlockNode getBody() { result = this.getBlock() }
3263
}
3364

65+
/**
66+
* An access to the parameters of an HTTP request in a Sinatra route handler or callback.
67+
*/
3468
private class Params extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
3569
Params() {
3670
this.asExpr().getExpr().getEnclosingCallable() =
@@ -45,6 +79,9 @@ module Sinatra {
4579
}
4680
}
4781

82+
/**
83+
* A call which renders an ERB template as an HTTP response.
84+
*/
4885
class ErbCall extends DataFlow::CallNode {
4986
private Route route;
5087

@@ -57,6 +94,11 @@ module Sinatra {
5794
* Gets the template file corresponding to this call.
5895
*/
5996
ErbFile getTemplateFile() { result = getTemplateFile(this.asExpr().getExpr()) }
97+
98+
/**
99+
* Gets the route containing this call.
100+
*/
101+
Route getRoute() { result = route }
60102
}
61103

62104
/**
@@ -78,6 +120,9 @@ module Sinatra {
78120
loc.getEndLine() + ":" + loc.getEndColumn()
79121
}
80122

123+
/**
124+
* A synthetic global representing the hash of local variables passed to an ERB template.
125+
*/
81126
class ErbLocalsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
82127
private string id;
83128
private MethodCall erbCall;
@@ -90,25 +135,44 @@ module Sinatra {
90135
erbFile = getTemplateFile(erbCall)
91136
}
92137

138+
/**
139+
* Gets the `erb` call associated with this global.
140+
*/
141+
MethodCall getErbCall() { result = erbCall }
142+
143+
/**
144+
* Gets the ERB template that this global contains the locals for.
145+
*/
93146
ErbFile getErbFile() { result = erbFile }
94147

148+
/**
149+
* Gets a unique identifer for this global.
150+
*/
95151
string getId() { result = id }
96152
}
97153

154+
/**
155+
* A summary for `Sinatra::Base#erb`. This models the first half of the flow
156+
* from the `locals` keyword argument to variables in the ERB template. The
157+
* second half is modeled by `ErbLocalsAccessSummary`.
158+
*/
98159
private class ErbLocalsSummary extends SummarizedCallable {
99-
private ErbLocalsHashSyntheticGlobal global;
100-
101160
ErbLocalsSummary() { this = "Sinatra::Base#erb" }
102161

103162
override MethodCall getACall() { result = any(ErbCall c).asExpr().getExpr() }
104163

105164
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
106165
input = "Argument[locals:]" and
107-
output = "SyntheticGlobal[" + global + "]" and
166+
output = "SyntheticGlobal[" + any(ErbLocalsHashSyntheticGlobal global) + "]" and
108167
preservesValue = true
109168
}
110169
}
111170

171+
/**
172+
* A summary for accessing a local variable in an ERB template.
173+
* This is the second half of the modelling of the flow from the `locals` keyword argument to variables in the ERB template.
174+
* The first half is modeled by `ErbLocalsSummary`.
175+
*/
112176
private class ErbLocalsAccessSummary extends SummarizedCallable {
113177
private ErbLocalsHashSyntheticGlobal global;
114178
private string local;
@@ -133,6 +197,8 @@ module Sinatra {
133197
}
134198

135199
/**
200+
* A class representing Sinatra filters AKA callbacks.
201+
*
136202
* Filters are run before or after the route handler. They can modify the
137203
* request and response, and share instance variables with the route handler.
138204
*/
@@ -166,16 +232,24 @@ module Sinatra {
166232
DataFlow::BlockNode getBody() { result = this.getBlock() }
167233
}
168234

235+
/**
236+
* `before` filters run before the route handler.
237+
*/
169238
class BeforeFilter extends Filter {
170239
BeforeFilter() { this.getMethodName() = "before" }
171240
}
172241

242+
/**
243+
* `after` filters run after the route handler.
244+
*/
173245
class AfterFilter extends Filter {
174246
AfterFilter() { this.getMethodName() = "after" }
175247
}
176248

177249
/**
178250
* A class defining additional jump steps arising from filters.
251+
* This only models flow between filters with no patterns - i.e. those that apply to all routes.
252+
* Filters with patterns are not yet modeled.
179253
*/
180254
class FilterJumpStep extends DataFlowPrivate::AdditionalJumpStep {
181255
/**
@@ -191,28 +265,31 @@ module Sinatra {
191265
blockCapturedSelfParameterNode(succ, route.getBody().asExpr().getExpr())
192266
)
193267
}
268+
}
194269

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-
}
270+
/**
271+
* Holds if `n` is a post-update node for the `self` parameter of `app` in block `b`.
272+
*
273+
* In this example, `n` is the post-update node for `@foo = 1`.
274+
* ```rb
275+
* class MyApp < Sinatra::Base
276+
* before do
277+
* @foo = 1
278+
* end
279+
* end
280+
* ```
281+
*/
282+
private predicate selfPostUpdate(DataFlow::PostUpdateNode n, App app, Block b) {
283+
n.getPreUpdateNode().asExpr().getExpr() =
284+
any(SelfVariableAccess self |
285+
pragma[only_bind_into](b) = self.getEnclosingCallable() and
286+
self.getVariable().getDeclaringScope() = app.getADeclaration()
287+
)
214288
}
215289

290+
/**
291+
* Holds if `n` is a node representing the `self` parameter captured by block `b`.
292+
*/
216293
private predicate blockCapturedSelfParameterNode(DataFlow::Node n, Block b) {
217294
exists(Ssa::CapturedSelfDefinition d |
218295
n.(DataFlowPrivate::SsaDefinitionExtNode).getDefinitionExt() = d and
Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
routes
2-
| app.rb:1:1:105:3 | MyApp | app.rb:2:3:4:5 | call to get |
3-
| app.rb:1:1:105:3 | MyApp | app.rb:6:3:8:5 | call to get |
4-
| app.rb:1:1:105:3 | MyApp | app.rb:10:3:13:5 | call to get |
5-
| app.rb:1:1:105:3 | MyApp | app.rb:15:3:18:5 | call to get |
6-
| app.rb:1:1:105:3 | MyApp | app.rb:20:3:22:5 | call to get |
7-
| app.rb:1:1:105:3 | MyApp | app.rb:24:3:26:5 | call to get |
8-
| app.rb:1:1:105:3 | MyApp | app.rb:28:3:31:5 | call to get |
9-
| app.rb:1:1:105:3 | MyApp | app.rb:33:3:35:5 | call to get |
10-
| app.rb:1:1:105:3 | MyApp | app.rb:37:3:42:5 | call to get |
11-
| app.rb:1:1:105:3 | MyApp | app.rb:44:3:46:5 | call to get |
12-
| app.rb:1:1:105:3 | MyApp | app.rb:48:3:50:5 | call to get |
13-
| app.rb:1:1:105:3 | MyApp | app.rb:52:3:54:5 | call to get |
14-
| app.rb:1:1:105:3 | MyApp | app.rb:56:3:58:5 | call to get |
15-
| app.rb:1:1:105:3 | MyApp | app.rb:60:3:62:5 | call to get |
16-
| app.rb:1:1:105:3 | MyApp | app.rb:66:3:68:5 | call to get |
17-
| app.rb:1:1:105:3 | MyApp | app.rb:70:3:72:5 | call to get |
18-
| app.rb:1:1:105:3 | MyApp | app.rb:74:3:77:5 | call to get |
19-
| app.rb:1:1:105:3 | MyApp | app.rb:79:3:82:5 | call to get |
20-
| app.rb:1:1:105:3 | MyApp | app.rb:89:3:92:5 | call to get |
2+
| app.rb:1:1:114:3 | MyApp | app.rb:2:3:4:5 | call to get |
3+
| app.rb:1:1:114:3 | MyApp | app.rb:6:3:8:5 | call to get |
4+
| app.rb:1:1:114:3 | MyApp | app.rb:10:3:13:5 | call to get |
5+
| app.rb:1:1:114:3 | MyApp | app.rb:15:3:18:5 | call to get |
6+
| app.rb:1:1:114:3 | MyApp | app.rb:20:3:22:5 | call to get |
7+
| app.rb:1:1:114:3 | MyApp | app.rb:24:3:26:5 | call to get |
8+
| app.rb:1:1:114:3 | MyApp | app.rb:28:3:31:5 | call to get |
9+
| app.rb:1:1:114:3 | MyApp | app.rb:33:3:35:5 | call to get |
10+
| app.rb:1:1:114:3 | MyApp | app.rb:37:3:42:5 | call to get |
11+
| app.rb:1:1:114:3 | MyApp | app.rb:44:3:46:5 | call to get |
12+
| app.rb:1:1:114:3 | MyApp | app.rb:48:3:50:5 | call to get |
13+
| app.rb:1:1:114:3 | MyApp | app.rb:52:3:54:5 | call to get |
14+
| app.rb:1:1:114:3 | MyApp | app.rb:56:3:58:5 | call to get |
15+
| app.rb:1:1:114:3 | MyApp | app.rb:60:3:62:5 | call to get |
16+
| app.rb:1:1:114:3 | MyApp | app.rb:66:3:68:5 | call to get |
17+
| app.rb:1:1:114:3 | MyApp | app.rb:70:3:72:5 | call to get |
18+
| app.rb:1:1:114:3 | MyApp | app.rb:74:3:77:5 | call to get |
19+
| app.rb:1:1:114:3 | MyApp | app.rb:79:3:82:5 | call to get |
20+
| app.rb:1:1:114:3 | MyApp | app.rb:89:3:92:5 | call to get |
21+
| app.rb:1:1:114:3 | MyApp | app.rb:94:3:96:5 | call to get |
2122
params
2223
| app.rb:3:14:3:19 | call to params |
2324
| app.rb:12:5:12:10 | call to params |
@@ -34,9 +35,59 @@ erbSyntheticGlobals
3435
| SinatraErbLocalsHash(library-tests/frameworks/sinatra/views/index.erb,library-tests/frameworks/sinatra/app.rb@76:5:76:36) | views/index.erb:0:0:0:0 | views/index.erb |
3536
filters
3637
| app.rb:84:3:87:5 | call to before | before |
37-
| app.rb:94:3:96:5 | call to after | after |
38-
| app.rb:98:3:100:5 | call to before | before |
39-
| app.rb:102:3:104:5 | call to after | after |
38+
| app.rb:98:3:100:5 | call to after | after |
39+
| app.rb:102:3:104:5 | call to before | before |
40+
| app.rb:106:3:108:5 | call to before | before |
41+
| app.rb:111:3:113:5 | call to after | after |
4042
filterPatterns
41-
| app.rb:98:3:100:5 | call to before | app.rb:98:10:98:23 | "/protected/*" |
42-
| app.rb:102:3:104:5 | call to after | app.rb:102:9:102:23 | "/create/:slug" |
43+
| app.rb:106:3:108:5 | call to before | app.rb:106:10:106:23 | "/protected/*" |
44+
| app.rb:111:3:113:5 | call to after | app.rb:111:9:111:23 | "/create/:slug" |
45+
additionalFlowSteps
46+
| app.rb:85:5:85:9 | [post] self | app.rb:2:22:4:5 | <captured> self |
47+
| app.rb:85:5:85:9 | [post] self | app.rb:10:21:13:5 | <captured> self |
48+
| app.rb:85:5:85:9 | [post] self | app.rb:15:23:18:5 | <captured> self |
49+
| app.rb:85:5:85:9 | [post] self | app.rb:24:26:26:5 | <captured> self |
50+
| app.rb:85:5:85:9 | [post] self | app.rb:37:16:42:5 | <captured> self |
51+
| app.rb:85:5:85:9 | [post] self | app.rb:44:53:46:5 | <captured> self |
52+
| app.rb:85:5:85:9 | [post] self | app.rb:56:32:58:5 | <captured> self |
53+
| app.rb:85:5:85:9 | [post] self | app.rb:60:48:62:5 | <captured> self |
54+
| app.rb:85:5:85:9 | [post] self | app.rb:74:11:77:5 | <captured> self |
55+
| app.rb:85:5:85:9 | [post] self | app.rb:79:11:82:5 | <captured> self |
56+
| app.rb:85:5:85:9 | [post] self | app.rb:89:16:92:5 | <captured> self |
57+
| app.rb:85:5:85:9 | [post] self | app.rb:94:15:96:5 | <captured> self |
58+
| app.rb:86:5:86:11 | [post] self | app.rb:2:22:4:5 | <captured> self |
59+
| app.rb:86:5:86:11 | [post] self | app.rb:10:21:13:5 | <captured> self |
60+
| app.rb:86:5:86:11 | [post] self | app.rb:15:23:18:5 | <captured> self |
61+
| app.rb:86:5:86:11 | [post] self | app.rb:24:26:26:5 | <captured> self |
62+
| app.rb:86:5:86:11 | [post] self | app.rb:37:16:42:5 | <captured> self |
63+
| app.rb:86:5:86:11 | [post] self | app.rb:44:53:46:5 | <captured> self |
64+
| app.rb:86:5:86:11 | [post] self | app.rb:56:32:58:5 | <captured> self |
65+
| app.rb:86:5:86:11 | [post] self | app.rb:60:48:62:5 | <captured> self |
66+
| app.rb:86:5:86:11 | [post] self | app.rb:74:11:77:5 | <captured> self |
67+
| app.rb:86:5:86:11 | [post] self | app.rb:79:11:82:5 | <captured> self |
68+
| app.rb:86:5:86:11 | [post] self | app.rb:89:16:92:5 | <captured> self |
69+
| app.rb:86:5:86:11 | [post] self | app.rb:94:15:96:5 | <captured> self |
70+
| app.rb:103:5:103:9 | [post] self | app.rb:2:22:4:5 | <captured> self |
71+
| app.rb:103:5:103:9 | [post] self | app.rb:10:21:13:5 | <captured> self |
72+
| app.rb:103:5:103:9 | [post] self | app.rb:15:23:18:5 | <captured> self |
73+
| app.rb:103:5:103:9 | [post] self | app.rb:24:26:26:5 | <captured> self |
74+
| app.rb:103:5:103:9 | [post] self | app.rb:37:16:42:5 | <captured> self |
75+
| app.rb:103:5:103:9 | [post] self | app.rb:44:53:46:5 | <captured> self |
76+
| app.rb:103:5:103:9 | [post] self | app.rb:56:32:58:5 | <captured> self |
77+
| app.rb:103:5:103:9 | [post] self | app.rb:60:48:62:5 | <captured> self |
78+
| app.rb:103:5:103:9 | [post] self | app.rb:74:11:77:5 | <captured> self |
79+
| app.rb:103:5:103:9 | [post] self | app.rb:79:11:82:5 | <captured> self |
80+
| app.rb:103:5:103:9 | [post] self | app.rb:89:16:92:5 | <captured> self |
81+
| app.rb:103:5:103:9 | [post] self | app.rb:94:15:96:5 | <captured> self |
82+
| app.rb:103:13:103:22 | [post] self | app.rb:2:22:4:5 | <captured> self |
83+
| app.rb:103:13:103:22 | [post] self | app.rb:10:21:13:5 | <captured> self |
84+
| app.rb:103:13:103:22 | [post] self | app.rb:15:23:18:5 | <captured> self |
85+
| app.rb:103:13:103:22 | [post] self | app.rb:24:26:26:5 | <captured> self |
86+
| app.rb:103:13:103:22 | [post] self | app.rb:37:16:42:5 | <captured> self |
87+
| app.rb:103:13:103:22 | [post] self | app.rb:44:53:46:5 | <captured> self |
88+
| app.rb:103:13:103:22 | [post] self | app.rb:56:32:58:5 | <captured> self |
89+
| app.rb:103:13:103:22 | [post] self | app.rb:60:48:62:5 | <captured> self |
90+
| app.rb:103:13:103:22 | [post] self | app.rb:74:11:77:5 | <captured> self |
91+
| app.rb:103:13:103:22 | [post] self | app.rb:79:11:82:5 | <captured> self |
92+
| app.rb:103:13:103:22 | [post] self | app.rb:89:16:92:5 | <captured> self |
93+
| app.rb:103:13:103:22 | [post] self | app.rb:94:15:96:5 | <captured> self |

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ query predicate filters(Sinatra::Filter filter, string kind) {
2424
query predicate filterPatterns(Sinatra::Filter filter, DataFlow::ExprNode pattern) {
2525
pattern = filter.getPattern()
2626
}
27+
28+
query predicate additionalFlowSteps(DataFlow::Node pred, DataFlow::Node succ) {
29+
any(Sinatra::FilterJumpStep s).step(pred, succ)
30+
}

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

Lines changed: 1 addition & 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 = source "foo"
75+
@foo = params["foo"]
7676
erb :index, locals: {foo: @foo}
7777
end
7878

0 commit comments

Comments
 (0)