Skip to content

Ruby: split standard library models into multiple files #7886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ruby/ql/lib/codeql/ruby/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
* Helper file that imports all framework modeling.
*/

private import codeql.ruby.frameworks.Core
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.frameworks.ActiveRecord
private import codeql.ruby.frameworks.ActiveStorage
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.ActiveSupport
private import codeql.ruby.frameworks.GraphQL
private import codeql.ruby.frameworks.Rails
private import codeql.ruby.frameworks.StandardLibrary
private import codeql.ruby.frameworks.Stdlib
private import codeql.ruby.frameworks.Files
private import codeql.ruby.frameworks.HttpClients
private import codeql.ruby.frameworks.XmlParsing
Expand Down
10 changes: 10 additions & 0 deletions ruby/ql/lib/codeql/ruby/ast/Call.qll
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ class MethodCall extends Call instanceof MethodCallImpl {
}
}

/**
* A `Method` call that has no known target.
* These will typically be calls to methods inherited from a superclass.
* TODO: When API Graphs is able to resolve calls to methods like `Kernel.send`
* this class is no longer necessary and should be removed.
*/
class UnknownMethodCall extends MethodCall {
UnknownMethodCall() { not exists(this.(Call).getATarget()) }
}

/**
* A call to a setter method.
* ```rb
Expand Down
19 changes: 15 additions & 4 deletions ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Provides modeling for the `ActionController` library.
*/

private import codeql.ruby.AST
private import codeql.ruby.Concepts
private import codeql.ruby.controlflow.CfgNodes
Expand Down Expand Up @@ -66,10 +70,14 @@ class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler:
/** Gets a call to render from within this method. */
RenderCall getARenderCall() { result.getParent+() = this }

// TODO: model the implicit render call when a path through the method does
// not end at an explicit render or redirect
/** Gets the controller class containing this method. */
ActionControllerControllerClass getControllerClass() { result = controllerClass }
/**
* Gets the controller class containing this method.
*/
ActionControllerControllerClass getControllerClass() {
// TODO: model the implicit render call when a path through the method does
// not end at an explicit render or redirect
result = controllerClass
}

/**
* Gets a route to this handler, if one exists.
Expand Down Expand Up @@ -101,6 +109,9 @@ private class ActionControllerContextCall extends MethodCall {
this.getEnclosingModule() = controllerClass
}

/**
* Gets the controller class containing this method.
*/
ActionControllerControllerClass getControllerClass() { result = controllerClass }
}

Expand Down
16 changes: 16 additions & 0 deletions ruby/ql/lib/codeql/ruby/frameworks/ActionView.qll
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Provides modeling for the `ActionView` library.
*/

private import codeql.ruby.AST
private import codeql.ruby.Concepts
private import codeql.ruby.controlflow.CfgNodes
Expand All @@ -6,6 +10,9 @@ private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ast.internal.Module
private import ActionController

/**
* Holds if this AST node is in a context where `ActionView` methods are available.
*/
predicate inActionViewContext(AstNode n) {
// Within a template
n.getLocation().getFile() instanceof ErbFile
Expand Down Expand Up @@ -33,6 +40,9 @@ abstract class HtmlEscapeCall extends MethodCall {
HtmlEscapeCall() { this.getMethodName() = ["html_escape", "html_escape_once", "h"] }
}

/**
* A call to a Rails method that escapes HTML.
*/
class RailsHtmlEscaping extends Escaping::Range, DataFlow::CallNode {
RailsHtmlEscaping() { this.asExpr().getExpr() instanceof HtmlEscapeCall }

Expand All @@ -55,6 +65,9 @@ private class ActionViewContextCall extends MethodCall {
inActionViewContext(this)
}

/**
* Holds if this call is located inside an ERb template.
*/
predicate isInErbFile() { this.getLocation().getFile() instanceof ErbFile }
}

Expand Down Expand Up @@ -132,6 +145,9 @@ private class ActionViewRenderToCall extends ActionViewContextCall, RenderToCall
class LinkToCall extends ActionViewContextCall {
LinkToCall() { this.getMethodName() = "link_to" }

/**
* Gets the path argument to the call.
*/
Expr getPathArgument() {
// When `link_to` is called with a block, it uses the first argument as the
// path, and otherwise the second argument.
Expand Down
13 changes: 12 additions & 1 deletion ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
/**
* Provides modeling for the `ActiveRecord` library.
*/

private import codeql.ruby.AST
private import codeql.ruby.Concepts
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.ast.internal.Module
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.StandardLibrary
private import codeql.ruby.frameworks.Stdlib
private import codeql.ruby.frameworks.Core

/// See https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html
private string activeRecordPersistenceInstanceMethodName() {
Expand Down Expand Up @@ -182,6 +187,9 @@ class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMeth
)
}

/**
* Gets the SQL fragment argument of this method call.
*/
Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr }
}

Expand All @@ -207,6 +215,9 @@ class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
*/
abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range,
DataFlow::LocalSourceNode {
/**
* Gets the `ActiveRecordModelClass` that this instance belongs to.
*/
abstract ActiveRecordModelClass getClass();

bindingset[methodName]
Expand Down
4 changes: 4 additions & 0 deletions ruby/ql/lib/codeql/ruby/frameworks/ActiveStorage.qll
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Provides modeling for the `ActiveStorage` library.
*/

private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.Concepts
Expand Down
6 changes: 3 additions & 3 deletions ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* https://rubygems.org/gems/activesupport
*/

import codeql.ruby.Concepts
import codeql.ruby.DataFlow
import codeql.ruby.frameworks.StandardLibrary
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow

/**
* Modeling for `ActiveSupport`.
Expand Down
72 changes: 72 additions & 0 deletions ruby/ql/lib/codeql/ruby/frameworks/Core.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Provides modeling for the Ruby core libraries.
*/

private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
import core.BasicObject::BasicObject
import core.Object::Object
import core.Kernel::Kernel
import core.Module
import core.Array
import core.Regexp

/**
* A system command executed via subshell literal syntax.
* E.g.
* ```ruby
* `cat foo.txt`
* %x(cat foo.txt)
* %x[cat foo.txt]
* %x{cat foo.txt}
* %x/cat foo.txt/
* ```
*/
class SubshellLiteralExecution extends SystemCommandExecution::Range {
SubshellLiteral literal;

SubshellLiteralExecution() { this.asExpr().getExpr() = literal }

override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = literal.getComponent(_) }

override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
}

/**
* A system command executed via shell heredoc syntax.
* E.g.
* ```ruby
* <<`EOF`
* cat foo.text
* EOF
* ```
*/
class SubshellHeredocExecution extends SystemCommandExecution::Range {
HereDoc heredoc;

SubshellHeredocExecution() { this.asExpr().getExpr() = heredoc and heredoc.isSubShell() }

override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = heredoc.getComponent(_) }

override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
}

private class SplatSummary extends SummarizedCallable {
SplatSummary() { this = "*(splat)" }

override SplatExpr getACall() { any() }

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
// *1 = [1]
input = "Receiver" and
output = "ArrayElement[0] of ReturnValue"
or
// *[1] = [1]
input = "Receiver" and
output = "ReturnValue"
) and
preservesValue = true
}
}
2 changes: 1 addition & 1 deletion ruby/ql/lib/codeql/ruby/frameworks/Files.qll
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.frameworks.StandardLibrary
private import codeql.ruby.frameworks.Core
private import codeql.ruby.dataflow.FlowSummary

private DataFlow::Node ioInstanceInstantiation() {
Expand Down
Loading