Skip to content

Commit eada3b9

Browse files
committed
Ruby: track flow from sinatra routes to erb files
1 parent c82b463 commit eada3b9

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
private import codeql.ruby.controlflow.CfgNodes::ExprNodes
44
private import codeql.ruby.DataFlow
55
private import codeql.ruby.Concepts
6+
private import codeql.ruby.AST
7+
private import codeql.ruby.dataflow.FlowSummary
68

79
/** Provides modeling for the Sinatra library. */
810
module Sinatra {
@@ -41,4 +43,85 @@ module Sinatra {
4143
result = Http::Server::parameterInputKind()
4244
}
4345
}
46+
47+
private class ErbCall extends DataFlow::CallNode {
48+
private Route route;
49+
50+
ErbCall() {
51+
this.asExpr().getExpr().getEnclosingCallable() = route.getBody().asCallableAstNode() and
52+
this.getMethodName() = "erb"
53+
}
54+
55+
ErbFile getTemplateFile() {
56+
result.getTemplateName() =
57+
this.getArgument(0).asExpr().getConstantValue().getStringlikeValue() and
58+
result.getRelativePath().matches("%views/%")
59+
}
60+
}
61+
62+
ErbFile getTemplateFile(MethodCall erbCall) {
63+
result.getTemplateName() = erbCall.getArgument(0).getConstantValue().getStringlikeValue() and
64+
result.getRelativePath().matches("%views/%")
65+
}
66+
67+
/**
68+
* Like `Location.toString`, but displays the relative path rather than the full path.
69+
*/
70+
private string locationRelativePathToString(Location loc) {
71+
result =
72+
loc.getFile().getRelativePath() + "@" + loc.getStartLine() + ":" + loc.getStartColumn() + ":" +
73+
loc.getEndLine() + ":" + loc.getEndColumn()
74+
}
75+
76+
private class ErbLocalsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
77+
private string id;
78+
private MethodCall erbCall;
79+
private ErbFile erbFile;
80+
81+
ErbLocalsHashSyntheticGlobal() {
82+
this = "SinatraErbLocalsHash(" + id + ")" and
83+
id = erbFile.getRelativePath() + "," + locationRelativePathToString(erbCall.getLocation()) and
84+
erbCall.getMethodName() = "erb" and
85+
erbFile = getTemplateFile(erbCall)
86+
}
87+
88+
ErbFile getErbFile() { result = erbFile }
89+
90+
string getId() { result = id }
91+
}
92+
93+
private class ErbLocalsSummary extends SummarizedCallable {
94+
private ErbLocalsHashSyntheticGlobal global;
95+
96+
ErbLocalsSummary() { this = "Sinatra::Base#erb" }
97+
98+
override MethodCall getACall() { result = any(ErbCall c).asExpr().getExpr() }
99+
100+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
101+
input = "Argument[2]" and output = "SyntheticGlobal[" + global + "]" and preservesValue = true
102+
}
103+
}
104+
105+
private class ErbLocalsAccessSummary extends SummarizedCallable {
106+
private ErbLocalsHashSyntheticGlobal global;
107+
private string local;
108+
109+
ErbLocalsAccessSummary() {
110+
this = "sinatra_erb_locals_access()" + global.getId() + "#" + local and
111+
local = any(MethodCall c | c.getLocation().getFile() = global.getErbFile()).getMethodName() and
112+
local = any(Pair p).getKey().getConstantValue().getStringlikeValue()
113+
}
114+
115+
override MethodCall getACall() {
116+
result.getLocation().getFile() = global.getErbFile() and
117+
result.getMethodName() = local and
118+
result.getReceiver() instanceof SelfVariableReadAccess
119+
}
120+
121+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
122+
input = "SyntheticGlobal[" + global + "].Element[:" + local + "]" and
123+
output = "ReturnValue" and
124+
preservesValue = true
125+
}
126+
}
44127
}

0 commit comments

Comments
 (0)