Ruby: tests, patterns, fix erb flow

This commit is contained in:
Harry Maclean
2023-02-04 16:55:56 +13:00
parent eada3b91df
commit e65d7224db
7 changed files with 268 additions and 12 deletions

View File

@@ -8,13 +8,13 @@ private import codeql.ruby.dataflow.FlowSummary
/** Provides modeling for the Sinatra library. */
module Sinatra {
private class App extends DataFlow::ClassNode {
class App extends DataFlow::ClassNode {
App() { this = DataFlow::getConstant("Sinatra").getConstant("Base").getADescendentModule() }
Route getRoute() { result.getApp() = this }
Route getARoute() { result.getApp() = this }
}
private class Route extends DataFlow::CallNode {
class Route extends DataFlow::CallNode {
private App app;
Route() {
@@ -44,7 +44,7 @@ module Sinatra {
}
}
private class ErbCall extends DataFlow::CallNode {
class ErbCall extends DataFlow::CallNode {
private Route route;
ErbCall() {
@@ -52,14 +52,18 @@ module Sinatra {
this.getMethodName() = "erb"
}
ErbFile getTemplateFile() {
result.getTemplateName() =
this.getArgument(0).asExpr().getConstantValue().getStringlikeValue() and
result.getRelativePath().matches("%views/%")
}
/**
* Gets the template file corresponding to this call.
*/
ErbFile getTemplateFile() { result = getTemplateFile(this.asExpr().getExpr()) }
}
ErbFile getTemplateFile(MethodCall erbCall) {
/**
* Gets the template file referred to by `erbCall`.
* This works on the AST level to avoid non-monotonic reecursion in `ErbLocalsHashSyntheticGlobal`.
*/
private ErbFile getTemplateFile(MethodCall erbCall) {
erbCall.getMethodName() = "erb" and
result.getTemplateName() = erbCall.getArgument(0).getConstantValue().getStringlikeValue() and
result.getRelativePath().matches("%views/%")
}
@@ -73,7 +77,7 @@ module Sinatra {
loc.getEndLine() + ":" + loc.getEndColumn()
}
private class ErbLocalsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
class ErbLocalsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
private string id;
private MethodCall erbCall;
private ErbFile erbFile;
@@ -98,7 +102,9 @@ module Sinatra {
override MethodCall getACall() { result = any(ErbCall c).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[2]" and output = "SyntheticGlobal[" + global + "]" and preservesValue = true
input = "Argument[locals:]" and
output = "SyntheticGlobal[" + global + "]" and
preservesValue = true
}
}
@@ -124,4 +130,43 @@ module Sinatra {
preservesValue = true
}
}
/**
* Filters are run before or after the route handler. They can modify the
* request and response, and share instance variables with the route handler.
*/
class Filter extends DataFlow::CallNode {
private App app;
Filter() { this = app.getAModuleLevelCall(["before", "after"]) }
/**
* Gets the pattern which constrains this route, if any. In the example below, the pattern is `/protected/*`.
* Patterns are typically given as strings, and are interpreted by the `mustermann` gem (they are not regular expressions).
* ```rb
* before '/protected/*' do
* authenticate!
* end
* ```
*/
DataFlow::ExprNode getPattern() { result = this.getArgument(0) }
/**
* Holds if this filter has a pattern.
*/
predicate hasPattern() { exists(this.getPattern()) }
/**
* Gets the body of this filter.
*/
DataFlow::BlockNode getBody() { result = this.getBlock() }
}
class BeforeFilter extends Filter {
BeforeFilter() { this.getMethodName() = "before" }
}
class AfterFilter extends Filter {
AfterFilter() { this.getMethodName() = "after" }
}
}