Skip to content

Commit a738f1d

Browse files
committed
Ruby: remove public abstract classes for Action{View,Controller}
1 parent 1253657 commit a738f1d

File tree

7 files changed

+134
-113
lines changed

7 files changed

+134
-113
lines changed

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

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ private import codeql.ruby.controlflow.CfgNodes
88
private import codeql.ruby.DataFlow
99
private import codeql.ruby.dataflow.RemoteFlowSources
1010
private import codeql.ruby.ApiGraphs
11-
private import codeql.ruby.frameworks.ActionView
1211
private import codeql.ruby.frameworks.ActionDispatch
12+
private import codeql.ruby.frameworks.ActionView
13+
private import codeql.ruby.frameworks.Rails
14+
private import codeql.ruby.frameworks.internal.Rails
1315

1416
/**
1517
* A `ClassDeclaration` for a class that extends `ActionController::Base`.
@@ -119,13 +121,6 @@ private class ActionControllerContextCall extends MethodCall {
119121
ActionControllerControllerClass getControllerClass() { result = controllerClass }
120122
}
121123

122-
/**
123-
* A call to the `params` method to fetch the request parameters.
124-
*/
125-
abstract class ParamsCall extends MethodCall {
126-
ParamsCall() { this.getMethodName() = "params" }
127-
}
128-
129124
/**
130125
* A `RemoteFlowSource::Range` to represent accessing the
131126
* ActionController parameters available via the `params` method.
@@ -136,13 +131,6 @@ class ParamsSource extends Http::Server::RequestInputAccess::Range {
136131
override string getSourceType() { result = "ActionController::Metal#params" }
137132
}
138133

139-
/**
140-
* A call to the `cookies` method to fetch the request parameters.
141-
*/
142-
abstract class CookiesCall extends MethodCall {
143-
CookiesCall() { this.getMethodName() = "cookies" }
144-
}
145-
146134
/**
147135
* A `RemoteFlowSource::Range` to represent accessing the
148136
* ActionController parameters available via the `cookies` method.
@@ -154,27 +142,38 @@ class CookiesSource extends Http::Server::RequestInputAccess::Range {
154142
}
155143

156144
/** A call to `cookies` from within a controller. */
157-
private class ActionControllerCookiesCall extends ActionControllerContextCall, CookiesCall { }
145+
private class ActionControllerCookiesCall extends ActionControllerContextCall, CookiesCallImpl {
146+
ActionControllerCookiesCall() { this.getMethodName() = "cookies" }
147+
}
158148

159149
/** A call to `params` from within a controller. */
160-
private class ActionControllerParamsCall extends ActionControllerContextCall, ParamsCall { }
150+
private class ActionControllerParamsCall extends ActionControllerContextCall, ParamsCallImpl {
151+
ActionControllerParamsCall() { this.getMethodName() = "params" }
152+
}
161153

162154
/** A call to `render` from within a controller. */
163-
private class ActionControllerRenderCall extends ActionControllerContextCall, RenderCall { }
155+
private class ActionControllerRenderCall extends ActionControllerContextCall, RenderCallImpl {
156+
ActionControllerRenderCall() { this.getMethodName() = "render" }
157+
}
164158

165159
/** A call to `render_to` from within a controller. */
166-
private class ActionControllerRenderToCall extends ActionControllerContextCall, RenderToCall { }
160+
private class ActionControllerRenderToCall extends ActionControllerContextCall, RenderToCallImpl {
161+
ActionControllerRenderToCall() { this.getMethodName() = ["render_to_body", "render_to_string"] }
162+
}
167163

168164
/** A call to `html_safe` from within a controller. */
169-
private class ActionControllerHtmlSafeCall extends HtmlSafeCall {
165+
private class ActionControllerHtmlSafeCall extends HtmlSafeCallImpl {
170166
ActionControllerHtmlSafeCall() {
167+
this.getMethodName() = "html_safe" and
171168
this.getEnclosingModule() instanceof ActionControllerControllerClass
172169
}
173170
}
174171

175172
/** A call to `html_escape` from within a controller. */
176-
private class ActionControllerHtmlEscapeCall extends HtmlEscapeCall {
173+
private class ActionControllerHtmlEscapeCall extends HtmlEscapeCallImpl {
177174
ActionControllerHtmlEscapeCall() {
175+
// "h" is aliased to "html_escape" in ActiveSupport
176+
this.getMethodName() = ["html_escape", "html_escape_once", "h", "sanitize"] and
178177
this.getEnclosingModule() instanceof ActionControllerControllerClass
179178
}
180179
}

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

Lines changed: 40 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,20 @@ private import codeql.ruby.Concepts
88
private import codeql.ruby.controlflow.CfgNodes
99
private import codeql.ruby.DataFlow
1010
private import codeql.ruby.dataflow.RemoteFlowSources
11-
private import ActionController
11+
private import codeql.ruby.frameworks.internal.Rails
12+
private import codeql.ruby.frameworks.Rails
1213

1314
/**
1415
* Holds if this AST node is in a context where `ActionView` methods are available.
1516
*/
16-
predicate inActionViewContext(AstNode n) {
17+
private predicate inActionViewContext(AstNode n) {
1718
// Within a template
1819
n.getLocation().getFile() instanceof ErbFile
1920
}
2021

21-
/**
22-
* A method call on a string to mark it as HTML safe for Rails.
23-
* Strings marked as such will not be automatically escaped when inserted into
24-
* HTML.
25-
*/
26-
abstract class HtmlSafeCall extends MethodCall {
27-
HtmlSafeCall() { this.getMethodName() = "html_safe" }
28-
}
29-
30-
// A call to `html_safe` from within a template.
31-
private class ActionViewHtmlSafeCall extends HtmlSafeCall {
32-
ActionViewHtmlSafeCall() { inActionViewContext(this) }
33-
}
34-
35-
/**
36-
* A call to a method named "html_escape", "html_escape_once", or "h".
37-
*/
38-
abstract class HtmlEscapeCall extends MethodCall {
39-
// "h" is aliased to "html_escape" in ActiveSupport
40-
HtmlEscapeCall() { this.getMethodName() = ["html_escape", "html_escape_once", "h", "sanitize"] }
22+
/** A call to `html_safe` from within a template. */
23+
private class ActionViewHtmlSafeCall extends HtmlSafeCallImpl {
24+
ActionViewHtmlSafeCall() { this.getMethodName() = "html_safe" and inActionViewContext(this) }
4125
}
4226

4327
/**
@@ -53,12 +37,16 @@ class RailsHtmlEscaping extends Escaping::Range, DataFlow::CallNode {
5337
override string getKind() { result = Escaping::getHtmlKind() }
5438
}
5539

56-
// A call to `html_escape` from within a template.
57-
private class ActionViewHtmlEscapeCall extends HtmlEscapeCall {
58-
ActionViewHtmlEscapeCall() { inActionViewContext(this) }
40+
/** A call to `html_escape` from within a template. */
41+
private class ActionViewHtmlEscapeCall extends HtmlEscapeCallImpl {
42+
ActionViewHtmlEscapeCall() {
43+
// "h" is aliased to "html_escape" in ActiveSupport
44+
this.getMethodName() = ["html_escape", "html_escape_once", "h", "sanitize"] and
45+
inActionViewContext(this)
46+
}
5947
}
6048

61-
// A call in a context where some commonly used `ActionView` methods are available.
49+
/** A call in a context where some commonly used `ActionView` methods are available. */
6250
private class ActionViewContextCall extends MethodCall {
6351
ActionViewContextCall() {
6452
this.getReceiver() instanceof SelfVariableAccess and
@@ -76,51 +64,14 @@ class RawCall extends ActionViewContextCall {
7664
RawCall() { this.getMethodName() = "raw" }
7765
}
7866

79-
// A call to the `params` method within the context of a template.
80-
private class ActionViewParamsCall extends ActionViewContextCall, ParamsCall { }
67+
/** A call to the `params` method within the context of a template. */
68+
private class ActionViewParamsCall extends ActionViewContextCall, ParamsCallImpl {
69+
ActionViewParamsCall() { this.getMethodName() = "params" }
70+
}
8171

8272
// A call to the `cookies` method within the context of a template.
83-
private class ActionViewCookiesCall extends ActionViewContextCall, CookiesCall { }
84-
85-
/**
86-
* A call to a `render` method that will populate the response body with the
87-
* rendered content.
88-
*/
89-
abstract class RenderCall extends MethodCall {
90-
RenderCall() { this.getMethodName() = "render" }
91-
92-
private Expr getTemplatePathArgument() {
93-
// TODO: support other ways of specifying paths (e.g. `file`)
94-
result = [this.getKeywordArgument(["partial", "template", "action"]), this.getArgument(0)]
95-
}
96-
97-
private string getTemplatePathValue() {
98-
result = this.getTemplatePathArgument().getConstantValue().getStringlikeValue()
99-
}
100-
101-
// everything up to and including the final slash, but ignoring any leading slash
102-
private string getSubPath() {
103-
result = this.getTemplatePathValue().regexpCapture("^/?(.*/)?(?:[^/]*?)$", 1)
104-
}
105-
106-
// everything after the final slash, or the whole string if there is no slash
107-
private string getBaseName() {
108-
result = this.getTemplatePathValue().regexpCapture("^/?(?:.*/)?([^/]*?)$", 1)
109-
}
110-
111-
/**
112-
* Gets the template file to be rendered by this call, if any.
113-
*/
114-
ErbFile getTemplateFile() {
115-
result.getTemplateName() = this.getBaseName() and
116-
result.getRelativePath().matches("%app/views/" + this.getSubPath() + "%")
117-
}
118-
119-
/**
120-
* Get the local variables passed as context to the renderer
121-
*/
122-
HashLiteral getLocals() { result = this.getKeywordArgument("locals") }
123-
// TODO: implicit renders in controller actions
73+
private class ActionViewCookiesCall extends ActionViewContextCall, CookiesCallImpl {
74+
ActionViewCookiesCall() { this.getMethodName() = "cookies" }
12475
}
12576

12677
/**
@@ -172,17 +123,14 @@ private class RenderCallAsHttpResponse extends DataFlow::CallNode, Http::Server:
172123
}
173124

174125
/** A call to the `render` method within the context of a template. */
175-
private class ActionViewRenderCall extends RenderCall, ActionViewContextCall { }
176-
177-
/**
178-
* A render call that does not automatically set the HTTP response body.
179-
*/
180-
abstract class RenderToCall extends MethodCall {
181-
RenderToCall() { this.getMethodName() = ["render_to_body", "render_to_string"] }
126+
private class ActionViewRenderCall extends ActionViewContextCall, RenderCallImpl {
127+
ActionViewRenderCall() { this.getMethodName() = "render" }
182128
}
183129

184-
// A call to `render_to` from within a template.
185-
private class ActionViewRenderToCall extends ActionViewContextCall, RenderToCall { }
130+
/** A call to `render_to` from within a template. */
131+
private class ActionViewRenderToCall extends ActionViewContextCall, RenderToCallImpl {
132+
ActionViewRenderToCall() { this.getMethodName() = ["render_to_body", "render_to_string"] }
133+
}
186134

187135
/**
188136
* A call to the ActionView `link_to` helper method.
@@ -224,24 +172,26 @@ module ActionView {
224172
* Action view helper methods which are XSS sinks.
225173
*/
226174
module Helpers {
175+
abstract private class RawHelperCallImpl extends MethodCall {
176+
abstract Expr getRawArgument();
177+
}
178+
227179
/**
228180
* A call to an ActionView helper which renders its argument without escaping.
229181
* The argument should be treated as an XSS sink. In the documentation for
230182
* classes in this module, the vulnerable argument is named `x`.
231183
*/
232-
abstract class RawHelperCall extends MethodCall {
233-
/**
234-
* Get an argument which is rendered without escaping.
235-
*/
236-
abstract Expr getRawArgument();
184+
class RawHelperCall extends MethodCall instanceof RawHelperCallImpl {
185+
/** Gets an argument that is rendered without escaping. */
186+
Expr getRawArgument() { result = super.getRawArgument() }
237187
}
238188

239189
/**
240190
* `ActionView::Helpers::TextHelper#simple_format`.
241191
*
242192
* `simple_format(x, y, sanitize: false)`.
243193
*/
244-
private class SimpleFormat extends ActionViewContextCall, RawHelperCall {
194+
private class SimpleFormat extends ActionViewContextCall, RawHelperCallImpl {
245195
SimpleFormat() {
246196
this.getMethodName() = "simple_format" and
247197
this.getKeywordArgument("sanitize").getConstantValue().isBoolean(false)
@@ -255,7 +205,7 @@ module ActionView {
255205
*
256206
* `truncate(x, escape: false)`.
257207
*/
258-
private class Truncate extends ActionViewContextCall, RawHelperCall {
208+
private class Truncate extends ActionViewContextCall, RawHelperCallImpl {
259209
Truncate() {
260210
this.getMethodName() = "truncate" and
261211
this.getKeywordArgument("escape").getConstantValue().isBoolean(false)
@@ -269,7 +219,7 @@ module ActionView {
269219
*
270220
* `highlight(x, y, sanitize: false)`.
271221
*/
272-
private class Highlight extends ActionViewContextCall, RawHelperCall {
222+
private class Highlight extends ActionViewContextCall, RawHelperCallImpl {
273223
Highlight() {
274224
this.getMethodName() = "highlight" and
275225
this.getKeywordArgument("sanitize").getConstantValue().isBoolean(false)
@@ -283,7 +233,7 @@ module ActionView {
283233
*
284234
* `javascript_tag(x)`.
285235
*/
286-
private class JavascriptTag extends ActionViewContextCall, RawHelperCall {
236+
private class JavascriptTag extends ActionViewContextCall, RawHelperCallImpl {
287237
JavascriptTag() { this.getMethodName() = "javascript_tag" }
288238

289239
override Expr getRawArgument() { result = this.getArgument(0) }
@@ -294,7 +244,7 @@ module ActionView {
294244
*
295245
* `content_tag(x, x, y, false)`.
296246
*/
297-
private class ContentTag extends ActionViewContextCall, RawHelperCall {
247+
private class ContentTag extends ActionViewContextCall, RawHelperCallImpl {
298248
ContentTag() {
299249
this.getMethodName() = "content_tag" and
300250
this.getArgument(3).getConstantValue().isBoolean(false)
@@ -308,7 +258,7 @@ module ActionView {
308258
*
309259
* `tag(x, x, y, false)`.
310260
*/
311-
private class Tag extends ActionViewContextCall, RawHelperCall {
261+
private class Tag extends ActionViewContextCall, RawHelperCallImpl {
312262
Tag() {
313263
this.getMethodName() = "tag" and
314264
this.getArgument(3).getConstantValue().isBoolean(false)
@@ -322,7 +272,7 @@ module ActionView {
322272
*
323273
* `tag.h1(x, escape: false)`.
324274
*/
325-
private class TagMethod extends MethodCall, RawHelperCall {
275+
private class TagMethod extends MethodCall, RawHelperCallImpl {
326276
TagMethod() {
327277
inActionViewContext(this) and
328278
this.getReceiver().(MethodCall).getMethodName() = "tag" and

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,67 @@ private import codeql.ruby.frameworks.ActionController
99
private import codeql.ruby.frameworks.ActionView
1010
private import codeql.ruby.frameworks.ActiveRecord
1111
private import codeql.ruby.frameworks.ActiveStorage
12+
private import codeql.ruby.frameworks.internal.Rails
1213
private import codeql.ruby.ApiGraphs
1314
private import codeql.ruby.security.OpenSSL
1415

16+
/**
17+
* A method call on a string to mark it as HTML safe for Rails. Strings marked
18+
* as such will not be automatically escaped when inserted into HTML.
19+
*/
20+
class HtmlSafeCall extends MethodCall instanceof HtmlSafeCallImpl { }
21+
22+
/** A call to a Rails method to escape HTML. */
23+
class HtmlEscapeCall extends MethodCall instanceof HtmlEscapeCallImpl { }
24+
25+
/** A call to fetch the request parameters in a Rails app. */
26+
class ParamsCall extends MethodCall instanceof ParamsCallImpl { }
27+
28+
/** A call to fetch the request cookies in a Rails app. */
29+
class CookiesCall extends MethodCall instanceof CookiesCallImpl { }
30+
31+
/**
32+
* A call to a render method that will populate the response body with the
33+
* rendered content.
34+
*/
35+
class RenderCall extends MethodCall instanceof RenderCallImpl {
36+
private Expr getTemplatePathArgument() {
37+
// TODO: support other ways of specifying paths (e.g. `file`)
38+
result = [this.getKeywordArgument(["partial", "template", "action"]), this.getArgument(0)]
39+
}
40+
41+
private string getTemplatePathValue() {
42+
result = this.getTemplatePathArgument().getConstantValue().getStringlikeValue()
43+
}
44+
45+
// everything up to and including the final slash, but ignoring any leading slash
46+
private string getSubPath() {
47+
result = this.getTemplatePathValue().regexpCapture("^/?(.*/)?(?:[^/]*?)$", 1)
48+
}
49+
50+
// everything after the final slash, or the whole string if there is no slash
51+
private string getBaseName() {
52+
result = this.getTemplatePathValue().regexpCapture("^/?(?:.*/)?([^/]*?)$", 1)
53+
}
54+
55+
/**
56+
* Gets the template file to be rendered by this call, if any.
57+
*/
58+
ErbFile getTemplateFile() {
59+
result.getTemplateName() = this.getBaseName() and
60+
result.getRelativePath().matches("%app/views/" + this.getSubPath() + "%")
61+
}
62+
63+
/**
64+
* Get the local variables passed as context to the renderer
65+
*/
66+
HashLiteral getLocals() { result = this.getKeywordArgument("locals") }
67+
// TODO: implicit renders in controller actions
68+
}
69+
70+
/** A render call that does not automatically set the HTTP response body. */
71+
class RenderToCall extends MethodCall instanceof RenderToCallImpl { }
72+
1573
/**
1674
* A reference to either `Rails::Railtie`, `Rails::Engine`, or `Rails::Application`.
1775
* `Engine` and `Application` extend `Railtie`, but may not have definitions present in the database.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
private import codeql.ruby.AST
2+
3+
abstract class HtmlSafeCallImpl extends MethodCall { }
4+
5+
abstract class HtmlEscapeCallImpl extends MethodCall { }
6+
7+
abstract class RenderCallImpl extends MethodCall { }
8+
9+
abstract class RenderToCallImpl extends MethodCall { }
10+
11+
abstract class ParamsCallImpl extends MethodCall { }
12+
13+
abstract class CookiesCallImpl extends MethodCall { }

0 commit comments

Comments
 (0)