mirror of
https://github.com/github/codeql.git
synced 2026-04-26 01:05:15 +02:00
Merge branch 'main' into maikypedia/ldap-improper-auth
This commit is contained in:
@@ -10,7 +10,7 @@ runs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ruby/extractor-pack
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-extractor-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/scripts/create-extractor-pack.sh', 'ruby/extractor/**/Cargo.lock', 'ruby/actions/create-extractor-pack/action.yml') }}-${{ hashFiles('ruby/extractor/**/*.rs') }}-${{ hashFiles('ruby/codeql-extractor.yml', 'ruby/downgrades', 'ruby/tools', 'ruby/ql/lib/ruby.dbscheme', 'ruby/ql/lib/ruby.dbscheme.stats') }}
|
||||
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-extractor-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/scripts/create-extractor-pack.sh', 'ruby/extractor/**/Cargo.lock', 'ruby/actions/create-extractor-pack/action.yml') }}-${{ hashFiles('shared/tree-sitter-extractor') }}-${{ hashFiles('ruby/extractor/**/*.rs') }}-${{ hashFiles('ruby/codeql-extractor.yml', 'ruby/downgrades', 'ruby/tools', 'ruby/ql/lib/ruby.dbscheme', 'ruby/ql/lib/ruby.dbscheme.stats') }}
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Sync dbscheme fragments
|
||||
compatibility: full
|
||||
@@ -2,3 +2,4 @@ name: codeql/ruby-downgrades
|
||||
groups: ruby
|
||||
downgrades: .
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
|
||||
BIN
ruby/extractor/Cargo.lock
generated
BIN
ruby/extractor/Cargo.lock
generated
Binary file not shown.
@@ -10,7 +10,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
tree-sitter = "0.20"
|
||||
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "203f7bd3c1bbfbd98fc19add4b8fcb213c059205" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "206c7077164372c596ffa8eaadb9435c28941364" }
|
||||
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "2edbd437ee901b8fa95861ec538e56efe3ebd127" }
|
||||
clap = { version = "4.2", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||
|
||||
@@ -23,3 +23,8 @@ query predicate multipleParents(AstNode node, AstNode parent, string cls) {
|
||||
one != two
|
||||
)
|
||||
}
|
||||
|
||||
query predicate multipleToString(AstNode n, string s) {
|
||||
s = strictconcat(n.toString(), ",") and
|
||||
strictcount(n.toString()) > 1
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::Consistency
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::Consistency
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.CFG
|
||||
import codeql.ruby.controlflow.internal.Completion
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl
|
||||
|
||||
/**
|
||||
* All `Expr` nodes are `PostOrderTree`s
|
||||
@@ -13,8 +14,13 @@ query predicate nonPostOrderExpr(Expr e, string cls) {
|
||||
not e instanceof Namespace and
|
||||
not e instanceof Toplevel and
|
||||
exists(AstNode last, Completion c |
|
||||
last(e, last, c) and
|
||||
CfgImpl::last(e, last, c) and
|
||||
last != e and
|
||||
c instanceof NormalCompletion
|
||||
)
|
||||
}
|
||||
|
||||
query predicate multipleToString(CfgNode n, string s) {
|
||||
s = strictconcat(n.toString(), ",") and
|
||||
strictcount(n.toString()) > 1
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import codeql.ruby.dataflow.internal.DataFlowPrivate
|
||||
import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency
|
||||
|
||||
private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
override predicate postWithInFlowExclude(Node n) { n instanceof SummaryNode }
|
||||
override predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
|
||||
|
||||
override predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
n instanceof BlockArgumentNode
|
||||
or
|
||||
n instanceof SummaryNode
|
||||
n instanceof FlowSummaryNode
|
||||
or
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
or
|
||||
@@ -33,3 +33,8 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
query predicate multipleToString(Node n, string s) {
|
||||
s = strictconcat(n.toString(), ",") and
|
||||
strictcount(n.toString()) > 1
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ name: codeql/ruby-consistency-queries
|
||||
groups: [ruby, test, consistency-queries]
|
||||
dependencies:
|
||||
codeql/ruby-all: ${workspace}
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -4,3 +4,4 @@ groups:
|
||||
- examples
|
||||
dependencies:
|
||||
codeql/ruby-all: ${workspace}
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
dependencies:
|
||||
codeql/ruby-all: '*'
|
||||
codeql/ruby-queries: '*'
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,3 +1,63 @@
|
||||
## 0.7.1
|
||||
|
||||
### New Features
|
||||
|
||||
* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`.
|
||||
Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed.
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* The API graph library (`codeql.ruby.ApiGraphs`) has been significantly improved, with better support for inheritance,
|
||||
and data-flow nodes can now be converted to API nodes by calling `.track()` or `.backtrack()` on the node.
|
||||
API graphs allow for efficient modelling of how a given value is used by the code base, or how values produced by the code base
|
||||
are consumed by a library. See the documentation for `API::Node` for details and examples.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Data flow configurations can now include a predicate `neverSkip(Node node)`
|
||||
in order to ensure inclusion of certain nodes in the path explanations. The
|
||||
predicate defaults to the end-points of the additional flow steps provided in
|
||||
the configuration, which means that such steps now always are visible by
|
||||
default in path explanations.
|
||||
* The `'QUERY_STRING'` field of a Rack `env` parameter is now recognized as a source of remote user input.
|
||||
* Query parameters and cookies from `Rack::Response` objects are recognized as potential sources of remote flow input.
|
||||
* Calls to `Rack::Utils.parse_query` now propagate taint.
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `Configuration` taint flow configuration class from `codeql.ruby.security.InsecureDownloadQuery` has been deprecated. Use the `Flow` module instead.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* More kinds of rack applications are now recognized.
|
||||
* Rack::Response instances are now recognized as potential responses from rack applications.
|
||||
* HTTP redirect responses from Rack applications are now recognized as a potential sink for open redirect alerts.
|
||||
* Additional sinks for `rb/unsafe-deserialization` have been added. This includes various methods from the `yaml` and `plist` gems, which deserialize YAML and Property List data, respectively.
|
||||
|
||||
## 0.6.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Deleted many deprecated predicates and classes with uppercase `URL`, `XSS`, etc. in their names. Use the PascalCased versions instead.
|
||||
* Deleted the deprecated `getValueText` predicate from the `Expr`, `StringComponent`, and `ExprCfgNode` classes. Use `getConstantValue` instead.
|
||||
* Deleted the deprecated `VariableReferencePattern` class, use `ReferencePattern` instead.
|
||||
* Deleted all deprecated aliases in `StandardLibrary.qll`, use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.
|
||||
* Support for the `sequel` gem has been added. Method calls that execute queries against a database that may be vulnerable to injection attacks will now be recognized.
|
||||
* Support for the `mysql2` gem has been added. Method calls that execute queries against an MySQL database that may be vulnerable to injection attacks will now be recognized.
|
||||
* Support for the `pg` gem has been added. Method calls that execute queries against a PostgreSQL database that may be vulnerable to injection attacks will now be recognized.
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
|
||||
11
ruby/ql/lib/change-notes/released/0.6.3.md
Normal file
11
ruby/ql/lib/change-notes/released/0.6.3.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## 0.6.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Deleted many deprecated predicates and classes with uppercase `URL`, `XSS`, etc. in their names. Use the PascalCased versions instead.
|
||||
* Deleted the deprecated `getValueText` predicate from the `Expr`, `StringComponent`, and `ExprCfgNode` classes. Use `getConstantValue` instead.
|
||||
* Deleted the deprecated `VariableReferencePattern` class, use `ReferencePattern` instead.
|
||||
* Deleted all deprecated aliases in `StandardLibrary.qll`, use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.
|
||||
* Support for the `sequel` gem has been added. Method calls that execute queries against a database that may be vulnerable to injection attacks will now be recognized.
|
||||
* Support for the `mysql2` gem has been added. Method calls that execute queries against an MySQL database that may be vulnerable to injection attacks will now be recognized.
|
||||
* Support for the `pg` gem has been added. Method calls that execute queries against a PostgreSQL database that may be vulnerable to injection attacks will now be recognized.
|
||||
3
ruby/ql/lib/change-notes/released/0.6.4.md
Normal file
3
ruby/ql/lib/change-notes/released/0.6.4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.6.4
|
||||
|
||||
No user-facing changes.
|
||||
12
ruby/ql/lib/change-notes/released/0.7.0.md
Normal file
12
ruby/ql/lib/change-notes/released/0.7.0.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## 0.7.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `Configuration` taint flow configuration class from `codeql.ruby.security.InsecureDownloadQuery` has been deprecated. Use the `Flow` module instead.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* More kinds of rack applications are now recognized.
|
||||
* Rack::Response instances are now recognized as potential responses from rack applications.
|
||||
* HTTP redirect responses from Rack applications are now recognized as a potential sink for open redirect alerts.
|
||||
* Additional sinks for `rb/unsafe-deserialization` have been added. This includes various methods from the `yaml` and `plist` gems, which deserialize YAML and Property List data, respectively.
|
||||
24
ruby/ql/lib/change-notes/released/0.7.1.md
Normal file
24
ruby/ql/lib/change-notes/released/0.7.1.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## 0.7.1
|
||||
|
||||
### New Features
|
||||
|
||||
* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`.
|
||||
Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed.
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* The API graph library (`codeql.ruby.ApiGraphs`) has been significantly improved, with better support for inheritance,
|
||||
and data-flow nodes can now be converted to API nodes by calling `.track()` or `.backtrack()` on the node.
|
||||
API graphs allow for efficient modelling of how a given value is used by the code base, or how values produced by the code base
|
||||
are consumed by a library. See the documentation for `API::Node` for details and examples.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Data flow configurations can now include a predicate `neverSkip(Node node)`
|
||||
in order to ensure inclusion of certain nodes in the path explanations. The
|
||||
predicate defaults to the end-points of the additional flow steps provided in
|
||||
the configuration, which means that such steps now always are visible by
|
||||
default in path explanations.
|
||||
* The `'QUERY_STRING'` field of a Rack `env` parameter is now recognized as a source of remote user input.
|
||||
* Query parameters and cookies from `Rack::Response` objects are recognized as potential sources of remote flow input.
|
||||
* Calls to `Rack::Utils.parse_query` now propagate taint.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.1
|
||||
lastReleaseVersion: 0.7.1
|
||||
|
||||
@@ -2,22 +2,13 @@
|
||||
|
||||
import files.FileSystem
|
||||
|
||||
bindingset[loc]
|
||||
pragma[inline_late]
|
||||
private string locationToString(Location loc) {
|
||||
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
loc.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
|
||||
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A location as given by a file, a start line, a start column,
|
||||
* an end line, and an end column.
|
||||
*
|
||||
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
class Location extends @location {
|
||||
class Location extends @location_default {
|
||||
/** Gets the file for this location. */
|
||||
File getFile() { locations_default(this, result, _, _, _, _) }
|
||||
|
||||
@@ -37,8 +28,14 @@ class Location extends @location {
|
||||
int getNumLines() { result = this.getEndLine() - this.getStartLine() + 1 }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
pragma[inline]
|
||||
string toString() { result = locationToString(this) }
|
||||
bindingset[this]
|
||||
pragma[inline_late]
|
||||
string toString() {
|
||||
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
this.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
|
||||
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,19 @@ module SqlExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that performs SQL sanitization.
|
||||
*/
|
||||
class SqlSanitization extends DataFlow::Node instanceof SqlSanitization::Range { }
|
||||
|
||||
/** Provides a class for modeling new SQL sanitization APIs. */
|
||||
module SqlSanitization {
|
||||
/**
|
||||
* A data-flow node that performs SQL sanitization.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes a regular expression.
|
||||
*
|
||||
@@ -687,9 +700,7 @@ module Http {
|
||||
* Gets a node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
deprecated DataFlow::Node getURL() {
|
||||
result = super.getURL() or result = Request::Range.super.getAUrlPart()
|
||||
}
|
||||
deprecated DataFlow::Node getURL() { result = Request::Range.super.getAUrlPart() }
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
@@ -715,14 +726,6 @@ module Http {
|
||||
/** Gets a node which returns the body of the response */
|
||||
abstract DataFlow::Node getResponseBody();
|
||||
|
||||
/**
|
||||
* DEPRECATED: overwrite `getAUrlPart` instead.
|
||||
*
|
||||
* Gets a node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
deprecated DataFlow::Node getURL() { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: override `disablesCertificateValidation/2` instead.
|
||||
*
|
||||
@@ -840,6 +843,58 @@ module XmlParserCall {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that constructs an XPath expression.
|
||||
*
|
||||
* If it is important that the XPath expression is indeed executed, then use `XPathExecution`.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `XPathConstruction::Range` instead.
|
||||
*/
|
||||
class XPathConstruction extends DataFlow::Node instanceof XPathConstruction::Range {
|
||||
/** Gets the argument that specifies the XPath expressions to be constructed. */
|
||||
DataFlow::Node getXPath() { result = super.getXPath() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new XPath construction APIs. */
|
||||
module XPathConstruction {
|
||||
/**
|
||||
* A data-flow node that constructs an XPath expression.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `XPathConstruction` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the XPath expressions to be constructed. */
|
||||
abstract DataFlow::Node getXPath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes an XPath expression.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `XPathExecution::Range` instead.
|
||||
*/
|
||||
class XPathExecution extends DataFlow::Node instanceof XPathExecution::Range {
|
||||
/** Gets the argument that specifies the XPath expressions to be executed. */
|
||||
DataFlow::Node getXPath() { result = super.getXPath() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new XPath execution APIs. */
|
||||
module XPathExecution {
|
||||
/**
|
||||
* A data-flow node that executes an XPath expression.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `XPathExecution` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the XPath expressions to be executed. */
|
||||
abstract DataFlow::Node getXPath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that may represent a database object in an ORM system.
|
||||
*
|
||||
@@ -1191,7 +1246,7 @@ module LdapExecution {
|
||||
* extend `LdapExecution` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
//** Gets the argument that specifies the query to be executed. */
|
||||
/** Gets the argument that specifies the query to be executed. */
|
||||
abstract DataFlow::Node getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import codeql.Locations
|
||||
* global (inter-procedural) data flow analyses.
|
||||
*/
|
||||
module DataFlow {
|
||||
import codeql.ruby.dataflow.internal.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplSpecific
|
||||
private import codeql.dataflow.DataFlow
|
||||
import DataFlowMake<RubyDataFlow>
|
||||
import codeql.ruby.dataflow.internal.DataFlowImpl1
|
||||
}
|
||||
|
||||
@@ -32,4 +32,8 @@ private import codeql.ruby.frameworks.Slim
|
||||
private import codeql.ruby.frameworks.Sinatra
|
||||
private import codeql.ruby.frameworks.Twirp
|
||||
private import codeql.ruby.frameworks.Sqlite3
|
||||
private import codeql.ruby.frameworks.Ldap
|
||||
private import codeql.ruby.frameworks.Mysql2
|
||||
private import codeql.ruby.frameworks.Pg
|
||||
private import codeql.ruby.frameworks.Yaml
|
||||
private import codeql.ruby.frameworks.Sequel
|
||||
private import codeql.ruby.frameworks.Ldap
|
||||
|
||||
@@ -11,13 +11,6 @@ private import internal.TreeSitter
|
||||
* This is the root QL class for all expressions.
|
||||
*/
|
||||
class Expr extends Stmt, TExpr {
|
||||
/**
|
||||
* DEPRECATED: Use `getConstantValue` instead.
|
||||
*
|
||||
* Gets the textual (constant) value of this expression, if any.
|
||||
*/
|
||||
deprecated string getValueText() { result = this.getConstantValue().toString() }
|
||||
|
||||
/** Gets the constant value of this expression, if any. */
|
||||
ConstantValue getConstantValue() { result = getConstantValueExpr(this) }
|
||||
}
|
||||
|
||||
@@ -165,14 +165,6 @@ class FileLiteral extends Literal instanceof FileLiteralImpl {
|
||||
* `StringEscapeSequenceComponent`, or `StringInterpolationComponent`.
|
||||
*/
|
||||
class StringComponent extends AstNode instanceof StringComponentImpl {
|
||||
/**
|
||||
* DEPRECATED: Use `getConstantValue` instead.
|
||||
*
|
||||
* Gets the source text for this string component. Has no result if this is
|
||||
* a `StringInterpolationComponent`.
|
||||
*/
|
||||
deprecated string getValueText() { result = this.getConstantValue().toString() }
|
||||
|
||||
/** Gets the constant value of this string component, if any. */
|
||||
ConstantValue::ConstantStringValue getConstantValue() { result = TString(super.getValue()) }
|
||||
}
|
||||
@@ -218,8 +210,6 @@ class StringInterpolationComponent extends StringComponent, StmtSequence instanc
|
||||
|
||||
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
deprecated final override string getValueText() { none() }
|
||||
|
||||
final override ConstantValue::ConstantStringValue getConstantValue() {
|
||||
result = StmtSequence.super.getConstantValue()
|
||||
}
|
||||
@@ -267,8 +257,6 @@ class RegExpInterpolationComponent extends RegExpComponent, StmtSequence instanc
|
||||
|
||||
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
deprecated final override string getValueText() { none() }
|
||||
|
||||
final override ConstantValue::ConstantStringValue getConstantValue() {
|
||||
result = StmtSequence.super.getConstantValue()
|
||||
}
|
||||
|
||||
@@ -363,19 +363,3 @@ class ReferencePattern extends CasePattern, TReferencePattern {
|
||||
pred = "getExpr" and result = this.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `ReferencePattern` instead.
|
||||
*
|
||||
* A variable reference in a pattern, i.e. `^x` in the following example:
|
||||
* ```rb
|
||||
* x = 10
|
||||
* case expr
|
||||
* in ^x then puts "ok"
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
deprecated class VariableReferencePattern extends ReferencePattern, TVariableReferencePattern {
|
||||
/** Gets the variable access corresponding to this variable reference pattern. */
|
||||
final VariableReadAccess getVariableAccess() { result = this.getExpr() }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ private import codeql.ruby.CFG
|
||||
private import internal.AST
|
||||
private import internal.TreeSitter
|
||||
private import internal.Variable
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl
|
||||
|
||||
/**
|
||||
* A statement.
|
||||
@@ -12,13 +12,13 @@ private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl
|
||||
*/
|
||||
class Stmt extends AstNode, TStmt {
|
||||
/** Gets a control-flow node for this statement, if any. */
|
||||
CfgNodes::AstCfgNode getAControlFlowNode() { result.getNode() = this }
|
||||
CfgNodes::AstCfgNode getAControlFlowNode() { result.getAstNode() = this }
|
||||
|
||||
/** Gets a control-flow entry node for this statement, if any */
|
||||
AstNode getAControlFlowEntryNode() { result = getAControlFlowEntryNode(this) }
|
||||
AstNode getAControlFlowEntryNode() { result = CfgImpl::getAControlFlowEntryNode(this) }
|
||||
|
||||
/** Gets the control-flow scope of this statement, if any. */
|
||||
CfgScope getCfgScope() { result = getCfgScope(this) }
|
||||
CfgScope getCfgScope() { result = CfgImpl::getCfgScope(this) }
|
||||
|
||||
/** Gets the enclosing callable, if any. */
|
||||
Callable getEnclosingCallable() { result = this.getCfgScope() }
|
||||
|
||||
@@ -121,13 +121,15 @@ private Ruby::AstNode getSuperParent(Ruby::Super sup) {
|
||||
result = sup
|
||||
or
|
||||
result = getSuperParent(sup).getParent() and
|
||||
not result instanceof Ruby::Method
|
||||
not result instanceof Ruby::Method and
|
||||
not result instanceof Ruby::SingletonMethod
|
||||
}
|
||||
|
||||
private string getSuperMethodName(Ruby::Super sup) {
|
||||
exists(Ruby::Method meth |
|
||||
meth = getSuperParent(sup).getParent() and
|
||||
exists(Ruby::AstNode meth | meth = getSuperParent(sup).getParent() |
|
||||
result = any(Method c | toGenerated(c) = meth).getName()
|
||||
or
|
||||
result = any(SingletonMethod c | toGenerated(c) = meth).getName()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ private module Propagation {
|
||||
any(StringComponentCfgNode c |
|
||||
isString(c, result)
|
||||
or
|
||||
result = c.getNode().(StringComponentImpl).getValue()
|
||||
result = c.getAstNode().(StringComponentImpl).getValue()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.ast.internal.AST
|
||||
private import codeql.ruby.ast.internal.TreeSitter
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import CfgNodes
|
||||
private import SuccessorTypes
|
||||
|
||||
@@ -390,7 +389,7 @@ private module JoinBlockPredecessors {
|
||||
private predicate idOf(Ruby::AstNode x, int y) = equivalenceRelation(id/2)(x, y)
|
||||
|
||||
int getId(JoinBlockPredecessor jbp) {
|
||||
idOf(toGeneratedInclSynth(jbp.getFirstNode().(AstCfgNode).getNode()), result)
|
||||
idOf(toGeneratedInclSynth(jbp.getFirstNode().(AstCfgNode).getAstNode()), result)
|
||||
or
|
||||
idOf(toGeneratedInclSynth(jbp.(EntryBasicBlock).getScope()), result)
|
||||
}
|
||||
|
||||
@@ -6,62 +6,26 @@ private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.ruby.ast.internal.Constant
|
||||
private import codeql.ruby.ast.internal.Literal
|
||||
private import ControlFlowGraph
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import internal.ControlFlowGraphImpl as CfgImpl
|
||||
private import internal.Splitting
|
||||
|
||||
/** An entry node for a given scope. */
|
||||
class EntryNode extends CfgNode, TEntryNode {
|
||||
class EntryNode extends CfgNode, CfgImpl::EntryNode {
|
||||
override string getAPrimaryQlClass() { result = "EntryNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
|
||||
EntryNode() { this = TEntryNode(scope) }
|
||||
|
||||
final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() }
|
||||
|
||||
final override Location getLocation() { result = scope.getLocation() }
|
||||
|
||||
final override string toString() { result = "enter " + scope }
|
||||
}
|
||||
|
||||
/** An exit node for a given scope, annotated with the type of exit. */
|
||||
class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
|
||||
class AnnotatedExitNode extends CfgNode, CfgImpl::AnnotatedExitNode {
|
||||
override string getAPrimaryQlClass() { result = "AnnotatedExitNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
private boolean normal;
|
||||
|
||||
AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
|
||||
|
||||
/** Holds if this node represent a normal exit. */
|
||||
final predicate isNormal() { normal = true }
|
||||
|
||||
final override AnnotatedExitBasicBlock getBasicBlock() { result = super.getBasicBlock() }
|
||||
|
||||
final override Location getLocation() { result = scope.getLocation() }
|
||||
|
||||
final override string toString() {
|
||||
exists(string s |
|
||||
normal = true and s = "normal"
|
||||
or
|
||||
normal = false and s = "abnormal"
|
||||
|
|
||||
result = "exit " + scope + " (" + s + ")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An exit node for a given scope. */
|
||||
class ExitNode extends CfgNode, TExitNode {
|
||||
class ExitNode extends CfgNode, CfgImpl::ExitNode {
|
||||
override string getAPrimaryQlClass() { result = "ExitNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
|
||||
ExitNode() { this = TExitNode(scope) }
|
||||
|
||||
final override Location getLocation() { result = scope.getLocation() }
|
||||
|
||||
final override string toString() { result = "exit " + scope }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,55 +35,22 @@ class ExitNode extends CfgNode, TExitNode {
|
||||
* (dead) code or not important for control flow, and multiple when there are different
|
||||
* splits for the AST node.
|
||||
*/
|
||||
class AstCfgNode extends CfgNode, TElementNode {
|
||||
class AstCfgNode extends CfgNode, CfgImpl::AstCfgNode {
|
||||
/** Gets the name of the primary QL class for this node. */
|
||||
override string getAPrimaryQlClass() { result = "AstCfgNode" }
|
||||
|
||||
private Splits splits;
|
||||
AstNode e;
|
||||
|
||||
AstCfgNode() { this = TElementNode(_, e, splits) }
|
||||
|
||||
final override AstNode getNode() { result = e }
|
||||
|
||||
override Location getLocation() { result = e.getLocation() }
|
||||
|
||||
final override string toString() {
|
||||
exists(string s | s = e.toString() |
|
||||
result = "[" + this.getSplitsString() + "] " + s
|
||||
or
|
||||
not exists(this.getSplitsString()) and result = s
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a comma-separated list of strings for each split in this node, if any. */
|
||||
final string getSplitsString() {
|
||||
result = splits.toString() and
|
||||
result != ""
|
||||
}
|
||||
|
||||
/** Gets a split for this control flow node, if any. */
|
||||
final Split getASplit() { result = splits.getASplit() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an AST expression. */
|
||||
class ExprCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ExprCfgNode" }
|
||||
|
||||
override Expr e;
|
||||
Expr e;
|
||||
|
||||
ExprCfgNode() { e = this.getNode() }
|
||||
ExprCfgNode() { e = this.getAstNode() }
|
||||
|
||||
/** Gets the underlying expression. */
|
||||
Expr getExpr() { result = e }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getConstantValue` instead.
|
||||
*
|
||||
* Gets the textual (constant) value of this expression, if any.
|
||||
*/
|
||||
deprecated string getValueText() { result = this.getConstantValue().toString() }
|
||||
|
||||
/** Gets the constant value of this expression, if any. */
|
||||
ConstantValue getConstantValue() { result = getConstantValue(this) }
|
||||
}
|
||||
@@ -130,12 +61,12 @@ class ReturningCfgNode extends AstCfgNode {
|
||||
|
||||
ReturningStmt s;
|
||||
|
||||
ReturningCfgNode() { s = this.getNode() }
|
||||
ReturningCfgNode() { s = this.getAstNode() }
|
||||
|
||||
/** Gets the node of the returned value, if any. */
|
||||
ExprCfgNode getReturnedValueNode() {
|
||||
result = this.getAPredecessor() and
|
||||
result.getNode() = s.getValue()
|
||||
result.getAstNode() = s.getValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,17 +74,19 @@ class ReturningCfgNode extends AstCfgNode {
|
||||
class StringComponentCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringComponentCfgNode" }
|
||||
|
||||
StringComponentCfgNode() { this.getNode() instanceof StringComponent }
|
||||
StringComponentCfgNode() { this.getAstNode() instanceof StringComponent }
|
||||
|
||||
/** Gets the constant value of this string component. */
|
||||
ConstantValue getConstantValue() { result = this.getNode().(StringComponent).getConstantValue() }
|
||||
ConstantValue getConstantValue() {
|
||||
result = this.getAstNode().(StringComponent).getConstantValue()
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `RegExpComponent` AST expression. */
|
||||
class RegExpComponentCfgNode extends StringComponentCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "RegExpComponentCfgNode" }
|
||||
|
||||
RegExpComponentCfgNode() { e instanceof RegExpComponent }
|
||||
RegExpComponentCfgNode() { this.getAstNode() instanceof RegExpComponent }
|
||||
}
|
||||
|
||||
private AstNode desugar(AstNode n) {
|
||||
@@ -186,7 +119,7 @@ abstract private class ChildMapping extends AstNode {
|
||||
cached
|
||||
predicate hasCfgChild(AstNode child, CfgNode cfn, CfgNode cfnChild) {
|
||||
this.reachesBasicBlock(child, cfn, cfnChild.getBasicBlock()) and
|
||||
cfnChild.getNode() = desugar(child)
|
||||
cfnChild.getAstNode() = desugar(child)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +136,7 @@ abstract private class ExprChildMapping extends Expr, ChildMapping {
|
||||
exists(BasicBlock mid |
|
||||
this.reachesBasicBlock(child, cfn, mid) and
|
||||
bb = mid.getAPredecessor() and
|
||||
not mid.getANode().getNode() = child
|
||||
not mid.getANode().getAstNode() = child
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -217,13 +150,13 @@ abstract private class NonExprChildMapping extends ChildMapping {
|
||||
pragma[nomagic]
|
||||
override predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb) {
|
||||
this.relevantChild(child) and
|
||||
cfn.getNode() = this and
|
||||
cfn.getAstNode() = this and
|
||||
bb.getANode() = cfn
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
this.reachesBasicBlock(child, cfn, mid) and
|
||||
bb = mid.getASuccessor() and
|
||||
not mid.getANode().getNode() = child
|
||||
not mid.getANode().getAstNode() = child
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -447,9 +380,11 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `InClause` AST expression. */
|
||||
class InClauseCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "InClauseCfgNode" }
|
||||
private InClauseChildMapping e;
|
||||
|
||||
override InClauseChildMapping e;
|
||||
InClauseCfgNode() { e = this.getAstNode() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "InClauseCfgNode" }
|
||||
|
||||
/** Gets the pattern in this `in`-clause. */
|
||||
final AstCfgNode getPattern() { e.hasCfgChild(e.getPattern(), this, result) }
|
||||
@@ -467,56 +402,60 @@ module ExprNodes {
|
||||
predicate patternReachesBasicBlock(int i, CfgNode cfnPattern, BasicBlock bb) {
|
||||
exists(Expr pattern |
|
||||
pattern = this.getPattern(i) and
|
||||
cfnPattern.getNode() = pattern and
|
||||
cfnPattern.getAstNode() = pattern and
|
||||
bb.getANode() = cfnPattern
|
||||
)
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
this.patternReachesBasicBlock(i, cfnPattern, mid) and
|
||||
bb = mid.getASuccessor() and
|
||||
not mid.getANode().getNode() = this
|
||||
not mid.getANode().getAstNode() = this
|
||||
)
|
||||
}
|
||||
|
||||
predicate bodyReachesBasicBlock(CfgNode cfnBody, BasicBlock bb) {
|
||||
exists(Stmt body |
|
||||
body = this.getBody() and
|
||||
cfnBody.getNode() = body and
|
||||
cfnBody.getAstNode() = body and
|
||||
bb.getANode() = cfnBody
|
||||
)
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
this.bodyReachesBasicBlock(cfnBody, mid) and
|
||||
bb = mid.getAPredecessor() and
|
||||
not mid.getANode().getNode() = this
|
||||
not mid.getANode().getAstNode() = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `WhenClause` AST expression. */
|
||||
class WhenClauseCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "WhenClauseCfgNode" }
|
||||
private WhenClauseChildMapping e;
|
||||
|
||||
override WhenClauseChildMapping e;
|
||||
WhenClauseCfgNode() { e = this.getAstNode() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "WhenClauseCfgNode" }
|
||||
|
||||
/** Gets the body of this `when`-clause. */
|
||||
final ExprCfgNode getBody() {
|
||||
result.getNode() = desugar(e.getBody()) and
|
||||
result.getAstNode() = desugar(e.getBody()) and
|
||||
e.bodyReachesBasicBlock(result, this.getBasicBlock())
|
||||
}
|
||||
|
||||
/** Gets the `i`th pattern this `when`-clause. */
|
||||
final ExprCfgNode getPattern(int i) {
|
||||
result.getNode() = desugar(e.getPattern(i)) and
|
||||
result.getAstNode() = desugar(e.getPattern(i)) and
|
||||
e.patternReachesBasicBlock(i, result, this.getBasicBlock())
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `CasePattern`. */
|
||||
class CasePatternCfgNode extends AstCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "CasePatternCfgNode" }
|
||||
CasePattern e;
|
||||
|
||||
override CasePattern e;
|
||||
CasePatternCfgNode() { e = this.getAstNode() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "CasePatternCfgNode" }
|
||||
}
|
||||
|
||||
private class ArrayPatternChildMapping extends NonExprChildMapping, ArrayPattern {
|
||||
@@ -878,7 +817,7 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `InstanceVariableReadAccess` AST expression. */
|
||||
class InstanceVariableReadAccessCfgNode extends InstanceVariableAccessCfgNode {
|
||||
InstanceVariableReadAccessCfgNode() { this.getNode() instanceof InstanceVariableReadAccess }
|
||||
InstanceVariableReadAccessCfgNode() { this.getAstNode() instanceof InstanceVariableReadAccess }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "InstanceVariableReadAccessCfgNode" }
|
||||
|
||||
@@ -887,7 +826,9 @@ module ExprNodes {
|
||||
|
||||
/** A control-flow node that wraps an `InstanceVariableWriteAccess` AST expression. */
|
||||
class InstanceVariableWriteAccessCfgNode extends InstanceVariableAccessCfgNode {
|
||||
InstanceVariableWriteAccessCfgNode() { this.getNode() instanceof InstanceVariableWriteAccess }
|
||||
InstanceVariableWriteAccessCfgNode() {
|
||||
this.getAstNode() instanceof InstanceVariableWriteAccess
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "InstanceVariableWriteAccessCfgNode" }
|
||||
|
||||
@@ -898,7 +839,9 @@ module ExprNodes {
|
||||
class StringInterpolationComponentCfgNode extends StringComponentCfgNode, StmtSequenceCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringInterpolationComponentCfgNode" }
|
||||
|
||||
StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent }
|
||||
StringInterpolationComponentCfgNode() {
|
||||
this.getAstNode() instanceof StringInterpolationComponent
|
||||
}
|
||||
|
||||
final override ConstantValue getConstantValue() {
|
||||
result = StmtSequenceCfgNode.super.getConstantValue()
|
||||
@@ -909,7 +852,9 @@ module ExprNodes {
|
||||
class RegExpInterpolationComponentCfgNode extends RegExpComponentCfgNode, StmtSequenceCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "RegExpInterpolationComponentCfgNode" }
|
||||
|
||||
RegExpInterpolationComponentCfgNode() { this.getNode() instanceof RegExpInterpolationComponent }
|
||||
RegExpInterpolationComponentCfgNode() {
|
||||
this.getAstNode() instanceof RegExpInterpolationComponent
|
||||
}
|
||||
|
||||
final override ConstantValue getConstantValue() {
|
||||
result = StmtSequenceCfgNode.super.getConstantValue()
|
||||
@@ -936,10 +881,10 @@ module ExprNodes {
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringLiteral` AST expression. */
|
||||
class StringLiteralCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||
class StringLiteralCfgNode extends StringlikeLiteralCfgNode {
|
||||
StringLiteralCfgNode() { e instanceof StringLiteral }
|
||||
|
||||
override StringLiteral e;
|
||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||
|
||||
final override StringLiteral getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.controlflow.BasicBlocks
|
||||
private import SuccessorTypes
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import internal.ControlFlowGraphImpl as CfgImpl
|
||||
private import internal.Splitting
|
||||
private import internal.Completion
|
||||
|
||||
@@ -15,12 +15,12 @@ private import internal.Completion
|
||||
* Note that module declarations are not themselves CFG scopes, as they are part of
|
||||
* the CFG of the enclosing top-level or callable.
|
||||
*/
|
||||
class CfgScope extends Scope instanceof CfgScopeImpl {
|
||||
class CfgScope extends Scope instanceof CfgImpl::CfgScopeImpl {
|
||||
/** Gets the CFG scope that this scope is nested under, if any. */
|
||||
final CfgScope getOuterCfgScope() {
|
||||
exists(AstNode parent |
|
||||
parent = this.getParent() and
|
||||
result = getCfgScope(parent)
|
||||
result = CfgImpl::getCfgScope(parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -33,33 +33,18 @@ class CfgScope extends Scope instanceof CfgScopeImpl {
|
||||
*
|
||||
* Only nodes that can be reached from an entry point are included in the CFG.
|
||||
*/
|
||||
class CfgNode extends TCfgNode {
|
||||
class CfgNode extends CfgImpl::Node {
|
||||
/** Gets the name of the primary QL class for this node. */
|
||||
string getAPrimaryQlClass() { none() }
|
||||
|
||||
/** Gets a textual representation of this control flow node. */
|
||||
string toString() { none() }
|
||||
|
||||
/** Gets the AST node that this node corresponds to, if any. */
|
||||
AstNode getNode() { none() }
|
||||
|
||||
/** Gets the location of this control flow node. */
|
||||
Location getLocation() { none() }
|
||||
|
||||
/** Gets the file of this control flow node. */
|
||||
final File getFile() { result = this.getLocation().getFile() }
|
||||
|
||||
/** Holds if this control flow node has conditional successors. */
|
||||
final predicate isCondition() { exists(this.getASuccessor(any(ConditionalSuccessor bs))) }
|
||||
|
||||
/** Gets the scope of this node. */
|
||||
final CfgScope getScope() { result = getNodeCfgScope(this) }
|
||||
|
||||
/** Gets the basic block that this control flow node belongs to. */
|
||||
BasicBlock getBasicBlock() { result.getANode() = this }
|
||||
/** DEPRECATED: Use `getAstNode` instead. */
|
||||
deprecated AstNode getNode() { result = this.getAstNode() }
|
||||
|
||||
/** Gets a successor node of a given type, if any. */
|
||||
final CfgNode getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
|
||||
final CfgNode getASuccessor(SuccessorType t) { result = super.getASuccessor(t) }
|
||||
|
||||
/** Gets an immediate successor, if any. */
|
||||
final CfgNode getASuccessor() { result = this.getASuccessor(_) }
|
||||
@@ -70,15 +55,12 @@ class CfgNode extends TCfgNode {
|
||||
/** Gets an immediate predecessor, if any. */
|
||||
final CfgNode getAPredecessor() { result = this.getAPredecessor(_) }
|
||||
|
||||
/** Holds if this node has more than one predecessor. */
|
||||
final predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
|
||||
|
||||
/** Holds if this node has more than one successor. */
|
||||
final predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
|
||||
/** Gets the basic block that this control flow node belongs to. */
|
||||
BasicBlock getBasicBlock() { result.getANode() = this }
|
||||
}
|
||||
|
||||
/** The type of a control flow successor. */
|
||||
class SuccessorType extends TSuccessorType {
|
||||
class SuccessorType extends CfgImpl::TSuccessorType {
|
||||
/** Gets a textual representation of successor type. */
|
||||
string toString() { none() }
|
||||
}
|
||||
@@ -86,7 +68,7 @@ class SuccessorType extends TSuccessorType {
|
||||
/** Provides different types of control flow successor types. */
|
||||
module SuccessorTypes {
|
||||
/** A normal control flow successor. */
|
||||
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
|
||||
class NormalSuccessor extends SuccessorType, CfgImpl::TSuccessorSuccessor {
|
||||
final override string toString() { result = "successor" }
|
||||
}
|
||||
|
||||
@@ -99,9 +81,9 @@ module SuccessorTypes {
|
||||
boolean value;
|
||||
|
||||
ConditionalSuccessor() {
|
||||
this = TBooleanSuccessor(value) or
|
||||
this = TEmptinessSuccessor(value) or
|
||||
this = TMatchingSuccessor(value)
|
||||
this = CfgImpl::TBooleanSuccessor(value) or
|
||||
this = CfgImpl::TEmptinessSuccessor(value) or
|
||||
this = CfgImpl::TMatchingSuccessor(value)
|
||||
}
|
||||
|
||||
/** Gets the Boolean value of this successor. */
|
||||
@@ -125,7 +107,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* `x >= 0` has both a `true` successor and a `false` successor.
|
||||
*/
|
||||
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { }
|
||||
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor { }
|
||||
|
||||
/**
|
||||
* An emptiness control flow successor.
|
||||
@@ -158,7 +140,7 @@ module SuccessorTypes {
|
||||
* \___/
|
||||
* ```
|
||||
*/
|
||||
class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor {
|
||||
class EmptinessSuccessor extends ConditionalSuccessor, CfgImpl::TEmptinessSuccessor {
|
||||
override string toString() { if value = true then result = "empty" else result = "non-empty" }
|
||||
}
|
||||
|
||||
@@ -189,7 +171,7 @@ module SuccessorTypes {
|
||||
* puts "one" puts "not one"
|
||||
* ```
|
||||
*/
|
||||
class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor {
|
||||
class MatchingSuccessor extends ConditionalSuccessor, CfgImpl::TMatchingSuccessor {
|
||||
override string toString() { if value = true then result = "match" else result = "no-match" }
|
||||
}
|
||||
|
||||
@@ -207,7 +189,7 @@ module SuccessorTypes {
|
||||
* The exit node of `sum` is a `return` successor of the `return x + y`
|
||||
* statement.
|
||||
*/
|
||||
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
|
||||
class ReturnSuccessor extends SuccessorType, CfgImpl::TReturnSuccessor {
|
||||
final override string toString() { result = "return" }
|
||||
}
|
||||
|
||||
@@ -230,7 +212,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `puts "done"` is `break` successor of the node `break`.
|
||||
*/
|
||||
class BreakSuccessor extends SuccessorType, TBreakSuccessor {
|
||||
class BreakSuccessor extends SuccessorType, CfgImpl::TBreakSuccessor {
|
||||
final override string toString() { result = "break" }
|
||||
}
|
||||
|
||||
@@ -253,7 +235,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `x >= 0` is `next` successor of the node `next`.
|
||||
*/
|
||||
class NextSuccessor extends SuccessorType, TNextSuccessor {
|
||||
class NextSuccessor extends SuccessorType, CfgImpl::TNextSuccessor {
|
||||
final override string toString() { result = "next" }
|
||||
}
|
||||
|
||||
@@ -278,7 +260,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `x -= 1` is `redo` successor of the node `redo`.
|
||||
*/
|
||||
class RedoSuccessor extends SuccessorType, TRedoSuccessor {
|
||||
class RedoSuccessor extends SuccessorType, CfgImpl::TRedoSuccessor {
|
||||
final override string toString() { result = "redo" }
|
||||
}
|
||||
|
||||
@@ -302,7 +284,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `puts "Retry"` is `retry` successor of the node `retry`.
|
||||
*/
|
||||
class RetrySuccessor extends SuccessorType, TRetrySuccessor {
|
||||
class RetrySuccessor extends SuccessorType, CfgImpl::TRetrySuccessor {
|
||||
final override string toString() { result = "retry" }
|
||||
}
|
||||
|
||||
@@ -323,7 +305,7 @@ module SuccessorTypes {
|
||||
* The exit node of `m` is an exceptional successor of the node
|
||||
* `raise "x > 2"`.
|
||||
*/
|
||||
class RaiseSuccessor extends SuccessorType, TRaiseSuccessor {
|
||||
class RaiseSuccessor extends SuccessorType, CfgImpl::TRaiseSuccessor {
|
||||
final override string toString() { result = "raise" }
|
||||
}
|
||||
|
||||
@@ -344,7 +326,7 @@ module SuccessorTypes {
|
||||
* The exit node of `m` is an exit successor of the node
|
||||
* `exit 1`.
|
||||
*/
|
||||
class ExitSuccessor extends SuccessorType, TExitSuccessor {
|
||||
class ExitSuccessor extends SuccessorType, CfgImpl::TExitSuccessor {
|
||||
final override string toString() { result = "exit" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.ast.internal.AST
|
||||
private import codeql.ruby.ast.internal.Control
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import ControlFlowGraphImpl
|
||||
private import ControlFlowGraphImpl as CfgImpl
|
||||
private import NonReturning
|
||||
private import SuccessorTypes
|
||||
|
||||
@@ -53,7 +53,7 @@ private predicate nestedEnsureCompletion(TCompletion outer, int nestLevel) {
|
||||
or
|
||||
outer = TExitCompletion()
|
||||
) and
|
||||
nestLevel = any(Trees::BodyStmtTree t).getNestLevel()
|
||||
nestLevel = any(CfgImpl::Trees::BodyStmtTree t).getNestLevel()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
@@ -72,7 +72,7 @@ private predicate completionIsValidForStmt(AstNode n, Completion c) {
|
||||
}
|
||||
|
||||
private AstNode getARescuableBodyChild() {
|
||||
exists(Trees::BodyStmtTree bst | result = bst.getBodyChild(_, true) |
|
||||
exists(CfgImpl::Trees::BodyStmtTree bst | result = bst.getBodyChild(_, true) |
|
||||
exists(bst.getARescue())
|
||||
or
|
||||
exists(bst.getEnsure())
|
||||
@@ -247,7 +247,7 @@ private predicate inMatchingContext(AstNode n) {
|
||||
or
|
||||
n = any(ReferencePattern p).getExpr()
|
||||
or
|
||||
n.(Trees::DefaultValueParameterTree).hasDefaultValue()
|
||||
n.(CfgImpl::Trees::DefaultValueParameterTree).hasDefaultValue()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
private import codeql.ruby.AST as RB
|
||||
private import ControlFlowGraphImpl as Impl
|
||||
private import Completion as Comp
|
||||
private import codeql.ruby.ast.internal.Synthesis
|
||||
private import Splitting as Splitting
|
||||
private import codeql.ruby.CFG as Cfg
|
||||
|
||||
/** The base class for `ControlFlowTree`. */
|
||||
class ControlFlowTreeBase extends RB::AstNode {
|
||||
ControlFlowTreeBase() { not any(Synthesis s).excludeFromControlFlowTree(this) }
|
||||
}
|
||||
|
||||
class ControlFlowElement = RB::AstNode;
|
||||
|
||||
class Completion = Comp::Completion;
|
||||
|
||||
/**
|
||||
* Hold if `c` represents normal evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate completionIsNormal(Completion c) { c instanceof Comp::NormalCompletion }
|
||||
|
||||
/**
|
||||
* Hold if `c` represents simple (normal) evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate completionIsSimple(Completion c) { c instanceof Comp::SimpleCompletion }
|
||||
|
||||
/** Holds if `c` is a valid completion for `e`. */
|
||||
predicate completionIsValidFor(Completion c, ControlFlowElement e) { c.isValidFor(e) }
|
||||
|
||||
class CfgScope = Cfg::CfgScope;
|
||||
|
||||
predicate getCfgScope = Impl::getCfgScope/1;
|
||||
|
||||
/** Holds if `first` is first executed when entering `scope`. */
|
||||
predicate scopeFirst(CfgScope scope, ControlFlowElement first) {
|
||||
scope.(Impl::CfgScopeImpl).entry(first)
|
||||
}
|
||||
|
||||
/** Holds if `scope` is exited when `last` finishes with completion `c`. */
|
||||
predicate scopeLast(CfgScope scope, ControlFlowElement last, Completion c) {
|
||||
scope.(Impl::CfgScopeImpl).exit(last, c)
|
||||
}
|
||||
|
||||
/** The maximum number of splits allowed for a given node. */
|
||||
int maxSplits() { result = 5 }
|
||||
|
||||
class SplitKindBase = Splitting::TSplitKind;
|
||||
|
||||
class Split = Splitting::Split;
|
||||
|
||||
class SuccessorType = Cfg::SuccessorType;
|
||||
|
||||
/** Gets a successor type that matches completion `c`. */
|
||||
SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() }
|
||||
|
||||
/**
|
||||
* Hold if `c` represents simple (normal) evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate successorTypeIsSimple(SuccessorType t) {
|
||||
t instanceof Cfg::SuccessorTypes::NormalSuccessor
|
||||
}
|
||||
|
||||
/** Holds if `t` is an abnormal exit type out of a CFG scope. */
|
||||
predicate isAbnormalExitType(SuccessorType t) {
|
||||
t instanceof Cfg::SuccessorTypes::RaiseSuccessor or
|
||||
t instanceof Cfg::SuccessorTypes::ExitSuccessor
|
||||
}
|
||||
|
||||
class Location = RB::Location;
|
||||
|
||||
class Node = Cfg::CfgNode;
|
||||
@@ -2,7 +2,7 @@
|
||||
* Provides classes and predicates relevant for splitting the control flow graph.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.AST as Ast
|
||||
private import Completion
|
||||
private import ControlFlowGraphImpl
|
||||
private import SuccessorTypes
|
||||
@@ -64,31 +64,36 @@ private module ConditionalCompletionSplitting {
|
||||
|
||||
int getNextListOrder() { result = 1 }
|
||||
|
||||
private class ConditionalCompletionSplitImpl extends SplitImpl, ConditionalCompletionSplit {
|
||||
private class ConditionalCompletionSplitImpl extends SplitImpl instanceof ConditionalCompletionSplit
|
||||
{
|
||||
ConditionalCompletion completion;
|
||||
|
||||
ConditionalCompletionSplitImpl() { this = TConditionalCompletionSplit(completion) }
|
||||
|
||||
override ConditionalCompletionSplitKind getKind() { any() }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
succ(pred, succ, c) and
|
||||
last(succ, _, completion) and
|
||||
(
|
||||
last(succ.(NotExpr).getOperand(), pred, c) and
|
||||
last(succ.(Ast::NotExpr).getOperand(), pred, c) and
|
||||
completion.(BooleanCompletion).getDual() = c
|
||||
or
|
||||
last(succ.(LogicalAndExpr).getAnOperand(), pred, c) and
|
||||
last(succ.(Ast::LogicalAndExpr).getAnOperand(), pred, c) and
|
||||
completion = c
|
||||
or
|
||||
last(succ.(LogicalOrExpr).getAnOperand(), pred, c) and
|
||||
last(succ.(Ast::LogicalOrExpr).getAnOperand(), pred, c) and
|
||||
completion = c
|
||||
or
|
||||
last(succ.(StmtSequence).getLastStmt(), pred, c) and
|
||||
last(succ.(Ast::StmtSequence).getLastStmt(), pred, c) and
|
||||
completion = c
|
||||
or
|
||||
last(succ.(ConditionalExpr).getBranch(_), pred, c) and
|
||||
last(succ.(Ast::ConditionalExpr).getBranch(_), pred, c) and
|
||||
completion = c
|
||||
)
|
||||
or
|
||||
succ(pred, succ, c) and
|
||||
succ instanceof WhenClause and
|
||||
succ instanceof Ast::WhenClause and
|
||||
completion = c
|
||||
}
|
||||
|
||||
@@ -144,7 +149,7 @@ module EnsureSplitting {
|
||||
/** Holds if this node is the entry node in the `ensure` block it belongs to. */
|
||||
predicate isEntryNode() { first(block.getEnsure(), this) }
|
||||
|
||||
BodyStmt getBlock() { result = block }
|
||||
Ast::BodyStmt getBlock() { result = block }
|
||||
|
||||
pragma[noinline]
|
||||
predicate isEntered(AstNode pred, int nestLevel, Completion c) {
|
||||
@@ -221,12 +226,12 @@ module EnsureSplitting {
|
||||
override string toString() { result = "ensure (" + nestLevel + ")" }
|
||||
}
|
||||
|
||||
private class EnsureSplitImpl extends SplitImpl, EnsureSplit {
|
||||
override EnsureSplitKind getKind() { result.getNestLevel() = this.getNestLevel() }
|
||||
private class EnsureSplitImpl extends SplitImpl instanceof EnsureSplit {
|
||||
override EnsureSplitKind getKind() { result.getNestLevel() = super.getNestLevel() }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
succ.(EnsureNode).isEntered(pred, this.getNestLevel(), c) and
|
||||
this.getType().isSplitForEntryCompletion(c)
|
||||
succ.(EnsureNode).isEntered(pred, super.getNestLevel(), c) and
|
||||
super.getType().isSplitForEntryCompletion(c)
|
||||
}
|
||||
|
||||
override predicate hasEntryScope(CfgScope scope, AstNode first) { none() }
|
||||
@@ -253,8 +258,8 @@ module EnsureSplitting {
|
||||
*/
|
||||
private predicate exit(AstNode pred, Completion c, boolean inherited) {
|
||||
exists(Trees::BodyStmtTree block, EnsureSplitType type |
|
||||
this.exit0(pred, block, this.getNestLevel(), c) and
|
||||
type = this.getType()
|
||||
this.exit0(pred, block, super.getNestLevel(), c) and
|
||||
type = super.getType()
|
||||
|
|
||||
if last(block.getEnsure(), pred, c)
|
||||
then
|
||||
@@ -302,9 +307,9 @@ module EnsureSplitting {
|
||||
// split must be able to exit with a `return` completion.
|
||||
this.appliesToPredecessor(pred) and
|
||||
exists(EnsureSplitImpl outer |
|
||||
outer.getNestLevel() = this.getNestLevel() - 1 and
|
||||
outer.(EnsureSplit).getNestLevel() = super.getNestLevel() - 1 and
|
||||
outer.exit(pred, c, inherited) and
|
||||
this.getType() instanceof NormalSuccessor and
|
||||
super.getType() instanceof NormalSuccessor and
|
||||
inherited = true
|
||||
)
|
||||
}
|
||||
@@ -335,10 +340,10 @@ module EnsureSplitting {
|
||||
if en.isEntryNode() and en.getBlock() != pred.(EnsureNode).getBlock()
|
||||
then
|
||||
// entering a nested `ensure` block
|
||||
en.getNestLevel() > this.getNestLevel()
|
||||
en.getNestLevel() > super.getNestLevel()
|
||||
else
|
||||
// staying in the same (possibly nested) `ensure` block as `pred`
|
||||
en.getNestLevel() >= this.getNestLevel()
|
||||
en.getNestLevel() >= super.getNestLevel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,412 +0,0 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) data flow. This file
|
||||
* re-exports the local (intraprocedural) data flow analysis from
|
||||
* `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
|
||||
* through the `Global` and `GlobalWithState` modules.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
private import DataFlowImpl
|
||||
|
||||
/** An input configuration for data flow. */
|
||||
signature module ConfigSig {
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `flowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `flowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/** An input configuration for data flow using flow state. */
|
||||
signature module StateConfigSig {
|
||||
bindingset[this]
|
||||
class FlowState;
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state);
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2);
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `flowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `flowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exploration limit for `partialFlow` and `partialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
signature int explorationLimitSig();
|
||||
|
||||
/**
|
||||
* The output of a global data flow computation.
|
||||
*/
|
||||
signature module GlobalFlowSig {
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks) and an access path.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode;
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate flowPath(PathNode source, PathNode sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*/
|
||||
predicate flow(Node source, Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate flowTo(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate flowToExpr(DataFlowExpr sink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a global data flow computation.
|
||||
*/
|
||||
module Global<ConfigSig Config> implements GlobalFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import DefaultState<Config>
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use `Global` instead. */
|
||||
deprecated module Make<ConfigSig Config> implements GlobalFlowSig {
|
||||
import Global<Config>
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a global data flow computation using flow state.
|
||||
*/
|
||||
module GlobalWithState<StateConfigSig Config> implements GlobalFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use `GlobalWithState` instead. */
|
||||
deprecated module MakeWithState<StateConfigSig Config> implements GlobalFlowSig {
|
||||
import GlobalWithState<Config>
|
||||
}
|
||||
|
||||
signature class PathNodeSig {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString();
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
);
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
Node getNode();
|
||||
}
|
||||
|
||||
signature module PathGraphSig<PathNodeSig PathNode> {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
predicate edges(PathNode a, PathNode b);
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
predicate nodes(PathNode n, string key, string val);
|
||||
|
||||
/**
|
||||
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `PathGraph` from two `PathGraph`s by disjoint union.
|
||||
*/
|
||||
module MergePathGraph<
|
||||
PathNodeSig PathNode1, PathNodeSig PathNode2, PathGraphSig<PathNode1> Graph1,
|
||||
PathGraphSig<PathNode2> Graph2>
|
||||
{
|
||||
private newtype TPathNode =
|
||||
TPathNode1(PathNode1 p) or
|
||||
TPathNode2(PathNode2 p)
|
||||
|
||||
/** A node in a graph of path explanations that is formed by disjoint union of the two given graphs. */
|
||||
class PathNode extends TPathNode {
|
||||
/** Gets this as a projection on the first given `PathGraph`. */
|
||||
PathNode1 asPathNode1() { this = TPathNode1(result) }
|
||||
|
||||
/** Gets this as a projection on the second given `PathGraph`. */
|
||||
PathNode2 asPathNode2() { this = TPathNode2(result) }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
result = this.asPathNode1().toString() or
|
||||
result = this.asPathNode2().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.asPathNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or
|
||||
this.asPathNode2().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
Node getNode() {
|
||||
result = this.asPathNode1().getNode() or
|
||||
result = this.asPathNode2().getNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||
*/
|
||||
module PathGraph implements PathGraphSig<PathNode> {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) {
|
||||
Graph1::edges(a.asPathNode1(), b.asPathNode1()) or
|
||||
Graph2::edges(a.asPathNode2(), b.asPathNode2())
|
||||
}
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
Graph1::nodes(n.asPathNode1(), key, val) or
|
||||
Graph2::nodes(n.asPathNode2(), key, val)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
|
||||
Graph1::subpaths(arg.asPathNode1(), par.asPathNode1(), ret.asPathNode1(), out.asPathNode1()) or
|
||||
Graph2::subpaths(arg.asPathNode2(), par.asPathNode2(), ret.asPathNode2(), out.asPathNode2())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `PathGraph` from three `PathGraph`s by disjoint union.
|
||||
*/
|
||||
module MergePathGraph3<
|
||||
PathNodeSig PathNode1, PathNodeSig PathNode2, PathNodeSig PathNode3,
|
||||
PathGraphSig<PathNode1> Graph1, PathGraphSig<PathNode2> Graph2, PathGraphSig<PathNode3> Graph3>
|
||||
{
|
||||
private module MergedInner = MergePathGraph<PathNode1, PathNode2, Graph1, Graph2>;
|
||||
|
||||
private module Merged =
|
||||
MergePathGraph<MergedInner::PathNode, PathNode3, MergedInner::PathGraph, Graph3>;
|
||||
|
||||
/** A node in a graph of path explanations that is formed by disjoint union of the three given graphs. */
|
||||
class PathNode instanceof Merged::PathNode {
|
||||
/** Gets this as a projection on the first given `PathGraph`. */
|
||||
PathNode1 asPathNode1() { result = super.asPathNode1().asPathNode1() }
|
||||
|
||||
/** Gets this as a projection on the second given `PathGraph`. */
|
||||
PathNode2 asPathNode2() { result = super.asPathNode1().asPathNode2() }
|
||||
|
||||
/** Gets this as a projection on the third given `PathGraph`. */
|
||||
PathNode3 asPathNode3() { result = super.asPathNode2() }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
Node getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||
*/
|
||||
module PathGraph = Merged::PathGraph;
|
||||
}
|
||||
@@ -8,6 +8,26 @@ private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.util.Boolean
|
||||
private import codeql.util.Unit
|
||||
|
||||
/**
|
||||
* A `LocalSourceNode` for a `self` variable. This is the implicit `self`
|
||||
* parameter, when it exists, otherwise the implicit SSA entry definition.
|
||||
*/
|
||||
private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
|
||||
private SelfVariable self;
|
||||
|
||||
SelfLocalSourceNode() {
|
||||
self = this.(SelfParameterNodeImpl).getSelfVariable()
|
||||
or
|
||||
self = this.(SsaSelfDefinitionNode).getVariable() and
|
||||
not LocalFlow::localFlowSsaParamInput(_, this)
|
||||
}
|
||||
|
||||
/** Gets the `self` variable. */
|
||||
SelfVariable getVariable() { result = self }
|
||||
}
|
||||
|
||||
newtype TReturnKind =
|
||||
TNormalReturnKind() or
|
||||
@@ -133,12 +153,12 @@ class DataFlowCall extends TDataFlowCall {
|
||||
*/
|
||||
class SummaryCall extends DataFlowCall, TSummaryCall {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable c;
|
||||
private DataFlow::Node receiver;
|
||||
private FlowSummaryImpl::Private::SummaryNode receiver;
|
||||
|
||||
SummaryCall() { this = TSummaryCall(c, receiver) }
|
||||
|
||||
/** Gets the data flow node that this call targets. */
|
||||
DataFlow::Node getReceiver() { result = receiver }
|
||||
FlowSummaryImpl::Private::SummaryNode getReceiver() { result = receiver }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
|
||||
@@ -244,7 +264,7 @@ private predicate selfInToplevel(SelfVariable self, Module m) {
|
||||
private predicate asModulePattern(SsaDefinitionExtNode def, Module m) {
|
||||
exists(AsPattern ap |
|
||||
m = resolveConstantReadAccess(ap.getPattern()) and
|
||||
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getNode() =
|
||||
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getAstNode() =
|
||||
ap.getVariableAccess()
|
||||
)
|
||||
}
|
||||
@@ -316,7 +336,7 @@ private predicate extendCall(DataFlow::ExprNode receiver, Module m) {
|
||||
exists(DataFlow::CallNode extendCall |
|
||||
extendCall.getMethodName() = "extend" and
|
||||
exists(DataFlow::LocalSourceNode sourceNode | sourceNode.flowsTo(extendCall.getArgument(_)) |
|
||||
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m) or
|
||||
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) or
|
||||
m = resolveConstantReadAccess(sourceNode.asExpr().getExpr())
|
||||
) and
|
||||
receiver = extendCall.getReceiver()
|
||||
@@ -329,7 +349,7 @@ private predicate extendCallModule(Module m, Module n) {
|
||||
exists(DataFlow::LocalSourceNode receiver, DataFlow::ExprNode e |
|
||||
receiver.flowsTo(e) and extendCall(e, n)
|
||||
|
|
||||
selfInModule(receiver.(SsaSelfDefinitionNode).getVariable(), m) or
|
||||
selfInModule(receiver.(SelfLocalSourceNode).getVariable(), m) or
|
||||
m = resolveConstantReadAccess(receiver.asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
@@ -357,7 +377,9 @@ private module Cached {
|
||||
cached
|
||||
newtype TDataFlowCall =
|
||||
TNormalCall(CfgNodes::ExprNodes::CallCfgNode c) or
|
||||
TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, DataFlow::Node receiver) {
|
||||
TSummaryCall(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
|
||||
) {
|
||||
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
|
||||
}
|
||||
|
||||
@@ -453,35 +475,28 @@ private module Cached {
|
||||
import Cached
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccess(Module m, TypeTracker t) {
|
||||
t.start() and m = resolveConstantReadAccess(result.asExpr().getExpr())
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackModuleAccessRec(m, t2, summary) and t = t2.append(summary)
|
||||
)
|
||||
private predicate isNotSelf(DataFlow::Node n) { not n instanceof SelfParameterNodeImpl }
|
||||
|
||||
private module TrackModuleInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = Module;
|
||||
|
||||
predicate start(DataFlow::Node start, Module m) {
|
||||
m = resolveConstantReadAccess(start.asExpr().getExpr())
|
||||
}
|
||||
|
||||
// We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
// enclosing module
|
||||
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
|
||||
}
|
||||
|
||||
/**
|
||||
* We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
* enclosing module.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccessRec(Module m, TypeTracker t, StepSummary summary) {
|
||||
StepSummary::step(trackModuleAccess(m, t), result, summary) and
|
||||
not result instanceof SelfParameterNode
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
|
||||
result = trackModuleAccess(m, TypeTracker::end())
|
||||
}
|
||||
predicate trackModuleAccess = CallGraphConstruction::Simple::Make<TrackModuleInput>::track/1;
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasUserDefinedNew(Module m) {
|
||||
exists(DataFlow::MethodNode method |
|
||||
// not `getAnAncestor` because singleton methods cannot be included
|
||||
singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and
|
||||
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturningNode())
|
||||
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturnNode())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -502,153 +517,174 @@ private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
|
||||
exact = true
|
||||
or
|
||||
// `self.new` inside a module
|
||||
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m) and
|
||||
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) and
|
||||
exact = true
|
||||
or
|
||||
// `self.new` inside a singleton method
|
||||
exists(MethodBase caller |
|
||||
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), caller, m) and
|
||||
selfInMethod(sourceNode.(SelfLocalSourceNode).getVariable(), caller, m) and
|
||||
singletonMethod(caller, _, _) and
|
||||
exact = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is an instance of type `tp`. */
|
||||
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
|
||||
n.asExpr().getExpr() instanceof NilLiteral and
|
||||
tp = TResolved("NilClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
tp = TResolved("FalseClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
|
||||
tp = TResolved("TrueClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof IntegerLiteral and
|
||||
tp = TResolved("Integer") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof FloatLiteral and
|
||||
tp = TResolved("Float") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof RationalLiteral and
|
||||
tp = TResolved("Rational") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof ComplexLiteral and
|
||||
tp = TResolved("Complex") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof StringlikeLiteral and
|
||||
tp = TResolved("String") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
|
||||
tp = TResolved("Array") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
|
||||
tp = TResolved("Hash") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof MethodBase and
|
||||
tp = TResolved("Symbol") and
|
||||
exact = true
|
||||
or
|
||||
n.asParameter() instanceof BlockParameter and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof Lambda and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
isStandardNewCall(n.asExpr(), tp, exact)
|
||||
or
|
||||
// `self` reference in method or top-level (but not in module or singleton method,
|
||||
// where instance methods cannot be called; only singleton methods)
|
||||
n =
|
||||
any(SsaSelfDefinitionNode self |
|
||||
exists(MethodBase m |
|
||||
selfInMethod(self.getVariable(), m, tp) and
|
||||
not m instanceof SingletonMethod and
|
||||
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
|
||||
)
|
||||
or
|
||||
selfInToplevel(self.getVariable(), tp) and
|
||||
exact = true
|
||||
)
|
||||
or
|
||||
// `in C => c then c.foo`
|
||||
asModulePattern(n, tp) and
|
||||
exact = false
|
||||
or
|
||||
// `case object when C then object.foo`
|
||||
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
|
||||
exact = false
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
isInstance(result, tp, exact)
|
||||
or
|
||||
exists(Module m |
|
||||
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
|
||||
exact = true
|
||||
|
|
||||
// needed for e.g. `C.new`
|
||||
m = resolveConstantReadAccess(result.asExpr().getExpr())
|
||||
or
|
||||
// needed for e.g. `self.include`
|
||||
selfInModule(result.(SsaSelfDefinitionNode).getVariable(), m)
|
||||
or
|
||||
// needed for e.g. `self.puts`
|
||||
selfInMethod(result.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), m)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackInstanceRec(tp, t2, exact, summary) and t = t2.append(summary)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
localFlowStepTypeTracker(nodeFrom, nodeTo) and
|
||||
summary.toString() = "level"
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
|
||||
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
|
||||
}
|
||||
|
||||
/**
|
||||
* We exclude steps into `self` parameters and type checked variables. For those,
|
||||
* we instead rely on the type of the enclosing module resp. the type being checked
|
||||
* against, and apply an open-world assumption when determining possible dispatch
|
||||
* targets.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstanceRec(Module tp, TypeTracker t, boolean exact, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | mid = trackInstance(tp, exact, t) |
|
||||
StepSummary::smallstep(mid, result, summary) and
|
||||
not result instanceof SelfParameterNode
|
||||
private module TrackInstanceInput implements CallGraphConstruction::InputSig {
|
||||
pragma[nomagic]
|
||||
private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) {
|
||||
n.asExpr().getExpr() instanceof NilLiteral and
|
||||
tp = TResolved("NilClass") and
|
||||
exact = true
|
||||
or
|
||||
localFlowStep(mid, result, summary) and
|
||||
not hasAdjacentTypeCheckedReads(result)
|
||||
)
|
||||
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
tp = TResolved("FalseClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
|
||||
tp = TResolved("TrueClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof IntegerLiteral and
|
||||
tp = TResolved("Integer") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof FloatLiteral and
|
||||
tp = TResolved("Float") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof RationalLiteral and
|
||||
tp = TResolved("Rational") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof ComplexLiteral and
|
||||
tp = TResolved("Complex") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof StringlikeLiteral and
|
||||
tp = TResolved("String") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
|
||||
tp = TResolved("Array") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
|
||||
tp = TResolved("Hash") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof MethodBase and
|
||||
tp = TResolved("Symbol") and
|
||||
exact = true
|
||||
or
|
||||
n.asParameter() instanceof BlockParameter and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof Lambda and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
// `self` reference in method or top-level (but not in module or singleton method,
|
||||
// where instance methods cannot be called; only singleton methods)
|
||||
n =
|
||||
any(SelfLocalSourceNode self |
|
||||
exists(MethodBase m |
|
||||
selfInMethod(self.getVariable(), m, tp) and
|
||||
not m instanceof SingletonMethod and
|
||||
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
|
||||
)
|
||||
or
|
||||
selfInToplevel(self.getVariable(), tp) and
|
||||
exact = true
|
||||
)
|
||||
or
|
||||
// `in C => c then c.foo`
|
||||
asModulePattern(n, tp) and
|
||||
exact = false
|
||||
or
|
||||
// `case object when C then object.foo`
|
||||
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
|
||||
exact = false
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) {
|
||||
isStandardNewCall(n.asExpr(), tp, exact)
|
||||
}
|
||||
|
||||
/** Holds if `n` is an instance of type `tp`. */
|
||||
pragma[inline]
|
||||
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
|
||||
isInstanceNoCall(n, tp, exact)
|
||||
or
|
||||
isInstanceCall(n, tp, exact)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
|
||||
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
|
||||
}
|
||||
|
||||
newtype State = additional MkState(Module m, Boolean exact)
|
||||
|
||||
predicate start(DataFlow::Node start, State state) {
|
||||
exists(Module tp, boolean exact | state = MkState(tp, exact) |
|
||||
isInstance(start, tp, exact)
|
||||
or
|
||||
exists(Module m |
|
||||
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
|
||||
exact = true
|
||||
|
|
||||
// needed for e.g. `C.new`
|
||||
m = resolveConstantReadAccess(start.asExpr().getExpr())
|
||||
or
|
||||
// needed for e.g. `self.include`
|
||||
selfInModule(start.(SelfLocalSourceNode).getVariable(), m)
|
||||
or
|
||||
// needed for e.g. `self.puts`
|
||||
selfInMethod(start.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
// We exclude steps into `self` parameters. For those, we instead rely on the type of
|
||||
// the enclosing module
|
||||
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) and
|
||||
isNotSelf(nodeTo)
|
||||
or
|
||||
// We exclude steps into type checked variables. For those, we instead rely on the
|
||||
// type being checked against
|
||||
localFlowStep(nodeFrom, nodeTo, summary) and
|
||||
not hasAdjacentTypeCheckedReads(nodeTo)
|
||||
}
|
||||
|
||||
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
class StateProj = Unit;
|
||||
|
||||
Unit stateProj(State state) { exists(state) and exists(result) }
|
||||
|
||||
// We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
// enclosing module
|
||||
predicate filter(DataFlow::Node n, Unit u) {
|
||||
n instanceof SelfParameterNodeImpl and
|
||||
exists(u)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstance(Module tp, boolean exact) {
|
||||
result = trackInstance(tp, exact, TypeTracker::end())
|
||||
result =
|
||||
CallGraphConstruction::Make<TrackInstanceInput>::track(TrackInstanceInput::MkState(tp, exact))
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
@@ -689,30 +725,17 @@ private CfgScope getTargetInstance(RelevantCall call, string method) {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
|
||||
t.start() and result.asExpr().getExpr() = block
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackBlockRec(block, t2, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = Block;
|
||||
|
||||
predicate start(DataFlow::Node start, Block block) { start.asExpr().getExpr() = block }
|
||||
|
||||
// We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
// enclosing module
|
||||
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
|
||||
}
|
||||
|
||||
/**
|
||||
* We exclude steps into `self` parameters, which may happen when the code
|
||||
* base contains implementations of `call`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) {
|
||||
StepSummary::step(trackBlock(block, t), result, summary) and
|
||||
not result instanceof SelfParameterNode
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlock(Block block) {
|
||||
result = trackBlock(block, TypeTracker::end())
|
||||
}
|
||||
private predicate trackBlock = CallGraphConstruction::Simple::Make<TrackBlockInput>::track/1;
|
||||
|
||||
/** Holds if `m` is a singleton method named `name`, defined on `object. */
|
||||
private predicate singletonMethod(MethodBase m, string name, Expr object) {
|
||||
@@ -879,98 +902,104 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
|
||||
*
|
||||
* This is only used for tracking singleton methods, where we want to be able
|
||||
* to handle cases like
|
||||
*
|
||||
* ```rb
|
||||
* def add_singleton x
|
||||
* def x.foo; end
|
||||
* end
|
||||
*
|
||||
* y = add_singleton C.new
|
||||
* y.foo
|
||||
* ```
|
||||
*
|
||||
* and
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* def add_singleton_to_self
|
||||
* def self.foo; end
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* y = C.new
|
||||
* y.add_singleton_to_self
|
||||
* y.foo
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate paramReturnFlow(
|
||||
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
|
||||
TypeTrackerSpecific::callStep(call, arg, p) and
|
||||
nodeTo.getPreUpdateNode() = arg and
|
||||
summary.toString() = "return" and
|
||||
(
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
|
||||
private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruction::InputSig {
|
||||
/**
|
||||
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
|
||||
*
|
||||
* This is only used for tracking singleton methods, where we want to be able
|
||||
* to handle cases like
|
||||
*
|
||||
* ```rb
|
||||
* def add_singleton x
|
||||
* def x.foo; end
|
||||
* end
|
||||
*
|
||||
* y = add_singleton C.new
|
||||
* y.foo
|
||||
* ```
|
||||
*
|
||||
* and
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* def add_singleton_to_self
|
||||
* def self.foo; end
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* y = C.new
|
||||
* y.add_singleton_to_self
|
||||
* y.foo
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate paramReturnFlow(
|
||||
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
|
||||
TypeTrackerSpecific::callStep(call, arg, p) and
|
||||
nodeTo.getPreUpdateNode() = arg and
|
||||
summary.toString() = "return" and
|
||||
(
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
|
||||
or
|
||||
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr)
|
||||
)
|
||||
|
|
||||
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
|
||||
or
|
||||
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr)
|
||||
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess()
|
||||
)
|
||||
|
|
||||
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
|
||||
or
|
||||
nodeFromPreExpr = p.(SelfParameterNode).getSelfVariable().getAnAccess()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name, TypeTracker t) {
|
||||
t.start() and
|
||||
singletonMethodOnInstance(method, name, result.asExpr().getExpr())
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackSingletonMethodOnInstanceRec(method, name, t2, summary) and
|
||||
t = t2.append(summary) and
|
||||
// Stop flow at redefinitions.
|
||||
//
|
||||
// Example:
|
||||
// ```rb
|
||||
// def x.foo; end
|
||||
// def x.foo; end
|
||||
// x.foo # <- we want to resolve this call to the second definition only
|
||||
// ```
|
||||
not singletonMethodOnInstance(_, name, result.asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
class State = MethodBase;
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackSingletonMethodOnInstanceRec(
|
||||
MethodBase method, string name, TypeTracker t, StepSummary summary
|
||||
) {
|
||||
exists(DataFlow::Node mid | mid = trackSingletonMethodOnInstance(method, name, t) |
|
||||
StepSummary::smallstep(mid, result, summary)
|
||||
predicate start(DataFlow::Node start, MethodBase method) {
|
||||
singletonMethodOnInstance(method, _, start.asExpr().getExpr())
|
||||
}
|
||||
|
||||
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
paramReturnFlow(mid, result, summary)
|
||||
localFlowStep(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
localFlowStep(mid, result, summary)
|
||||
)
|
||||
paramReturnFlow(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
class StateProj extends string {
|
||||
StateProj() { singletonMethodOnInstance(_, this, _) }
|
||||
}
|
||||
|
||||
StateProj stateProj(MethodBase method) { singletonMethodOnInstance(method, result, _) }
|
||||
|
||||
// Stop flow at redefinitions.
|
||||
//
|
||||
// Example:
|
||||
// ```rb
|
||||
// def x.foo; end
|
||||
// def x.foo; end
|
||||
// x.foo # <- we want to resolve this call to the second definition only
|
||||
// ```
|
||||
predicate filter(DataFlow::Node n, StateProj name) {
|
||||
singletonMethodOnInstance(_, name, n.asExpr().getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name) {
|
||||
result = trackSingletonMethodOnInstance(method, name, TypeTracker::end())
|
||||
result = CallGraphConstruction::Make<TrackSingletonMethodOnInstanceInput>::track(method) and
|
||||
singletonMethodOnInstance(method, name, _)
|
||||
}
|
||||
|
||||
/** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */
|
||||
pragma[nomagic]
|
||||
private predicate selfInModuleFlowsToMethodCallReceiver(RelevantCall call, Module m, string method) {
|
||||
exists(SsaSelfDefinitionNode self |
|
||||
exists(SelfLocalSourceNode self |
|
||||
flowsToMethodCallReceiver(call, self, method) and
|
||||
selfInModule(self.getVariable(), m)
|
||||
)
|
||||
@@ -984,7 +1013,7 @@ pragma[nomagic]
|
||||
private predicate selfInSingletonMethodFlowsToMethodCallReceiver(
|
||||
RelevantCall call, Module m, string method
|
||||
) {
|
||||
exists(SsaSelfDefinitionNode self, MethodBase caller |
|
||||
exists(SelfLocalSourceNode self, MethodBase caller |
|
||||
flowsToMethodCallReceiver(call, self, method) and
|
||||
selfInMethod(self.getVariable(), caller, m) and
|
||||
singletonMethod(caller, _, _)
|
||||
@@ -1062,10 +1091,13 @@ private CfgScope getTargetSingleton(RelevantCall call, string method) {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate argMustFlowToReceiver(
|
||||
RelevantCall ctx, DataFlow::LocalSourceNode source, DataFlow::Node arg,
|
||||
SsaDefinitionExtNode paramDef, RelevantCall call, Callable encl, string name
|
||||
RelevantCall ctx, DataFlow::LocalSourceNode source, DataFlow::Node arg, RelevantCall call,
|
||||
Callable encl, string name
|
||||
) {
|
||||
exists(ParameterNodeImpl p, ParameterPosition ppos, ArgumentPosition apos |
|
||||
exists(
|
||||
ParameterNodeImpl p, SsaDefinitionExtNode paramDef, ParameterPosition ppos,
|
||||
ArgumentPosition apos
|
||||
|
|
||||
// the receiver of `call` references `p`
|
||||
exists(DataFlow::Node receiver |
|
||||
LocalFlow::localFlowSsaParamInput(p, paramDef) and
|
||||
@@ -1106,7 +1138,7 @@ private predicate mayBenefitFromCallContextInitialize(
|
||||
RelevantCall ctx, RelevantCall new, DataFlow::Node arg, Callable encl, Module tp, string name
|
||||
) {
|
||||
exists(DataFlow::LocalSourceNode source |
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, new, encl, "new") and
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, new, encl, "new") and
|
||||
source = trackModuleAccess(tp) and
|
||||
name = "initialize" and
|
||||
exists(lookupMethod(tp, name))
|
||||
@@ -1127,7 +1159,7 @@ private predicate mayBenefitFromCallContextInstance(
|
||||
string name
|
||||
) {
|
||||
exists(DataFlow::LocalSourceNode source |
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, call, encl,
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, call, encl,
|
||||
pragma[only_bind_into](name)) and
|
||||
source = trackInstance(tp, exact) and
|
||||
exists(lookupMethod(tp, pragma[only_bind_into](name)))
|
||||
@@ -1148,7 +1180,7 @@ private predicate mayBenefitFromCallContextSingleton(
|
||||
string name
|
||||
) {
|
||||
exists(DataFlow::LocalSourceNode source |
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), pragma[only_bind_into](arg), _, call,
|
||||
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), pragma[only_bind_into](arg), call,
|
||||
encl, pragma[only_bind_into](name)) and
|
||||
exists(lookupSingletonMethod(tp, pragma[only_bind_into](name), exact))
|
||||
|
|
||||
@@ -1216,13 +1248,10 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
or
|
||||
// `ctx` cannot provide a type bound, and the receiver of the call is `self`;
|
||||
// in this case, still apply an open-world assumption
|
||||
exists(
|
||||
RelevantCall call0, RelevantCall ctx0, DataFlow::Node arg, SsaSelfDefinitionNode self,
|
||||
string name
|
||||
|
|
||||
exists(RelevantCall call0, RelevantCall ctx0, DataFlow::Node arg, string name |
|
||||
call0 = call.asCall() and
|
||||
ctx0 = ctx.asCall() and
|
||||
argMustFlowToReceiver(ctx0, _, arg, self, call0, _, name) and
|
||||
argMustFlowToReceiver(ctx0, _, arg, call0, _, name) and
|
||||
not mayBenefitFromCallContextInitialize(ctx0, call0, arg, _, _, _) and
|
||||
not mayBenefitFromCallContextInstance(ctx0, call0, arg, _, _, _, name) and
|
||||
not mayBenefitFromCallContextSingleton(ctx0, call0, arg, _, _, _, name) and
|
||||
@@ -1230,7 +1259,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
)
|
||||
or
|
||||
// library calls should always be able to resolve
|
||||
argMustFlowToReceiver(ctx.asCall(), _, _, _, call.asCall(), _, _) and
|
||||
argMustFlowToReceiver(ctx.asCall(), _, _, call.asCall(), _, _) and
|
||||
result = viableLibraryCallable(call)
|
||||
)
|
||||
}
|
||||
@@ -1387,6 +1416,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
* This is a temporary hook to support technical debt in the Go language; do not use.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate golangSpecificParamArgFilter(DataFlowCall call, DataFlow::Node p, ArgumentNode arg) {
|
||||
predicate golangSpecificParamArgFilter(
|
||||
DataFlowCall call, DataFlow::ParameterNode p, ArgumentNode arg
|
||||
) {
|
||||
any()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
predicate neverSkip(Node node) { none() }
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
predicate neverSkip(Node node) { none() }
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
predicate neverSkip(Node node) { none() }
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
predicate neverSkip(Node node) { none() }
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* Provides Ruby-specific definitions for use in the data flow library.
|
||||
*/
|
||||
|
||||
private import codeql.dataflow.DataFlowParameter
|
||||
|
||||
module Private {
|
||||
import DataFlowPrivate
|
||||
import DataFlowDispatch
|
||||
@@ -9,3 +12,16 @@ module Private {
|
||||
module Public {
|
||||
import DataFlowPublic
|
||||
}
|
||||
|
||||
module RubyDataFlow implements DataFlowParameter {
|
||||
import Private
|
||||
import Public
|
||||
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
Private::isParameterNode(p, c, pos)
|
||||
}
|
||||
|
||||
predicate neverSkipInPathGraph = Private::neverSkipInPathGraph/1;
|
||||
|
||||
Node exprNode(DataFlowExpr e) { result = Public::exprNode(e) }
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.(NodeImpl).getEnclosingCallable() }
|
||||
|
||||
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
|
||||
predicate isParameterNode(ParameterNodeImpl p, DataFlowCallable c, ParameterPosition pos) {
|
||||
@@ -118,7 +118,7 @@ module LocalFlow {
|
||||
/** Gets the SSA definition node corresponding to parameter `p`. */
|
||||
SsaDefinitionExtNode getParameterDefNode(NamedParameter p) {
|
||||
exists(BasicBlock bb, int i |
|
||||
bb.getNode(i).getNode() = p.getDefiningAccess() and
|
||||
bb.getNode(i).getAstNode() = p.getDefiningAccess() and
|
||||
result.getDefinitionExt().definesAt(_, bb, i, _)
|
||||
)
|
||||
}
|
||||
@@ -131,10 +131,10 @@ module LocalFlow {
|
||||
/**
|
||||
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
|
||||
*/
|
||||
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
|
||||
predicate localFlowSsaParamInput(Node nodeFrom, SsaDefinitionExtNode nodeTo) {
|
||||
nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter())
|
||||
or
|
||||
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
|
||||
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,13 +143,13 @@ module LocalFlow {
|
||||
*
|
||||
* This is intended to recover from flow not currently recognised by ordinary capture flow.
|
||||
*/
|
||||
predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) {
|
||||
predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::CapturedEntryDefinition def |
|
||||
nodeFrom.asParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
|
|
||||
nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
or
|
||||
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
||||
|
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
)
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ module LocalFlow {
|
||||
|
||||
/**
|
||||
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
|
||||
* SSA definition `def`.
|
||||
* some SSA definition.
|
||||
*/
|
||||
private predicate localSsaFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(SsaImpl::DefinitionExt def |
|
||||
@@ -181,6 +181,8 @@ module LocalFlow {
|
||||
// Flow into phi (read) SSA definition node from def
|
||||
localFlowSsaInputFromDef(nodeFrom, def, nodeTo)
|
||||
)
|
||||
or
|
||||
localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
// TODO
|
||||
// or
|
||||
// // Flow into uncertain SSA definition
|
||||
@@ -201,8 +203,8 @@ module LocalFlow {
|
||||
exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n |
|
||||
nodeFrom = n and
|
||||
exprTo = nodeTo.asExpr() and
|
||||
n.getReturningNode().getNode() instanceof BreakStmt and
|
||||
exprTo.getNode() instanceof Loop and
|
||||
n.getReturningNode().getAstNode() instanceof BreakStmt and
|
||||
exprTo.getAstNode() instanceof Loop and
|
||||
nodeTo.asExpr().getAPredecessor(any(SuccessorTypes::BreakSuccessor s)) = n.getReturningNode()
|
||||
)
|
||||
or
|
||||
@@ -222,6 +224,13 @@ module LocalFlow {
|
||||
op.getExpr() instanceof BinaryLogicalOperation and
|
||||
nodeFrom.asExpr() = op.getAnOperand()
|
||||
)
|
||||
or
|
||||
nodeTo.(ParameterNodeImpl).getParameter() =
|
||||
any(NamedParameter p |
|
||||
p.(OptionalParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
|
||||
or
|
||||
p.(KeywordParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,12 +287,6 @@ private module Cached {
|
||||
newtype TNode =
|
||||
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
|
||||
TReturningNode(CfgNodes::ReturningCfgNode n) or
|
||||
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
|
||||
exists(ReturningNode ret |
|
||||
ret.(NodeImpl).getCfgScope() = scope and
|
||||
ret.getKind() = kind
|
||||
)
|
||||
} or
|
||||
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
|
||||
TNormalParameterNode(Parameter p) {
|
||||
p instanceof SimpleParameter or
|
||||
@@ -306,30 +309,16 @@ private module Cached {
|
||||
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
|
||||
)
|
||||
} or
|
||||
TSummaryNode(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c,
|
||||
FlowSummaryImpl::Private::SummaryNodeState state
|
||||
) {
|
||||
FlowSummaryImpl::Private::summaryNodeRange(c, state)
|
||||
} or
|
||||
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
|
||||
} or
|
||||
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
|
||||
TSynthHashSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
|
||||
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
|
||||
or
|
||||
c.getAnArgument() instanceof CfgNodes::ExprNodes::PairCfgNode
|
||||
}
|
||||
|
||||
class TParameterNode =
|
||||
class TSourceParameterNode =
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
|
||||
TSynthHashSplatParameterNode or TSummaryParameterNode;
|
||||
|
||||
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
|
||||
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
or
|
||||
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
}
|
||||
TSynthHashSplatParameterNode;
|
||||
|
||||
cached
|
||||
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
|
||||
@@ -345,12 +334,6 @@ private module Cached {
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
or
|
||||
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
|
||||
or
|
||||
@@ -364,7 +347,8 @@ private module Cached {
|
||||
exprFrom = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
|
||||
nodeTo.(FlowSummaryNode).getSummaryNode(), true)
|
||||
}
|
||||
|
||||
/** This is the local flow predicate that is exposed. */
|
||||
@@ -372,10 +356,6 @@ private module Cached {
|
||||
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
@@ -385,19 +365,11 @@ private module Cached {
|
||||
|
||||
/**
|
||||
* This is the local flow predicate that is used in type tracking.
|
||||
*
|
||||
* This needs to exclude `localFlowSsaParamInput` due to a performance trick
|
||||
* in type tracking, where such steps are treated as call steps.
|
||||
*/
|
||||
cached
|
||||
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
exists(NamedParameter p |
|
||||
defaultValueFlow(p, nodeFrom) and
|
||||
nodeTo = LocalFlow::getParameterDefNode(p)
|
||||
)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// Flow into phi node from read
|
||||
@@ -433,18 +405,18 @@ private module Cached {
|
||||
|
||||
cached
|
||||
predicate isLocalSourceNode(Node n) {
|
||||
n instanceof TParameterNode
|
||||
n instanceof TSourceParameterNode
|
||||
or
|
||||
n instanceof SummaryParameterNode
|
||||
or
|
||||
// Expressions that can't be reached from another entry definition or expression
|
||||
n instanceof ExprNode and
|
||||
not reachedFromExprOrEntrySsaDef(n)
|
||||
or
|
||||
// Ensure all entry SSA definitions are local sources -- for parameters, this
|
||||
// is needed by type tracking
|
||||
entrySsaDefinition(n)
|
||||
or
|
||||
// Needed for flow out in type tracking
|
||||
n instanceof SynthReturnNode
|
||||
// Ensure all entry SSA definitions are local sources, except those that correspond
|
||||
// to parameters (which are themselves local sources)
|
||||
entrySsaDefinition(n) and
|
||||
not LocalFlow::localFlowSsaParamInput(_, n)
|
||||
or
|
||||
// Needed for stores in type tracking
|
||||
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
|
||||
@@ -506,7 +478,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate exprNodeReturnedFromCached(ExprNode e, Callable c) {
|
||||
exists(ReturningNode r |
|
||||
exists(ReturnNode r |
|
||||
nodeGetEnclosingCallable(r).asCallable() = c and
|
||||
(
|
||||
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
|
||||
@@ -537,11 +509,7 @@ predicate nodeIsHidden(Node n) {
|
||||
or
|
||||
isDesugarNode(n.(ExprNode).getExprNode().getExpr())
|
||||
or
|
||||
n instanceof SummaryNode
|
||||
or
|
||||
n instanceof SummaryParameterNode
|
||||
or
|
||||
n instanceof SynthReturnNode
|
||||
n instanceof FlowSummaryNode
|
||||
or
|
||||
n instanceof SynthHashSplatParameterNode
|
||||
or
|
||||
@@ -657,10 +625,10 @@ private module ParameterNodes {
|
||||
* The value of the `self` parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode {
|
||||
class SelfParameterNodeImpl extends ParameterNodeImpl, TSelfParameterNode {
|
||||
private MethodBase method;
|
||||
|
||||
SelfParameterNode() { this = TSelfParameterNode(method) }
|
||||
SelfParameterNodeImpl() { this = TSelfParameterNode(method) }
|
||||
|
||||
final MethodBase getMethod() { result = method }
|
||||
|
||||
@@ -782,47 +750,41 @@ private module ParameterNodes {
|
||||
}
|
||||
|
||||
/** A parameter for a library callable with a flow summary. */
|
||||
class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable sc;
|
||||
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
|
||||
private ParameterPosition pos_;
|
||||
|
||||
SummaryParameterNode() { this = TSummaryParameterNode(sc, pos_) }
|
||||
SummaryParameterNode() {
|
||||
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), pos_)
|
||||
}
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
sc = c.asLibraryCallable() and pos = pos_
|
||||
this.getSummarizedCallable() = c.asLibraryCallable() and pos = pos_
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { none() }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }
|
||||
|
||||
override EmptyLocation getLocationImpl() { any() }
|
||||
|
||||
override string toStringImpl() { result = "parameter " + pos_ + " of " + sc }
|
||||
}
|
||||
}
|
||||
|
||||
import ParameterNodes
|
||||
|
||||
/** A data-flow node used to model flow summaries. */
|
||||
class SummaryNode extends NodeImpl, TSummaryNode {
|
||||
FlowSummaryImpl::Public::SummarizedCallable c;
|
||||
FlowSummaryImpl::Private::SummaryNodeState state;
|
||||
|
||||
SummaryNode() { this = TSummaryNode(c, state) }
|
||||
class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
|
||||
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }
|
||||
|
||||
/** Gets the summarized callable that this node belongs to. */
|
||||
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = c }
|
||||
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
|
||||
result = this.getSummaryNode().getSummarizedCallable()
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { none() }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
override DataFlowCallable getEnclosingCallable() {
|
||||
result.asLibraryCallable() = this.getSummarizedCallable()
|
||||
}
|
||||
|
||||
override EmptyLocation getLocationImpl() { any() }
|
||||
|
||||
override string toStringImpl() { result = "[summary] " + state + " in " + c }
|
||||
override string toStringImpl() { result = this.getSummaryNode().toString() }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a call argument. */
|
||||
@@ -882,15 +844,20 @@ private module ArgumentNodes {
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryArgumentNode extends SummaryNode, ArgumentNode {
|
||||
SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) }
|
||||
private class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
|
||||
private DataFlowCall call_;
|
||||
private ArgumentPosition pos_;
|
||||
|
||||
SummaryArgumentNode() {
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(call_, this.getSummaryNode(), pos_)
|
||||
}
|
||||
|
||||
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
|
||||
none()
|
||||
}
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
|
||||
call = call_ and pos = pos_
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,22 +903,30 @@ private class NewCall extends DataFlowCall {
|
||||
NewCall() { this.asCall().getExpr().(MethodCall).getMethodName() = "new" }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value syntactically returned by a callable. */
|
||||
abstract class ReturningNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value returned by a callable. */
|
||||
abstract class ReturnNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value returned by a callable. */
|
||||
abstract class SourceReturnNode extends ReturnNode {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKindSource(); // only exists to avoid spurious negative recursion
|
||||
|
||||
final override ReturnKind getKind() { result = this.getKindSource() }
|
||||
|
||||
pragma[nomagic]
|
||||
predicate hasKind(ReturnKind kind, CfgScope scope) {
|
||||
kind = this.getKindSource() and
|
||||
scope = this.(NodeImpl).getCfgScope()
|
||||
}
|
||||
}
|
||||
|
||||
private module ReturnNodes {
|
||||
private predicate isValid(CfgNodes::ReturningCfgNode node) {
|
||||
exists(ReturningStmt stmt, Callable scope |
|
||||
stmt = node.getNode() and
|
||||
stmt = node.getAstNode() and
|
||||
scope = node.getScope()
|
||||
|
|
||||
stmt instanceof ReturnStmt and
|
||||
@@ -969,15 +944,15 @@ private module ReturnNodes {
|
||||
* A data-flow node that represents an expression explicitly returned by
|
||||
* a callable.
|
||||
*/
|
||||
class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
|
||||
class ExplicitReturnNode extends SourceReturnNode, ReturningStatementNode {
|
||||
ExplicitReturnNode() {
|
||||
isValid(n) and
|
||||
n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
|
||||
n.getScope() instanceof Callable
|
||||
}
|
||||
|
||||
override ReturnKind getKind() {
|
||||
if n.getNode() instanceof BreakStmt
|
||||
override ReturnKind getKindSource() {
|
||||
if n.getAstNode() instanceof BreakStmt
|
||||
then result instanceof BreakReturnKind
|
||||
else
|
||||
exists(CfgScope scope | scope = this.getCfgScope() |
|
||||
@@ -1005,10 +980,10 @@ private module ReturnNodes {
|
||||
* a callable. An implicit return happens when an expression can be the
|
||||
* last thing that is evaluated in the body of the callable.
|
||||
*/
|
||||
class ExprReturnNode extends ReturningNode, ExprNode {
|
||||
class ExprReturnNode extends SourceReturnNode, ExprNode {
|
||||
ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) }
|
||||
|
||||
override ReturnKind getKind() {
|
||||
override ReturnKind getKindSource() {
|
||||
exists(CfgScope scope | scope = this.(NodeImpl).getCfgScope() |
|
||||
if isUserDefinedNew(scope)
|
||||
then result instanceof NewReturnKind
|
||||
@@ -1033,7 +1008,7 @@ private module ReturnNodes {
|
||||
* the implicit `self` reference in `@x` will return data stored in the field
|
||||
* `x` out to the call `C.new`.
|
||||
*/
|
||||
class InitializeReturnNode extends ExprPostUpdateNode, ReturningNode {
|
||||
class InitializeReturnNode extends ExprPostUpdateNode, ReturnNode {
|
||||
InitializeReturnNode() {
|
||||
exists(Method initialize |
|
||||
this.getCfgScope() = initialize and
|
||||
@@ -1046,44 +1021,16 @@ private module ReturnNodes {
|
||||
override ReturnKind getKind() { result instanceof NewReturnKind }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic data-flow node for joining flow from different syntactic
|
||||
* returns into a single node.
|
||||
*
|
||||
* This node only exists to avoid computing the product of a large fan-in
|
||||
* with a large fan-out.
|
||||
*/
|
||||
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
|
||||
private CfgScope scope;
|
||||
private ReturnKind kind;
|
||||
|
||||
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
|
||||
|
||||
/** Gets a syntactic return node that flows into this synthetic node. */
|
||||
ReturningNode getAnInput() {
|
||||
result.(NodeImpl).getCfgScope() = scope and
|
||||
result.getKind() = kind
|
||||
}
|
||||
|
||||
override ReturnKind getKind() { result = kind }
|
||||
|
||||
override CfgScope getCfgScope() { result = scope }
|
||||
|
||||
override Location getLocationImpl() { result = scope.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "return " + kind + " in " + scope }
|
||||
}
|
||||
|
||||
private class SummaryReturnNode extends SummaryNode, ReturnNode {
|
||||
private class SummaryReturnNode extends FlowSummaryNode, ReturnNode {
|
||||
private ReturnKind rk;
|
||||
|
||||
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) }
|
||||
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this.getSummaryNode(), rk) }
|
||||
|
||||
override ReturnKind getKind() {
|
||||
result = rk
|
||||
or
|
||||
exists(NewCall new |
|
||||
TLibraryCallable(c) = viableLibraryCallable(new) and
|
||||
TLibraryCallable(this.getSummarizedCallable()) = viableLibraryCallable(new) and
|
||||
result instanceof NewReturnKind
|
||||
)
|
||||
}
|
||||
@@ -1116,12 +1063,15 @@ private module OutNodes {
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryOutNode extends SummaryNode, OutNode {
|
||||
SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) }
|
||||
private class SummaryOutNode extends FlowSummaryNode, OutNode {
|
||||
private DataFlowCall call;
|
||||
private ReturnKind kind_;
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) {
|
||||
FlowSummaryImpl::Private::summaryOutNode(result, this, kind)
|
||||
SummaryOutNode() {
|
||||
FlowSummaryImpl::Private::summaryOutNode(call, this.getSummaryNode(), kind_)
|
||||
}
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) { result = call and kind = kind_ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1136,7 +1086,8 @@ predicate jumpStep(Node pred, Node succ) {
|
||||
or
|
||||
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred, succ)
|
||||
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
|
||||
succ.(FlowSummaryNode).getSummaryNode())
|
||||
or
|
||||
any(AdditionalJumpStep s).step(pred, succ)
|
||||
}
|
||||
@@ -1201,7 +1152,8 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
))
|
||||
).getReceiver()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
||||
node2.(FlowSummaryNode).getSummaryNode())
|
||||
or
|
||||
storeStepCommon(node1, c, node2)
|
||||
}
|
||||
@@ -1235,7 +1187,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
or
|
||||
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
||||
node2.(FlowSummaryNode).getSummaryNode())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1244,7 +1197,7 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
* in `x.f = newValue`.
|
||||
*/
|
||||
predicate clearsContent(Node n, ContentSet c) {
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
||||
or
|
||||
// Filter out keyword arguments that are part of the method signature from
|
||||
// the hash-splat parameter
|
||||
@@ -1265,7 +1218,7 @@ predicate clearsContent(Node n, ContentSet c) {
|
||||
* at node `n`.
|
||||
*/
|
||||
predicate expectsContent(Node n, ContentSet c) {
|
||||
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n, c)
|
||||
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
|
||||
}
|
||||
|
||||
private newtype TDataFlowType =
|
||||
@@ -1276,8 +1229,10 @@ class DataFlowType extends TDataFlowType {
|
||||
string toString() { result = "" }
|
||||
}
|
||||
|
||||
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
|
||||
|
||||
/** Gets the type of `n` used for type pruning. */
|
||||
DataFlowType getNodeType(NodeImpl n) { result = TTodoDataFlowType() and exists(n) }
|
||||
DataFlowType getNodeType(Node n) { result = TTodoDataFlowType() and exists(n) }
|
||||
|
||||
/** Gets a string representation of a `DataFlowType`. */
|
||||
string ppReprType(DataFlowType t) { none() }
|
||||
@@ -1320,10 +1275,12 @@ private module PostUpdateNodes {
|
||||
override string toStringImpl() { result = "[post] " + e.toString() }
|
||||
}
|
||||
|
||||
private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNodeImpl {
|
||||
private Node pre;
|
||||
private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNodeImpl {
|
||||
private FlowSummaryNode pre;
|
||||
|
||||
SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) }
|
||||
SummaryPostUpdateNode() {
|
||||
FlowSummaryImpl::Private::summaryPostUpdateNode(this.getSummaryNode(), pre.getSummaryNode())
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
}
|
||||
@@ -1333,19 +1290,20 @@ private import PostUpdateNodes
|
||||
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends Node {
|
||||
CastNode() {
|
||||
// ensure that actual return nodes are included in the path graph
|
||||
this instanceof ReturningNode
|
||||
or
|
||||
// ensure that all variable assignments are included in the path graph
|
||||
this.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition
|
||||
}
|
||||
CastNode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` should never be skipped over in the `PathGraph` and in path
|
||||
* explanations.
|
||||
*/
|
||||
predicate neverSkipInPathGraph(Node n) {
|
||||
// ensure that all variable assignments are included in the path graph
|
||||
n.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition
|
||||
}
|
||||
|
||||
class DataFlowExpr = CfgNodes::ExprCfgNode;
|
||||
|
||||
int accessPathLimit() { result = 5 }
|
||||
|
||||
/**
|
||||
* Holds if access paths with `c` at their head always should be tracked at high
|
||||
* precision. This disables adaptive access path precision for such access paths.
|
||||
@@ -1373,11 +1331,20 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
|
||||
creation.asExpr() =
|
||||
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
|
||||
c.asCallable() = mc.getBlock().getExpr() and
|
||||
mc.getExpr().getMethodName() = ["lambda", "proc"]
|
||||
isProcCreationCall(mc.getExpr())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `call` is a call to `lambda`, `proc`, or `Proc.new` */
|
||||
pragma[nomagic]
|
||||
private predicate isProcCreationCall(MethodCall call) {
|
||||
call.getMethodName() = ["proc", "lambda"]
|
||||
or
|
||||
call.getMethodName() = "new" and
|
||||
call.getReceiver().(ConstantReadAccess).getAQualifiedName() = "Proc"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is a from-source lambda call of kind `kind` where `receiver`
|
||||
* is the lambda expression.
|
||||
@@ -1401,7 +1368,7 @@ predicate lambdaSourceCall(CfgNodes::ExprNodes::CallCfgNode call, LambdaCallKind
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
lambdaSourceCall(call.asCall(), kind, receiver)
|
||||
or
|
||||
receiver = call.(SummaryCall).getReceiver() and
|
||||
receiver.(FlowSummaryNode).getSummaryNode() = call.(SummaryCall).getReceiver() and
|
||||
if receiver.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pos | pos.isBlock()))
|
||||
then kind = TYieldCallKind()
|
||||
else kind = TLambdaCallKind()
|
||||
|
||||
@@ -6,12 +6,17 @@ private import codeql.ruby.typetracking.TypeTracker
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import SsaImpl as SsaImpl
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* An element, viewed as a node in a data flow graph. Either an expression
|
||||
* (`ExprNode`) or a parameter (`ParameterNode`).
|
||||
*/
|
||||
class Node extends TNode {
|
||||
/** Starts backtracking from this node using API graphs. */
|
||||
pragma[inline]
|
||||
API::Node backtrack() { result = API::Internal::getNodeForBacktracking(this) }
|
||||
|
||||
/** Gets the expression corresponding to this node, if any. */
|
||||
CfgNodes::ExprCfgNode asExpr() { result = this.(ExprNode).getExprNode() }
|
||||
|
||||
@@ -76,6 +81,11 @@ class Node extends TNode {
|
||||
result.asCallableAstNode() = c.asCallable()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the enclosing method, if any. */
|
||||
MethodNode getEnclosingMethod() {
|
||||
result.asCallableAstNode() = this.asExpr().getExpr().getEnclosingMethod()
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node corresponding to a call in the control-flow graph. */
|
||||
@@ -144,6 +154,18 @@ class CallNode extends LocalSourceNode, ExprNode {
|
||||
result.asExpr() = pair.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a potential target of this call, if any.
|
||||
*/
|
||||
final CallableNode getATarget() {
|
||||
result.asCallableAstNode() = this.asExpr().getExpr().(Call).getATarget()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is a `super` call.
|
||||
*/
|
||||
final predicate isSuperCall() { this.asExpr().getExpr() instanceof SuperCall }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,20 +213,36 @@ class ExprNode extends Node, TExprNode {
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class ParameterNode extends LocalSourceNode, TParameterNode instanceof ParameterNodeImpl {
|
||||
class ParameterNode extends LocalSourceNode instanceof ParameterNodeImpl {
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
final Parameter getParameter() { result = super.getParameter() }
|
||||
|
||||
/** Gets the callable that this parameter belongs to. */
|
||||
final Callable getCallable() { result = super.getCfgScope() }
|
||||
|
||||
/** Gets the name of the parameter, if any. */
|
||||
final string getName() { result = this.getParameter().(NamedParameter).getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of an implicit `self` parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl {
|
||||
/** Gets the underlying `self` variable. */
|
||||
final SelfVariable getSelfVariable() { result = super.getSelfVariable() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that is a source of local flow.
|
||||
*/
|
||||
class LocalSourceNode extends Node {
|
||||
LocalSourceNode() { isLocalSourceNode(this) }
|
||||
|
||||
/** Starts tracking this node forward using API graphs. */
|
||||
pragma[inline]
|
||||
API::Node track() { result = API::Internal::getNodeForForwardTracking(this) }
|
||||
|
||||
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
|
||||
pragma[inline]
|
||||
predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) }
|
||||
@@ -328,9 +366,6 @@ private module Cached {
|
||||
exists(Node mid | hasLocalSource(mid, source) |
|
||||
localFlowStepTypeTracker(mid, sink)
|
||||
or
|
||||
// Explicitly include the SSA param input step as type-tracking omits this step.
|
||||
LocalFlow::localFlowSsaParamInput(mid, sink)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
|
||||
)
|
||||
}
|
||||
@@ -350,6 +385,11 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate methodHasSuperCall(MethodNode method, CallNode call) {
|
||||
call.isSuperCall() and method = call.getEnclosingMethod()
|
||||
}
|
||||
|
||||
/**
|
||||
* A place in which a named constant can be looked up during constant lookup.
|
||||
*/
|
||||
@@ -378,6 +418,39 @@ private module Cached {
|
||||
result.asExpr().getExpr() = access
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a module for which `constRef` is the reference to an ancestor module.
|
||||
*
|
||||
* For example, `M` is the ancestry target of `C` in the following examples:
|
||||
* ```rb
|
||||
* class M < C {}
|
||||
*
|
||||
* module M
|
||||
* include C
|
||||
* end
|
||||
*
|
||||
* module M
|
||||
* prepend C
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private ModuleNode getAncestryTarget(ConstRef constRef) { result.getAnAncestorExpr() = constRef }
|
||||
|
||||
/**
|
||||
* Gets a scope in which a constant lookup may access the contents of the module referenced by `constRef`.
|
||||
*/
|
||||
cached
|
||||
TConstLookupScope getATargetScope(ConstRef constRef) {
|
||||
result = MkAncestorLookup(getAncestryTarget(constRef).getAnImmediateDescendent*())
|
||||
or
|
||||
constRef.asConstantAccess() = any(ConstantAccess ac).getScopeExpr() and
|
||||
result = MkQualifiedLookup(constRef.asConstantAccess())
|
||||
or
|
||||
result = MkNestedLookup(getAncestryTarget(constRef))
|
||||
or
|
||||
result = MkExactLookup(constRef.asConstantAccess().(Namespace).getModule())
|
||||
}
|
||||
|
||||
cached
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
|
||||
@@ -1019,6 +1092,33 @@ class ModuleNode instanceof Module {
|
||||
* this predicate.
|
||||
*/
|
||||
ModuleNode getNestedModule(string name) { result = super.getNestedModule(name) }
|
||||
|
||||
/**
|
||||
* Starts tracking the module object using API graphs.
|
||||
*
|
||||
* Concretely, this tracks forward from the following starting points:
|
||||
* - A constant access that resolves to this module.
|
||||
* - `self` in the module scope or in a singleton method of the module.
|
||||
* - A call to `self.class` in an instance method of this module or an ancestor module.
|
||||
*/
|
||||
bindingset[this]
|
||||
pragma[inline]
|
||||
API::Node trackModule() { result = API::Internal::getModuleNode(this) }
|
||||
|
||||
/**
|
||||
* Starts tracking instances of this module forward using API graphs.
|
||||
*
|
||||
* Concretely, this tracks forward from the following starting points:
|
||||
* - `self` in instance methods of this module and ancestor modules
|
||||
* - Calls to `new` on the module object
|
||||
*
|
||||
* Note that this includes references to `self` in ancestor modules, but not in descendent modules.
|
||||
* This is usually the desired behavior, particularly if this module was itself found using
|
||||
* a call to `getADescendentModule()`.
|
||||
*/
|
||||
bindingset[this]
|
||||
pragma[inline]
|
||||
API::Node trackInstance() { result = API::Internal::getModuleInstance(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1176,19 +1276,15 @@ class CallableNode extends StmtSequenceNode {
|
||||
result = this.getBlockParameter().getAMethodCall("call")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the canonical return node from this callable.
|
||||
*
|
||||
* Each callable has exactly one such node, and its location may not correspond
|
||||
* to any particular return site - consider using `getAReturningNode` to get nodes
|
||||
* whose locations correspond to return sites.
|
||||
*/
|
||||
Node getReturn() { result.(SynthReturnNode).getCfgScope() = callable }
|
||||
|
||||
/**
|
||||
* Gets a data flow node whose value is about to be returned by this callable.
|
||||
*/
|
||||
Node getAReturningNode() { result = this.getReturn().(SynthReturnNode).getAnInput() }
|
||||
Node getAReturnNode() { result.(ReturnNode).(NodeImpl).getCfgScope() = callable }
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `getAReturnNode` instead.
|
||||
*/
|
||||
deprecated Node getAReturningNode() { result = this.getAReturnNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1211,6 +1307,9 @@ class MethodNode extends CallableNode {
|
||||
|
||||
/** Holds if this method is protected. */
|
||||
predicate isProtected() { this.asCallableAstNode().isProtected() }
|
||||
|
||||
/** Gets a `super` call in this method. */
|
||||
CallNode getASuperCall() { methodHasSuperCall(this, result) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1279,13 +1378,16 @@ class HashLiteralNode extends LocalSourceNode, ExprNode {
|
||||
* into calls to `Array.[]`, so this includes both desugared calls as well as
|
||||
* explicit calls.
|
||||
*/
|
||||
class ArrayLiteralNode extends LocalSourceNode, ExprNode {
|
||||
class ArrayLiteralNode extends LocalSourceNode, CallNode {
|
||||
ArrayLiteralNode() { super.getExprNode() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode }
|
||||
|
||||
/**
|
||||
* Gets an element of the array.
|
||||
*/
|
||||
Node getAnElement() { result = this.(CallNode).getPositionalArgument(_) }
|
||||
Node getAnElement() { result = this.getElement(_) }
|
||||
|
||||
/** Gets the `i`th element of the array. */
|
||||
Node getElement(int i) { result = this.getPositionalArgument(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1326,24 +1428,6 @@ class ConstRef extends LocalSourceNode {
|
||||
not exists(access.getScopeExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a module for which this constant is the reference to an ancestor module.
|
||||
*
|
||||
* For example, `M` is the ancestry target of `C` in the following examples:
|
||||
* ```rb
|
||||
* class M < C {}
|
||||
*
|
||||
* module M
|
||||
* include C
|
||||
* end
|
||||
*
|
||||
* module M
|
||||
* prepend C
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private ModuleNode getAncestryTarget() { result.getAnAncestorExpr() = this }
|
||||
|
||||
/**
|
||||
* Gets the known target module.
|
||||
*
|
||||
@@ -1351,22 +1435,6 @@ class ConstRef extends LocalSourceNode {
|
||||
*/
|
||||
private Module getExactTarget() { result.getAnImmediateReference() = access }
|
||||
|
||||
/**
|
||||
* Gets a scope in which a constant lookup may access the contents of the module referenced by this constant.
|
||||
*/
|
||||
cached
|
||||
private TConstLookupScope getATargetScope() {
|
||||
forceCachingInSameStage() and
|
||||
result = MkAncestorLookup(this.getAncestryTarget().getAnImmediateDescendent*())
|
||||
or
|
||||
access = any(ConstantAccess ac).getScopeExpr() and
|
||||
result = MkQualifiedLookup(access)
|
||||
or
|
||||
result = MkNestedLookup(this.getAncestryTarget())
|
||||
or
|
||||
result = MkExactLookup(access.(Namespace).getModule())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scope expression, or the immediately enclosing `Namespace` (skipping over singleton classes).
|
||||
*
|
||||
@@ -1428,7 +1496,7 @@ class ConstRef extends LocalSourceNode {
|
||||
pragma[inline]
|
||||
ConstRef getConstant(string name) {
|
||||
exists(TConstLookupScope scope |
|
||||
pragma[only_bind_into](scope) = pragma[only_bind_out](this).getATargetScope() and
|
||||
pragma[only_bind_into](scope) = getATargetScope(pragma[only_bind_out](this)) and
|
||||
result.accesses(pragma[only_bind_out](scope), name)
|
||||
)
|
||||
}
|
||||
@@ -1450,7 +1518,9 @@ class ConstRef extends LocalSourceNode {
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
ModuleNode getADescendentModule() { MkAncestorLookup(result) = this.getATargetScope() }
|
||||
bindingset[this]
|
||||
pragma[inline_late]
|
||||
ModuleNode getADescendentModule() { MkAncestorLookup(result) = getATargetScope(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,29 +23,30 @@ module Public {
|
||||
* content type, or a return kind.
|
||||
*/
|
||||
class SummaryComponent extends TSummaryComponent {
|
||||
/** Gets a textual representation of this summary component. */
|
||||
string toString() {
|
||||
exists(ContentSet c | this = TContentSummaryComponent(c) and result = c.toString())
|
||||
or
|
||||
exists(ContentSet c | this = TWithoutContentSummaryComponent(c) and result = "without " + c)
|
||||
or
|
||||
exists(ContentSet c | this = TWithContentSummaryComponent(c) and result = "with " + c)
|
||||
/** Gets a textual representation of this component used for MaD models. */
|
||||
string getMadRepresentation() {
|
||||
result = getMadRepresentationSpecific(this)
|
||||
or
|
||||
exists(ArgumentPosition pos |
|
||||
this = TParameterSummaryComponent(pos) and result = "parameter " + pos
|
||||
this = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
this = TArgumentSummaryComponent(pos) and result = "argument " + pos
|
||||
this = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")")
|
||||
or
|
||||
exists(SummaryComponent::SyntheticGlobal sg |
|
||||
this = TSyntheticGlobalSummaryComponent(sg) and
|
||||
result = "synthetic global (" + sg + ")"
|
||||
exists(string synthetic |
|
||||
this = TSyntheticGlobalSummaryComponent(synthetic) and
|
||||
result = "SyntheticGlobal[" + synthetic + "]"
|
||||
)
|
||||
or
|
||||
this = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary component. */
|
||||
string toString() { result = this.getMadRepresentation() }
|
||||
}
|
||||
|
||||
/** Provides predicates for constructing summary components. */
|
||||
@@ -110,7 +111,6 @@ module Public {
|
||||
}
|
||||
|
||||
/** Gets the stack obtained by dropping the first `i` elements, if any. */
|
||||
pragma[assume_small_delta]
|
||||
SummaryComponentStack drop(int i) {
|
||||
i = 0 and result = this
|
||||
or
|
||||
@@ -125,19 +125,22 @@ module Public {
|
||||
this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom()
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this stack. */
|
||||
string toString() {
|
||||
/** Gets a textual representation of this stack used for MaD models. */
|
||||
string getMadRepresentation() {
|
||||
exists(SummaryComponent head, SummaryComponentStack tail |
|
||||
head = this.head() and
|
||||
tail = this.tail() and
|
||||
result = tail + "." + head
|
||||
result = tail.getMadRepresentation() + "." + head.getMadRepresentation()
|
||||
)
|
||||
or
|
||||
exists(SummaryComponent c |
|
||||
this = TSingletonSummaryComponentStack(c) and
|
||||
result = c.toString()
|
||||
result = c.getMadRepresentation()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this stack. */
|
||||
string toString() { result = this.getMadRepresentation() }
|
||||
}
|
||||
|
||||
/** Provides predicates for constructing stacks of summary components. */
|
||||
@@ -166,44 +169,6 @@ module Public {
|
||||
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
|
||||
}
|
||||
|
||||
private predicate noComponentSpecific(SummaryComponent sc) {
|
||||
not exists(getComponentSpecific(sc))
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this component used for flow summaries. */
|
||||
private string getComponent(SummaryComponent sc) {
|
||||
result = getComponentSpecific(sc)
|
||||
or
|
||||
noComponentSpecific(sc) and
|
||||
(
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this stack used for flow summaries. */
|
||||
string getComponentStack(SummaryComponentStack stack) {
|
||||
exists(SummaryComponent head, SummaryComponentStack tail |
|
||||
head = stack.head() and
|
||||
tail = stack.tail() and
|
||||
result = getComponentStack(tail) + "." + getComponent(head)
|
||||
)
|
||||
or
|
||||
exists(SummaryComponent c |
|
||||
stack = TSingletonSummaryComponentStack(c) and
|
||||
result = getComponent(c)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that exists for QL technical reasons only (the IPA type used
|
||||
* to represent component stacks needs to be bounded).
|
||||
@@ -512,6 +477,9 @@ module Private {
|
||||
or
|
||||
// Add the post-update node corresponding to the requested argument node
|
||||
outputState(c, s) and isCallbackParameter(s)
|
||||
or
|
||||
// Add the parameter node for parameter side-effects
|
||||
outputState(c, s) and s = SummaryComponentStack::argument(_)
|
||||
}
|
||||
|
||||
private newtype TSummaryNodeState =
|
||||
@@ -537,7 +505,7 @@ module Private {
|
||||
* this state represents that the components in `s` _remain to be written_ to
|
||||
* the output.
|
||||
*/
|
||||
class SummaryNodeState extends TSummaryNodeState {
|
||||
private class SummaryNodeState extends TSummaryNodeState {
|
||||
/** Holds if this state is a valid input state for `c`. */
|
||||
pragma[nomagic]
|
||||
predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
@@ -566,6 +534,42 @@ module Private {
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TSummaryNode =
|
||||
TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) {
|
||||
summaryNodeRange(c, state)
|
||||
} or
|
||||
TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) {
|
||||
summaryParameterNodeRange(c, pos)
|
||||
}
|
||||
|
||||
abstract class SummaryNode extends TSummaryNode {
|
||||
abstract string toString();
|
||||
|
||||
abstract SummarizedCallable getSummarizedCallable();
|
||||
}
|
||||
|
||||
private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode {
|
||||
private SummarizedCallable c;
|
||||
private SummaryNodeState state;
|
||||
|
||||
SummaryInternalNode() { this = TSummaryInternalNode(c, state) }
|
||||
|
||||
override string toString() { result = "[summary] " + state + " in " + c }
|
||||
|
||||
override SummarizedCallable getSummarizedCallable() { result = c }
|
||||
}
|
||||
|
||||
private class SummaryParamNode extends SummaryNode, TSummaryParameterNode {
|
||||
private SummarizedCallable c;
|
||||
private ParameterPosition pos;
|
||||
|
||||
SummaryParamNode() { this = TSummaryParameterNode(c, pos) }
|
||||
|
||||
override string toString() { result = "[summary param] " + pos + " in " + c }
|
||||
|
||||
override SummarizedCallable getSummarizedCallable() { result = c }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `state` represents having read from a parameter at position
|
||||
* `pos` in `c`. In this case we are not synthesizing a data-flow node,
|
||||
@@ -581,7 +585,7 @@ module Private {
|
||||
* Holds if a synthesized summary node is needed for the state `state` in summarized
|
||||
* callable `c`.
|
||||
*/
|
||||
predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
|
||||
private predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
|
||||
state.isInputState(c, _) and
|
||||
not parameterReadState(c, state, _)
|
||||
or
|
||||
@@ -589,22 +593,22 @@ module Private {
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
private SummaryNode summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
exists(SummaryNodeState state | state.isInputState(c, s) |
|
||||
result = summaryNode(c, state)
|
||||
result = TSummaryInternalNode(c, state)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
parameterReadState(c, state, pos) and
|
||||
result.(ParamNode).isParameterOf(inject(c), pos)
|
||||
result = TSummaryParameterNode(c, pos)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
private SummaryNode summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
exists(SummaryNodeState state |
|
||||
state.isOutputState(c, s) and
|
||||
result = summaryNode(c, state)
|
||||
result = TSummaryInternalNode(c, state)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -612,12 +616,14 @@ module Private {
|
||||
* Holds if a write targets `post`, which is a post-update node for a
|
||||
* parameter at position `pos` in `c`.
|
||||
*/
|
||||
private predicate isParameterPostUpdate(Node post, SummarizedCallable c, ParameterPosition pos) {
|
||||
private predicate isParameterPostUpdate(
|
||||
SummaryNode post, SummarizedCallable c, ParameterPosition pos
|
||||
) {
|
||||
post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos))
|
||||
}
|
||||
|
||||
/** Holds if a parameter node at position `pos` is required for `c`. */
|
||||
predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) {
|
||||
private predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) {
|
||||
parameterReadState(c, _, pos)
|
||||
or
|
||||
// Same as `isParameterPostUpdate(_, c, pos)`, but can be used in a negative context
|
||||
@@ -625,7 +631,7 @@ module Private {
|
||||
}
|
||||
|
||||
private predicate callbackOutput(
|
||||
SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
|
||||
SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ReturnKind rk
|
||||
) {
|
||||
any(SummaryNodeState state).isInputState(c, s) and
|
||||
s.head() = TReturnSummaryComponent(rk) and
|
||||
@@ -633,7 +639,7 @@ module Private {
|
||||
}
|
||||
|
||||
private predicate callbackInput(
|
||||
SummarizedCallable c, SummaryComponentStack s, Node receiver, ArgumentPosition pos
|
||||
SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ArgumentPosition pos
|
||||
) {
|
||||
any(SummaryNodeState state).isOutputState(c, s) and
|
||||
s.head() = TParameterSummaryComponent(pos) and
|
||||
@@ -641,7 +647,7 @@ module Private {
|
||||
}
|
||||
|
||||
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
|
||||
predicate summaryCallbackRange(SummarizedCallable c, Node receiver) {
|
||||
predicate summaryCallbackRange(SummarizedCallable c, SummaryNode receiver) {
|
||||
callbackOutput(c, _, receiver, _)
|
||||
or
|
||||
callbackInput(c, _, receiver, _)
|
||||
@@ -654,10 +660,10 @@ module Private {
|
||||
* `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and
|
||||
* `getCallbackReturnType()`.
|
||||
*/
|
||||
DataFlowType summaryNodeType(Node n) {
|
||||
exists(Node pre |
|
||||
DataFlowType summaryNodeType(SummaryNode n) {
|
||||
exists(SummaryNode pre |
|
||||
summaryPostUpdateNode(n, pre) and
|
||||
result = getNodeType(pre)
|
||||
result = summaryNodeType(pre)
|
||||
)
|
||||
or
|
||||
exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
|
||||
@@ -669,12 +675,12 @@ module Private {
|
||||
)
|
||||
or
|
||||
head = TWithoutContentSummaryComponent(_) and
|
||||
result = getNodeType(summaryNodeInputState(c, s.tail()))
|
||||
result = summaryNodeType(summaryNodeInputState(c, s.tail()))
|
||||
or
|
||||
exists(ReturnKind rk |
|
||||
head = TReturnSummaryComponent(rk) and
|
||||
result =
|
||||
getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
|
||||
getCallbackReturnType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c),
|
||||
s.tail())), rk)
|
||||
)
|
||||
or
|
||||
@@ -682,6 +688,11 @@ module Private {
|
||||
head = TSyntheticGlobalSummaryComponent(sg) and
|
||||
result = getSyntheticGlobalType(sg)
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
head = TArgumentSummaryComponent(pos) and
|
||||
result = getParameterType(c, pos)
|
||||
)
|
||||
)
|
||||
or
|
||||
n = summaryNodeOutputState(c, s) and
|
||||
@@ -698,7 +709,7 @@ module Private {
|
||||
or
|
||||
exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) |
|
||||
result =
|
||||
getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
|
||||
getCallbackParameterType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c),
|
||||
s.tail())), pos)
|
||||
)
|
||||
or
|
||||
@@ -710,9 +721,14 @@ module Private {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if summary node `p` is a parameter with position `pos`. */
|
||||
predicate summaryParameterNode(SummaryNode p, ParameterPosition pos) {
|
||||
p = TSummaryParameterNode(_, pos)
|
||||
}
|
||||
|
||||
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
|
||||
predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) {
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
|
||||
predicate summaryOutNode(DataFlowCall c, SummaryNode out, ReturnKind rk) {
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver |
|
||||
callbackOutput(callable, s, receiver, rk) and
|
||||
out = summaryNodeInputState(callable, s) and
|
||||
c = summaryDataFlowCall(receiver)
|
||||
@@ -720,8 +736,8 @@ module Private {
|
||||
}
|
||||
|
||||
/** Holds if summary node `arg` is at position `pos` in the call `c`. */
|
||||
predicate summaryArgumentNode(DataFlowCall c, Node arg, ArgumentPosition pos) {
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
|
||||
predicate summaryArgumentNode(DataFlowCall c, SummaryNode arg, ArgumentPosition pos) {
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver |
|
||||
callbackInput(callable, s, receiver, pos) and
|
||||
arg = summaryNodeOutputState(callable, s) and
|
||||
c = summaryDataFlowCall(receiver)
|
||||
@@ -729,10 +745,10 @@ module Private {
|
||||
}
|
||||
|
||||
/** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
|
||||
predicate summaryPostUpdateNode(Node post, Node pre) {
|
||||
predicate summaryPostUpdateNode(SummaryNode post, SummaryNode pre) {
|
||||
exists(SummarizedCallable c, ParameterPosition pos |
|
||||
isParameterPostUpdate(post, c, pos) and
|
||||
pre.(ParamNode).isParameterOf(inject(c), pos)
|
||||
pre = TSummaryParameterNode(c, pos)
|
||||
)
|
||||
or
|
||||
exists(SummarizedCallable callable, SummaryComponentStack s |
|
||||
@@ -743,7 +759,7 @@ module Private {
|
||||
}
|
||||
|
||||
/** Holds if summary node `ret` is a return node of kind `rk`. */
|
||||
predicate summaryReturnNode(Node ret, ReturnKind rk) {
|
||||
predicate summaryReturnNode(SummaryNode ret, ReturnKind rk) {
|
||||
exists(SummaryComponentStack s |
|
||||
ret = summaryNodeOutputState(_, s) and
|
||||
s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk))
|
||||
@@ -755,7 +771,9 @@ module Private {
|
||||
* node, and back out to `p`.
|
||||
*/
|
||||
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
|
||||
exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(inject(c), ppos) |
|
||||
exists(SummarizedCallable c, ParameterPosition ppos |
|
||||
p.isParameterOf(inject(c), pragma[only_bind_into](ppos))
|
||||
|
|
||||
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
|
||||
summary(c, inputContents, outputContents, _) and
|
||||
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and
|
||||
@@ -770,7 +788,7 @@ module Private {
|
||||
* Holds if there is a local step from `pred` to `succ`, which is synthesized
|
||||
* from a flow summary.
|
||||
*/
|
||||
predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) {
|
||||
predicate summaryLocalStep(SummaryNode pred, SummaryNode succ, boolean preservesValue) {
|
||||
exists(
|
||||
SummarizedCallable c, SummaryComponentStack inputContents,
|
||||
SummaryComponentStack outputContents
|
||||
@@ -796,7 +814,7 @@ module Private {
|
||||
* Holds if there is a read step of content `c` from `pred` to `succ`, which
|
||||
* is synthesized from a flow summary.
|
||||
*/
|
||||
predicate summaryReadStep(Node pred, ContentSet c, Node succ) {
|
||||
predicate summaryReadStep(SummaryNode pred, ContentSet c, SummaryNode succ) {
|
||||
exists(SummarizedCallable sc, SummaryComponentStack s |
|
||||
pred = summaryNodeInputState(sc, s.tail()) and
|
||||
succ = summaryNodeInputState(sc, s) and
|
||||
@@ -808,7 +826,7 @@ module Private {
|
||||
* Holds if there is a store step of content `c` from `pred` to `succ`, which
|
||||
* is synthesized from a flow summary.
|
||||
*/
|
||||
predicate summaryStoreStep(Node pred, ContentSet c, Node succ) {
|
||||
predicate summaryStoreStep(SummaryNode pred, ContentSet c, SummaryNode succ) {
|
||||
exists(SummarizedCallable sc, SummaryComponentStack s |
|
||||
pred = summaryNodeOutputState(sc, s) and
|
||||
succ = summaryNodeOutputState(sc, s.tail()) and
|
||||
@@ -820,7 +838,7 @@ module Private {
|
||||
* Holds if there is a jump step from `pred` to `succ`, which is synthesized
|
||||
* from a flow summary.
|
||||
*/
|
||||
predicate summaryJumpStep(Node pred, Node succ) {
|
||||
predicate summaryJumpStep(SummaryNode pred, SummaryNode succ) {
|
||||
exists(SummaryComponentStack s |
|
||||
s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and
|
||||
pred = summaryNodeOutputState(_, s) and
|
||||
@@ -847,9 +865,9 @@ module Private {
|
||||
* `a` on line 2 to the post-update node for `a` on that line (via an intermediate
|
||||
* node where field `b` is cleared).
|
||||
*/
|
||||
predicate summaryClearsContent(Node n, ContentSet c) {
|
||||
predicate summaryClearsContent(SummaryNode n, ContentSet c) {
|
||||
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
|
||||
n = summaryNode(sc, state) and
|
||||
n = TSummaryInternalNode(sc, state) and
|
||||
state.isInputState(sc, stack) and
|
||||
stack.head() = SummaryComponent::withoutContent(c)
|
||||
)
|
||||
@@ -859,9 +877,9 @@ module Private {
|
||||
* Holds if the value that is being tracked is expected to be stored inside
|
||||
* content `c` at `n`.
|
||||
*/
|
||||
predicate summaryExpectsContent(Node n, ContentSet c) {
|
||||
predicate summaryExpectsContent(SummaryNode n, ContentSet c) {
|
||||
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
|
||||
n = summaryNode(sc, state) and
|
||||
n = TSummaryInternalNode(sc, state) and
|
||||
state.isInputState(sc, stack) and
|
||||
stack.head() = SummaryComponent::withContent(c)
|
||||
)
|
||||
@@ -869,17 +887,17 @@ module Private {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate viableParam(
|
||||
DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, ParamNode p
|
||||
DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, SummaryParamNode p
|
||||
) {
|
||||
exists(DataFlowCallable c |
|
||||
c = inject(sc) and
|
||||
p.isParameterOf(c, ppos) and
|
||||
p = TSummaryParameterNode(sc, ppos) and
|
||||
c = viableCallable(call)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg, SummarizedCallable sc) {
|
||||
private SummaryParamNode summaryArgParam(DataFlowCall call, ArgNode arg, SummarizedCallable sc) {
|
||||
exists(ParameterPosition ppos |
|
||||
argumentPositionMatch(call, arg, ppos) and
|
||||
viableParam(call, sc, ppos, result)
|
||||
@@ -891,12 +909,12 @@ module Private {
|
||||
* local steps. `clearsOrExpects` records whether any node on the path from `p` to
|
||||
* `n` either clears or expects contents.
|
||||
*/
|
||||
private predicate paramReachesLocal(ParamNode p, Node n, boolean clearsOrExpects) {
|
||||
private predicate paramReachesLocal(SummaryParamNode p, SummaryNode n, boolean clearsOrExpects) {
|
||||
viableParam(_, _, _, p) and
|
||||
n = p and
|
||||
clearsOrExpects = false
|
||||
or
|
||||
exists(Node mid, boolean clearsOrExpectsMid |
|
||||
exists(SummaryNode mid, boolean clearsOrExpectsMid |
|
||||
paramReachesLocal(p, mid, clearsOrExpectsMid) and
|
||||
summaryLocalStep(mid, n, true) and
|
||||
if
|
||||
@@ -916,21 +934,33 @@ module Private {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) {
|
||||
exists(ParamNode p, ParameterPosition ppos, Node ret |
|
||||
exists(SummaryParamNode p, ParameterPosition ppos, SummaryNode ret |
|
||||
paramReachesLocal(p, ret, true) and
|
||||
p = summaryArgParam0(_, arg, sc) and
|
||||
p.isParameterOf(_, pragma[only_bind_into](ppos)) and
|
||||
p = summaryArgParam(_, arg, sc) and
|
||||
p = TSummaryParameterNode(_, pragma[only_bind_into](ppos)) and
|
||||
isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate summaryReturnNodeExt(SummaryNode ret, ReturnKindExt rk) {
|
||||
summaryReturnNode(ret, rk.(ValueReturnKind).getKind())
|
||||
or
|
||||
exists(SummaryParamNode p, SummaryNode pre, ParameterPosition pos |
|
||||
paramReachesLocal(p, pre, _) and
|
||||
summaryPostUpdateNode(ret, pre) and
|
||||
p = TSummaryParameterNode(_, pos) and
|
||||
rk.(ParamUpdateReturnKind).getPosition() = pos
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[ret]
|
||||
private ParamNode summaryArgParam(
|
||||
ArgNode arg, ReturnNodeExt ret, OutNodeExt out, SummarizedCallable sc
|
||||
private SummaryParamNode summaryArgParamRetOut(
|
||||
ArgNode arg, SummaryNode ret, OutNodeExt out, SummarizedCallable sc
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKindExt rk |
|
||||
result = summaryArgParam0(call, arg, sc) and
|
||||
ret.getKind() = pragma[only_bind_into](rk) and
|
||||
result = summaryArgParam(call, arg, sc) and
|
||||
summaryReturnNodeExt(ret, pragma[only_bind_into](rk)) and
|
||||
out = pragma[only_bind_into](rk).getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
@@ -943,9 +973,9 @@ module Private {
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) {
|
||||
exists(ReturnKind rk, ReturnNode ret, DataFlowCall call |
|
||||
summaryLocalStep(summaryArgParam0(call, arg, sc), ret, true) and
|
||||
ret.getKind() = pragma[only_bind_into](rk) and
|
||||
exists(ReturnKind rk, SummaryNode ret, DataFlowCall call |
|
||||
summaryLocalStep(summaryArgParam(call, arg, sc), ret, true) and
|
||||
summaryReturnNode(ret, pragma[only_bind_into](rk)) and
|
||||
out = getAnOutNode(call, pragma[only_bind_into](rk))
|
||||
)
|
||||
}
|
||||
@@ -958,7 +988,9 @@ module Private {
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) {
|
||||
exists(ReturnNodeExt ret | summaryLocalStep(summaryArgParam(arg, ret, out, sc), ret, false))
|
||||
exists(SummaryNode ret |
|
||||
summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), ret, false)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -969,8 +1001,8 @@ module Private {
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
|
||||
exists(Node mid, ReturnNodeExt ret |
|
||||
summaryReadStep(summaryArgParam(arg, ret, out, sc), c, mid) and
|
||||
exists(SummaryNode mid, SummaryNode ret |
|
||||
summaryReadStep(summaryArgParamRetOut(arg, ret, out, sc), c, mid) and
|
||||
summaryLocalStep(mid, ret, _)
|
||||
)
|
||||
}
|
||||
@@ -983,8 +1015,8 @@ module Private {
|
||||
* be useful to include in the exposed local data-flow/taint-tracking relations.
|
||||
*/
|
||||
predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
|
||||
exists(Node mid, ReturnNodeExt ret |
|
||||
summaryLocalStep(summaryArgParam(arg, ret, out, sc), mid, _) and
|
||||
exists(SummaryNode mid, SummaryNode ret |
|
||||
summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), mid, _) and
|
||||
summaryStoreStep(mid, c, ret)
|
||||
)
|
||||
}
|
||||
@@ -1317,8 +1349,8 @@ module Private {
|
||||
c.relevantSummary(input, output, preservesValue) and
|
||||
csv =
|
||||
c.getCallableCsv() // Callable information
|
||||
+ getComponentStack(input) + ";" // input
|
||||
+ getComponentStack(output) + ";" // output
|
||||
+ input.getMadRepresentation() + ";" // input
|
||||
+ output.getMadRepresentation() + ";" // output
|
||||
+ renderKind(preservesValue) + ";" // kind
|
||||
+ renderProvenance(c) // provenance
|
||||
)
|
||||
@@ -1351,11 +1383,11 @@ module Private {
|
||||
}
|
||||
|
||||
private newtype TNodeOrCall =
|
||||
MkNode(Node n) {
|
||||
MkNode(SummaryNode n) {
|
||||
exists(RelevantSummarizedCallable c |
|
||||
n = summaryNode(c, _)
|
||||
n = TSummaryInternalNode(c, _)
|
||||
or
|
||||
n.(ParamNode).isParameterOf(inject(c), _)
|
||||
n = TSummaryParameterNode(c, _)
|
||||
)
|
||||
} or
|
||||
MkCall(DataFlowCall call) {
|
||||
@@ -1364,7 +1396,7 @@ module Private {
|
||||
}
|
||||
|
||||
private class NodeOrCall extends TNodeOrCall {
|
||||
Node asNode() { this = MkNode(result) }
|
||||
SummaryNode asNode() { this = MkNode(result) }
|
||||
|
||||
DataFlowCall asCall() { this = MkCall(result) }
|
||||
|
||||
@@ -1384,9 +1416,11 @@ module Private {
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.asNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
or
|
||||
this.asCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
filepath = "" and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,15 +18,15 @@ DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
|
||||
/** Gets the parameter position representing a callback itself, if any. */
|
||||
ArgumentPosition callbackSelfParameterPosition() { none() } // disables implicit summary flow to `self` for callbacks
|
||||
|
||||
/** Gets the synthesized summary data-flow node for the given values. */
|
||||
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = TSummaryNode(c, state) }
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
SummaryCall summaryDataFlowCall(Node receiver) { receiver = result.getReceiver() }
|
||||
SummaryCall summaryDataFlowCall(SummaryNode receiver) { receiver = result.getReceiver() }
|
||||
|
||||
/** Gets the type of content `c`. */
|
||||
DataFlowType getContentType(ContentSet c) { any() }
|
||||
|
||||
/** Gets the type of the parameter at the given position. */
|
||||
DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) { any() }
|
||||
|
||||
/** Gets the return type of kind `rk` for callable `c`. */
|
||||
bindingset[c, rk]
|
||||
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() }
|
||||
@@ -139,8 +139,53 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a summary component in the format used for flow summaries. */
|
||||
string getComponentSpecific(SummaryComponent sc) { none() }
|
||||
private string getContentSpecific(Content c) {
|
||||
exists(string name | c = TFieldContent(name) and result = "Field[" + name + "]")
|
||||
or
|
||||
exists(ConstantValue cv |
|
||||
c = TKnownElementContent(cv) and result = "Element[" + cv.serialize() + "!]"
|
||||
)
|
||||
or
|
||||
c = TUnknownElementContent() and result = "Element[?]"
|
||||
}
|
||||
|
||||
private string getContentSetSpecific(ContentSet cs) {
|
||||
exists(Content c | cs = TSingletonContent(c) and result = getContentSpecific(c))
|
||||
or
|
||||
cs = TAnyElementContent() and result = "Element[any]"
|
||||
or
|
||||
exists(Content::KnownElementContent kec |
|
||||
cs = TKnownOrUnknownElementContent(kec) and
|
||||
result = "Element[" + kec.getIndex().serialize() + "]"
|
||||
)
|
||||
or
|
||||
exists(int lower, boolean includeUnknown, string unknown |
|
||||
cs = TElementLowerBoundContent(lower, includeUnknown) and
|
||||
(if includeUnknown = true then unknown = "" else unknown = "!") and
|
||||
result = "Element[" + lower + ".." + unknown + "]"
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a summary component in the format used for MaD models. */
|
||||
string getMadRepresentationSpecific(SummaryComponent sc) {
|
||||
exists(ContentSet cs | sc = TContentSummaryComponent(cs) and result = getContentSetSpecific(cs))
|
||||
or
|
||||
exists(ContentSet cs |
|
||||
sc = TWithoutContentSummaryComponent(cs) and
|
||||
result = "WithoutElement[" + getContentSetSpecific(cs) + "]"
|
||||
)
|
||||
or
|
||||
exists(ContentSet cs |
|
||||
sc = TWithContentSummaryComponent(cs) and
|
||||
result = "WithElement[" + getContentSetSpecific(cs) + "]"
|
||||
)
|
||||
or
|
||||
exists(ReturnKind rk |
|
||||
sc = TReturnSummaryComponent(rk) and
|
||||
not rk = getReturnValueKind() and
|
||||
result = "ReturnValue[" + rk + "]"
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a parameter position in the format used for flow summaries. */
|
||||
string getParameterPosition(ParameterPosition pos) {
|
||||
@@ -170,6 +215,9 @@ string getParameterPosition(ParameterPosition pos) {
|
||||
or
|
||||
pos.isAnyNamed() and
|
||||
result = "any-named"
|
||||
or
|
||||
pos.isHashSplat() and
|
||||
result = "hash-splat"
|
||||
}
|
||||
|
||||
/** Gets the textual representation of an argument position in the format used for flow summaries. */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
private import codeql.ssa.Ssa as SsaImplCommon
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG as Cfg
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared as ControlFlowGraphImplShared
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as ControlFlowGraphImpl
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.ruby.ast.Variable
|
||||
private import Cfg::CfgNodes::ExprNodes
|
||||
@@ -31,7 +31,7 @@ private module SsaInput implements SsaImplCommon::InputSig {
|
||||
i = 0
|
||||
or
|
||||
// ...or a class or module block.
|
||||
bb.getNode(i).getNode() = scope.(ModuleBase).getAControlFlowEntryNode() and
|
||||
bb.getNode(i).getAstNode() = scope.(ModuleBase).getAControlFlowEntryNode() and
|
||||
not scope instanceof Toplevel // handled by case above
|
||||
)
|
||||
or
|
||||
@@ -124,10 +124,10 @@ private predicate capturedExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, Local
|
||||
private predicate namespaceSelfExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, SelfVariable v) {
|
||||
exists(Namespace ns, AstNode last |
|
||||
v.getDeclaringScope() = ns and
|
||||
last = ControlFlowGraphImplShared::getAControlFlowExitNode(ns) and
|
||||
last = ControlFlowGraphImpl::getAControlFlowExitNode(ns) and
|
||||
if last = ns
|
||||
then bb.getNode(i).getAPredecessor().getNode() = last
|
||||
else bb.getNode(i).getNode() = last
|
||||
then bb.getNode(i).getAPredecessor().getAstNode() = last
|
||||
else bb.getNode(i).getAstNode() = last
|
||||
)
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ private predicate capturedCallRead(CallCfgNode call, Cfg::BasicBlock bb, int i,
|
||||
private predicate variableReadActual(Cfg::BasicBlock bb, int i, LocalVariable v) {
|
||||
exists(VariableReadAccess read |
|
||||
read.getVariable() = v and
|
||||
read = bb.getNode(i).getNode()
|
||||
read = bb.getNode(i).getAstNode()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,8 @@ private module Cached {
|
||||
)
|
||||
)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
|
||||
nodeTo.(FlowSummaryNode).getSummaryNode(), false)
|
||||
or
|
||||
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo)
|
||||
or
|
||||
@@ -112,6 +113,13 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate summaryThroughStepTaint(
|
||||
DataFlow::Node arg, DataFlow::Node out, FlowSummaryImpl::Public::SummarizedCallable sc
|
||||
) {
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(arg, out, sc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
|
||||
* (intra-procedural) step.
|
||||
@@ -122,7 +130,7 @@ private module Cached {
|
||||
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(nodeFrom, nodeTo, _)
|
||||
summaryThroughStepTaint(nodeFrom, nodeTo, _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Unicode transformation"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "Unicode transformation"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module UnicodeBypassValidation {
|
||||
/**
|
||||
* A data flow source for "Unicode transformation" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for "Unicode transformation" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "Unicode transformation" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.ApiGraphs
|
||||
import UnicodeBypassValidationCustomizations::UnicodeBypassValidation
|
||||
|
||||
/** A state signifying that a logical validation has not been performed. */
|
||||
class PreValidation extends DataFlow::FlowState {
|
||||
PreValidation() { this = "PreValidation" }
|
||||
}
|
||||
|
||||
/** A state signifying that a logical validation has been performed. */
|
||||
class PostValidation extends DataFlow::FlowState {
|
||||
PostValidation() { this = "PostValidation" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
|
||||
*
|
||||
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
|
||||
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnicodeBypassValidation" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
|
||||
source instanceof RemoteFlowSource and state instanceof PreValidation
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(
|
||||
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
|
||||
DataFlow::FlowState stateTo
|
||||
) {
|
||||
(
|
||||
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
|
||||
or
|
||||
exists(RegexExecution re | nodeFrom = re.getString() and nodeTo = re)
|
||||
or
|
||||
// String Manipulation Method Calls
|
||||
// https://ruby-doc.org/core-2.7.0/String.html
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() =
|
||||
[
|
||||
[
|
||||
"ljust", "lstrip", "succ", "next", "rjust", "capitalize", "chomp", "gsub", "chop",
|
||||
"downcase", "swapcase", "uprcase", "scrub", "slice", "squeeze", "strip", "sub",
|
||||
"tr", "tr_s", "reverse"
|
||||
] + ["", "!"], "concat", "dump", "each_line", "replace", "insert", "inspect", "lines",
|
||||
"partition", "prepend", "replace", "rpartition", "scan", "split", "undump",
|
||||
"unpack" + ["", "1"]
|
||||
] and
|
||||
nodeFrom = cn.getReceiver() and
|
||||
nodeTo = cn
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() =
|
||||
[
|
||||
"casecmp" + ["", "?"], "center", "count", "each_char", "index", "rindex", "sum",
|
||||
["delete", "delete_prefix", "delete_suffix"] + ["", "!"],
|
||||
["start_with", "end_with" + "eql", "include"] + ["?", "!"], "match" + ["", "?"],
|
||||
] and
|
||||
nodeFrom = cn.getReceiver() and
|
||||
nodeTo = nodeFrom
|
||||
)
|
||||
or
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn = API::getTopLevelMember("CGI").getAMethodCall("escapeHTML") and
|
||||
nodeFrom = cn.getArgument(0) and
|
||||
nodeTo = cn
|
||||
)
|
||||
) and
|
||||
stateFrom instanceof PreValidation and
|
||||
stateTo instanceof PostValidation
|
||||
}
|
||||
|
||||
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
|
||||
(
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn.getMethodName() = "unicode_normalize" and
|
||||
cn.getArgument(0).getConstantValue().getSymbol() = ["nfkc", "nfc", "nfkd", "nfd"] and
|
||||
sink = cn.getReceiver()
|
||||
)
|
||||
or
|
||||
// unicode_utils
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("UnicodeUtils").getMethod(["nfkd", "nfc", "nfd", "nfkc"]) and
|
||||
sink = mac.getArgument(0).asSink()
|
||||
)
|
||||
or
|
||||
// eprun
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("Eprun").getMethod("normalize") and
|
||||
sink = mac.getArgument(0).asSink()
|
||||
)
|
||||
or
|
||||
// unf
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("UNF").getMember("Normalizer").getMethod("normalize") and
|
||||
sink = mac.getArgument(0).asSink()
|
||||
)
|
||||
or
|
||||
// ActiveSupport::Multibyte::Chars
|
||||
exists(DataFlow::CallNode cn, DataFlow::CallNode n |
|
||||
cn =
|
||||
API::getTopLevelMember("ActiveSupport")
|
||||
.getMember("Multibyte")
|
||||
.getMember("Chars")
|
||||
.getMethod("new")
|
||||
.asCall() and
|
||||
n = cn.getAMethodCall("normalize") and
|
||||
sink = cn.getArgument(0)
|
||||
)
|
||||
) and
|
||||
state instanceof PostValidation
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ module ZipSlip {
|
||||
// If argument refers to a string object, then it's a hardcoded path and
|
||||
// this file is safe.
|
||||
not zipOpen
|
||||
.getCallNode()
|
||||
.asCall()
|
||||
.getArgument(0)
|
||||
.getALocalSource()
|
||||
.getConstantValue()
|
||||
|
||||
@@ -83,8 +83,8 @@ class ActionControllerClass extends DataFlow::ClassNode {
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode actionControllerInstance() {
|
||||
result = any(ActionControllerClass cls).getSelf()
|
||||
private API::Node actionControllerInstance() {
|
||||
result = any(ActionControllerClass cls).getSelf().track()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,149 +204,6 @@ private class ActionControllerParamsCall extends ParamsCallImpl {
|
||||
}
|
||||
}
|
||||
|
||||
/** Modeling for `ActionDispatch::Request`. */
|
||||
private module Request {
|
||||
/**
|
||||
* A call to `request` from within a controller. This is an instance of
|
||||
* `ActionDispatch::Request`.
|
||||
*/
|
||||
private class RequestNode extends DataFlow::CallNode {
|
||||
RequestNode() { this = actionControllerInstance().getAMethodCall("request") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request`.
|
||||
*/
|
||||
private class RequestMethodCall extends DataFlow::CallNode {
|
||||
RequestMethodCall() {
|
||||
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class RequestInputAccess extends RequestMethodCall,
|
||||
Http::Server::RequestInputAccess::Range
|
||||
{
|
||||
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns request parameters.
|
||||
*/
|
||||
private class ParametersCall extends RequestInputAccess {
|
||||
ParametersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
|
||||
"filtered_parameters"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns part or all of the request path. */
|
||||
private class PathCall extends RequestInputAccess {
|
||||
PathCall() {
|
||||
this.getMethodName() =
|
||||
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns a specific request header. */
|
||||
private class HeadersCall extends RequestInputAccess {
|
||||
HeadersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
|
||||
"host_authority", "content_type", "host", "hostname", "accept_encoding",
|
||||
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
|
||||
]
|
||||
or
|
||||
// Request headers are prefixed with `HTTP_` to distinguish them from
|
||||
// "headers" supplied by Rack middleware.
|
||||
this.getMethodName() = ["get_header", "fetch_header"] and
|
||||
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
// TODO: each_header
|
||||
/**
|
||||
* A method call on `request` which returns part or all of the host.
|
||||
* This can be influenced by headers such as Host and X-Forwarded-Host.
|
||||
*/
|
||||
private class HostCall extends RequestInputAccess {
|
||||
HostCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
|
||||
"forwarded_host", "port", "forwarded_port"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which is influenced by one or more request
|
||||
* headers.
|
||||
*/
|
||||
private class HeaderTaintedCall extends RequestInputAccess {
|
||||
HeaderTaintedCall() {
|
||||
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns the request body. */
|
||||
private class BodyCall extends RequestInputAccess {
|
||||
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
|
||||
}
|
||||
|
||||
private module Env {
|
||||
abstract private class Env extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns the rack env.
|
||||
* This is a hash containing all the information about the request. Values
|
||||
* under keys starting with `HTTP_` are user-controlled.
|
||||
*/
|
||||
private class RequestEnvCall extends DataFlow::CallNode, Env {
|
||||
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
|
||||
}
|
||||
|
||||
private import codeql.ruby.frameworks.Rack
|
||||
|
||||
private class RackEnv extends Env {
|
||||
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A read of a user-controlled parameter from the request env.
|
||||
*/
|
||||
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
|
||||
EnvHttpAccess() {
|
||||
this = any(Env c).getAMethodCall("[]") and
|
||||
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
|
||||
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
|
||||
)
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
|
||||
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `render` from within a controller. */
|
||||
private class ActionControllerRenderCall extends RenderCallImpl {
|
||||
ActionControllerRenderCall() {
|
||||
@@ -365,19 +222,19 @@ private class ActionControllerRenderToCall extends RenderToCallImpl {
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::CallNode renderCall() {
|
||||
// ActionController#render is an alias for ActionController::Renderer#render
|
||||
result =
|
||||
[
|
||||
any(ActionControllerClass c).trackModule().getAMethodCall("render"),
|
||||
any(ActionControllerClass c).trackModule().getReturn("renderer").getAMethodCall("render")
|
||||
]
|
||||
}
|
||||
|
||||
/** A call to `ActionController::Renderer#render`. */
|
||||
private class RendererRenderCall extends RenderCallImpl {
|
||||
RendererRenderCall() {
|
||||
this =
|
||||
[
|
||||
// ActionController#render is an alias for ActionController::Renderer#render
|
||||
any(ActionControllerClass c).getAnImmediateReference().getAMethodCall("render"),
|
||||
any(ActionControllerClass c)
|
||||
.getAnImmediateReference()
|
||||
.getAMethodCall("renderer")
|
||||
.getAMethodCall("render")
|
||||
].asExpr().getExpr()
|
||||
}
|
||||
RendererRenderCall() { this = renderCall().asExpr().getExpr() }
|
||||
}
|
||||
|
||||
/** A call to `html_escape` from within a controller. */
|
||||
@@ -403,6 +260,7 @@ class RedirectToCall extends MethodCall {
|
||||
this =
|
||||
controller
|
||||
.getSelf()
|
||||
.track()
|
||||
.getAMethodCall(["redirect_to", "redirect_back", "redirect_back_or_to"])
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
@@ -743,9 +601,7 @@ private module ParamsSummaries {
|
||||
* response.
|
||||
*/
|
||||
private module Response {
|
||||
DataFlow::LocalSourceNode response() {
|
||||
result = actionControllerInstance().getAMethodCall("response")
|
||||
}
|
||||
API::Node response() { result = actionControllerInstance().getReturn("response") }
|
||||
|
||||
class BodyWrite extends DataFlow::CallNode, Http::Server::HttpResponse::Range {
|
||||
BodyWrite() { this = response().getAMethodCall("body=") }
|
||||
@@ -771,7 +627,7 @@ private module Response {
|
||||
HeaderWrite() {
|
||||
// response.header[key] = val
|
||||
// response.headers[key] = val
|
||||
this = response().getAMethodCall(["header", "headers"]).getAMethodCall("[]=")
|
||||
this = response().getReturn(["header", "headers"]).getAMethodCall("[]=")
|
||||
or
|
||||
// response.set_header(key) = val
|
||||
// response[header] = val
|
||||
@@ -816,18 +672,12 @@ private module Response {
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionControllerLoggerInstance extends DataFlow::Node {
|
||||
ActionControllerLoggerInstance() {
|
||||
this = actionControllerInstance().getAMethodCall("logger")
|
||||
or
|
||||
any(ActionControllerLoggerInstance i).(DataFlow::LocalSourceNode).flowsTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionControllerLoggingCall extends DataFlow::CallNode, Logging::Range {
|
||||
ActionControllerLoggingCall() {
|
||||
this.getReceiver() instanceof ActionControllerLoggerInstance and
|
||||
this.getMethodName() = ["debug", "error", "fatal", "info", "unknown", "warn"]
|
||||
this =
|
||||
actionControllerInstance()
|
||||
.getReturn("logger")
|
||||
.getAMethodCall(["debug", "error", "fatal", "info", "unknown", "warn"])
|
||||
}
|
||||
|
||||
// Note: this is identical to the definition `stdlib.Logger.LoggerInfoStyleCall`.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,8 +27,8 @@ module ActionMailbox {
|
||||
Mail() {
|
||||
this =
|
||||
[
|
||||
controller().getAnInstanceSelf().getAMethodCall("inbound_email").getAMethodCall("mail"),
|
||||
controller().getAnInstanceSelf().getAMethodCall("mail")
|
||||
controller().trackInstance().getReturn("inbound_email").getAMethodCall("mail"),
|
||||
controller().trackInstance().getAMethodCall("mail")
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ module ActionMailbox {
|
||||
RemoteContent() {
|
||||
this =
|
||||
any(Mail m)
|
||||
.(DataFlow::LocalSourceNode)
|
||||
.track()
|
||||
.getAMethodCall([
|
||||
"body", "to", "from", "raw_source", "subject", "from_address",
|
||||
"recipients_addresses", "cc_addresses", "bcc_addresses", "in_reply_to",
|
||||
|
||||
@@ -4,12 +4,26 @@
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.frameworks.internal.Rails
|
||||
|
||||
/**
|
||||
* Provides modeling for the `ActionMailer` library.
|
||||
*/
|
||||
module ActionMailer {
|
||||
private DataFlow::ClassNode actionMailerClass() {
|
||||
result =
|
||||
[
|
||||
DataFlow::getConstant("ActionMailer").getConstant("Base"),
|
||||
// In Rails applications `ApplicationMailer` typically extends
|
||||
// `ActionMailer::Base`, but we treat it separately in case the
|
||||
// `ApplicationMailer` definition is not in the database.
|
||||
DataFlow::getConstant("ApplicationMailer")
|
||||
].getADescendentModule()
|
||||
}
|
||||
|
||||
private API::Node actionMailerInstance() { result = actionMailerClass().trackInstance() }
|
||||
|
||||
/**
|
||||
* A `ClassDeclaration` for a class that extends `ActionMailer::Base`.
|
||||
* For example,
|
||||
@@ -21,33 +35,11 @@ module ActionMailer {
|
||||
* ```
|
||||
*/
|
||||
class MailerClass extends ClassDeclaration {
|
||||
MailerClass() {
|
||||
this.getSuperclassExpr() =
|
||||
[
|
||||
API::getTopLevelMember("ActionMailer").getMember("Base"),
|
||||
// In Rails applications `ApplicationMailer` typically extends
|
||||
// `ActionMailer::Base`, but we treat it separately in case the
|
||||
// `ApplicationMailer` definition is not in the database.
|
||||
API::getTopLevelMember("ApplicationMailer")
|
||||
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method call with a `self` receiver from within a mailer class */
|
||||
private class ContextCall extends MethodCall {
|
||||
private MailerClass mailerClass;
|
||||
|
||||
ContextCall() {
|
||||
this.getReceiver() instanceof SelfVariableAccess and
|
||||
this.getEnclosingModule() = mailerClass
|
||||
}
|
||||
|
||||
/** Gets the mailer class containing this method. */
|
||||
MailerClass getMailerClass() { result = mailerClass }
|
||||
MailerClass() { this = actionMailerClass().getADeclaration() }
|
||||
}
|
||||
|
||||
/** A call to `params` from within a mailer. */
|
||||
class ParamsCall extends ContextCall, ParamsCallImpl {
|
||||
ParamsCall() { this.getMethodName() = "params" }
|
||||
class ParamsCall extends ParamsCallImpl {
|
||||
ParamsCall() { this = actionMailerInstance().getAMethodCall("params").asExpr().getExpr() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ 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.dataflow.internal.DataFlowPrivate
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.Stdlib
|
||||
private import codeql.ruby.frameworks.Core
|
||||
@@ -31,10 +30,8 @@ private predicate isBuiltInMethodForActiveRecordModelInstance(string methodName)
|
||||
methodName = objectInstanceMethodName()
|
||||
}
|
||||
|
||||
private API::Node activeRecordClassApiNode() {
|
||||
private API::Node activeRecordBaseClass() {
|
||||
result =
|
||||
// class Foo < ActiveRecord::Base
|
||||
// class Bar < Foo
|
||||
[
|
||||
API::getTopLevelMember("ActiveRecord").getMember("Base"),
|
||||
// In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we
|
||||
@@ -43,6 +40,46 @@ private API::Node activeRecordClassApiNode() {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object with methods from the ActiveRecord query interface.
|
||||
*/
|
||||
private API::Node activeRecordQueryBuilder() {
|
||||
result = activeRecordBaseClass()
|
||||
or
|
||||
result = activeRecordBaseClass().getInstance()
|
||||
or
|
||||
// Assume any method call might return an ActiveRecord::Relation
|
||||
// These are dynamically generated
|
||||
result = activeRecordQueryBuilderMethodAccess(_).getReturn()
|
||||
}
|
||||
|
||||
/** Gets a call targeting the ActiveRecord query interface. */
|
||||
private API::MethodAccessNode activeRecordQueryBuilderMethodAccess(string name) {
|
||||
result = activeRecordQueryBuilder().getMethod(name) and
|
||||
// Due to the heuristic tracking of query builder objects, add a restriction for methods with a known call target
|
||||
not isUnlikelyExternalCall(result)
|
||||
}
|
||||
|
||||
/** Gets a call targeting the ActiveRecord query interface. */
|
||||
private DataFlow::CallNode activeRecordQueryBuilderCall(string name) {
|
||||
result = activeRecordQueryBuilderMethodAccess(name).asCall()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` is unlikely to call into an external library, since it has a possible
|
||||
* call target in its enclosing module.
|
||||
*/
|
||||
private predicate isUnlikelyExternalCall(API::MethodAccessNode node) {
|
||||
exists(DataFlow::ModuleNode mod, DataFlow::CallNode call | call = node.asCall() |
|
||||
call.getATarget() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()] and
|
||||
call.getEnclosingMethod() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()]
|
||||
)
|
||||
}
|
||||
|
||||
private API::Node activeRecordConnectionInstance() {
|
||||
result = activeRecordBaseClass().getReturn("connection")
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ClassDeclaration` for a class that inherits from `ActiveRecord::Base`. For example,
|
||||
*
|
||||
@@ -56,20 +93,19 @@ private API::Node activeRecordClassApiNode() {
|
||||
* ```
|
||||
*/
|
||||
class ActiveRecordModelClass extends ClassDeclaration {
|
||||
private DataFlow::ClassNode cls;
|
||||
|
||||
ActiveRecordModelClass() {
|
||||
this.getSuperclassExpr() =
|
||||
activeRecordClassApiNode().getASubclass().getAValueReachableFromSource().asExpr().getExpr()
|
||||
cls = activeRecordBaseClass().getADescendentModule() and this = cls.getADeclaration()
|
||||
}
|
||||
|
||||
// Gets the class declaration for this class and all of its super classes
|
||||
private ModuleBase getAllClassDeclarations() {
|
||||
result = this.getModule().getSuperClass*().getADeclaration()
|
||||
}
|
||||
private ModuleBase getAllClassDeclarations() { result = cls.getAnAncestor().getADeclaration() }
|
||||
|
||||
/**
|
||||
* Gets methods defined in this class that may access a field from the database.
|
||||
*/
|
||||
Method getAPotentialFieldAccessMethod() {
|
||||
deprecated Method getAPotentialFieldAccessMethod() {
|
||||
// It's a method on this class or one of its super classes
|
||||
result = this.getAllClassDeclarations().getAMethod() and
|
||||
// There is a value that can be returned by this method which may include field data
|
||||
@@ -91,58 +127,84 @@ class ActiveRecordModelClass extends ClassDeclaration {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the class as a `DataFlow::ClassNode`. */
|
||||
DataFlow::ClassNode getClassNode() { result = cls }
|
||||
}
|
||||
|
||||
/** A class method call whose receiver is an `ActiveRecordModelClass`. */
|
||||
class ActiveRecordModelClassMethodCall extends MethodCall {
|
||||
private ActiveRecordModelClass recvCls;
|
||||
/**
|
||||
* Gets a potential reference to an ActiveRecord class object.
|
||||
*/
|
||||
deprecated private API::Node getAnActiveRecordModelClassRef() {
|
||||
result = any(ActiveRecordModelClass cls).getClassNode().trackModule()
|
||||
or
|
||||
// For methods with an unknown call target, assume this might be a database field, thus returning another ActiveRecord object.
|
||||
// In this case we do not know which class it belongs to, which is why this predicate can't associate the reference with a specific class.
|
||||
result = getAnUnknownActiveRecordModelClassCall().getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call performed on an ActiveRecord class object, without a known call target in the codebase.
|
||||
*/
|
||||
deprecated private API::MethodAccessNode getAnUnknownActiveRecordModelClassCall() {
|
||||
result = getAnActiveRecordModelClassRef().getMethod(_) and
|
||||
result.asCall().asExpr().getExpr() instanceof UnknownMethodCall
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `ActiveRecordModelClass.getClassNode().trackModule().getMethod()` instead.
|
||||
*
|
||||
* A class method call whose receiver is an `ActiveRecordModelClass`.
|
||||
*/
|
||||
deprecated class ActiveRecordModelClassMethodCall extends MethodCall {
|
||||
ActiveRecordModelClassMethodCall() {
|
||||
// e.g. Foo.where(...)
|
||||
recvCls.getModule() = this.getReceiver().(ConstantReadAccess).getModule()
|
||||
or
|
||||
// e.g. Foo.joins(:bars).where(...)
|
||||
recvCls = this.getReceiver().(ActiveRecordModelClassMethodCall).getReceiverClass()
|
||||
or
|
||||
// e.g. self.where(...) within an ActiveRecordModelClass
|
||||
this.getReceiver() instanceof SelfVariableAccess and
|
||||
this.getEnclosingModule() = recvCls
|
||||
this = getAnUnknownActiveRecordModelClassCall().asCall().asExpr().getExpr()
|
||||
}
|
||||
|
||||
/** The `ActiveRecordModelClass` of the receiver of this method. */
|
||||
ActiveRecordModelClass getReceiverClass() { result = recvCls }
|
||||
/** Gets the `ActiveRecordModelClass` of the receiver of this method, if it can be determined. */
|
||||
ActiveRecordModelClass getReceiverClass() {
|
||||
this = result.getClassNode().trackModule().getMethod(_).asCall().asExpr().getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
private Expr sqlFragmentArgument(MethodCall call) {
|
||||
exists(string methodName |
|
||||
methodName = call.getMethodName() and
|
||||
(
|
||||
methodName =
|
||||
[
|
||||
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
|
||||
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
|
||||
"group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where",
|
||||
"rewhere", "select", "reselect", "update_all"
|
||||
] and
|
||||
result = call.getArgument(0)
|
||||
or
|
||||
methodName = "calculate" and result = call.getArgument(1)
|
||||
or
|
||||
methodName in ["average", "count", "maximum", "minimum", "sum", "count_by_sql"] and
|
||||
result = call.getArgument(0)
|
||||
or
|
||||
// This format was supported until Rails 2.3.8
|
||||
methodName = ["all", "find", "first", "last"] and
|
||||
result = call.getKeywordArgument("conditions")
|
||||
or
|
||||
methodName = "reload" and
|
||||
result = call.getKeywordArgument("lock")
|
||||
or
|
||||
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
|
||||
// SQLi if user supplied input is passed in as an argument.
|
||||
methodName = "annotate" and
|
||||
result = call.getArgument(_)
|
||||
)
|
||||
private predicate sqlFragmentArgumentInner(DataFlow::CallNode call, DataFlow::Node sink) {
|
||||
call =
|
||||
activeRecordQueryBuilderCall([
|
||||
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
|
||||
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
|
||||
"group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where", "rewhere",
|
||||
"select", "reselect", "update_all"
|
||||
]) and
|
||||
sink = call.getArgument(0)
|
||||
or
|
||||
call = activeRecordQueryBuilderCall("calculate") and
|
||||
sink = call.getArgument(1)
|
||||
or
|
||||
call =
|
||||
activeRecordQueryBuilderCall(["average", "count", "maximum", "minimum", "sum", "count_by_sql"]) and
|
||||
sink = call.getArgument(0)
|
||||
or
|
||||
// This format was supported until Rails 2.3.8
|
||||
call = activeRecordQueryBuilderCall(["all", "find", "first", "last"]) and
|
||||
sink = call.getKeywordArgument("conditions")
|
||||
or
|
||||
call = activeRecordQueryBuilderCall("reload") and
|
||||
sink = call.getKeywordArgument("lock")
|
||||
or
|
||||
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
|
||||
// SQLi if user supplied input is passed in as an argument.
|
||||
call = activeRecordQueryBuilderCall("annotate") and
|
||||
sink = call.getArgument(_)
|
||||
or
|
||||
call = activeRecordConnectionInstance().getAMethodCall("execute") and
|
||||
sink = call.getArgument(0)
|
||||
}
|
||||
|
||||
private predicate sqlFragmentArgument(DataFlow::CallNode call, DataFlow::Node sink) {
|
||||
exists(DataFlow::Node arg |
|
||||
sqlFragmentArgumentInner(call, arg) and
|
||||
sink = [arg, arg.(DataFlow::ArrayLiteralNode).getElement(0)] and
|
||||
unsafeSqlExpr(sink.asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -163,6 +225,8 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use the `SqlExecution` concept or `ActiveRecordSqlExecutionRange`.
|
||||
*
|
||||
* A method call that may result in executing unintended user-controlled SQL
|
||||
* queries if the `getSqlFragmentSinkArgument()` expression is tainted by
|
||||
* unsanitized user-controlled input. For example, supposing that `User` is an
|
||||
@@ -176,55 +240,32 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
|
||||
* as `"') OR 1=1 --"` could result in the application looking up all users
|
||||
* rather than just one with a matching name.
|
||||
*/
|
||||
class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
|
||||
// The SQL fragment argument itself
|
||||
private Expr sqlFragmentExpr;
|
||||
deprecated class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
|
||||
private DataFlow::CallNode call;
|
||||
|
||||
PotentiallyUnsafeSqlExecutingMethodCall() {
|
||||
exists(Expr arg |
|
||||
arg = sqlFragmentArgument(this) and
|
||||
unsafeSqlExpr(sqlFragmentExpr) and
|
||||
(
|
||||
sqlFragmentExpr = arg
|
||||
or
|
||||
sqlFragmentExpr = arg.(ArrayLiteral).getElement(0)
|
||||
) and
|
||||
// Check that method has not been overridden
|
||||
not exists(SingletonMethod m |
|
||||
m.getName() = this.getMethodName() and
|
||||
m.getOuterScope() = this.getReceiverClass()
|
||||
)
|
||||
)
|
||||
call.asExpr().getExpr() = this and sqlFragmentArgument(call, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL fragment argument of this method call.
|
||||
*/
|
||||
Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr }
|
||||
Expr getSqlFragmentSinkArgument() {
|
||||
exists(DataFlow::Node sink |
|
||||
sqlFragmentArgument(call, sink) and result = sink.asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `SqlExecution::Range` for an argument to a
|
||||
* `PotentiallyUnsafeSqlExecutingMethodCall` that may be vulnerable to being
|
||||
* controlled by user input.
|
||||
* A SQL execution arising from a call to the ActiveRecord library.
|
||||
*/
|
||||
class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
|
||||
ActiveRecordSqlExecutionRange() {
|
||||
exists(PotentiallyUnsafeSqlExecutingMethodCall mc |
|
||||
this.asExpr().getNode() = mc.getSqlFragmentSinkArgument()
|
||||
)
|
||||
or
|
||||
this = activeRecordConnectionInstance().getAMethodCall("execute").getArgument(0) and
|
||||
unsafeSqlExpr(this.asExpr().getExpr())
|
||||
}
|
||||
ActiveRecordSqlExecutionRange() { sqlFragmentArgument(_, this) }
|
||||
|
||||
override DataFlow::Node getSql() { result = this }
|
||||
}
|
||||
|
||||
private API::Node activeRecordConnectionInstance() {
|
||||
result = activeRecordClassApiNode().getReturn("connection")
|
||||
}
|
||||
|
||||
// TODO: model `ActiveRecord` sanitizers
|
||||
// https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html
|
||||
/**
|
||||
@@ -242,15 +283,8 @@ abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range,
|
||||
override predicate methodCallMayAccessField(string methodName) {
|
||||
// The method is not a built-in, and...
|
||||
not isBuiltInMethodForActiveRecordModelInstance(methodName) and
|
||||
(
|
||||
// ...There is no matching method definition in the class, or...
|
||||
not exists(this.getClass().getMethod(methodName))
|
||||
or
|
||||
// ...the called method can access a field.
|
||||
exists(Method m | m = this.getClass().getAPotentialFieldAccessMethod() |
|
||||
m.getName() = methodName
|
||||
)
|
||||
)
|
||||
// ...There is no matching method definition in the class
|
||||
not exists(this.getClass().getMethod(methodName))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,21 +352,10 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation
|
||||
}
|
||||
|
||||
// A `self` reference that may resolve to an active record model object
|
||||
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation,
|
||||
SsaSelfDefinitionNode
|
||||
{
|
||||
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation {
|
||||
private ActiveRecordModelClass cls;
|
||||
|
||||
ActiveRecordModelClassSelfReference() {
|
||||
exists(MethodBase m |
|
||||
m = this.getCfgScope() and
|
||||
m.getEnclosingModule() = cls and
|
||||
m = cls.getAMethod()
|
||||
) and
|
||||
// In a singleton method, `self` refers to the class itself rather than an
|
||||
// instance of that class
|
||||
not this.getSelfScope() instanceof SingletonMethod
|
||||
}
|
||||
ActiveRecordModelClassSelfReference() { this = cls.getClassNode().getAnOwnInstanceSelf() }
|
||||
|
||||
final override ActiveRecordModelClass getClass() { result = cls }
|
||||
}
|
||||
@@ -343,7 +366,7 @@ private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInsta
|
||||
class ActiveRecordInstance extends DataFlow::Node {
|
||||
private ActiveRecordModelInstantiation instantiation;
|
||||
|
||||
ActiveRecordInstance() { this = instantiation or instantiation.flowsTo(this) }
|
||||
ActiveRecordInstance() { this = instantiation.track().getAValueReachableFromSource() }
|
||||
|
||||
/** Gets the `ActiveRecordModelClass` that this is an instance of. */
|
||||
ActiveRecordModelClass getClass() { result = instantiation.getClass() }
|
||||
@@ -381,12 +404,12 @@ private module Persistence {
|
||||
/** A call to e.g. `User.create(name: "foo")` */
|
||||
private class CreateLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
|
||||
CreateLikeCall() {
|
||||
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
|
||||
this.getMethodName() =
|
||||
[
|
||||
"create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by",
|
||||
"find_or_create_by!", "insert", "insert!"
|
||||
]
|
||||
this =
|
||||
activeRecordBaseClass()
|
||||
.getAMethodCall([
|
||||
"create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by",
|
||||
"find_or_create_by!", "insert", "insert!"
|
||||
])
|
||||
}
|
||||
|
||||
override DataFlow::Node getValue() {
|
||||
@@ -403,8 +426,7 @@ private module Persistence {
|
||||
/** A call to e.g. `User.update(1, name: "foo")` */
|
||||
private class UpdateLikeClassMethodCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
|
||||
UpdateLikeClassMethodCall() {
|
||||
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
|
||||
this.getMethodName() = ["update", "update!", "upsert"]
|
||||
this = activeRecordBaseClass().getAMethodCall(["update", "update!", "upsert"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getValue() {
|
||||
@@ -449,10 +471,7 @@ private module Persistence {
|
||||
* ```
|
||||
*/
|
||||
private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
|
||||
TouchAllCall() {
|
||||
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
|
||||
this.getMethodName() = "touch_all"
|
||||
}
|
||||
TouchAllCall() { this = activeRecordQueryBuilderCall("touch_all") }
|
||||
|
||||
override DataFlow::Node getValue() { result = this.getKeywordArgument("time") }
|
||||
}
|
||||
@@ -462,8 +481,7 @@ private module Persistence {
|
||||
private ExprNodes::ArrayLiteralCfgNode arr;
|
||||
|
||||
InsertAllLikeCall() {
|
||||
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
|
||||
this.getMethodName() = ["insert_all", "insert_all!", "upsert_all"] and
|
||||
this = activeRecordBaseClass().getAMethodCall(["insert_all", "insert_all!", "upsert_all"]) and
|
||||
arr = this.getArgument(0).asExpr()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,12 @@ module ActiveResource {
|
||||
* An ActiveResource model class. This is any (transitive) subclass of ActiveResource.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node modelApiNode() {
|
||||
result = API::getTopLevelMember("ActiveResource").getMember("Base").getASubclass()
|
||||
private API::Node activeResourceBaseClass() {
|
||||
result = API::getTopLevelMember("ActiveResource").getMember("Base")
|
||||
}
|
||||
|
||||
private DataFlow::ClassNode activeResourceClass() {
|
||||
result = activeResourceBaseClass().getADescendentModule()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,16 +34,8 @@ module ActiveResource {
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class ModelClass extends ClassDeclaration {
|
||||
API::Node model;
|
||||
|
||||
ModelClass() {
|
||||
model = modelApiNode() and
|
||||
this.getSuperclassExpr() = model.getAValueReachableFromSource().asExpr().getExpr()
|
||||
}
|
||||
|
||||
/** Gets the API node for this model */
|
||||
API::Node getModelApiNode() { result = model }
|
||||
class ModelClassNode extends DataFlow::ClassNode {
|
||||
ModelClassNode() { this = activeResourceClass() }
|
||||
|
||||
/** Gets a call to `site=`, which sets the base URL for this model. */
|
||||
SiteAssignCall getASiteAssignment() { result.getModelClass() = this }
|
||||
@@ -49,6 +45,46 @@ module ActiveResource {
|
||||
c = this.getASiteAssignment() and
|
||||
c.disablesCertificateValidation()
|
||||
}
|
||||
|
||||
/** Gets a method call on this class that returns an instance of the class. */
|
||||
private DataFlow::CallNode getAChainedCall() {
|
||||
result.(FindCall).getModelClass() = this
|
||||
or
|
||||
result.(CreateCall).getModelClass() = this
|
||||
or
|
||||
result.(CustomHttpCall).getModelClass() = this
|
||||
or
|
||||
result.(CollectionCall).getCollection().getModelClass() = this and
|
||||
result.getMethodName() = ["first", "last"]
|
||||
}
|
||||
|
||||
/** Gets an API node referring to an instance of this class. */
|
||||
API::Node getAnInstanceReference() {
|
||||
result = this.trackInstance()
|
||||
or
|
||||
result = this.getAChainedCall().track()
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED. Use `ModelClassNode` instead. */
|
||||
deprecated class ModelClass extends ClassDeclaration {
|
||||
private ModelClassNode cls;
|
||||
|
||||
ModelClass() { this = cls.getADeclaration() }
|
||||
|
||||
/** Gets the class for which this is a declaration. */
|
||||
ModelClassNode getClassNode() { result = cls }
|
||||
|
||||
/** Gets the API node for this class object. */
|
||||
deprecated API::Node getModelApiNode() { result = cls.trackModule() }
|
||||
|
||||
/** Gets a call to `site=`, which sets the base URL for this model. */
|
||||
SiteAssignCall getASiteAssignment() { result = cls.getASiteAssignment() }
|
||||
|
||||
/** Holds if `c` sets a base URL which does not use HTTPS. */
|
||||
predicate disablesCertificateValidation(SiteAssignCall c) {
|
||||
cls.disablesCertificateValidation(c)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,25 +98,20 @@ module ActiveResource {
|
||||
* ```
|
||||
*/
|
||||
class ModelClassMethodCall extends DataFlow::CallNode {
|
||||
API::Node model;
|
||||
private ModelClassNode cls;
|
||||
|
||||
ModelClassMethodCall() {
|
||||
model = modelApiNode() and
|
||||
this = classMethodCall(model, _)
|
||||
}
|
||||
ModelClassMethodCall() { this = cls.trackModule().getAMethodCall(_) }
|
||||
|
||||
/** Gets the model class for this call. */
|
||||
ModelClass getModelClass() { result.getModelApiNode() = model }
|
||||
ModelClassNode getModelClass() { result = cls }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `site=` on an ActiveResource model class.
|
||||
* This sets the base URL for all HTTP requests made by this class.
|
||||
*/
|
||||
private class SiteAssignCall extends DataFlow::CallNode {
|
||||
API::Node model;
|
||||
|
||||
SiteAssignCall() { model = modelApiNode() and this = classMethodCall(model, "site=") }
|
||||
private class SiteAssignCall extends ModelClassMethodCall {
|
||||
SiteAssignCall() { this.getMethodName() = "site=" }
|
||||
|
||||
/**
|
||||
* Gets a node that contributes to the URLs used for HTTP requests by the parent
|
||||
@@ -88,12 +119,10 @@ module ActiveResource {
|
||||
*/
|
||||
DataFlow::Node getAUrlPart() { result = this.getArgument(0) }
|
||||
|
||||
/** Gets the model class for this call. */
|
||||
ModelClass getModelClass() { result.getModelApiNode() = model }
|
||||
|
||||
/** Holds if this site value specifies HTTP rather than HTTPS. */
|
||||
predicate disablesCertificateValidation() {
|
||||
this.getAUrlPart()
|
||||
// TODO: We should not need all this just to get the string value
|
||||
.asExpr()
|
||||
.(ExprNodes::AssignExprCfgNode)
|
||||
.getRhs()
|
||||
@@ -141,87 +170,70 @@ module ActiveResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `ModelClassNode.getAnInstanceReference()` instead.
|
||||
*
|
||||
* An ActiveResource model object.
|
||||
*/
|
||||
class ModelInstance extends DataFlow::Node {
|
||||
ModelClass cls;
|
||||
deprecated class ModelInstance extends DataFlow::Node {
|
||||
private ModelClassNode cls;
|
||||
|
||||
ModelInstance() {
|
||||
exists(API::Node model | model = modelApiNode() |
|
||||
this = model.getInstance().getAValueReachableFromSource() and
|
||||
cls.getModelApiNode() = model
|
||||
)
|
||||
or
|
||||
exists(FindCall call | call.flowsTo(this) | cls = call.getModelClass())
|
||||
or
|
||||
exists(CreateCall call | call.flowsTo(this) | cls = call.getModelClass())
|
||||
or
|
||||
exists(CustomHttpCall call | call.flowsTo(this) | cls = call.getModelClass())
|
||||
or
|
||||
exists(CollectionCall call |
|
||||
call.getMethodName() = ["first", "last"] and
|
||||
call.flowsTo(this)
|
||||
|
|
||||
cls = call.getCollection().getModelClass()
|
||||
)
|
||||
}
|
||||
ModelInstance() { this = cls.getAnInstanceReference().getAValueReachableFromSource() }
|
||||
|
||||
/** Gets the model class for this instance. */
|
||||
ModelClass getModelClass() { result = cls }
|
||||
ModelClassNode getModelClass() { result = cls }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method on an ActiveResource model object.
|
||||
*/
|
||||
class ModelInstanceMethodCall extends DataFlow::CallNode {
|
||||
ModelInstance i;
|
||||
private ModelClassNode cls;
|
||||
|
||||
ModelInstanceMethodCall() { this.getReceiver() = i }
|
||||
ModelInstanceMethodCall() { this = cls.getAnInstanceReference().getAMethodCall(_) }
|
||||
|
||||
/** Gets the model instance for this call. */
|
||||
ModelInstance getInstance() { result = i }
|
||||
deprecated ModelInstance getInstance() { result = this.getReceiver() }
|
||||
|
||||
/** Gets the model class for this call. */
|
||||
ModelClass getModelClass() { result = i.getModelClass() }
|
||||
ModelClassNode getModelClass() { result = cls }
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of ActiveResource model objects.
|
||||
* DEPRECATED. Use `CollectionSource` instead.
|
||||
*
|
||||
* A data flow node that may refer to a collection of ActiveResource model objects.
|
||||
*/
|
||||
class Collection extends DataFlow::Node {
|
||||
ModelClassMethodCall classMethodCall;
|
||||
deprecated class Collection extends DataFlow::Node {
|
||||
Collection() { this = any(CollectionSource src).track().getAValueReachableFromSource() }
|
||||
}
|
||||
|
||||
Collection() {
|
||||
classMethodCall.flowsTo(this) and
|
||||
(
|
||||
classMethodCall.getMethodName() = "all"
|
||||
or
|
||||
classMethodCall.getMethodName() = "find" and
|
||||
classMethodCall.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all")
|
||||
)
|
||||
/**
|
||||
* A call that returns a collection of ActiveResource model objects.
|
||||
*/
|
||||
class CollectionSource extends ModelClassMethodCall {
|
||||
CollectionSource() {
|
||||
this.getMethodName() = "all"
|
||||
or
|
||||
this.getArgument(0).asExpr().getConstantValue().isStringlikeValue("all")
|
||||
}
|
||||
|
||||
/** Gets the model class for this collection. */
|
||||
ModelClass getModelClass() { result = classMethodCall.getModelClass() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on a collection.
|
||||
*/
|
||||
class CollectionCall extends DataFlow::CallNode {
|
||||
CollectionCall() { this.getReceiver() instanceof Collection }
|
||||
private CollectionSource collection;
|
||||
|
||||
CollectionCall() { this = collection.track().getAMethodCall(_) }
|
||||
|
||||
/** Gets the collection for this call. */
|
||||
Collection getCollection() { result = this.getReceiver() }
|
||||
CollectionSource getCollection() { result = collection }
|
||||
}
|
||||
|
||||
private class ModelClassMethodCallAsHttpRequest extends Http::Client::Request::Range,
|
||||
ModelClassMethodCall
|
||||
{
|
||||
ModelClass cls;
|
||||
|
||||
ModelClassMethodCallAsHttpRequest() {
|
||||
this.getModelClass() = cls and
|
||||
this.getMethodName() = ["all", "build", "create", "create!", "find", "first", "last"]
|
||||
}
|
||||
|
||||
@@ -230,12 +242,14 @@ module ActiveResource {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
cls.disablesCertificateValidation(disablingNode) and
|
||||
this.getModelClass().disablesCertificateValidation(disablingNode) and
|
||||
// TODO: highlight real argument origin
|
||||
argumentOrigin = disablingNode
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() }
|
||||
override DataFlow::Node getAUrlPart() {
|
||||
result = this.getModelClass().getASiteAssignment().getAUrlPart()
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = this }
|
||||
}
|
||||
@@ -243,10 +257,7 @@ module ActiveResource {
|
||||
private class ModelInstanceMethodCallAsHttpRequest extends Http::Client::Request::Range,
|
||||
ModelInstanceMethodCall
|
||||
{
|
||||
ModelClass cls;
|
||||
|
||||
ModelInstanceMethodCallAsHttpRequest() {
|
||||
this.getModelClass() = cls and
|
||||
this.getMethodName() =
|
||||
[
|
||||
"exists?", "reload", "save", "save!", "destroy", "delete", "get", "patch", "post", "put",
|
||||
@@ -259,42 +270,15 @@ module ActiveResource {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
cls.disablesCertificateValidation(disablingNode) and
|
||||
this.getModelClass().disablesCertificateValidation(disablingNode) and
|
||||
// TODO: highlight real argument origin
|
||||
argumentOrigin = disablingNode
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result = cls.getASiteAssignment().getAUrlPart() }
|
||||
override DataFlow::Node getAUrlPart() {
|
||||
result = this.getModelClass().getASiteAssignment().getAUrlPart()
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a class method.
|
||||
*
|
||||
* TODO: is this general enough to be useful elsewhere?
|
||||
*
|
||||
* Examples:
|
||||
* ```rb
|
||||
* class A
|
||||
* def self.m; end
|
||||
*
|
||||
* m # call
|
||||
* end
|
||||
*
|
||||
* A.m # call
|
||||
* ```
|
||||
*/
|
||||
private DataFlow::CallNode classMethodCall(API::Node classNode, string methodName) {
|
||||
// A.m
|
||||
result = classNode.getAMethodCall(methodName)
|
||||
or
|
||||
// class A
|
||||
// A.m
|
||||
// end
|
||||
result.getReceiver().asExpr() instanceof ExprNodes::SelfVariableAccessCfgNode and
|
||||
result.asExpr().getExpr().getEnclosingModule().(ClassDeclaration).getSuperclassExpr() =
|
||||
classNode.getAValueReachableFromSource().asExpr().getExpr() and
|
||||
result.getMethodName() = methodName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,8 @@ private API::Node graphQlSchema() { result = API::getTopLevelMember("GraphQL").g
|
||||
*/
|
||||
private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
|
||||
GraphqlRelayClassicMutationClass() {
|
||||
this.getSuperclassExpr() =
|
||||
graphQlSchema()
|
||||
.getMember("RelayClassicMutation")
|
||||
.getASubclass()
|
||||
.getAValueReachableFromSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
this =
|
||||
graphQlSchema().getMember("RelayClassicMutation").getADescendentModule().getADeclaration()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,13 +69,7 @@ private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
|
||||
*/
|
||||
private class GraphqlSchemaResolverClass extends ClassDeclaration {
|
||||
GraphqlSchemaResolverClass() {
|
||||
this.getSuperclassExpr() =
|
||||
graphQlSchema()
|
||||
.getMember("Resolver")
|
||||
.getASubclass()
|
||||
.getAValueReachableFromSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
this = graphQlSchema().getMember("Resolver").getADescendentModule().getADeclaration()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,13 +92,7 @@ private string getASupportedHttpMethod() { result = ["get", "post"] }
|
||||
*/
|
||||
class GraphqlSchemaObjectClass extends ClassDeclaration {
|
||||
GraphqlSchemaObjectClass() {
|
||||
this.getSuperclassExpr() =
|
||||
graphQlSchema()
|
||||
.getMember("Object")
|
||||
.getASubclass()
|
||||
.getAValueReachableFromSource()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
this = graphQlSchema().getMember("Object").getADescendentModule().getADeclaration()
|
||||
}
|
||||
|
||||
/** Gets a `GraphqlFieldDefinitionMethodCall` called in this class. */
|
||||
|
||||
@@ -14,8 +14,8 @@ module NetLdap {
|
||||
/**
|
||||
* Flow summary for `Net::LDAP.new`. This method establishes a connection to a LDAP server.
|
||||
*/
|
||||
private class LdapSummary extends SummarizedCallable {
|
||||
LdapSummary() { this = "Net::LDAP.new" }
|
||||
private class LdapConnSummary extends SummarizedCallable {
|
||||
LdapConnSummary() { this = "Net::LDAP.new" }
|
||||
|
||||
override MethodCall getACall() { result = any(NetLdapConnection l).asExpr().getExpr() }
|
||||
|
||||
@@ -24,11 +24,25 @@ module NetLdap {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `Net::LDAP.Filter`.
|
||||
*/
|
||||
private class LdapFilterSummary extends SummarizedCallable {
|
||||
LdapFilterSummary() { this = "Net::LDAP::Filter" }
|
||||
|
||||
override MethodCall getACall() { result = any(NetLdapFilter l).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = ["Argument[0]", "Argument[1]"] and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** Net LDAP Api Node */
|
||||
private API::Node ldap() { result = API::getTopLevelMember("Net").getMember("LDAP") }
|
||||
|
||||
/** A call that establishes a LDAP Connection */
|
||||
private class NetLdapConnection extends DataFlow::CallNode {
|
||||
|
||||
NetLdapConnection() { this in [ldap().getAnInstantiation(), ldap().getAMethodCall(["open"])] }
|
||||
|
||||
predicate usesSsl() {
|
||||
@@ -41,11 +55,21 @@ module NetLdap {
|
||||
DataFlow::Node getAuthValue(string arg) {
|
||||
result = this.getKeywordArgument("auth").(DataFlow::CallNode).getKeywordArgument(arg)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A call that constructs a LDAP query */
|
||||
private class NetLdapFilter extends LdapConstruction::Range, DataFlow::CallNode {
|
||||
NetLdapFilter() { this = any(ldap().getMember("Filter").getAMethodCall(["eq"])) }
|
||||
NetLdapFilter() {
|
||||
this =
|
||||
any(ldap()
|
||||
.getMember("Filter")
|
||||
.getAMethodCall([
|
||||
"begins", "bineq", "contains", "ends", "eq", "equals", "ex", "ge", "le", "ne",
|
||||
"present"
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getArgument([0, 1]) }
|
||||
}
|
||||
|
||||
73
ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll
Normal file
73
ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
|
||||
*/
|
||||
module Mysql2 {
|
||||
/**
|
||||
* Flow summary for `Mysql2::Client.new()`.
|
||||
*/
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "Mysql2::Client.new()" }
|
||||
|
||||
override MethodCall getACall() { result = any(Mysql2Connection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to Mysql2::Client.new() is used to establish a connection to a MySql database. */
|
||||
private class Mysql2Connection extends DataFlow::CallNode {
|
||||
Mysql2Connection() {
|
||||
this = API::getTopLevelMember("Mysql2").getMember("Client").getAnInstantiation()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a MySQL database. */
|
||||
private class Mysql2Execution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
private DataFlow::Node query;
|
||||
|
||||
Mysql2Execution() {
|
||||
exists(Mysql2Connection mysql2Connection |
|
||||
this = mysql2Connection.getAMethodCall("query") and query = this.getArgument(0)
|
||||
or
|
||||
exists(DataFlow::CallNode prepareCall |
|
||||
prepareCall = mysql2Connection.getAMethodCall("prepare") and
|
||||
query = prepareCall.getArgument(0) and
|
||||
this = prepareCall.getAMethodCall("execute")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Mysql2::Client.escape`, considered as a sanitizer for SQL statements.
|
||||
*/
|
||||
private class Mysql2EscapeSanitization extends SqlSanitization::Range {
|
||||
Mysql2EscapeSanitization() {
|
||||
this = API::getTopLevelMember("Mysql2").getMember("Client").getAMethodCall("escape")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `Mysql2::Client.escape()`.
|
||||
*/
|
||||
private class EscapeSummary extends SummarizedCallable {
|
||||
EscapeSummary() { this = "Mysql2::Client.escape()" }
|
||||
|
||||
override MethodCall getACall() { result = any(Mysql2EscapeSanitization c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
77
ruby/ql/lib/codeql/ruby/frameworks/Pg.qll
Normal file
77
ruby/ql/lib/codeql/ruby/frameworks/Pg.qll
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Provides modeling for Pg, a Ruby library (gem) for interacting with PostgreSQL databases.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for Pg, a Ruby library (gem) for interacting with PostgreSQL databases.
|
||||
*/
|
||||
module Pg {
|
||||
/**
|
||||
* Flow summary for `PG.new()`. This method initializes a database connection.
|
||||
*/
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "PG.new()" }
|
||||
|
||||
override MethodCall getACall() { result = any(PgConnection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to PG::Connection.open() is used to establish a connection to a PostgreSQL database. */
|
||||
private class PgConnection extends DataFlow::CallNode {
|
||||
PgConnection() {
|
||||
this =
|
||||
API::getTopLevelMember("PG")
|
||||
.getMember("Connection")
|
||||
.getAMethodCall(["open", "new", "connect_start"])
|
||||
or
|
||||
this = API::getTopLevelMember("PG").getAnInstantiation()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that prepares an SQL statement to be executed later. */
|
||||
private class PgPrepareCall extends SqlConstruction::Range, DataFlow::CallNode {
|
||||
private DataFlow::Node query;
|
||||
private PgConnection pgConnection;
|
||||
private string queryName;
|
||||
|
||||
PgPrepareCall() {
|
||||
this = pgConnection.getAMethodCall("prepare") and
|
||||
queryName = this.getArgument(0).getConstantValue().getStringlikeValue() and
|
||||
query = this.getArgument(1)
|
||||
}
|
||||
|
||||
PgConnection getConnection() { result = pgConnection }
|
||||
|
||||
string getQueryName() { result = queryName }
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a PostgreSQL database. */
|
||||
private class PgExecution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
private DataFlow::Node query;
|
||||
|
||||
PgExecution() {
|
||||
exists(PgConnection pgConnection |
|
||||
this =
|
||||
pgConnection.getAMethodCall(["exec", "async_exec", "exec_params", "async_exec_params"]) and
|
||||
query = this.getArgument(0)
|
||||
or
|
||||
exists(PgPrepareCall prepareCall |
|
||||
pgConnection = prepareCall.getConnection() and
|
||||
this.getArgument(0).getConstantValue().isStringlikeValue(prepareCall.getQueryName()) and
|
||||
query = prepareCall.getSql()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
}
|
||||
@@ -2,47 +2,15 @@
|
||||
* Provides modeling for the Rack library.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.controlflow.CfgNodes::ExprNodes
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
|
||||
/**
|
||||
* Provides modeling for the Rack library.
|
||||
*/
|
||||
module Rack {
|
||||
/**
|
||||
* A class that may be a rack application.
|
||||
* This is a class that has a `call` method that takes a single argument
|
||||
* (traditionally called `env`) and returns a rack-compatible response.
|
||||
*/
|
||||
class AppCandidate extends DataFlow::ClassNode {
|
||||
private DataFlow::MethodNode call;
|
||||
import rack.internal.App
|
||||
import rack.internal.Request
|
||||
import rack.internal.Response::Public as Response
|
||||
import rack.internal.Utils
|
||||
|
||||
AppCandidate() {
|
||||
call = this.getInstanceMethod("call") and
|
||||
call.getNumberOfParameters() = 1 and
|
||||
call.getReturn() = trackRackResponse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the environment of the request, which is the lone parameter to the `call` method.
|
||||
*/
|
||||
DataFlow::ParameterNode getEnv() { result = call.getParameter(0) }
|
||||
}
|
||||
|
||||
private predicate isRackResponse(DataFlow::Node r) {
|
||||
// [status, headers, body]
|
||||
r.asExpr().(ArrayLiteralCfgNode).getNumberOfArguments() = 3
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackRackResponse(TypeTracker t) {
|
||||
t.start() and
|
||||
isRackResponse(result)
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackRackResponse(t2).track(t2, t))
|
||||
}
|
||||
|
||||
private DataFlow::Node trackRackResponse() {
|
||||
trackRackResponse(TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
/** DEPRECATED: Alias for App::AppCandidate */
|
||||
deprecated class AppCandidate = App::AppCandidate;
|
||||
}
|
||||
|
||||
71
ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll
Normal file
71
ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Provides modeling for `Sequel`, the database toolkit for Ruby.
|
||||
* https://github.com/jeremyevans/sequel
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for `Sequel`, the database toolkit for Ruby.
|
||||
* https://github.com/jeremyevans/sequel
|
||||
*/
|
||||
module Sequel {
|
||||
/** Flow Summary for `Sequel`. */
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "Sequel.connect" }
|
||||
|
||||
override MethodCall getACall() { result = any(SequelConnection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to establish a connection to a database */
|
||||
private class SequelConnection extends DataFlow::CallNode {
|
||||
SequelConnection() {
|
||||
this =
|
||||
API::getTopLevelMember("Sequel").getAMethodCall(["connect", "sqlite", "mysql2", "jdbc"])
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that constructs SQL statements */
|
||||
private class SequelConstruction extends SqlConstruction::Range, DataFlow::CallNode {
|
||||
DataFlow::Node query;
|
||||
|
||||
SequelConstruction() {
|
||||
this = API::getTopLevelMember("Sequel").getAMethodCall("cast") and query = this.getArgument(1)
|
||||
or
|
||||
this = API::getTopLevelMember("Sequel").getAMethodCall("function") and
|
||||
query = this.getArgument(0)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a database */
|
||||
private class SequelExecution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
SequelExecution() {
|
||||
exists(SequelConnection sequelConnection |
|
||||
this =
|
||||
sequelConnection
|
||||
.getAMethodCall([
|
||||
"execute", "execute_ddl", "execute_dui", "execute_insert", "run", "<<", "fetch",
|
||||
"fetch_rows", "[]", "log_connection_yield"
|
||||
]) or
|
||||
this =
|
||||
sequelConnection
|
||||
.getAMethodCall("dataset")
|
||||
.getAMethodCall([
|
||||
"with_sql", "with_sql_all", "with_sql_delete", "with_sql_each", "with_sql_first",
|
||||
"with_sql_insert", "with_sql_single_value", "with_sql_update"
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
@@ -105,15 +105,25 @@ module Sinatra {
|
||||
* Gets the template file referred to by `erbCall`.
|
||||
* This works on the AST level to avoid non-monotonic reecursion in `ErbLocalsHashSyntheticGlobal`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private ErbFile getTemplateFile(MethodCall erbCall) {
|
||||
erbCall.getMethodName() = "erb" and
|
||||
result.getTemplateName() = erbCall.getArgument(0).getConstantValue().getStringlikeValue() and
|
||||
result.getRelativePath().matches("%views/%")
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate erbCallAtLocation(MethodCall erbCall, ErbFile erbFile, Location l) {
|
||||
erbCall.getMethodName() = "erb" and
|
||||
erbFile = getTemplateFile(erbCall) and
|
||||
l = erbCall.getLocation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `Location.toString`, but displays the relative path rather than the full path.
|
||||
*/
|
||||
bindingset[loc]
|
||||
pragma[inline_late]
|
||||
private string locationRelativePathToString(Location loc) {
|
||||
result =
|
||||
loc.getFile().getRelativePath() + "@" + loc.getStartLine() + ":" + loc.getStartColumn() + ":" +
|
||||
@@ -121,7 +131,7 @@ module Sinatra {
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic global representing the hash of local variables passed to an ERB template.
|
||||
* A synthetic global representing the hash of local variables passed to an ERB template.
|
||||
*/
|
||||
class ErbLocalsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
|
||||
private string id;
|
||||
@@ -129,10 +139,11 @@ module Sinatra {
|
||||
private ErbFile erbFile;
|
||||
|
||||
ErbLocalsHashSyntheticGlobal() {
|
||||
this = "SinatraErbLocalsHash(" + id + ")" and
|
||||
id = erbFile.getRelativePath() + "," + locationRelativePathToString(erbCall.getLocation()) and
|
||||
erbCall.getMethodName() = "erb" and
|
||||
erbFile = getTemplateFile(erbCall)
|
||||
exists(Location l |
|
||||
erbCallAtLocation(erbCall, erbFile, l) and
|
||||
id = erbFile.getRelativePath() + "," + locationRelativePathToString(l) and
|
||||
this = "SinatraErbLocalsHash(" + id + ")"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,21 +15,20 @@ private import codeql.ruby.Concepts
|
||||
* https://github.com/sparklemotion/sqlite3-ruby
|
||||
*/
|
||||
module Sqlite3 {
|
||||
private API::Node databaseConst() {
|
||||
result = API::getTopLevelMember("SQLite3").getMember("Database")
|
||||
}
|
||||
|
||||
private API::Node dbInstance() {
|
||||
result = databaseConst().getInstance()
|
||||
or
|
||||
// e.g. SQLite3::Database.new("foo.db") |db| { db.some_method }
|
||||
result = databaseConst().getMethod("new").getBlock().getParameter(0)
|
||||
}
|
||||
|
||||
/** Gets a method call with a receiver that is a database instance. */
|
||||
private DataFlow::CallNode getADatabaseMethodCall(string methodName) {
|
||||
exists(API::Node dbInstance |
|
||||
dbInstance = API::getTopLevelMember("SQLite3").getMember("Database").getInstance() and
|
||||
(
|
||||
result = dbInstance.getAMethodCall(methodName)
|
||||
or
|
||||
// e.g. SQLite3::Database.new("foo.db") |db| { db.some_method }
|
||||
exists(DataFlow::BlockNode block |
|
||||
result.getMethodName() = methodName and
|
||||
block = dbInstance.getAValueReachableFromSource().(DataFlow::CallNode).getBlock() and
|
||||
block.getParameter(0).flowsTo(result.getReceiver())
|
||||
)
|
||||
)
|
||||
)
|
||||
result = dbInstance().getAMethodCall(methodName)
|
||||
}
|
||||
|
||||
/** A prepared but unexecuted SQL statement. */
|
||||
@@ -48,7 +47,7 @@ module Sqlite3 {
|
||||
this.getMethodName() = ["columns", "execute", "execute!", "get_metadata", "types"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = stmt.getReceiver() }
|
||||
override DataFlow::Node getSql() { result = stmt.getSql() }
|
||||
}
|
||||
|
||||
/** Gets the name of a method called against a database that executes an SQL statement. */
|
||||
@@ -77,4 +76,26 @@ module Sqlite3 {
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `SQLite3::Database.quote`, considered as a sanitizer for SQL statements.
|
||||
*/
|
||||
private class SQLite3QuoteSanitization extends SqlSanitization {
|
||||
SQLite3QuoteSanitization() {
|
||||
this = API::getTopLevelMember("SQLite3").getMember("Database").getAMethodCall("quote")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `SQLite3::Database.quote()`.
|
||||
*/
|
||||
private class QuoteSummary extends SummarizedCallable {
|
||||
QuoteSummary() { this = "SQLite3::Database.quote()" }
|
||||
|
||||
override MethodCall getACall() { result = any(SQLite3QuoteSanitization c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* This module is deprecated, and exists as a shim to support any existing code that relies on it.
|
||||
* New code should use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.frameworks.Core as Core
|
||||
private import codeql.ruby.frameworks.Stdlib as Stdlib
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class SubshellLiteralExecution = Core::SubshellLiteralExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class SubshellHeredocExecution = Core::SubshellHeredocExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class BasicObjectInstanceMethodCall = Core::BasicObjectInstanceMethodCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated predicate basicObjectInstanceMethodName = Core::basicObjectInstanceMethodName/0;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class InstanceEvalCallCodeExecution = Core::InstanceEvalCallCodeExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class ObjectInstanceMethodCall = Core::ObjectInstanceMethodCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated predicate objectInstanceMethodName = Core::objectInstanceMethodName/0;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelMethodCall = Core::KernelMethodCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelSystemCall = Core::KernelSystemCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelExecCall = Core::KernelExecCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelSpawnCall = Core::KernelSpawnCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class EvalCallCodeExecution = Core::EvalCallCodeExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class SendCallCodeExecution = Core::SendCallCodeExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Module = Core::Module;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Array = Core::Array;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Regexp = Core::Regexp;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Open3 = Stdlib::Open3;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Logger = Stdlib::Logger;
|
||||
@@ -16,50 +16,28 @@ module Twirp {
|
||||
/**
|
||||
* A Twirp service instantiation
|
||||
*/
|
||||
class ServiceInstantiation extends DataFlow::CallNode {
|
||||
deprecated class ServiceInstantiation extends DataFlow::CallNode {
|
||||
ServiceInstantiation() {
|
||||
this = API::getTopLevelMember("Twirp").getMember("Service").getAnInstantiation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a local source node for the Service instantiation argument (the service handler).
|
||||
*/
|
||||
private DataFlow::LocalSourceNode getHandlerSource() {
|
||||
result = this.getArgument(0).getALocalSource()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API::Node for the service handler's class.
|
||||
*/
|
||||
private API::Node getAHandlerClassApiNode() {
|
||||
result.getAnInstantiation() = this.getHandlerSource()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AST module for the service handler's class.
|
||||
*/
|
||||
private Ast::Module getAHandlerClassAstNode() {
|
||||
result =
|
||||
this.getAHandlerClassApiNode()
|
||||
.asSource()
|
||||
.asExpr()
|
||||
.(CfgNodes::ExprNodes::ConstantReadAccessCfgNode)
|
||||
.getExpr()
|
||||
.getModule()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a handler's method.
|
||||
*/
|
||||
Ast::Method getAHandlerMethod() {
|
||||
result = this.getAHandlerClassAstNode().getAnInstanceMethod()
|
||||
DataFlow::MethodNode getAHandlerMethodNode() {
|
||||
result = this.getArgument(0).backtrack().getMethod(_).asCallable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a handler's method as an AST node.
|
||||
*/
|
||||
Ast::Method getAHandlerMethod() { result = this.getAHandlerMethodNode().asCallableAstNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Twirp client
|
||||
*/
|
||||
class ClientInstantiation extends DataFlow::CallNode {
|
||||
deprecated class ClientInstantiation extends DataFlow::CallNode {
|
||||
ClientInstantiation() {
|
||||
this = API::getTopLevelMember("Twirp").getMember("Client").getAnInstantiation()
|
||||
}
|
||||
@@ -67,7 +45,10 @@ module Twirp {
|
||||
|
||||
/** The URL of a Twirp service, considered as a sink. */
|
||||
class ServiceUrlAsSsrfSink extends ServerSideRequestForgery::Sink {
|
||||
ServiceUrlAsSsrfSink() { exists(ClientInstantiation c | c.getArgument(0) = this) }
|
||||
ServiceUrlAsSsrfSink() {
|
||||
this =
|
||||
API::getTopLevelMember("Twirp").getMember("Client").getMethod("new").getArgument(0).asSink()
|
||||
}
|
||||
}
|
||||
|
||||
/** A parameter that will receive parts of the url when handling an incoming request. */
|
||||
@@ -75,7 +56,14 @@ module Twirp {
|
||||
DataFlow::ParameterNode
|
||||
{
|
||||
UnmarshaledParameter() {
|
||||
exists(ServiceInstantiation i | i.getAHandlerMethod().getParameter(0) = this.asParameter())
|
||||
this =
|
||||
API::getTopLevelMember("Twirp")
|
||||
.getMember("Service")
|
||||
.getMethod("new")
|
||||
.getArgument(0)
|
||||
.getMethod(_)
|
||||
.getParameter(0)
|
||||
.asSource()
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Twirp Unmarhaled Parameter" }
|
||||
|
||||
@@ -45,6 +45,17 @@ private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::Call
|
||||
}
|
||||
}
|
||||
|
||||
/** Execution of a XPath statement. */
|
||||
private class NokogiriXPathExecution extends XPathExecution::Range, DataFlow::CallNode {
|
||||
NokogiriXPathExecution() {
|
||||
exists(NokogiriXmlParserCall parserCall |
|
||||
this = parserCall.getAMethodCall(["xpath", "at_xpath", "search", "at"])
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getXPath() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `assign` enables the `default_substitute_entities` option in
|
||||
* libxml-ruby.
|
||||
@@ -123,6 +134,40 @@ private predicate xmlMiniEntitySubstitutionEnabled() {
|
||||
enablesLibXmlDefaultEntitySubstitution(_)
|
||||
}
|
||||
|
||||
/** Execution of a XPath statement. */
|
||||
private class LibXmlXPathExecution extends XPathExecution::Range, DataFlow::CallNode {
|
||||
LibXmlXPathExecution() {
|
||||
exists(LibXmlRubyXmlParserCall parserCall |
|
||||
this = parserCall.getAMethodCall(["find", "find_first"])
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getXPath() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A call to `REXML::Document.new`, considered as a XML parsing. */
|
||||
private class RexmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
|
||||
RexmlParserCall() {
|
||||
this = API::getTopLevelMember("REXML").getMember("Document").getAnInstantiation()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = this.getArgument(0) }
|
||||
|
||||
/** No option for parsing */
|
||||
override predicate externalEntitiesEnabled() { none() }
|
||||
}
|
||||
|
||||
/** Execution of a XPath statement. */
|
||||
private class RexmlXPathExecution extends XPathExecution::Range, DataFlow::CallNode {
|
||||
RexmlXPathExecution() {
|
||||
this =
|
||||
[API::getTopLevelMember("REXML").getMember("XPath"), API::getTopLevelMember("XPath")]
|
||||
.getAMethodCall(["each", "first", "match"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getXPath() { result = this.getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `ActiveSupport::XmlMini.parse` considered as an `XmlParserCall`.
|
||||
*/
|
||||
|
||||
35
ruby/ql/lib/codeql/ruby/frameworks/Yaml.qll
Normal file
35
ruby/ql/lib/codeql/ruby/frameworks/Yaml.qll
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides modeling for the `YAML` and `Psych` libraries.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.dataflow.FlowSteps
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* A taint step related to the result of `YAML.parse` calls, or similar.
|
||||
* In the following example, this step will propagate taint from
|
||||
* `source` to `sink`:
|
||||
*
|
||||
* ```rb
|
||||
* x = source
|
||||
* result = YAML.parse(x)
|
||||
* sink result.to_ruby # Unsafe call
|
||||
* ```
|
||||
*/
|
||||
private class YamlParseStep extends AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode yamlParserMethod |
|
||||
succ = yamlParserMethod.getAMethodCall("to_ruby") and
|
||||
(
|
||||
yamlParserMethod = yamlNode().getAMethodCall(["parse", "parse_stream"]) and
|
||||
pred = [yamlParserMethod.getArgument(0), yamlParserMethod.getKeywordArgument("yaml")]
|
||||
or
|
||||
yamlParserMethod = yamlNode().getAMethodCall("parse_file") and
|
||||
pred = [yamlParserMethod.getArgument(0), yamlParserMethod.getKeywordArgument("filename")]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private API::Node yamlNode() { result = API::getTopLevelMember(["YAML", "Psych"]) }
|
||||
@@ -412,9 +412,7 @@ module Filters {
|
||||
/**
|
||||
* Holds if `n` is the self parameter of method `m`.
|
||||
*/
|
||||
private predicate selfParameter(DataFlowPrivate::SelfParameterNode n, Method m) {
|
||||
m = n.getMethod()
|
||||
}
|
||||
private predicate selfParameter(DataFlow::SelfParameterNode n, Method m) { m = n.getCallable() }
|
||||
|
||||
/**
|
||||
* A class defining additional jump steps arising from filters.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Models MIME type handling using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.Regexp as RE
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Models MIME type handling using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
module Mime {
|
||||
/**
|
||||
* Type summaries for the `Mime::Type` class, i.e. method calls that produce new
|
||||
* `Mime::Type` instances.
|
||||
*/
|
||||
private class MimeTypeTypeSummary extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// type1;type2;path
|
||||
row =
|
||||
[
|
||||
// Mime[type] : Mime::Type (omitted)
|
||||
// Method names with brackets like [] cannot be represented in MaD.
|
||||
// Mime.fetch(type) : Mime::Type
|
||||
"Mime::Type;Mime!;Method[fetch].ReturnValue",
|
||||
// Mime::Type.lookup(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[lookup].ReturnValue",
|
||||
// Mime::Type.lookup_by_extension(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[lookup_by_extension].ReturnValue",
|
||||
// Mime::Type.register(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[register].ReturnValue",
|
||||
// Mime::Type.register_alias(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[register_alias].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to `Mime::Type#match?`, which is converted to a RegExp via
|
||||
* `Regexp.new`.
|
||||
*/
|
||||
class MimeTypeMatchRegExpInterpretation extends RE::RegExpInterpretation::Range {
|
||||
MimeTypeMatchRegExpInterpretation() {
|
||||
this = ModelOutput::getATypeNode("Mime::Type").getAMethodCall(["match?", "=~"]).getArgument(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/** Modeling for `ActionDispatch::Request`. */
|
||||
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
|
||||
/** Modeling for `ActionDispatch::Request`. */
|
||||
module Request {
|
||||
/**
|
||||
* A method call against an `ActionDispatch::Request` instance.
|
||||
*/
|
||||
private class RequestMethodCall extends DataFlow::CallNode {
|
||||
RequestMethodCall() {
|
||||
any(ActionControllerClass cls)
|
||||
.getSelf()
|
||||
.getAMethodCall("request")
|
||||
.(DataFlow::LocalSourceNode)
|
||||
.flowsTo(this.getReceiver()) or
|
||||
this =
|
||||
API::getTopLevelMember("ActionDispatch")
|
||||
.getMember("Request")
|
||||
.getInstance()
|
||||
.getAMethodCall(_)
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class RequestInputAccess extends RequestMethodCall,
|
||||
Http::Server::RequestInputAccess::Range
|
||||
{
|
||||
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns request parameters.
|
||||
*/
|
||||
private class ParametersCall extends RequestInputAccess {
|
||||
ParametersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
|
||||
"filtered_parameters"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns part or all of the request path. */
|
||||
private class PathCall extends RequestInputAccess {
|
||||
PathCall() {
|
||||
this.getMethodName() =
|
||||
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns a specific request header. */
|
||||
private class HeadersCall extends RequestInputAccess {
|
||||
HeadersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
|
||||
"host_authority", "content_type", "host", "hostname", "accept_encoding",
|
||||
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
|
||||
]
|
||||
or
|
||||
// Request headers are prefixed with `HTTP_` to distinguish them from
|
||||
// "headers" supplied by Rack middleware.
|
||||
this.getMethodName() = ["get_header", "fetch_header"] and
|
||||
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
// TODO: each_header
|
||||
/**
|
||||
* A method call on `request` which returns part or all of the host.
|
||||
* This can be influenced by headers such as Host and X-Forwarded-Host.
|
||||
*/
|
||||
private class HostCall extends RequestInputAccess {
|
||||
HostCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
|
||||
"forwarded_host", "port", "forwarded_port"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which is influenced by one or more request
|
||||
* headers.
|
||||
*/
|
||||
private class HeaderTaintedCall extends RequestInputAccess {
|
||||
HeaderTaintedCall() {
|
||||
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns the request body. */
|
||||
private class BodyCall extends RequestInputAccess {
|
||||
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
|
||||
}
|
||||
|
||||
private module Env {
|
||||
abstract private class Env extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns the rack env.
|
||||
* This is a hash containing all the information about the request. Values
|
||||
* under keys starting with `HTTP_` are user-controlled.
|
||||
*/
|
||||
private class RequestEnvCall extends DataFlow::CallNode, Env {
|
||||
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
|
||||
}
|
||||
|
||||
private import codeql.ruby.frameworks.Rack
|
||||
|
||||
private class RackEnv extends Env {
|
||||
RackEnv() { this = any(Rack::App::RequestHandler handler).getEnv().getALocalUse() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A read of a user-controlled parameter from the request env.
|
||||
*/
|
||||
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
|
||||
EnvHttpAccess() {
|
||||
this = any(Env c).getAMethodCall("[]") and
|
||||
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
|
||||
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
|
||||
)
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
|
||||
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,961 @@
|
||||
/**
|
||||
* Models routing configuration specified using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
|
||||
/**
|
||||
* Models routing configuration specified using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
module Routing {
|
||||
/**
|
||||
* A block that defines some routes.
|
||||
* Route blocks can contribute to the path or controller namespace of their child routes.
|
||||
* For example, in the block below
|
||||
* ```rb
|
||||
* scope path: "/admin" do
|
||||
* get "/dashboard", to: "admin_dashboard#show"
|
||||
* end
|
||||
* ```
|
||||
* the route defined by the call to `get` has the full path `/admin/dashboard`.
|
||||
* We track these contributions via `getPathComponent` and `getControllerComponent`.
|
||||
*/
|
||||
abstract private class RouteBlock extends TRouteBlock {
|
||||
/**
|
||||
* Gets the name of a primary CodeQL class to which this route block belongs.
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "RouteBlock" }
|
||||
|
||||
/**
|
||||
* Gets a string representation of this route block.
|
||||
*/
|
||||
string toString() { none() }
|
||||
|
||||
/**
|
||||
* Gets a `Stmt` within this route block.
|
||||
*/
|
||||
abstract Stmt getAStmt();
|
||||
|
||||
/**
|
||||
* Gets the parent of this route block, if one exists.
|
||||
*/
|
||||
abstract RouteBlock getParent();
|
||||
|
||||
/**
|
||||
* Gets the `n`th parent of this route block.
|
||||
* The zeroth parent is this block, the first parent is the direct parent of this block, etc.
|
||||
*/
|
||||
RouteBlock getParent(int n) {
|
||||
if n = 0 then result = this else result = this.getParent().getParent(n - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component of the path defined by this block, if it exists.
|
||||
*/
|
||||
abstract string getPathComponent();
|
||||
|
||||
/**
|
||||
* Gets the component of the controller namespace defined by this block, if it exists.
|
||||
*/
|
||||
abstract string getControllerComponent();
|
||||
|
||||
/**
|
||||
* Gets the location of this route block.
|
||||
*/
|
||||
abstract Location getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block that is not the top-level block.
|
||||
* This block will always have a parent.
|
||||
*/
|
||||
abstract private class NestedRouteBlock extends RouteBlock {
|
||||
RouteBlock parent;
|
||||
|
||||
override RouteBlock getParent() { result = parent }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "NestedRouteBlock" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A top-level routes block.
|
||||
* ```rb
|
||||
* Rails.application.routes.draw do
|
||||
* ...
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private class TopLevelRouteBlock extends RouteBlock, TTopLevelRouteBlock {
|
||||
MethodCall call;
|
||||
// Routing blocks create scopes which define the namespace for controllers and paths,
|
||||
// though they can be overridden in various ways.
|
||||
// The namespaces can differ, so we track them separately.
|
||||
Block block;
|
||||
|
||||
TopLevelRouteBlock() { this = TTopLevelRouteBlock(_, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "TopLevelRouteBlock" }
|
||||
|
||||
Block getBlock() { result = block }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override RouteBlock getParent() { none() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
|
||||
override string getPathComponent() { none() }
|
||||
|
||||
override string getControllerComponent() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `constraints`.
|
||||
* ```rb
|
||||
* constraints(foo: /some_regex/) do
|
||||
* get "/posts/:foo", to "posts#something"
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-constraints
|
||||
*/
|
||||
private class ConstraintsRouteBlock extends NestedRouteBlock, TConstraintsRouteBlock {
|
||||
private Block block;
|
||||
private MethodCall call;
|
||||
|
||||
ConstraintsRouteBlock() { this = TConstraintsRouteBlock(parent, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ConstraintsRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override string getPathComponent() { result = "" }
|
||||
|
||||
override string getControllerComponent() { result = "" }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `scope`.
|
||||
* ```rb
|
||||
* scope(path: "/some_path", module: "some_module") do
|
||||
* get "/posts/:foo", to "posts#something"
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-scope
|
||||
*/
|
||||
private class ScopeRouteBlock extends NestedRouteBlock, TScopeRouteBlock {
|
||||
private MethodCall call;
|
||||
private Block block;
|
||||
|
||||
ScopeRouteBlock() { this = TScopeRouteBlock(parent, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ScopeRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
|
||||
override string getPathComponent() {
|
||||
call.getKeywordArgument("path").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(call.getKeywordArgument("path")) and
|
||||
call.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getControllerComponent() {
|
||||
call.getKeywordArgument(["controller", "module"]).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `resources`.
|
||||
* ```rb
|
||||
* resources :articles do
|
||||
* get "/comments", to "comments#index"
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources
|
||||
*/
|
||||
private class ResourcesRouteBlock extends NestedRouteBlock, TResourcesRouteBlock {
|
||||
private MethodCall call;
|
||||
private Block block;
|
||||
|
||||
ResourcesRouteBlock() { this = TResourcesRouteBlock(parent, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ResourcesRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
/**
|
||||
* Gets the `resources` call that gives rise to this route block.
|
||||
*/
|
||||
MethodCall getDefiningMethodCall() { result = call }
|
||||
|
||||
override string getPathComponent() {
|
||||
exists(string resource | call.getArgument(0).getConstantValue().isStringlikeValue(resource) |
|
||||
result = resource + "/:" + singularize(resource) + "_id"
|
||||
)
|
||||
}
|
||||
|
||||
override string getControllerComponent() { result = "" }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block that is guarded by a conditional statement.
|
||||
* For example:
|
||||
* ```rb
|
||||
* if Rails.env.test?
|
||||
* get "/foo/bar", to: "foo#bar"
|
||||
* end
|
||||
* ```
|
||||
* We ignore the condition and analyze both branches to obtain as
|
||||
* much routing information as possible.
|
||||
*/
|
||||
private class ConditionalRouteBlock extends NestedRouteBlock, TConditionalRouteBlock {
|
||||
private ConditionalExpr e;
|
||||
|
||||
ConditionalRouteBlock() { this = TConditionalRouteBlock(parent, e) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ConditionalRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = e.getBranch(_).(StmtSequence).getAStmt() }
|
||||
|
||||
override string getPathComponent() { none() }
|
||||
|
||||
override string getControllerComponent() { none() }
|
||||
|
||||
override string toString() { result = e.toString() }
|
||||
|
||||
override Location getLocation() { result = e.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `namespace`.
|
||||
* ```rb
|
||||
* namespace :admin do
|
||||
* resources :posts
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-namespace
|
||||
*/
|
||||
private class NamespaceRouteBlock extends NestedRouteBlock, TNamespaceRouteBlock {
|
||||
private MethodCall call;
|
||||
private Block block;
|
||||
|
||||
NamespaceRouteBlock() { this = TNamespaceRouteBlock(parent, call, block) }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override string getPathComponent() { result = this.getNamespace() }
|
||||
|
||||
override string getControllerComponent() { result = this.getNamespace() }
|
||||
|
||||
private string getNamespace() {
|
||||
call.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route configuration. This defines a combination of HTTP method and URL
|
||||
* path which should be routed to a particular controller-action pair.
|
||||
* This can arise from an explicit call to a routing method, for example:
|
||||
* ```rb
|
||||
* get "/photos", to: "photos#index"
|
||||
* ```
|
||||
* or via a convenience method like `resources`, which defines multiple routes at once:
|
||||
* ```rb
|
||||
* resources :photos
|
||||
* ```
|
||||
*/
|
||||
class Route extends TRoute instanceof RouteImpl {
|
||||
/**
|
||||
* Gets the name of a primary CodeQL class to which this route belongs.
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "Route" }
|
||||
|
||||
/** Gets a string representation of this route. */
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets the location of the method call that defines this route.
|
||||
*/
|
||||
Location getLocation() { result = super.getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the full controller targeted by this route.
|
||||
*/
|
||||
string getController() { result = super.getController() }
|
||||
|
||||
/**
|
||||
* Gets the action targeted by this route.
|
||||
*/
|
||||
string getAction() { result = super.getAction() }
|
||||
|
||||
/**
|
||||
* Gets the HTTP method of this route.
|
||||
* The result is one of [get, post, put, patch, delete].
|
||||
*/
|
||||
string getHttpMethod() { result = super.getHttpMethod() }
|
||||
|
||||
/**
|
||||
* Gets the full path of the route.
|
||||
*/
|
||||
string getPath() { result = super.getPath() }
|
||||
|
||||
/**
|
||||
* Get a URL capture. This is a wildcard URL segment whose value is placed in `params`.
|
||||
* For example, in
|
||||
* ```ruby
|
||||
* get "/foo/:bar/baz", to: "users#index"
|
||||
* ```
|
||||
* the capture is `:bar`.
|
||||
*/
|
||||
string getACapture() { result = super.getACapture() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of `Route`.
|
||||
* This class is abstract and is thus kept private so we don't expose it to
|
||||
* users.
|
||||
* Extend this class to add new instances of routes.
|
||||
*/
|
||||
abstract private class RouteImpl extends TRoute {
|
||||
/**
|
||||
* Gets the name of a primary CodeQL class to which this route belongs.
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "RouteImpl" }
|
||||
|
||||
MethodCall method;
|
||||
|
||||
/** Gets a string representation of this route. */
|
||||
string toString() { result = method.toString() }
|
||||
|
||||
/**
|
||||
* Gets the location of the method call that defines this route.
|
||||
*/
|
||||
Location getLocation() { result = method.getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the method call that defines this route.
|
||||
*/
|
||||
MethodCall getDefiningMethodCall() { result = method }
|
||||
|
||||
/**
|
||||
* Get the last component of the path. For example, in
|
||||
* ```rb
|
||||
* get "/photos", to: "photos#index"
|
||||
* ```
|
||||
* this is `/photos`.
|
||||
* If the string has any interpolations, this predicate will have no result.
|
||||
*/
|
||||
abstract string getLastPathComponent();
|
||||
|
||||
/**
|
||||
* Gets the HTTP method of this route.
|
||||
* The result is one of [get, post, put, patch, delete].
|
||||
*/
|
||||
abstract string getHttpMethod();
|
||||
|
||||
/**
|
||||
* Gets the last controller component.
|
||||
* This is the controller specified in the route itself.
|
||||
*/
|
||||
abstract string getLastControllerComponent();
|
||||
|
||||
/**
|
||||
* Gets a component of the controller.
|
||||
* This behaves identically to `getPathComponent`, but for controller information.
|
||||
*/
|
||||
string getControllerComponent(int n) {
|
||||
if n = 0
|
||||
then result = this.getLastControllerComponent()
|
||||
else result = this.getParentBlock().getParent(n - 1).getControllerComponent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full controller targeted by this route.
|
||||
*/
|
||||
string getController() {
|
||||
result =
|
||||
concat(int n |
|
||||
this.getControllerComponent(n) != ""
|
||||
|
|
||||
this.getControllerComponent(n), "/" order by n desc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the action targeted by this route.
|
||||
*/
|
||||
abstract string getAction();
|
||||
|
||||
/**
|
||||
* Gets the parent `RouteBlock` of this route.
|
||||
*/
|
||||
abstract RouteBlock getParentBlock();
|
||||
|
||||
/**
|
||||
* Gets a component of the path. Components are numbered from 0 up, where 0
|
||||
* is the last component, 1 is the second-last, etc.
|
||||
* For example, in the following route:
|
||||
*
|
||||
* ```rb
|
||||
* namespace path: "foo" do
|
||||
* namespace path: "bar" do
|
||||
* get "baz", to: "foo#bar
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the components are:
|
||||
*
|
||||
* | n | component
|
||||
* |---|----------
|
||||
* | 0 | baz
|
||||
* | 1 | bar
|
||||
* | 2 | foo
|
||||
*/
|
||||
string getPathComponent(int n) {
|
||||
if n = 0
|
||||
then result = this.getLastPathComponent()
|
||||
else result = this.getParentBlock().getParent(n - 1).getPathComponent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path of the route.
|
||||
*/
|
||||
string getPath() {
|
||||
result =
|
||||
concat(int n |
|
||||
this.getPathComponent(n) != ""
|
||||
|
|
||||
// Strip leading and trailing slashes from each path component before combining
|
||||
stripSlashes(this.getPathComponent(n)), "/" order by n desc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL capture. This is a wildcard URL segment whose value is placed in `params`.
|
||||
* For example, in
|
||||
* ```ruby
|
||||
* get "/foo/:bar/baz", to: "users#index"
|
||||
* ```
|
||||
* the capture is `:bar`.
|
||||
* We don't currently make use of this, but it may be useful in future to more accurately
|
||||
* model the contents of the `params` hash.
|
||||
*/
|
||||
string getACapture() { result = this.getPathComponent(_).regexpFind(":[^:/]+", _, _) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by an explicit call to `get`, `post`, etc.
|
||||
*
|
||||
* ```ruby
|
||||
* get "/photos", to: "photos#index"
|
||||
* put "/photos/:id", to: "photos#update"
|
||||
* ```
|
||||
*/
|
||||
private class ExplicitRoute extends RouteImpl, TExplicitRoute {
|
||||
RouteBlock parentBlock;
|
||||
|
||||
ExplicitRoute() { this = TExplicitRoute(parentBlock, method) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ExplicitRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parentBlock }
|
||||
|
||||
override string getLastPathComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(method.getKeywordArgument("controller")) and
|
||||
(
|
||||
result = extractController(this.getActionString())
|
||||
or
|
||||
// If controller is not specified, and we're in a `resources` route block, use the controller of that route.
|
||||
// For example, in
|
||||
//
|
||||
// resources :posts do
|
||||
// get "timestamp", to: :timestamp
|
||||
// end
|
||||
//
|
||||
// The route is GET /posts/:post_id/timestamp => posts/timestamp
|
||||
not exists(extractController(this.getActionString())) and
|
||||
exists(ResourcesRoute r |
|
||||
r.getDefiningMethodCall() = parentBlock.(ResourcesRouteBlock).getDefiningMethodCall()
|
||||
|
|
||||
result = r.getLastControllerComponent()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private string getActionString() {
|
||||
method.getKeywordArgument("to").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
method.getKeywordArgument("to").(MethodCall).getMethodName() = "redirect" and
|
||||
result = "<redirect>#<redirect>"
|
||||
}
|
||||
|
||||
override string getAction() {
|
||||
// get "/photos", action: "index"
|
||||
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(method.getKeywordArgument("action")) and
|
||||
(
|
||||
// get "/photos", to: "photos#index"
|
||||
// get "/photos", to: redirect("some_url")
|
||||
result = extractAction(this.getActionString())
|
||||
or
|
||||
// resources :photos, only: [] do
|
||||
// get "/", to: "index"
|
||||
// end
|
||||
not exists(extractAction(this.getActionString())) and result = this.getActionString()
|
||||
or
|
||||
// get :some_action
|
||||
not exists(this.getActionString()) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
)
|
||||
}
|
||||
|
||||
override string getHttpMethod() { result = method.getMethodName().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by a call to `resources`.
|
||||
*
|
||||
* ```ruby
|
||||
* resources :photos
|
||||
* ```
|
||||
* This creates eight routes, equivalent to the following code:
|
||||
* ```ruby
|
||||
* get "/photos", to: "photos#index"
|
||||
* get "/photos/new", to: "photos#new"
|
||||
* post "/photos", to: "photos#create"
|
||||
* get "/photos/:id", to: "photos#show"
|
||||
* get "/photos/:id/edit", to: "photos#edit"
|
||||
* patch "/photos/:id", to: "photos#update"
|
||||
* put "/photos/:id", to: "photos#update"
|
||||
* delete "/photos/:id", to: "photos#delete"
|
||||
* ```
|
||||
*
|
||||
* `resources` can take a block. Any routes defined inside the block will inherit a path component of
|
||||
* `/<resource>/:<resource>_id`. For example:
|
||||
*
|
||||
* ```ruby
|
||||
* resources :photos do
|
||||
* get "/foo", to: "photos#foo"
|
||||
* end
|
||||
* ```
|
||||
* This creates the eight default routes, plus one more, which is nested under "/photos/:photo_id", equivalent to:
|
||||
* ```ruby
|
||||
* get "/photos/:photo_id/foo", to: "photos#foo"
|
||||
* ```
|
||||
*/
|
||||
private class ResourcesRoute extends RouteImpl, TResourcesRoute {
|
||||
RouteBlock parent;
|
||||
string action;
|
||||
string httpMethod;
|
||||
string pathComponent;
|
||||
|
||||
ResourcesRoute() {
|
||||
exists(string resource |
|
||||
this = TResourcesRoute(parent, method, action) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
isDefaultResourceRoute(resource, httpMethod, pathComponent, action)
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ResourcesRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parent }
|
||||
|
||||
override string getLastPathComponent() { result = pathComponent }
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getAction() { result = action }
|
||||
|
||||
override string getHttpMethod() { result = httpMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by a call to `resource`.
|
||||
* This is like a `resources` route, but creates routes for a singular resource.
|
||||
* This means there's no index route, no id parameter, and the resource name is expected to be singular.
|
||||
* It will still be routed to a pluralised controller name.
|
||||
* ```ruby
|
||||
* resource :account
|
||||
* ```
|
||||
*/
|
||||
private class SingularResourceRoute extends RouteImpl, TResourceRoute {
|
||||
RouteBlock parent;
|
||||
string action;
|
||||
string httpMethod;
|
||||
string pathComponent;
|
||||
|
||||
SingularResourceRoute() {
|
||||
exists(string resource |
|
||||
this = TResourceRoute(parent, method, action) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
isDefaultSingularResourceRoute(resource, httpMethod, pathComponent, action)
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "SingularResourceRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parent }
|
||||
|
||||
override string getLastPathComponent() { result = pathComponent }
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getAction() { result = action }
|
||||
|
||||
override string getHttpMethod() { result = httpMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by a call to `match`.
|
||||
* This is a lower level primitive that powers `get`, `post` etc.
|
||||
* The first argument can be a path or a (path, controller-action) pair.
|
||||
* The controller, action and HTTP method can be specified with the
|
||||
* `controller:`, `action:` and `via:` keyword arguments, respectively.
|
||||
* ```ruby
|
||||
* match 'photos/:id' => 'photos#show', via: :get
|
||||
* match 'photos/:id', to: 'photos#show', via: :get
|
||||
* match 'photos/:id', to 'photos#show', via: [:get, :post]
|
||||
* match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
||||
* ```
|
||||
*/
|
||||
private class MatchRoute extends RouteImpl, TMatchRoute {
|
||||
private RouteBlock parent;
|
||||
|
||||
MatchRoute() { this = TMatchRoute(parent, method) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "MatchRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parent }
|
||||
|
||||
override string getLastPathComponent() {
|
||||
[method.getArgument(0), method.getArgument(0).(Pair).getKey()]
|
||||
.getConstantValue()
|
||||
.isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
result =
|
||||
extractController(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
|
||||
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractController(method
|
||||
.getArgument(0)
|
||||
.(Pair)
|
||||
.getValue()
|
||||
.getConstantValue()
|
||||
.getStringlikeValue())
|
||||
}
|
||||
|
||||
override string getHttpMethod() {
|
||||
exists(string via |
|
||||
method.getKeywordArgument("via").getConstantValue().isStringlikeValue(via)
|
||||
|
|
||||
via = "all" and result = anyHttpMethod()
|
||||
or
|
||||
via != "all" and result = via
|
||||
)
|
||||
or
|
||||
result =
|
||||
method
|
||||
.getKeywordArgument("via")
|
||||
.(ArrayLiteral)
|
||||
.getElement(_)
|
||||
.getConstantValue()
|
||||
.getStringlikeValue()
|
||||
}
|
||||
|
||||
override string getAction() {
|
||||
result =
|
||||
extractAction(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
|
||||
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractAction(method
|
||||
.getArgument(0)
|
||||
.(Pair)
|
||||
.getValue()
|
||||
.getConstantValue()
|
||||
.getStringlikeValue())
|
||||
}
|
||||
}
|
||||
|
||||
private import Cached
|
||||
|
||||
/**
|
||||
* This module contains the IPA types backing `RouteBlock` and `Route`, cached for performance.
|
||||
*/
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
newtype TRouteBlock =
|
||||
TTopLevelRouteBlock(MethodCall routes, MethodCall draw, Block b) {
|
||||
routes.getMethodName() = "routes" and
|
||||
draw.getMethodName() = "draw" and
|
||||
draw.getReceiver() = routes and
|
||||
draw.getBlock() = b
|
||||
} or
|
||||
// constraints(foo: /some_regex/) do
|
||||
// get "/posts/:foo", to "posts#something"
|
||||
// end
|
||||
TConstraintsRouteBlock(RouteBlock parent, MethodCall constraints, Block b) {
|
||||
parent.getAStmt() = constraints and
|
||||
constraints.getMethodName() = "constraints" and
|
||||
constraints.getBlock() = b
|
||||
} or
|
||||
// scope(path: "/some_path", module: "some_module") do
|
||||
// get "/posts/:foo", to "posts#something"
|
||||
// end
|
||||
TScopeRouteBlock(RouteBlock parent, MethodCall scope, Block b) {
|
||||
parent.getAStmt() = scope and scope.getMethodName() = "scope" and scope.getBlock() = b
|
||||
} or
|
||||
// resources :articles do
|
||||
// get "/comments", to "comments#index"
|
||||
// end
|
||||
TResourcesRouteBlock(RouteBlock parent, MethodCall resources, Block b) {
|
||||
parent.getAStmt() = resources and
|
||||
resources.getMethodName() = "resources" and
|
||||
resources.getBlock() = b
|
||||
} or
|
||||
// A conditional statement guarding some routes.
|
||||
// We ignore the condition and analyze both branches to obtain as
|
||||
// much routing information as possible.
|
||||
TConditionalRouteBlock(RouteBlock parent, ConditionalExpr e) { parent.getAStmt() = e } or
|
||||
// namespace :admin do
|
||||
// resources :posts
|
||||
// end
|
||||
TNamespaceRouteBlock(RouteBlock parent, MethodCall namespace, Block b) {
|
||||
parent.getAStmt() = namespace and
|
||||
namespace.getMethodName() = "namespace" and
|
||||
namespace.getBlock() = b
|
||||
}
|
||||
|
||||
/**
|
||||
* A route configuration. See `Route` for more info
|
||||
*/
|
||||
cached
|
||||
newtype TRoute =
|
||||
/**
|
||||
* See `ExplicitRoute`
|
||||
*/
|
||||
TExplicitRoute(RouteBlock b, MethodCall m) {
|
||||
b.getAStmt() = m and m.getMethodName() = anyHttpMethod()
|
||||
} or
|
||||
/**
|
||||
* See `ResourcesRoute`
|
||||
*/
|
||||
TResourcesRoute(RouteBlock b, MethodCall m, string action) {
|
||||
b.getAStmt() = m and
|
||||
m.getMethodName() = "resources" and
|
||||
action in ["show", "index", "new", "edit", "create", "update", "destroy"] and
|
||||
applyActionFilters(m, action)
|
||||
} or
|
||||
/**
|
||||
* See `SingularResourceRoute`
|
||||
*/
|
||||
TResourceRoute(RouteBlock b, MethodCall m, string action) {
|
||||
b.getAStmt() = m and
|
||||
m.getMethodName() = "resource" and
|
||||
action in ["show", "new", "edit", "create", "update", "destroy"] and
|
||||
applyActionFilters(m, action)
|
||||
} or
|
||||
/**
|
||||
* See `MatchRoute`
|
||||
*/
|
||||
TMatchRoute(RouteBlock b, MethodCall m) { b.getAStmt() = m and m.getMethodName() = "match" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Several routing methods support the keyword arguments `only:` and `except:`.
|
||||
* - `only:` restricts the set of actions to just those in the argument.
|
||||
* - `except:` removes the given actions from the set.
|
||||
*/
|
||||
bindingset[action]
|
||||
private predicate applyActionFilters(MethodCall m, string action) {
|
||||
// Respect the `only` keyword argument, which restricts the set of actions.
|
||||
(
|
||||
not exists(m.getKeywordArgument("only"))
|
||||
or
|
||||
exists(Expr only | only = m.getKeywordArgument("only") |
|
||||
[only.(ArrayLiteral).getElement(_), only].getConstantValue().isStringlikeValue(action)
|
||||
)
|
||||
) and
|
||||
// Respect the `except` keyword argument, which removes actions from the default set.
|
||||
(
|
||||
not exists(m.getKeywordArgument("except"))
|
||||
or
|
||||
exists(Expr except | except = m.getKeywordArgument("except") |
|
||||
[except.(ArrayLiteral).getElement(_), except].getConstantValue().getStringlikeValue() !=
|
||||
action
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the (resource, method, path, action) combination would be generated by a call to `resources :<resource>`.
|
||||
*/
|
||||
bindingset[resource]
|
||||
private predicate isDefaultResourceRoute(
|
||||
string resource, string method, string path, string action
|
||||
) {
|
||||
action = "create" and
|
||||
(method = "post" and path = "/" + resource)
|
||||
or
|
||||
action = "index" and
|
||||
(method = "get" and path = "/" + resource)
|
||||
or
|
||||
action = "new" and
|
||||
(method = "get" and path = "/" + resource + "/new")
|
||||
or
|
||||
action = "edit" and
|
||||
(method = "get" and path = "/" + resource + ":id/edit")
|
||||
or
|
||||
action = "show" and
|
||||
(method = "get" and path = "/" + resource + "/:id")
|
||||
or
|
||||
action = "update" and
|
||||
(method in ["put", "patch"] and path = "/" + resource + "/:id")
|
||||
or
|
||||
action = "destroy" and
|
||||
(method = "delete" and path = "/" + resource + "/:id")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the (resource, method, path, action) combination would be generated by a call to `resource :<resource>`.
|
||||
*/
|
||||
bindingset[resource]
|
||||
private predicate isDefaultSingularResourceRoute(
|
||||
string resource, string method, string path, string action
|
||||
) {
|
||||
action = "create" and
|
||||
(method = "post" and path = "/" + resource)
|
||||
or
|
||||
action = "new" and
|
||||
(method = "get" and path = "/" + resource + "/new")
|
||||
or
|
||||
action = "edit" and
|
||||
(method = "get" and path = "/" + resource + "/edit")
|
||||
or
|
||||
action = "show" and
|
||||
(method = "get" and path = "/" + resource)
|
||||
or
|
||||
action = "update" and
|
||||
(method in ["put", "patch"] and path = "/" + resource)
|
||||
or
|
||||
action = "destroy" and
|
||||
(method = "delete" and path = "/" + resource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the controller from a Rails routing string
|
||||
* ```
|
||||
* extractController("posts#show") = "posts"
|
||||
* ```
|
||||
*/
|
||||
bindingset[input]
|
||||
private string extractController(string input) { result = input.regexpCapture("([^#]+)#.+", 1) }
|
||||
|
||||
/**
|
||||
* Extract the action from a Rails routing string
|
||||
* ```
|
||||
* extractAction("posts#show") = "show"
|
||||
*/
|
||||
bindingset[input]
|
||||
private string extractAction(string input) { result = input.regexpCapture("[^#]+#(.+)", 1) }
|
||||
|
||||
/**
|
||||
* Returns the lowercase name of every HTTP method we support.
|
||||
*/
|
||||
private string anyHttpMethod() { result = ["get", "post", "put", "patch", "delete"] }
|
||||
|
||||
/**
|
||||
* The inverse of `pluralize`. If `input` is a plural word, it returns the
|
||||
* singular version.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - photos -> photo
|
||||
* - stories -> story
|
||||
* - not_plural -> not_plural
|
||||
*/
|
||||
bindingset[input]
|
||||
private string singularize(string input) {
|
||||
exists(string prefix | prefix = input.regexpCapture("(.*)ies", 1) | result = prefix + "y")
|
||||
or
|
||||
not input.matches("%ies") and
|
||||
exists(string prefix | prefix = input.regexpCapture("(.*)s", 1) | result = prefix)
|
||||
or
|
||||
not input.regexpMatch(".*(ies|s)") and result = input
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a camel-case string to underscore case. Converts `::` to `/`.
|
||||
* This can be used to convert ActiveRecord controller names to a canonical form that matches the routes they handle.
|
||||
* Note: All-uppercase words like `CONSTANT` are not handled correctly.
|
||||
*/
|
||||
bindingset[base]
|
||||
string underscore(string base) {
|
||||
base = "" and result = ""
|
||||
or
|
||||
result =
|
||||
base.charAt(0).toLowerCase() +
|
||||
// Walk along the string, keeping track of the previous character
|
||||
// in order to determine if we've crossed a boundary.
|
||||
// Boundaries are:
|
||||
// - lower case to upper case: B in FooBar
|
||||
// - entering a namespace: B in Foo::Bar
|
||||
concat(int i, string prev, string char |
|
||||
prev = base.charAt(i) and
|
||||
char = base.charAt(i + 1)
|
||||
|
|
||||
any(string s |
|
||||
char.regexpMatch("[A-Za-z0-9]") and
|
||||
if prev = ":"
|
||||
then s = "/" + char.toLowerCase()
|
||||
else
|
||||
if prev.isLowercase() and char.isUppercase()
|
||||
then s = "_" + char.toLowerCase()
|
||||
else s = char.toLowerCase()
|
||||
)
|
||||
order by
|
||||
i
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip leading and trailing forward slashes from the string.
|
||||
*/
|
||||
bindingset[input]
|
||||
private string stripSlashes(string input) {
|
||||
result = input.regexpReplaceAll("^/+(.+)$", "$1").regexpReplaceAll("^(.*[^/])/+$", "$1")
|
||||
}
|
||||
}
|
||||
@@ -583,7 +583,8 @@ module Array {
|
||||
|
||||
private class DeleteUnknownSummary extends DeleteSummary {
|
||||
DeleteUnknownSummary() {
|
||||
this = "delete" and
|
||||
// Note: take care to avoid a name clash with the "delete" summary from String.qll
|
||||
this = "delete-unknown-key" and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
|
||||
}
|
||||
|
||||
@@ -2066,7 +2067,11 @@ module Enumerable {
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
|
||||
output = "Argument[block].Parameter[0]" and
|
||||
preservesValue = true
|
||||
or
|
||||
input = "Argument[block].ReturnValue" and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ module Gem {
|
||||
|
||||
GemSpec() {
|
||||
this.getExtension() = "gemspec" and
|
||||
specCall = API::root().getMember("Gem").getMember("Specification").getMethod("new") and
|
||||
specCall = API::getTopLevelMember("Gem").getMember("Specification").getMethod("new") and
|
||||
specCall.getLocation().getFile() = this
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ module Gem {
|
||||
.getBlock()
|
||||
.getParameter(0)
|
||||
.getMethod(name + "=")
|
||||
.getParameter(0)
|
||||
.getArgument(0)
|
||||
.asSink()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
|
||||
@@ -199,11 +199,13 @@ module Hash {
|
||||
}
|
||||
}
|
||||
|
||||
private class AssocUnknownSummary extends AssocSummary {
|
||||
AssocUnknownSummary() {
|
||||
this = "assoc" and
|
||||
mc.getNumberOfArguments() = 1 and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
|
||||
private class AssocUnknownSummary extends SummarizedCallable {
|
||||
AssocUnknownSummary() { this = "assoc-unknown-arg" }
|
||||
|
||||
override MethodCall getACallSimple() {
|
||||
result.getMethodName() = "assoc" and
|
||||
result.getNumberOfArguments() = 1 and
|
||||
not exists(DataFlow::Content::getKnownElementIndex(result.getArgument(0)))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
|
||||
@@ -19,7 +19,8 @@ module Kernel {
|
||||
*/
|
||||
class KernelMethodCall extends DataFlow::CallNode {
|
||||
KernelMethodCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall(_)
|
||||
// Match Kernel calls using local flow, to avoid finding singleton calls on subclasses
|
||||
this = DataFlow::getConstant("Kernel").getAMethodCall(_)
|
||||
or
|
||||
this.asExpr().getExpr() instanceof UnknownMethodCall and
|
||||
(
|
||||
|
||||
@@ -44,7 +44,7 @@ private class SummarizedCallableFromModel extends SummarizedCallable {
|
||||
override Call getACall() {
|
||||
exists(API::MethodAccessNode base |
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
result = base.getCallNode().asExpr().getExpr()
|
||||
result = base.asCall().asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -454,6 +454,14 @@ private API::Node getNodeFromPath(string type, AccessPath path, int n) {
|
||||
or
|
||||
// Apply a type step
|
||||
typeStep(getNodeFromPath(type, path, n), result)
|
||||
or
|
||||
// Apply a fuzzy step (without advancing 'n')
|
||||
path.getToken(n).getName() = "Fuzzy" and
|
||||
result = Specific::getAFuzzySuccessor(getNodeFromPath(type, path, n))
|
||||
or
|
||||
// Skip a fuzzy step (advance 'n' without changing the current node)
|
||||
path.getToken(n - 1).getName() = "Fuzzy" and
|
||||
result = getNodeFromPath(type, path, n - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -500,6 +508,14 @@ private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath, int n)
|
||||
// will themselves find by following type-steps.
|
||||
n > 0 and
|
||||
n < subPath.getNumToken()
|
||||
or
|
||||
// Apply a fuzzy step (without advancing 'n')
|
||||
subPath.getToken(n).getName() = "Fuzzy" and
|
||||
result = Specific::getAFuzzySuccessor(getNodeFromSubPath(base, subPath, n))
|
||||
or
|
||||
// Skip a fuzzy step (advance 'n' without changing the current node)
|
||||
subPath.getToken(n - 1).getName() = "Fuzzy" and
|
||||
result = getNodeFromSubPath(base, subPath, n - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,7 +577,7 @@ private Specific::InvokeNode getInvocationFromPath(string type, AccessPath path)
|
||||
*/
|
||||
bindingset[name]
|
||||
private predicate isValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Argument", "Parameter", "ReturnValue", "WithArity", "TypeVar"]
|
||||
name = ["Argument", "Parameter", "ReturnValue", "WithArity", "TypeVar", "Fuzzy"]
|
||||
or
|
||||
Specific::isExtraValidTokenNameInIdentifyingAccessPath(name)
|
||||
}
|
||||
@@ -572,7 +588,7 @@ private predicate isValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
*/
|
||||
bindingset[name]
|
||||
private predicate isValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
name = "ReturnValue"
|
||||
name = ["ReturnValue", "Fuzzy"]
|
||||
or
|
||||
Specific::isExtraValidNoArgumentTokenInIdentifyingAccessPath(name)
|
||||
}
|
||||
@@ -643,6 +659,15 @@ module ModelOutput {
|
||||
baseNode = getInvocationFromPath(type, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a `baseNode` is a callable identified by the `type,path` part of a summary row.
|
||||
*/
|
||||
cached
|
||||
predicate resolvedSummaryRefBase(string type, string path, API::Node baseNode) {
|
||||
summaryModel(type, path, _, _, _) and
|
||||
baseNode = getNodeFromPath(type, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is seen as an instance of `type` due to a type definition
|
||||
* contributed by a CSV model.
|
||||
@@ -653,6 +678,17 @@ module ModelOutput {
|
||||
|
||||
import Cached
|
||||
import Specific::ModelOutputSpecific
|
||||
private import codeql.mad.ModelValidation as SharedModelVal
|
||||
|
||||
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
|
||||
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind) }
|
||||
|
||||
predicate sinkKind(string kind) { sinkModel(_, _, kind) }
|
||||
|
||||
predicate sourceKind(string kind) { sourceModel(_, _, kind) }
|
||||
}
|
||||
|
||||
private module KindVal = SharedModelVal::KindValidation<KindValConfig>;
|
||||
|
||||
/**
|
||||
* Gets an error message relating to an invalid CSV row in a model.
|
||||
@@ -698,5 +734,8 @@ module ModelOutput {
|
||||
not isValidNoArgumentTokenInIdentifyingAccessPath(token.getName()) and
|
||||
result = "Invalid token '" + token + "' is missing its arguments, in access path: " + path
|
||||
)
|
||||
or
|
||||
// Check for invalid model kinds
|
||||
result = KindVal::getInvalidModelKind()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +99,10 @@ API::Node getExtraNodeFromPath(string type, AccessPath path, int n) {
|
||||
// A row of form `any;Method[foo]` should match any method named `foo`.
|
||||
type = "any" and
|
||||
n = 1 and
|
||||
exists(EntryPointFromAnyType entry |
|
||||
methodMatchedByName(path, entry.getName()) and
|
||||
result = entry.getANode()
|
||||
exists(string methodName, DataFlow::CallNode call |
|
||||
methodMatchedByName(path, methodName) and
|
||||
call.getMethodName() = methodName and
|
||||
result.(API::MethodAccessNode).asCall() = call
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,20 +113,10 @@ API::Node getExtraNodeFromType(string type) {
|
||||
constRef = getConstantFromConstPath(consts)
|
||||
|
|
||||
suffix = "!" and
|
||||
(
|
||||
result.(API::Node::Internal).asSourceInternal() = constRef
|
||||
or
|
||||
result.(API::Node::Internal).asSourceInternal() =
|
||||
constRef.getADescendentModule().getAnOwnModuleSelf()
|
||||
)
|
||||
result = constRef.track()
|
||||
or
|
||||
suffix = "" and
|
||||
(
|
||||
result.(API::Node::Internal).asSourceInternal() = constRef.getAMethodCall("new")
|
||||
or
|
||||
result.(API::Node::Internal).asSourceInternal() =
|
||||
constRef.getADescendentModule().getAnInstanceSelf()
|
||||
)
|
||||
result = constRef.track().getInstance()
|
||||
)
|
||||
or
|
||||
type = "" and
|
||||
@@ -145,21 +136,6 @@ private predicate methodMatchedByName(AccessPath path, string methodName) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An API graph entry point corresponding to a method name such as `foo` in `;any;Method[foo]`.
|
||||
*
|
||||
* This ensures that the API graph rooted in that method call is materialized.
|
||||
*/
|
||||
private class EntryPointFromAnyType extends API::EntryPoint {
|
||||
string name;
|
||||
|
||||
EntryPointFromAnyType() { this = "AnyMethod[" + name + "]" and methodMatchedByName(_, name) }
|
||||
|
||||
override DataFlow::CallNode getACall() { result.getMethodName() = name }
|
||||
|
||||
string getName() { result = name }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Ruby-specific API graph successor of `node` reachable by resolving `token`.
|
||||
*/
|
||||
@@ -175,9 +151,11 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
result = node.getInstance()
|
||||
or
|
||||
token.getName() = "Parameter" and
|
||||
result =
|
||||
node.getASuccessor(API::Label::getLabelFromParameterPosition(FlowSummaryImplSpecific::parseArgBody(token
|
||||
.getAnArgument())))
|
||||
exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos |
|
||||
argPos = FlowSummaryImplSpecific::parseParamBody(token.getAnArgument()) and
|
||||
DataFlowDispatch::parameterMatch(paramPos, argPos) and
|
||||
result = node.getParameterAtPosition(paramPos)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ContentSet contents |
|
||||
SummaryComponent::content(contents) = FlowSummaryImplSpecific::interpretComponentSpecific(token) and
|
||||
@@ -191,9 +169,30 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
bindingset[token]
|
||||
API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathToken token) {
|
||||
token.getName() = "Argument" and
|
||||
exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos |
|
||||
paramPos = FlowSummaryImplSpecific::parseArgBody(token.getAnArgument()) and
|
||||
DataFlowDispatch::parameterMatch(paramPos, argPos) and
|
||||
result = node.getArgumentAtPosition(argPos)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
API::Node getAFuzzySuccessor(API::Node node) {
|
||||
result = node.getAMember()
|
||||
or
|
||||
result = node.getMethod(_)
|
||||
or
|
||||
result =
|
||||
node.getASuccessor(API::Label::getLabelFromArgumentPosition(FlowSummaryImplSpecific::parseParamBody(token
|
||||
.getAnArgument())))
|
||||
node.getArgumentAtPosition(any(DataFlowDispatch::ArgumentPosition apos | not apos.isSelf()))
|
||||
or
|
||||
result =
|
||||
node.getParameterAtPosition(any(DataFlowDispatch::ParameterPosition ppos | not ppos.isSelf()))
|
||||
or
|
||||
result = node.getReturn()
|
||||
or
|
||||
result = node.getAnElement()
|
||||
or
|
||||
result = node.getInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,7 +210,7 @@ predicate invocationMatchesExtraCallSiteFilter(InvokeNode invoke, AccessPathToke
|
||||
/** An API graph node representing a method call. */
|
||||
class InvokeNode extends API::MethodAccessNode {
|
||||
/** Gets the number of arguments to the call. */
|
||||
int getNumArgument() { result = this.getCallNode().getNumberOfArguments() }
|
||||
int getNumArgument() { result = this.asCall().getNumberOfArguments() }
|
||||
}
|
||||
|
||||
/** Gets the `InvokeNode` corresponding to a specific invocation of `node`. */
|
||||
|
||||
97
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll
Normal file
97
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Provides modeling for Rack applications.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
private import Response::Private as RP
|
||||
|
||||
/**
|
||||
* A callable node that takes a single argument and, if it has a method name,
|
||||
* is called "call".
|
||||
*/
|
||||
private class PotentialRequestHandler extends DataFlow::CallableNode {
|
||||
PotentialRequestHandler() {
|
||||
this.getNumberOfParameters() = 1 and
|
||||
(
|
||||
this.(DataFlow::MethodNode).getMethodName() = "call"
|
||||
or
|
||||
this = API::getTopLevelCall("run").getArgument(0).asCallable()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode trackRackResponse(TypeBackTracker t, PotentialRequestHandler call) {
|
||||
t.start() and
|
||||
result = call.getAReturnNode().getALocalSource()
|
||||
or
|
||||
exists(TypeBackTracker t2 | result = trackRackResponse(t2, call).backtrack(t2, t))
|
||||
}
|
||||
|
||||
private RP::PotentialResponseNode trackRackResponse(PotentialRequestHandler call) {
|
||||
result = trackRackResponse(TypeBackTracker::end(), call)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides modeling for Rack applications.
|
||||
*/
|
||||
module App {
|
||||
/**
|
||||
* DEPRECATED: Use `RequestHandler` instead.
|
||||
* A class that may be a rack application.
|
||||
* This is a class that has a `call` method that takes a single argument
|
||||
* (traditionally called `env`) and returns a rack-compatible response.
|
||||
*/
|
||||
deprecated class AppCandidate extends DataFlow::ClassNode {
|
||||
private RequestHandler call;
|
||||
private RP::PotentialResponseNode resp;
|
||||
|
||||
AppCandidate() {
|
||||
call = this.getInstanceMethod("call") and
|
||||
call.getNumberOfParameters() = 1 and
|
||||
resp = trackRackResponse(call)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the environment of the request, which is the lone parameter to the `call` method.
|
||||
*/
|
||||
DataFlow::ParameterNode getEnv() { result = call.getParameter(0) }
|
||||
|
||||
/** Gets the response returned from a request to this application. */
|
||||
RP::PotentialResponseNode getResponse() { result = resp }
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable node that looks like it implements the rack specification.
|
||||
*/
|
||||
class RequestHandler extends PotentialRequestHandler {
|
||||
private RP::PotentialResponseNode resp;
|
||||
|
||||
RequestHandler() { resp = trackRackResponse(this) }
|
||||
|
||||
/** Gets the `env` parameter passed to this request handler. */
|
||||
DataFlow::ParameterNode getEnv() { result = this.getParameter(0) }
|
||||
|
||||
/** Gets a response returned from this request handler. */
|
||||
RP::PotentialResponseNode getAResponse() { result = resp }
|
||||
}
|
||||
|
||||
/** A read of the query string via `env['QUERY_STRING']`. */
|
||||
private class EnvQueryStringRead extends Http::Server::RequestInputAccess::Range {
|
||||
EnvQueryStringRead() {
|
||||
this =
|
||||
any(RequestHandler h)
|
||||
.getEnv()
|
||||
.getAnElementRead(ConstantValue::fromStringlikeValue("QUERY_STRING"))
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Rack env" }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
}
|
||||
39
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Request.qll
Normal file
39
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Request.qll
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Provides modeling for the `Request` component of the `Rack` library.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
|
||||
/**
|
||||
* Provides modeling for the `Request` component of the `Rack` library.
|
||||
*/
|
||||
module Request {
|
||||
private class RackRequest extends API::Node {
|
||||
RackRequest() { this = API::getTopLevelMember("Rack").getMember("Request").getInstance() }
|
||||
}
|
||||
|
||||
/** An access to the parameters of a request to a rack application via a `Rack::Request` instance. */
|
||||
private class RackRequestParamsAccess extends Http::Server::RequestInputAccess::Range {
|
||||
RackRequestParamsAccess() {
|
||||
this = any(RackRequest req).getAMethodCall(["params", "query_string", "[]", "fullpath"])
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Rack::Request#params" }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
|
||||
/** An access to the cookies of a request to a rack application via a `Rack::Request` instance. */
|
||||
private class RackRequestCookiesAccess extends Http::Server::RequestInputAccess::Range {
|
||||
RackRequestCookiesAccess() { this = any(RackRequest req).getAMethodCall("cookies") }
|
||||
|
||||
override string getSourceType() { result = "Rack::Request#cookies" }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::cookieInputKind() }
|
||||
}
|
||||
}
|
||||
117
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.qll
Normal file
117
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.qll
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Provides modeling for the `Response` component of the `Rack` library.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.controlflow.CfgNodes::ExprNodes
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
private import App as A
|
||||
|
||||
/** Contains implementation details for modeling `Rack::Response`. */
|
||||
module Private {
|
||||
/** 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. */
|
||||
abstract class PotentialResponseNode extends DataFlow::Node {
|
||||
/** Gets the headers returned with this response. */
|
||||
abstract DataFlow::Node getHeaders();
|
||||
|
||||
/** Gets the body of this response. */
|
||||
abstract DataFlow::Node getBody();
|
||||
}
|
||||
|
||||
/** A rack response constructed directly using an array literal. */
|
||||
private class PotentialArrayResponse extends PotentialResponseNode, DataFlow::ArrayLiteralNode {
|
||||
// [status, headers, body]
|
||||
PotentialArrayResponse() { this.getNumberOfArguments() = 3 }
|
||||
|
||||
override DataFlow::Node getHeaders() { result = this.getElement(1) }
|
||||
|
||||
override DataFlow::Node getBody() { result = this.getElement(2) }
|
||||
}
|
||||
|
||||
/** A rack response constructed by calling `finish` on an instance of `Rack::Response`. */
|
||||
private class RackResponseConstruction extends PotentialResponseNode, DataFlow::CallNode {
|
||||
private DataFlow::CallNode responseConstruction;
|
||||
|
||||
// (body, status, headers)
|
||||
RackResponseConstruction() {
|
||||
responseConstruction =
|
||||
API::getTopLevelMember("Rack").getMember("Response").getAnInstantiation() and
|
||||
this = responseConstruction.getAMethodCall() and
|
||||
this.getMethodName() = "finish"
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaders() { result = responseConstruction.getArgument(2) }
|
||||
|
||||
override DataFlow::Node getBody() { result = responseConstruction.getArgument(0) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides modeling for the `Response` component of the `Rack` library.
|
||||
*/
|
||||
module Public {
|
||||
bindingset[headerName]
|
||||
private DataFlow::Node getHeaderValue(ResponseNode resp, string headerName) {
|
||||
exists(DataFlow::Node headers | headers = resp.getHeaders() |
|
||||
// set via `headers.<header_name>=`
|
||||
exists(
|
||||
DataFlow::CallNode contentTypeAssignment, Assignment assignment,
|
||||
DataFlow::PostUpdateNode postUpdateHeaders
|
||||
|
|
||||
contentTypeAssignment.getMethodName() = headerName.replaceAll("-", "_").toLowerCase() + "=" and
|
||||
assignment =
|
||||
contentTypeAssignment.getArgument(0).(DataFlow::OperationNode).asOperationAstNode() and
|
||||
postUpdateHeaders.(DataFlow::LocalSourceNode).flowsTo(headers) and
|
||||
postUpdateHeaders.getPreUpdateNode() = contentTypeAssignment.getReceiver()
|
||||
|
|
||||
result.asExpr().getExpr() = assignment.getRightOperand()
|
||||
)
|
||||
or
|
||||
// set within a hash
|
||||
exists(DataFlow::HashLiteralNode headersHash | headersHash.flowsTo(headers) |
|
||||
result =
|
||||
headersHash
|
||||
.getElementFromKey(any(ConstantValue v |
|
||||
v.getStringlikeValue().toLowerCase() = headerName.toLowerCase()
|
||||
))
|
||||
)
|
||||
or
|
||||
// pair in a `Rack::Response.new` constructor
|
||||
exists(DataFlow::PairNode headerPair | headerPair = headers |
|
||||
headerPair.getKey().getConstantValue().getStringlikeValue().toLowerCase() =
|
||||
headerName.toLowerCase() and
|
||||
result = headerPair.getValue()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** A `DataFlow::Node` returned from a rack request. */
|
||||
class ResponseNode extends Http::Server::HttpResponse::Range instanceof Private::PotentialResponseNode
|
||||
{
|
||||
ResponseNode() { this = any(A::App::RequestHandler handler).getAResponse() }
|
||||
|
||||
override DataFlow::Node getBody() { result = this.(Private::PotentialResponseNode).getBody() }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() {
|
||||
result = getHeaderValue(this, "content-type")
|
||||
}
|
||||
|
||||
/** Gets the headers returned with this response. */
|
||||
DataFlow::Node getHeaders() { result = this.(Private::PotentialResponseNode).getHeaders() }
|
||||
|
||||
// TODO: is there a sensible value for this?
|
||||
override string getMimetypeDefault() { none() }
|
||||
}
|
||||
|
||||
/** A `DataFlow::Node` returned from a rack request that has a redirect HTTP status code. */
|
||||
class RedirectResponse extends ResponseNode, Http::Server::HttpRedirectResponse::Range {
|
||||
private DataFlow::Node redirectLocation;
|
||||
|
||||
RedirectResponse() { redirectLocation = getHeaderValue(this, "location") }
|
||||
|
||||
override DataFlow::Node getRedirectLocation() { result = redirectLocation }
|
||||
}
|
||||
}
|
||||
29
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll
Normal file
29
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Provides modeling for the `Utils` component of the `Rack` library.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
|
||||
/**
|
||||
* Provides modeling for the `Utils` component of the `Rack` library.
|
||||
*/
|
||||
module Utils {
|
||||
/** Flow summary for `Rack::Utils.parse_query`, which parses a query string. */
|
||||
private class ParseQuerySummary extends SummarizedCallable {
|
||||
ParseQuerySummary() { this = "Rack::Utils.parse_query" }
|
||||
|
||||
override MethodCall getACall() {
|
||||
result =
|
||||
API::getTopLevelMember("Rack")
|
||||
.getMember("Utils")
|
||||
.getAMethodCall("parse_query")
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,8 +195,8 @@ abstract class RegExp extends Ast::StringlikeLiteral {
|
||||
|
||||
/**
|
||||
* Holds if the character set starting at `charset_start` contains a character range
|
||||
* with lower bound found between `start` and `lower_end`
|
||||
* and upper bound found between `upper_start` and `end`.
|
||||
* with lower bound found between `start` and `lowerEnd`
|
||||
* and upper bound found between `upperStart` and `end`.
|
||||
*/
|
||||
predicate charRange(int charsetStart, int start, int lowerEnd, int upperStart, int end) {
|
||||
exists(int index |
|
||||
@@ -844,11 +844,11 @@ abstract class RegExp extends Ast::StringlikeLiteral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a qualified part is found between `start` and `part_end` and the qualifier is
|
||||
* found between `part_end` and `end`.
|
||||
* Holds if a qualified part is found between `start` and `partEnd` and the qualifier is
|
||||
* found between `partEnd` and `end`.
|
||||
*
|
||||
* `maybe_empty` is true if the part is optional.
|
||||
* `may_repeat_forever` is true if the part may be repeated unboundedly.
|
||||
* `maybeEmpty` is true if the part is optional.
|
||||
* `mayRepeatForever` is true if the part may be repeated unboundedly.
|
||||
*/
|
||||
predicate qualifiedPart(
|
||||
int start, int partEnd, int end, boolean maybeEmpty, boolean mayRepeatForever
|
||||
|
||||
@@ -21,6 +21,7 @@ private import codeql.ruby.typetracking.TypeTracker
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql.ruby.dataflow.internal.TaintTrackingPrivate as TaintTrackingPrivate
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.frameworks.core.String
|
||||
|
||||
@@ -37,43 +38,6 @@ DataFlow::LocalSourceNode strStart() {
|
||||
/** Gets a dataflow node for a regular expression literal. */
|
||||
DataFlow::LocalSourceNode regStart() { result.asExpr().getExpr() instanceof Ast::RegExpLiteral }
|
||||
|
||||
/**
|
||||
* Holds if the analysis should track flow from `nodeFrom` to `nodeTo` on top of the ordinary type-tracking steps.
|
||||
* `nodeFrom` and `nodeTo` has type `fromType` and `toType` respectively.
|
||||
* The types are either "string" or "regexp".
|
||||
*/
|
||||
predicate step(
|
||||
DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo, string fromType, string toType
|
||||
) {
|
||||
fromType = toType and
|
||||
fromType = "string" and
|
||||
(
|
||||
// include taint flow through `String` summaries
|
||||
TaintTracking::localTaintStep(nodeFrom, nodeTo) and
|
||||
nodeFrom.(DataFlowPrivate::SummaryNode).getSummarizedCallable() instanceof
|
||||
String::SummarizedCallable
|
||||
or
|
||||
// string concatenations, and
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode op |
|
||||
op = nodeTo.asExpr() and
|
||||
op.getAnOperand() = nodeFrom.asExpr() and
|
||||
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
|
||||
)
|
||||
or
|
||||
// string interpolations
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
)
|
||||
or
|
||||
fromType = "string" and
|
||||
toType = "reg" and
|
||||
exists(DataFlow::CallNode call |
|
||||
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
|
||||
nodeFrom = call.getArgument(0) and
|
||||
nodeTo = call
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node where string values that flow to the node are interpreted as regular expressions. */
|
||||
DataFlow::Node stringSink() {
|
||||
result instanceof RE::RegExpInterpretation::Range and
|
||||
@@ -91,28 +55,139 @@ DataFlow::Node stringSink() {
|
||||
/** Gets a node where regular expressions that flow to the node are used. */
|
||||
DataFlow::Node regSink() { result = any(RegexExecution exec).getRegex() }
|
||||
|
||||
/** Gets a node that is reachable by type-tracking from any string or regular expression. */
|
||||
DataFlow::LocalSourceNode forward(TypeTracker t) {
|
||||
t.start() and
|
||||
result = [strStart(), regStart()]
|
||||
or
|
||||
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
|
||||
or
|
||||
exists(TypeTracker t2 | t2 = t.continue() | step(forward(t2).getALocalUse(), result, _, _))
|
||||
private signature module TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start);
|
||||
|
||||
predicate end(DataFlow::Node n);
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is backwards reachable from any regular expression use,
|
||||
* where that use is reachable by type-tracking from any string or regular expression.
|
||||
* Provides a version of type tracking where we first prune for reachable nodes,
|
||||
* before doing the type tracking computation.
|
||||
*/
|
||||
DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
t.start() and
|
||||
result.flowsTo([stringSink(), regSink()]) and
|
||||
result = forward(TypeTracker::end())
|
||||
or
|
||||
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
|
||||
or
|
||||
exists(TypeBackTracker t2 | t2 = t.continue() | step(result.getALocalUse(), backwards(t2), _, _))
|
||||
private module TypeTrack<TypeTrackInputSig Input> {
|
||||
private predicate additionalStep(
|
||||
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
Input::additionalStep(nodeFrom.getALocalUse(), nodeTo)
|
||||
}
|
||||
|
||||
/** Gets a node that is forwards reachable by type-tracking. */
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode forward(TypeTracker t) {
|
||||
result = Input::start(t, _)
|
||||
or
|
||||
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
|
||||
or
|
||||
exists(TypeTracker t2 | t2 = t.continue() | additionalStep(forward(t2), result))
|
||||
}
|
||||
|
||||
bindingset[result, tbt]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private DataFlow::LocalSourceNode forwardLateInline(TypeBackTracker tbt) {
|
||||
exists(TypeTracker tt |
|
||||
result = forward(tt) and
|
||||
tt = tbt.getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that is backwards reachable by type-tracking. */
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
result = forwardLateInline(t) and
|
||||
(
|
||||
t.start() and
|
||||
Input::end(result.getALocalUse())
|
||||
or
|
||||
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
|
||||
or
|
||||
exists(TypeBackTracker t2 | t2 = t.continue() | additionalStep(result, backwards(t2)))
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[result, tt]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private DataFlow::LocalSourceNode backwardsInlineLate(TypeTracker tt) {
|
||||
exists(TypeBackTracker tbt |
|
||||
result = backwards(tbt) and
|
||||
tt = tbt.getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is forwards and backwards reachable with type tracker `t`. */
|
||||
pragma[nomagic]
|
||||
private predicate reached(DataFlow::LocalSourceNode n, TypeTracker t) {
|
||||
n = forward(t) and
|
||||
n = backwardsInlineLate(t)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTracker stepReached(
|
||||
TypeTracker t, DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, nodeTo, summary) and
|
||||
reached(nodeFrom, t) and
|
||||
reached(nodeTo, result) and
|
||||
result = t.append(summary)
|
||||
)
|
||||
or
|
||||
additionalStep(nodeFrom, nodeTo) and
|
||||
reached(nodeFrom, pragma[only_bind_into](t)) and
|
||||
reached(nodeTo, pragma[only_bind_into](t)) and
|
||||
result = t.continue()
|
||||
}
|
||||
|
||||
/** Gets a node that has been tracked from the start node `start`. */
|
||||
DataFlow::LocalSourceNode track(DataFlow::Node start, TypeTracker t) {
|
||||
t.start() and
|
||||
result = Input::start(t, start) and
|
||||
reached(result, t)
|
||||
or
|
||||
exists(TypeTracker t2 | t = stepReached(t2, track(start, t2), result))
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `inputStr` is compiled to a regular expression that is returned at `call`. */
|
||||
pragma[nomagic]
|
||||
private predicate regFromString(DataFlow::LocalSourceNode inputStr, DataFlow::CallNode call) {
|
||||
exists(DataFlow::Node mid |
|
||||
inputStr.flowsTo(mid) and
|
||||
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
|
||||
mid = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
private module StringTypeTrackInput implements TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
|
||||
start = strStart() and t.start() and result = start
|
||||
}
|
||||
|
||||
predicate end(DataFlow::Node n) {
|
||||
n = stringSink() or
|
||||
regFromString(n, _)
|
||||
}
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) {
|
||||
// include taint flow through `String` summaries
|
||||
TaintTrackingPrivate::summaryThroughStepTaint(nodeFrom, nodeTo,
|
||||
any(String::SummarizedCallable c))
|
||||
or
|
||||
// string concatenations, and
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode op |
|
||||
op = nodeTo.asExpr() and
|
||||
op.getAnOperand() = nodeFrom.asExpr() and
|
||||
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
|
||||
)
|
||||
or
|
||||
// string interpolations
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,41 +195,34 @@ DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
* This is used to figure out where `start` is evaluated as a regular expression against an input string,
|
||||
* or where `start` is compiled into a regular expression.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackStrings(DataFlow::Node start, TypeTracker t) {
|
||||
result = backwards(_) and
|
||||
(
|
||||
private predicate trackStrings = TypeTrack<StringTypeTrackInput>::track/2;
|
||||
|
||||
/** Holds if `strConst` flows to a regex compilation (tracked by `t`), where the resulting regular expression is stored in `reg`. */
|
||||
pragma[nomagic]
|
||||
private predicate regFromStringStart(DataFlow::Node strConst, TypeTracker t, DataFlow::CallNode reg) {
|
||||
regFromString(trackStrings(strConst, t), reg) and
|
||||
exists(t.continue())
|
||||
}
|
||||
|
||||
private module RegTypeTrackInput implements TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
|
||||
start = regStart() and
|
||||
t.start() and
|
||||
start = result and
|
||||
result = strStart()
|
||||
result = start
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackStrings(start, t2).track(t2, t))
|
||||
or
|
||||
// an additional step from string to string
|
||||
exists(TypeTracker t2 | t2 = t.continue() |
|
||||
step(trackStrings(start, t2).getALocalUse(), result, "string", "string")
|
||||
)
|
||||
)
|
||||
regFromStringStart(start, t, result)
|
||||
}
|
||||
|
||||
predicate end(DataFlow::Node n) { n = regSink() }
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that has been tracked from the regular expression `start` to some node.
|
||||
* This is used to figure out where `start` is executed against an input string.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackRegs(DataFlow::Node start, TypeTracker t) {
|
||||
result = backwards(_) and
|
||||
(
|
||||
t.start() and
|
||||
start = result and
|
||||
result = regStart()
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackRegs(start, t2).track(t2, t))
|
||||
or
|
||||
// an additional step where a string is converted to a regular expression
|
||||
exists(TypeTracker t2 | t2 = t.continue() |
|
||||
step(trackStrings(start, t2).getALocalUse(), result, "string", "reg")
|
||||
)
|
||||
)
|
||||
}
|
||||
private predicate trackRegs = TypeTrack<RegTypeTrackInput>::track/2;
|
||||
|
||||
/** Gets a node that references a regular expression. */
|
||||
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {
|
||||
|
||||
@@ -79,7 +79,7 @@ module HardcodedDataInterpretedAsCode {
|
||||
forex(StringComponentCfgNode c |
|
||||
c = this.asExpr().(ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
|
|
||||
c.getNode().(Ast::StringEscapeSequenceComponent).getRawText().matches("\\x%")
|
||||
c.getAstNode().(Ast::StringEscapeSequenceComponent).getRawText().matches("\\x%")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,14 @@ private class DangerousPrefix extends string {
|
||||
this = "<!--" or
|
||||
this = "<" + ["iframe", "script", "cript", "scrip", "style"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a character that is important to the dangerous prefix.
|
||||
* That is, a char that should be mentioned in a regular expression that explicitly sanitizes the dangerous prefix.
|
||||
*/
|
||||
string getAnImportantChar() {
|
||||
if this = ["/..", "../"] then result = ["/", "."] else result = "<"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +70,11 @@ private DangerousPrefixSubstring getADangerousMatchedChar(EmptyReplaceRegExpTerm
|
||||
*/
|
||||
private DangerousPrefix getADangerousMatchedPrefix(EmptyReplaceRegExpTerm t) {
|
||||
result = getADangerousMatchedPrefixSubstring(t) and
|
||||
not exists(EmptyReplaceRegExpTerm pred | pred = t.getPredecessor+() and not pred.isNullable())
|
||||
not exists(EmptyReplaceRegExpTerm pred | pred = t.getPredecessor+() and not pred.isNullable()) and
|
||||
// the regex must explicitly mention a char important to the prefix.
|
||||
forex(string char | char = result.getAnImportantChar() |
|
||||
t.getRootTerm().getAChild*().(RegExpConstant).getValue().matches("%" + char + "%")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ import InsecureDownloadCustomizations::InsecureDownload
|
||||
/**
|
||||
* A taint tracking configuration for download of sensitive file through insecure connection.
|
||||
*/
|
||||
class Configuration extends DataFlow::Configuration {
|
||||
deprecated class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "InsecureDownload" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowState label) {
|
||||
@@ -29,3 +29,22 @@ class Configuration extends DataFlow::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint tracking configuration for download of sensitive file through insecure connection.
|
||||
*/
|
||||
module Config implements DataFlow::StateConfigSig {
|
||||
class FlowState = string;
|
||||
|
||||
predicate isSource(DataFlow::Node source, DataFlow::FlowState label) {
|
||||
source.(Source).getALabel() = label
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, DataFlow::FlowState label) {
|
||||
sink.(Sink).getALabel() = label
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
module Flow = DataFlow::GlobalWithState<Config>;
|
||||
|
||||
@@ -55,7 +55,8 @@ class AmbiguousPathCall extends DataFlow::CallNode {
|
||||
}
|
||||
|
||||
private predicate methodCallOnlyOnIO(DataFlow::CallNode node, string methodName) {
|
||||
node = API::getTopLevelMember("IO").getAMethodCall(methodName) and
|
||||
// Use local flow to find calls to 'IO' without subclasses
|
||||
node = DataFlow::getConstant("IO").getAMethodCall(methodName) and
|
||||
not node = API::getTopLevelMember("File").getAMethodCall(methodName) // needed in e.g. opal/opal, where some calls have both paths (opal implements an own corelib)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,18 +26,20 @@ module LdapInjection {
|
||||
/**
|
||||
* Additional taint steps for "LDAP Injection" vulnerabilities.
|
||||
*/
|
||||
predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
filterTaintStep(nodeFrom, nodeTo)
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
attributeArrayTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
private predicate filterTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
/**
|
||||
* Additional taint step to handle elements inside an array,
|
||||
* specifically in the context of the following LDAP search function:
|
||||
*
|
||||
* ldap.search(base: "", filter: "", attributes: [name])
|
||||
*/
|
||||
private predicate attributeArrayTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
exists(DataFlow::CallNode filterCall |
|
||||
(
|
||||
filterCall =
|
||||
API::getTopLevelMember("Net").getMember("LDAP").getMember("Filter").getAMethodCall(["eq"]) or
|
||||
filterCall.getMethodName() = ["[]"]
|
||||
) and
|
||||
n1 = filterCall.getArgument([0, 1]) and
|
||||
filterCall.getMethodName() = "[]" and
|
||||
n1 = filterCall.getArgument(_) and
|
||||
n2 = filterCall
|
||||
)
|
||||
}
|
||||
@@ -64,5 +66,16 @@ module LdapInjection {
|
||||
* sanitizer-guard.
|
||||
*/
|
||||
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier { }
|
||||
StringConstArrayInclusionCallBarrier
|
||||
{ }
|
||||
|
||||
/**
|
||||
* A call to `Net::LDAP::Filter.escape`, considered as a sanitizer.
|
||||
*/
|
||||
class NetLdapFilterEscapeSanitization extends Sanitizer {
|
||||
NetLdapFilterEscapeSanitization() {
|
||||
this =
|
||||
API::getTopLevelMember("Net").getMember("LDAP").getMember("Filter").getAMethodCall("escape")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,25 @@
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import LdapInjectionCustomizations
|
||||
private import LdapInjectionCustomizations::LdapInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "LdapInjection" }
|
||||
/** Provides a taint-tracking configuration for detecting LDAP Injections vulnerabilities. */
|
||||
module LdapInjection {
|
||||
import LdapInjectionCustomizations::LdapInjection
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
/**
|
||||
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
LdapInjection::isAdditionalTaintStep(node1, node2)
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
LdapInjection::isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
import TaintTracking::Global<Config>
|
||||
}
|
||||
|
||||
@@ -597,7 +597,7 @@ private module Digest {
|
||||
call = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("new")
|
||||
|
|
||||
this = call.getReturn().getAMethodCall(["digest", "update", "<<"]) and
|
||||
algo.matchesName(call.getCallNode()
|
||||
algo.matchesName(call.asCall()
|
||||
.getArgument(0)
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
@@ -619,7 +619,7 @@ private module Digest {
|
||||
Cryptography::HashingAlgorithm algo;
|
||||
|
||||
DigestCallDirect() {
|
||||
this = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("digest").getCallNode() and
|
||||
this = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("digest").asCall() and
|
||||
algo.matchesName(this.getArgument(0).asExpr().getExpr().getConstantValue().getString())
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,3 @@ module ReflectedXss {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for ReflectedXss */
|
||||
deprecated module ReflectedXSS = ReflectedXss;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user