Skip to content

Commit 4d59181

Browse files
committed
Ruby: rack - Rack::Response#finish constructs a valid rack response
1 parent 521e65c commit 4d59181

File tree

1 file changed

+41
-8
lines changed
  • ruby/ql/lib/codeql/ruby/frameworks/rack/internal

1 file changed

+41
-8
lines changed

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

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,39 @@ private import App as A
1313
/** Contains implementation details for modeling `Rack::Response`. */
1414
module Private {
1515
/** A `DataFlow::Node` that may be a rack response. This is detected heuristically, if something "looks like" a rack response syntactically then we consider it to be a potential response node. */
16-
class PotentialResponseNode extends DataFlow::ArrayLiteralNode {
17-
// [status, headers, body]
18-
PotentialResponseNode() { this.getNumberOfArguments() = 3 }
19-
16+
abstract class PotentialResponseNode extends DataFlow::Node {
2017
/** Gets the headers returned with this response. */
21-
DataFlow::Node getHeaders() { result = this.getElement(1) }
18+
abstract DataFlow::Node getHeaders();
2219

2320
/** Gets the body of this response. */
24-
DataFlow::Node getBody() { result = this.getElement(2) }
21+
abstract DataFlow::Node getBody();
22+
}
23+
24+
/** A rack response constructed directly using an array literal. */
25+
private class PotentialArrayResponse extends PotentialResponseNode, DataFlow::ArrayLiteralNode {
26+
// [status, headers, body]
27+
PotentialArrayResponse() { this.getNumberOfArguments() = 3 }
28+
29+
override DataFlow::Node getHeaders() { result = this.getElement(1) }
30+
31+
override DataFlow::Node getBody() { result = this.getElement(2) }
32+
}
33+
34+
/** A rack response constructed by calling `finish` on an instance of `Rack::Response`. */
35+
private class RackResponseConstruction extends PotentialResponseNode, DataFlow::CallNode {
36+
private DataFlow::CallNode responseConstruction;
37+
38+
// (body, status, headers)
39+
RackResponseConstruction() {
40+
responseConstruction =
41+
API::getTopLevelMember("Rack").getMember("Response").getAnInstantiation() and
42+
this = responseConstruction.getAMethodCall() and
43+
this.getMethodName() = "finish"
44+
}
45+
46+
override DataFlow::Node getHeaders() { result = responseConstruction.getArgument(2) }
47+
48+
override DataFlow::Node getBody() { result = responseConstruction.getArgument(0) }
2549
}
2650
}
2751

@@ -54,20 +78,29 @@ module Public {
5478
v.getStringlikeValue().toLowerCase() = headerName.toLowerCase()
5579
))
5680
)
81+
or
82+
// pair in a `Rack::Response.new` constructor
83+
exists(DataFlow::PairNode headerPair | headerPair = headers |
84+
headerPair.getKey().getConstantValue().getStringlikeValue().toLowerCase() =
85+
headerName.toLowerCase() and
86+
result = headerPair.getValue()
87+
)
5788
)
5889
}
5990

6091
/** A `DataFlow::Node` returned from a rack request. */
61-
class ResponseNode extends Private::PotentialResponseNode, Http::Server::HttpResponse::Range
92+
class ResponseNode extends Http::Server::HttpResponse::Range instanceof Private::PotentialResponseNode
6293
{
6394
ResponseNode() { this = any(A::App::App app).getResponse() }
6495

65-
override DataFlow::Node getBody() { result = this.getElement(2) }
96+
override DataFlow::Node getBody() { result = this.(Private::PotentialResponseNode).getBody() }
6697

6798
override DataFlow::Node getMimetypeOrContentTypeArg() {
6899
result = getHeaderValue(this, "content-type")
69100
}
70101

102+
DataFlow::Node getHeaders() { result = this.(Private::PotentialResponseNode).getHeaders() }
103+
71104
// TODO: is there a sensible value for this?
72105
override string getMimetypeDefault() { none() }
73106
}

0 commit comments

Comments
 (0)