Skip to content

Commit 521e65c

Browse files
committed
Ruby: rack - extend rack applications to include instance methods, lambdas, and procs
1 parent 7a3b6f1 commit 521e65c

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

ruby/ql/lib/codeql/ruby/frameworks/actiondispatch/internal/Request.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ module Request {
128128
private import codeql.ruby.frameworks.Rack
129129

130130
private class RackEnv extends Env {
131-
RackEnv() { this = any(Rack::App::AppCandidate app).getEnv().getALocalUse() }
131+
RackEnv() { this = any(Rack::App::App app).getEnv().getALocalUse() }
132132
}
133133

134134
/**

ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,40 @@ private import codeql.ruby.DataFlow
77
private import codeql.ruby.typetracking.TypeTracker
88
private import Response::Private as RP
99

10-
/** A method node for a method named `call`. */
11-
private class CallMethodNode extends DataFlow::MethodNode {
12-
CallMethodNode() { this.getMethodName() = "call" }
10+
/**
11+
* A callable node that takes a single argument and, if it has a method name,
12+
* is called "call".
13+
*/
14+
private class PotentialCallNode extends DataFlow::CallableNode {
15+
PotentialCallNode() {
16+
this.getNumberOfParameters() = 1 and
17+
(
18+
this.(DataFlow::MethodNode).getMethodName() = "call" or
19+
not this instanceof DataFlow::MethodNode
20+
)
21+
}
22+
}
23+
24+
/**
25+
* A callable node that looks like it implements the rack specification.
26+
*/
27+
private class CallNode extends PotentialCallNode {
28+
private RP::PotentialResponseNode resp;
29+
30+
CallNode() { resp = trackRackResponse(this) }
31+
32+
/** Gets the response returned from a request to this application. */
33+
RP::PotentialResponseNode getResponse() { result = resp }
1334
}
1435

15-
private DataFlow::LocalSourceNode trackRackResponse(TypeBackTracker t, CallMethodNode call) {
36+
private DataFlow::LocalSourceNode trackRackResponse(TypeBackTracker t, DataFlow::CallableNode call) {
1637
t.start() and
1738
result = call.getAReturnNode().getALocalSource()
1839
or
1940
exists(TypeBackTracker t2 | result = trackRackResponse(t2, call).backtrack(t2, t))
2041
}
2142

22-
private RP::PotentialResponseNode trackRackResponse(CallMethodNode call) {
43+
private RP::PotentialResponseNode trackRackResponse(DataFlow::CallableNode call) {
2344
result = trackRackResponse(TypeBackTracker::end(), call)
2445
}
2546

@@ -28,12 +49,13 @@ private RP::PotentialResponseNode trackRackResponse(CallMethodNode call) {
2849
*/
2950
module App {
3051
/**
52+
* DEPRECATED: Use `App` instead.
3153
* A class that may be a rack application.
3254
* This is a class that has a `call` method that takes a single argument
3355
* (traditionally called `env`) and returns a rack-compatible response.
3456
*/
35-
class AppCandidate extends DataFlow::ClassNode {
36-
private CallMethodNode call;
57+
deprecated class AppCandidate extends DataFlow::ClassNode {
58+
private CallNode call;
3759
private RP::PotentialResponseNode resp;
3860

3961
AppCandidate() {
@@ -50,4 +72,61 @@ module App {
5072
/** Gets the response returned from a request to this application. */
5173
RP::PotentialResponseNode getResponse() { result = resp }
5274
}
75+
76+
private newtype TApp =
77+
TClassApp(DataFlow::ClassNode cn, CallNode call) or
78+
TAnonymousApp(CallNode call)
79+
80+
/**
81+
* A rack application. This is either some object that responds to `call`
82+
* taking a single argument and returns a rack response, or a lambda or
83+
* proc that takes a single `env` argument and returns a rack response.
84+
*/
85+
abstract class App extends TApp {
86+
string toString() { result = "Rack application" }
87+
88+
abstract CallNode getCall();
89+
90+
RP::PotentialResponseNode getResponse() { result = this.getCall().getResponse() }
91+
92+
DataFlow::ParameterNode getEnv() { result = this.getCall().getParameter(0) }
93+
}
94+
95+
/**
96+
* A rack application using a `DataFlow::ClassNode`. The class has either
97+
* an instance method or a singleton method named "call" which takes a
98+
* single `env` argument and returns a rack response.
99+
*/
100+
private class ClassApp extends TApp, App {
101+
private DataFlow::ClassNode cn;
102+
private CallNode call;
103+
104+
ClassApp() {
105+
this = TClassApp(cn, call) and
106+
call = [cn.getInstanceMethod("call"), cn.getSingletonMethod("call")]
107+
}
108+
109+
override string toString() { result = "Rack application: " + cn.toString() }
110+
111+
override CallNode getCall() { result = call }
112+
}
113+
114+
/**
115+
* A rack application that is either a lambda or a proc, which takes a
116+
* single `env` argument and returns a rack response.
117+
*/
118+
private class AnonymousApp extends TApp, App {
119+
private CallNode call;
120+
121+
AnonymousApp() {
122+
this = TAnonymousApp(call) and
123+
not exists(DataFlow::ClassNode cn |
124+
call = [cn.getInstanceMethod(_), cn.getSingletonMethod(_)]
125+
)
126+
}
127+
128+
override string toString() { result = "Rack application: " + call.toString() }
129+
130+
override CallNode getCall() { result = call }
131+
}
53132
}

ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.qll

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ module Public {
5858
}
5959

6060
/** A `DataFlow::Node` returned from a rack request. */
61-
class ResponseNode extends Private::PotentialResponseNode, Http::Server::HttpResponse::Range {
62-
ResponseNode() { this = any(A::App::AppCandidate app).getResponse() }
61+
class ResponseNode extends Private::PotentialResponseNode, Http::Server::HttpResponse::Range
62+
{
63+
ResponseNode() { this = any(A::App::App app).getResponse() }
6364

6465
override DataFlow::Node getBody() { result = this.getElement(2) }
6566

0 commit comments

Comments
 (0)