mirror of
https://github.com/github/codeql.git
synced 2026-04-26 09:15:12 +02:00
Merge branch 'github:main' into amammad-ruby-bombs
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.CFG
|
||||
import codeql.ruby.DataFlow::DataFlow
|
||||
import codeql.ruby.dataflow.internal.DataFlowPrivate
|
||||
import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency
|
||||
import codeql.ruby.DataFlow::DataFlow as DataFlow
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplSpecific
|
||||
private import codeql.ruby.dataflow.internal.TaintTrackingImplSpecific
|
||||
private import codeql.dataflow.internal.DataFlowImplConsistency
|
||||
|
||||
private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
override predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
|
||||
private module Input implements InputSig<RubyDataFlow> {
|
||||
private import RubyDataFlow
|
||||
|
||||
override predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
|
||||
|
||||
predicate argHasPostUpdateExclude(ArgumentNode n) {
|
||||
n instanceof BlockArgumentNode
|
||||
or
|
||||
n instanceof FlowSummaryNode
|
||||
@@ -17,7 +20,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
not isNonConstantExpr(getAPostUpdateNodeForArg(n.asExpr()))
|
||||
}
|
||||
|
||||
override predicate postHasUniquePreExclude(PostUpdateNode n) {
|
||||
predicate postHasUniquePreExclude(PostUpdateNode n) {
|
||||
exists(CfgNodes::ExprCfgNode e, CfgNodes::ExprCfgNode arg |
|
||||
e = getAPostUpdateNodeForArg(arg) and
|
||||
e != arg and
|
||||
@@ -25,7 +28,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate uniquePostUpdateExclude(Node n) {
|
||||
predicate uniquePostUpdateExclude(Node n) {
|
||||
exists(CfgNodes::ExprCfgNode e, CfgNodes::ExprCfgNode arg |
|
||||
e = getAPostUpdateNodeForArg(arg) and
|
||||
e != arg and
|
||||
@@ -33,3 +36,10 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import MakeConsistency<RubyDataFlow, RubyTaintTracking, Input>
|
||||
|
||||
query predicate multipleToString(DataFlow::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,68 @@
|
||||
## 0.7.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Flow between positional arguments and splat parameters (`*args`) is now tracked more precisely.
|
||||
* Flow between splat arguments (`*args`) and positional parameters is now tracked more precisely.
|
||||
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* 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.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* 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.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* 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.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* 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.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* 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.
|
||||
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.
|
||||
3
ruby/ql/lib/change-notes/released/0.7.2.md
Normal file
3
ruby/ql/lib/change-notes/released/0.7.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
6
ruby/ql/lib/change-notes/released/0.7.3.md
Normal file
6
ruby/ql/lib/change-notes/released/0.7.3.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 0.7.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Flow between positional arguments and splat parameters (`*args`) is now tracked more precisely.
|
||||
* Flow between splat arguments (`*args`) and positional parameters is now tracked more precisely.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.2
|
||||
lastReleaseVersion: 0.7.3
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
|
||||
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.
|
||||
@@ -37,8 +28,14 @@ class Location extends @location_default {
|
||||
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
@@ -843,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.
|
||||
*
|
||||
@@ -1132,3 +1184,106 @@ module TemplateRendering {
|
||||
abstract DataFlow::Node getTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that constructs a LDAP query.
|
||||
*
|
||||
* Often, it is worthy of an alert if an LDAP query is constructed such that
|
||||
* executing it would be a security risk.
|
||||
*
|
||||
* If it is important that the query is executed, use `LdapExecution`.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LdapConstruction::Range` instead.
|
||||
*/
|
||||
class LdapConstruction extends DataFlow::Node instanceof LdapConstruction::Range {
|
||||
/** Gets the argument that specifies the query to be constructed. */
|
||||
DataFlow::Node getQuery() { result = super.getQuery() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new LDAP query construction APIs. */
|
||||
module LdapConstruction {
|
||||
/**
|
||||
* A data-flow node that constructs a LDAP query.
|
||||
*
|
||||
* Often, it is worthy of an alert if an LDAP query is constructed such that
|
||||
* executing it would be a security risk.
|
||||
*
|
||||
* If it is important that the query is executed, use `LdapExecution`.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `LdapConstruction` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the query to be constructed. */
|
||||
abstract DataFlow::Node getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes LDAP queries.
|
||||
*
|
||||
* If the context of interest is such that merely constructing a LDAP query
|
||||
* would be valuable to report, consider using `LdapConstruction`.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LdapExecution::Range` instead.
|
||||
*/
|
||||
class LdapExecution extends DataFlow::Node instanceof LdapExecution::Range {
|
||||
/** Gets the argument that specifies the query to be executed. */
|
||||
DataFlow::Node getQuery() { result = super.getQuery() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new LDAP query execution APIs. */
|
||||
module LdapExecution {
|
||||
/**
|
||||
* A data-flow node that executes LDAP queries.
|
||||
*
|
||||
* If the context of interest is such that merely constructing a LDAP query
|
||||
* would be valuable to report, consider using `LdapConstruction`.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `LdapExecution` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the query to be executed. */
|
||||
abstract DataFlow::Node getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collects methods binding a LDAP connection.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LdapBind::Range` instead.
|
||||
*/
|
||||
class LdapBind extends DataFlow::Node instanceof LdapBind::Range {
|
||||
/** Gets the argument containing the binding host */
|
||||
DataFlow::Node getHost() { result = super.getHost() }
|
||||
|
||||
/** Gets the argument containing the binding expression. */
|
||||
DataFlow::Node getPassword() { result = super.getPassword() }
|
||||
|
||||
/** Holds if the binding process use SSL. */
|
||||
predicate usesSsl() { super.usesSsl() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling LDAP bind-related APIs. */
|
||||
module LdapBind {
|
||||
/**
|
||||
* A data-flow node that collects methods binding a LDAP connection.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `LdapBind` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument containing the binding host. */
|
||||
abstract DataFlow::Node getHost();
|
||||
|
||||
/** Gets the argument containing the binding expression. */
|
||||
abstract DataFlow::Node getPassword();
|
||||
|
||||
/** Holds if the binding process use SSL. */
|
||||
abstract predicate usesSsl();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -36,3 +36,4 @@ 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
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
* global (inter-procedural) taint-tracking analyses.
|
||||
*/
|
||||
module TaintTracking {
|
||||
import codeql.ruby.dataflow.internal.tainttracking1.TaintTracking
|
||||
import codeql.ruby.dataflow.internal.tainttracking1.TaintTrackingParameter::Public
|
||||
private import codeql.ruby.dataflow.internal.DataFlowImplSpecific
|
||||
private import codeql.ruby.dataflow.internal.TaintTrackingImplSpecific
|
||||
private import codeql.dataflow.TaintTracking
|
||||
import TaintFlowMake<RubyDataFlow, RubyTaintTracking>
|
||||
import codeql.ruby.dataflow.internal.tainttracking1.TaintTrackingImpl
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class UnaryLogicalOperation extends UnaryOperation, TUnaryLogicalOperation { }
|
||||
* not params.empty?
|
||||
* ```
|
||||
*/
|
||||
class NotExpr extends UnaryLogicalOperation, TNotExpr {
|
||||
class NotExpr extends UnaryLogicalOperation instanceof NotExprImpl {
|
||||
final override string getAPrimaryQlClass() { result = "NotExpr" }
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class ComplementExpr extends UnaryBitwiseOperation, TComplementExpr {
|
||||
* defined? some_method
|
||||
* ```
|
||||
*/
|
||||
class DefinedExpr extends UnaryOperation, TDefinedExpr {
|
||||
class DefinedExpr extends UnaryOperation instanceof DefinedExprImpl {
|
||||
final override string getAPrimaryQlClass() { result = "DefinedExpr" }
|
||||
}
|
||||
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -123,7 +123,8 @@ private module Cached {
|
||||
TConstantWriteAccessSynth(Ast::AstNode parent, int i, string value) {
|
||||
mkSynthChild(ConstantWriteAccessKind(value), parent, i)
|
||||
} or
|
||||
TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
|
||||
TDefinedExprReal(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
|
||||
TDefinedExprSynth(Ast::AstNode parent, int i) { mkSynthChild(DefinedExprKind(), parent, i) } or
|
||||
TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or
|
||||
TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) {
|
||||
not strictcount(int i | exists(g.getParent().(Ruby::LeftAssignmentList).getChild(i))) = 1
|
||||
@@ -228,7 +229,8 @@ private module Cached {
|
||||
TNilLiteralReal(Ruby::Nil g) or
|
||||
TNilLiteralSynth(Ast::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or
|
||||
TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
|
||||
TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
|
||||
TNotExprReal(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
|
||||
TNotExprSynth(Ast::AstNode parent, int i) { mkSynthChild(NotExprKind(), parent, i) } or
|
||||
TOptionalParameter(Ruby::OptionalParameter g) or
|
||||
TPair(Ruby::Pair g) or
|
||||
TParenthesizedExpr(Ruby::ParenthesizedStatements g) or
|
||||
@@ -354,21 +356,21 @@ private module Cached {
|
||||
TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or TBlockParameter or
|
||||
TBraceBlockReal or TBreakStmt or TCaseEqExpr or TCaseExpr or TCaseMatchReal or
|
||||
TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
|
||||
TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or
|
||||
TDestructuredParameter or TDivExprReal or TDo or TDoBlock or TElementReference or
|
||||
TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or TEnsure or TEqExpr or
|
||||
TExponentExprReal or TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or
|
||||
TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
|
||||
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or
|
||||
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
|
||||
TIfReal or TIfModifierExpr or TInClauseReal or TInstanceVariableAccessReal or
|
||||
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
|
||||
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
|
||||
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TMatchPattern or
|
||||
TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or
|
||||
TNilLiteralReal or TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or
|
||||
TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or
|
||||
TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
|
||||
TComplexLiteral or TDefinedExprReal or TDelimitedSymbolLiteral or
|
||||
TDestructuredLeftAssignment or TDestructuredParameter or TDivExprReal or TDo or TDoBlock or
|
||||
TElementReference or TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or
|
||||
TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFile or TFindPattern or
|
||||
TFloatLiteral or TForExpr or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
|
||||
TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashPattern or
|
||||
THashSplatExpr or THashSplatNilParameter or THashSplatParameter or THereDoc or
|
||||
TIdentifierMethodCall or TIfReal or TIfModifierExpr or TInClauseReal or
|
||||
TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
|
||||
TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLine or
|
||||
TLocalVariableAccessReal or TLogicalAndExprReal or TLogicalOrExprReal or TMethod or
|
||||
TMatchPattern or TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or
|
||||
TNextStmt or TNilLiteralReal or TNoRegExpMatchExpr or TNotExprReal or TOptionalParameter or
|
||||
TPair or TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or
|
||||
TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
|
||||
TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or
|
||||
TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
|
||||
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
|
||||
@@ -438,7 +440,7 @@ private module Cached {
|
||||
n = TClassVariableAccessReal(result, _) or
|
||||
n = TComplementExpr(result) or
|
||||
n = TComplexLiteral(result) or
|
||||
n = TDefinedExpr(result) or
|
||||
n = TDefinedExprReal(result) or
|
||||
n = TDelimitedSymbolLiteral(result) or
|
||||
n = TDestructuredLeftAssignment(result) or
|
||||
n = TDivExprReal(result) or
|
||||
@@ -495,7 +497,7 @@ private module Cached {
|
||||
n = TNextStmt(result) or
|
||||
n = TNilLiteralReal(result) or
|
||||
n = TNoRegExpMatchExpr(result) or
|
||||
n = TNotExpr(result) or
|
||||
n = TNotExprReal(result) or
|
||||
n = TOptionalParameter(result) or
|
||||
n = TPair(result) or
|
||||
n = TParenthesizedExpr(result) or
|
||||
@@ -585,6 +587,8 @@ private module Cached {
|
||||
or
|
||||
result = TConstantWriteAccessSynth(parent, i, _)
|
||||
or
|
||||
result = TDefinedExprSynth(parent, i)
|
||||
or
|
||||
result = TDivExprSynth(parent, i)
|
||||
or
|
||||
result = TElseSynth(parent, i)
|
||||
@@ -617,6 +621,8 @@ private module Cached {
|
||||
or
|
||||
result = TNilLiteralSynth(parent, i)
|
||||
or
|
||||
result = TNotExprSynth(parent, i)
|
||||
or
|
||||
result = TRangeLiteralSynth(parent, i, _)
|
||||
or
|
||||
result = TRShiftExprSynth(parent, i)
|
||||
@@ -789,10 +795,14 @@ class TNamespace = TClassDeclaration or TModuleDeclaration;
|
||||
|
||||
class TOperation = TUnaryOperation or TBinaryOperation or TAssignment;
|
||||
|
||||
class TDefinedExpr = TDefinedExprReal or TDefinedExprSynth;
|
||||
|
||||
class TUnaryOperation =
|
||||
TUnaryLogicalOperation or TUnaryArithmeticOperation or TUnaryBitwiseOperation or TDefinedExpr or
|
||||
TSplatExpr or THashSplatExpr;
|
||||
|
||||
class TNotExpr = TNotExprReal or TNotExprSynth;
|
||||
|
||||
class TUnaryLogicalOperation = TNotExpr;
|
||||
|
||||
class TUnaryArithmeticOperation = TUnaryPlusExpr or TUnaryMinusExpr;
|
||||
|
||||
@@ -214,7 +214,7 @@ private module Propagation {
|
||||
any(StringComponentCfgNode c |
|
||||
isString(c, result)
|
||||
or
|
||||
result = c.getNode().(StringComponentImpl).getValue()
|
||||
result = c.getAstNode().(StringComponentImpl).getValue()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,16 @@ class UnaryOperationGenerated extends UnaryOperationImpl {
|
||||
final override string getOperatorImpl() { result = g.getOperator() }
|
||||
}
|
||||
|
||||
abstract class NotExprImpl extends UnaryOperationImpl, TNotExpr { }
|
||||
|
||||
class NotExprReal extends NotExprImpl, UnaryOperationGenerated, TNotExprReal { }
|
||||
|
||||
class NotExprSynth extends NotExprImpl, TNotExprSynth {
|
||||
final override string getOperatorImpl() { result = "!" }
|
||||
|
||||
final override Expr getOperandImpl() { synthChild(this, 0, result) }
|
||||
}
|
||||
|
||||
class SplatExprReal extends UnaryOperationImpl, TSplatExprReal {
|
||||
private Ruby::SplatArgument g;
|
||||
|
||||
@@ -67,6 +77,16 @@ class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr {
|
||||
final override string getOperatorImpl() { result = "**" }
|
||||
}
|
||||
|
||||
abstract class DefinedExprImpl extends UnaryOperationImpl, TDefinedExpr { }
|
||||
|
||||
class DefinedExprReal extends DefinedExprImpl, UnaryOperationGenerated, TDefinedExprReal { }
|
||||
|
||||
class DefinedExprSynth extends DefinedExprImpl, TDefinedExprSynth {
|
||||
final override string getOperatorImpl() { result = "defined?" }
|
||||
|
||||
final override Expr getOperandImpl() { synthChild(this, 0, result) }
|
||||
}
|
||||
|
||||
abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinaryOperation {
|
||||
abstract Stmt getLeftOperandImpl();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ newtype SynthKind =
|
||||
BraceBlockKind() or
|
||||
CaseMatchKind() or
|
||||
ClassVariableAccessKind(ClassVariable v) or
|
||||
DefinedExprKind() or
|
||||
DivExprKind() or
|
||||
ElseKind() or
|
||||
ExponentExprKind() or
|
||||
@@ -40,6 +41,7 @@ newtype SynthKind =
|
||||
ModuloExprKind() or
|
||||
MulExprKind() or
|
||||
NilLiteralKind() or
|
||||
NotExprKind() or
|
||||
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
|
||||
RShiftExprKind() or
|
||||
SimpleParameterKind() or
|
||||
@@ -1258,6 +1260,7 @@ private module HashLiteralDesugar {
|
||||
* ```
|
||||
* desugars to, roughly,
|
||||
* ```rb
|
||||
* if not defined? x then x = nil end
|
||||
* xs.each { |__synth__0| x = __synth__0; <loop_body> }
|
||||
* ```
|
||||
*
|
||||
@@ -1267,58 +1270,160 @@ private module HashLiteralDesugar {
|
||||
* scoped to the synthesized block.
|
||||
*/
|
||||
private module ForLoopDesugar {
|
||||
private Ruby::AstNode getForLoopPatternChild(Ruby::For for) {
|
||||
result = for.getPattern()
|
||||
or
|
||||
result.getParent() = getForLoopPatternChild(for)
|
||||
}
|
||||
|
||||
/** Holds if `n` is an access to variable `v` in the pattern of `for`. */
|
||||
pragma[nomagic]
|
||||
private predicate forLoopVariableAccess(Ruby::For for, Ruby::AstNode n, VariableReal v) {
|
||||
n = getForLoopPatternChild(for) and
|
||||
access(n, v)
|
||||
}
|
||||
|
||||
/** Holds if `v` is the `i`th iteration variable of `for`. */
|
||||
private predicate forLoopVariable(Ruby::For for, VariableReal v, int i) {
|
||||
v =
|
||||
rank[i + 1](VariableReal v0, Ruby::AstNode n, Location l |
|
||||
forLoopVariableAccess(for, n, v0) and
|
||||
l = n.getLocation()
|
||||
|
|
||||
v0 order by l.getStartLine(), l.getStartColumn()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of iteration variables of `for`. */
|
||||
private int forLoopVariableCount(Ruby::For for) {
|
||||
result = count(int j | forLoopVariable(for, _, j))
|
||||
}
|
||||
|
||||
private Ruby::For toTsFor(ForExpr for) { for = TForExpr(result) }
|
||||
|
||||
/**
|
||||
* Synthesizes an assignment
|
||||
* ```rb
|
||||
* if not defined? v then v = nil end
|
||||
* ```
|
||||
* anchored at index `rootIndex` of `root`.
|
||||
*/
|
||||
bindingset[root, rootIndex, v]
|
||||
private predicate nilAssignUndefined(
|
||||
AstNode root, int rootIndex, AstNode parent, int i, Child child, VariableReal v
|
||||
) {
|
||||
parent = root and
|
||||
i = rootIndex and
|
||||
child = SynthChild(IfKind())
|
||||
or
|
||||
exists(AstNode if_ | if_ = TIfSynth(root, rootIndex) |
|
||||
parent = if_ and
|
||||
i = 0 and
|
||||
child = SynthChild(NotExprKind())
|
||||
or
|
||||
exists(AstNode not_ | not_ = TNotExprSynth(if_, 0) |
|
||||
parent = not_ and
|
||||
i = 0 and
|
||||
child = SynthChild(DefinedExprKind())
|
||||
or
|
||||
parent = TDefinedExprSynth(not_, 0) and
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessRealKind(v))
|
||||
)
|
||||
or
|
||||
parent = if_ and
|
||||
i = 1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
or
|
||||
parent = TAssignExprSynth(if_, 1) and
|
||||
(
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessRealKind(v))
|
||||
or
|
||||
i = 1 and
|
||||
child = SynthChild(NilLiteralKind())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
|
||||
exists(ForExpr for |
|
||||
// each call
|
||||
parent = for and
|
||||
i = -1 and
|
||||
child = SynthChild(MethodCallKind("each", false, 0))
|
||||
child = SynthChild(StmtSequenceKind())
|
||||
or
|
||||
exists(MethodCall eachCall | eachCall = TMethodCallSynth(for, -1, "each", false, 0) |
|
||||
// receiver
|
||||
parent = eachCall and
|
||||
i = 0 and
|
||||
child = childRef(for.getValue()) // value is the Enumerable
|
||||
exists(AstNode seq | seq = TStmtSequenceSynth(for, -1) |
|
||||
exists(VariableReal v, int j | forLoopVariable(toTsFor(for), v, j) |
|
||||
nilAssignUndefined(seq, j, parent, i, child, v)
|
||||
)
|
||||
or
|
||||
parent = eachCall and
|
||||
i = 1 and
|
||||
child = SynthChild(BraceBlockKind())
|
||||
or
|
||||
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
|
||||
// block params
|
||||
parent = block and
|
||||
i = 0 and
|
||||
child = SynthChild(SimpleParameterKind())
|
||||
exists(int numberOfVars | numberOfVars = forLoopVariableCount(toTsFor(for)) |
|
||||
// each call
|
||||
parent = seq and
|
||||
i = numberOfVars and
|
||||
child = SynthChild(MethodCallKind("each", false, 0))
|
||||
or
|
||||
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
|
||||
parent = param and
|
||||
exists(MethodCall eachCall |
|
||||
eachCall = TMethodCallSynth(seq, numberOfVars, "each", false, 0)
|
||||
|
|
||||
// receiver
|
||||
parent = eachCall and
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
child = childRef(for.getValue()) // value is the Enumerable
|
||||
or
|
||||
// assignment to pattern from for loop to synth parameter
|
||||
parent = block and
|
||||
parent = eachCall and
|
||||
i = 1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
child = SynthChild(BraceBlockKind())
|
||||
or
|
||||
parent = TAssignExprSynth(block, 1) and
|
||||
(
|
||||
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
|
||||
// block params
|
||||
parent = block and
|
||||
i = 0 and
|
||||
child = childRef(for.getPattern())
|
||||
child = SynthChild(SimpleParameterKind())
|
||||
or
|
||||
i = 1 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
|
||||
parent = param and
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
or
|
||||
// assignment to pattern from for loop to synth parameter
|
||||
parent = block and
|
||||
i = 1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
or
|
||||
parent = TAssignExprSynth(block, 1) and
|
||||
(
|
||||
i = 0 and
|
||||
child = childRef(for.getPattern())
|
||||
or
|
||||
i = 1 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
)
|
||||
)
|
||||
or
|
||||
// rest of block body
|
||||
parent = block and
|
||||
child = childRef(for.getBody().(Do).getStmt(i - 2))
|
||||
)
|
||||
)
|
||||
or
|
||||
// rest of block body
|
||||
parent = block and
|
||||
child = childRef(for.getBody().(Do).getStmt(i - 2))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isDesugaredInitNode(ForExpr for, Variable v, AstNode n) {
|
||||
exists(StmtSequence seq, AssignExpr ae |
|
||||
seq = for.getDesugared() and
|
||||
n = seq.getStmt(_) and
|
||||
ae = n.(IfExpr).getThen() and
|
||||
v = ae.getLeftOperand().getAVariable()
|
||||
)
|
||||
or
|
||||
isDesugaredInitNode(for, v, n.getParent())
|
||||
}
|
||||
|
||||
private class ForLoopSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
forLoopSynthesis(parent, i, child)
|
||||
@@ -1338,6 +1443,14 @@ private module ForLoopDesugar {
|
||||
final override predicate excludeFromControlFlowTree(AstNode n) {
|
||||
n = any(ForExpr for).getBody()
|
||||
}
|
||||
|
||||
final override predicate location(AstNode n, Location l) {
|
||||
exists(ForExpr for, Ruby::AstNode access, Variable v |
|
||||
forLoopVariableAccess(toTsFor(for), access, v) and
|
||||
isDesugaredInitNode(for, v, n) and
|
||||
l = access.getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
private import TreeSitter
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.ast.internal.AST
|
||||
private import codeql.ruby.ast.internal.Parameter
|
||||
private import codeql.ruby.ast.internal.Pattern
|
||||
@@ -364,22 +365,11 @@ private module Cached {
|
||||
|
||||
cached
|
||||
predicate isCapturedAccess(LocalVariableAccess access) {
|
||||
exists(Scope scope1, Scope scope2 |
|
||||
exists(Scope scope1, CfgScope scope2 |
|
||||
scope1 = access.getVariable().getDeclaringScope() and
|
||||
scope2 = access.getCfgScope() and
|
||||
scope1 != scope2
|
||||
|
|
||||
if access instanceof SelfVariableAccess
|
||||
then
|
||||
// ```
|
||||
// class C
|
||||
// def self.m // not a captured access
|
||||
// end
|
||||
// end
|
||||
// ```
|
||||
not scope2 instanceof Toplevel or
|
||||
not access = any(SingletonMethod m).getObject()
|
||||
else any()
|
||||
scope1 != scope2 and
|
||||
not scope2 instanceof Toplevel
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,44 +35,18 @@ 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 }
|
||||
@@ -123,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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,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) {
|
||||
@@ -179,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -210,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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -440,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) }
|
||||
@@ -460,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 {
|
||||
@@ -871,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" }
|
||||
|
||||
@@ -880,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" }
|
||||
|
||||
@@ -891,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()
|
||||
@@ -902,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()
|
||||
|
||||
@@ -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,22 +68,20 @@ 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" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`),
|
||||
* an emptiness successor (`EmptinessSuccessor`), or a matching successor
|
||||
* (`MatchingSuccessor`)
|
||||
* A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`)
|
||||
* or a matching successor (`MatchingSuccessor`)
|
||||
*/
|
||||
class ConditionalSuccessor extends SuccessorType {
|
||||
boolean value;
|
||||
|
||||
ConditionalSuccessor() {
|
||||
this = TBooleanSuccessor(value) or
|
||||
this = TEmptinessSuccessor(value) or
|
||||
this = TMatchingSuccessor(value)
|
||||
this = CfgImpl::TBooleanSuccessor(value) or
|
||||
this = CfgImpl::TMatchingSuccessor(value)
|
||||
}
|
||||
|
||||
/** Gets the Boolean value of this successor. */
|
||||
@@ -125,42 +105,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* `x >= 0` has both a `true` successor and a `false` successor.
|
||||
*/
|
||||
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { }
|
||||
|
||||
/**
|
||||
* An emptiness control flow successor.
|
||||
*
|
||||
* For example, this program fragment:
|
||||
*
|
||||
* ```rb
|
||||
* for arg in args do
|
||||
* puts arg
|
||||
* end
|
||||
* puts "done";
|
||||
* ```
|
||||
*
|
||||
* has a control flow graph containing emptiness successors:
|
||||
*
|
||||
* ```
|
||||
* args
|
||||
* |
|
||||
* for------<-----
|
||||
* / \ \
|
||||
* / \ |
|
||||
* / \ |
|
||||
* / \ |
|
||||
* empty non-empty |
|
||||
* | \ |
|
||||
* puts "done" \ |
|
||||
* arg |
|
||||
* | |
|
||||
* puts arg |
|
||||
* \___/
|
||||
* ```
|
||||
*/
|
||||
class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor {
|
||||
override string toString() { if value = true then result = "empty" else result = "non-empty" }
|
||||
}
|
||||
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor { }
|
||||
|
||||
/**
|
||||
* A matching control flow successor.
|
||||
@@ -189,7 +134,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 +152,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 +175,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 +198,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 +223,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 +247,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 +268,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 +289,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;
|
||||
}
|
||||
@@ -264,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()
|
||||
)
|
||||
}
|
||||
@@ -438,7 +438,8 @@ private module Cached {
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
|
||||
} or
|
||||
THashSplatArgumentPosition() or
|
||||
TSplatAllArgumentPosition() or
|
||||
TSplatArgumentPosition(int pos) { exists(Call c | c.getArgument(pos) instanceof SplatExpr) } or
|
||||
TSynthSplatArgumentPosition() or
|
||||
TAnyArgumentPosition() or
|
||||
TAnyKeywordArgumentPosition()
|
||||
|
||||
@@ -467,7 +468,11 @@ private module Cached {
|
||||
// position for multiple parameter nodes in the same callable, we introduce this
|
||||
// synthetic parameter position.
|
||||
TSynthHashSplatParameterPosition() or
|
||||
TSplatAllParameterPosition() or
|
||||
TSplatParameterPosition(int pos) {
|
||||
exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter)
|
||||
} or
|
||||
TSynthSplatParameterPosition() or
|
||||
TSynthArgSplatParameterPosition() or
|
||||
TAnyParameterPosition() or
|
||||
TAnyKeywordParameterPosition()
|
||||
}
|
||||
@@ -1288,7 +1293,12 @@ class ParameterPosition extends TParameterPosition {
|
||||
|
||||
predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() }
|
||||
|
||||
predicate isSplatAll() { this = TSplatAllParameterPosition() }
|
||||
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
|
||||
|
||||
// A fake position to indicate that this parameter node holds content from a synth arg splat node
|
||||
predicate isSynthArgSplat() { this = TSynthArgSplatParameterPosition() }
|
||||
|
||||
predicate isSplat(int n) { this = TSplatParameterPosition(n) }
|
||||
|
||||
/**
|
||||
* Holds if this position represents any parameter, except `self` parameters. This
|
||||
@@ -1315,11 +1325,15 @@ class ParameterPosition extends TParameterPosition {
|
||||
or
|
||||
this.isSynthHashSplat() and result = "synthetic **"
|
||||
or
|
||||
this.isSplatAll() and result = "*"
|
||||
or
|
||||
this.isAny() and result = "any"
|
||||
or
|
||||
this.isAnyNamed() and result = "any-named"
|
||||
or
|
||||
this.isSynthSplat() and result = "synthetic *"
|
||||
or
|
||||
this.isSynthArgSplat() and result = "synthetic * (from *args)"
|
||||
or
|
||||
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1352,7 +1366,9 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
*/
|
||||
predicate isHashSplat() { this = THashSplatArgumentPosition() }
|
||||
|
||||
predicate isSplatAll() { this = TSplatAllArgumentPosition() }
|
||||
predicate isSplat(int n) { this = TSplatArgumentPosition(n) }
|
||||
|
||||
predicate isSynthSplat() { this = TSynthSplatArgumentPosition() }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
@@ -1370,7 +1386,9 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
or
|
||||
this.isHashSplat() and result = "**"
|
||||
or
|
||||
this.isSplatAll() and result = "*"
|
||||
this.isSynthSplat() and result = "synthetic *"
|
||||
or
|
||||
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1399,7 +1417,14 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
ppos.isSynthHashSplat() and apos.isHashSplat()
|
||||
or
|
||||
ppos.isSplatAll() and apos.isSplatAll()
|
||||
ppos.isSplat(0) and apos.isSynthSplat()
|
||||
or
|
||||
ppos.isSynthSplat() and apos.isSplat(0)
|
||||
or
|
||||
apos.isSynthSplat() and ppos.isSynthArgSplat()
|
||||
or
|
||||
// Exact splat match
|
||||
exists(int n | apos.isSplat(n) and ppos.isSplat(n))
|
||||
or
|
||||
ppos.isAny() and argumentPositionIsNotSelf(apos)
|
||||
or
|
||||
@@ -1416,6 +1441,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
@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
@@ -313,6 +315,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() }
|
||||
|
||||
@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
@@ -313,6 +315,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
@@ -1,299 +0,0 @@
|
||||
/**
|
||||
* Provides consistency queries for checking invariants in the language-specific
|
||||
* data-flow classes and predicates.
|
||||
*/
|
||||
|
||||
private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
private import tainttracking1.TaintTrackingParameter::Private
|
||||
private import tainttracking1.TaintTrackingParameter::Public
|
||||
|
||||
module Consistency {
|
||||
private newtype TConsistencyConfiguration = MkConsistencyConfiguration()
|
||||
|
||||
/** A class for configuring the consistency queries. */
|
||||
class ConsistencyConfiguration extends TConsistencyConfiguration {
|
||||
string toString() { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueEnclosingCallable`. */
|
||||
predicate uniqueEnclosingCallableExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `call` should be excluded from the consistency test `uniqueCallEnclosingCallable`. */
|
||||
predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueNodeLocation`. */
|
||||
predicate uniqueNodeLocationExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `missingLocation`. */
|
||||
predicate missingLocationExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `postWithInFlow`. */
|
||||
predicate postWithInFlowExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `argHasPostUpdate`. */
|
||||
predicate argHasPostUpdateExclude(ArgumentNode n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `reverseRead`. */
|
||||
predicate reverseReadExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `postHasUniquePre`. */
|
||||
predicate postHasUniquePreExclude(PostUpdateNode n) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniquePostUpdate`. */
|
||||
predicate uniquePostUpdateExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `(call, ctx)` should be excluded from the consistency test `viableImplInCallContextTooLargeExclude`. */
|
||||
predicate viableImplInCallContextTooLargeExclude(
|
||||
DataFlowCall call, DataFlowCall ctx, DataFlowCallable callable
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/** Holds if `(c, pos, p)` should be excluded from the consistency test `uniqueParameterNodeAtPosition`. */
|
||||
predicate uniqueParameterNodeAtPositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
|
||||
none()
|
||||
}
|
||||
|
||||
/** Holds if `(c, pos, p)` should be excluded from the consistency test `uniqueParameterNodePosition`. */
|
||||
predicate uniqueParameterNodePositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
|
||||
none()
|
||||
}
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `identityLocalStep`. */
|
||||
predicate identityLocalStepExclude(Node n) { none() }
|
||||
}
|
||||
|
||||
private class RelevantNode extends Node {
|
||||
RelevantNode() {
|
||||
this instanceof ArgumentNode or
|
||||
this instanceof ParameterNode or
|
||||
this instanceof ReturnNode or
|
||||
this = getAnOutNode(_, _) or
|
||||
simpleLocalFlowStep(this, _) or
|
||||
simpleLocalFlowStep(_, this) or
|
||||
jumpStep(this, _) or
|
||||
jumpStep(_, this) or
|
||||
storeStep(this, _, _) or
|
||||
storeStep(_, _, this) or
|
||||
readStep(this, _, _) or
|
||||
readStep(_, _, this) or
|
||||
defaultAdditionalTaintStep(this, _) or
|
||||
defaultAdditionalTaintStep(_, this)
|
||||
}
|
||||
}
|
||||
|
||||
query predicate uniqueEnclosingCallable(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
c = count(nodeGetEnclosingCallable(n)) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueEnclosingCallableExclude(n) and
|
||||
msg = "Node should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueCallEnclosingCallable(DataFlowCall call, string msg) {
|
||||
exists(int c |
|
||||
c = count(call.getEnclosingCallable()) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueCallEnclosingCallableExclude(call) and
|
||||
msg = "Call should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueType(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
c = count(getNodeType(n)) and
|
||||
c != 1 and
|
||||
msg = "Node should have one type but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueNodeLocation(Node n, string msg) {
|
||||
exists(int c |
|
||||
c =
|
||||
count(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueNodeLocationExclude(n) and
|
||||
msg = "Node should have one location but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate missingLocation(string msg) {
|
||||
exists(int c |
|
||||
c =
|
||||
strictcount(Node n |
|
||||
not n.hasLocationInfo(_, _, _, _, _) and
|
||||
not any(ConsistencyConfiguration conf).missingLocationExclude(n)
|
||||
) and
|
||||
msg = "Nodes without location: " + c
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueNodeToString(Node n, string msg) {
|
||||
exists(int c |
|
||||
c = count(n.toString()) and
|
||||
c != 1 and
|
||||
msg = "Node should have one toString but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate missingToString(string msg) {
|
||||
exists(int c |
|
||||
c = strictcount(Node n | not exists(n.toString())) and
|
||||
msg = "Nodes without toString: " + c
|
||||
)
|
||||
}
|
||||
|
||||
query predicate parameterCallable(ParameterNode p, string msg) {
|
||||
exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
|
||||
msg = "Callable mismatch for parameter."
|
||||
}
|
||||
|
||||
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
|
||||
simpleLocalFlowStep(n1, n2) and
|
||||
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
|
||||
msg = "Local flow step does not preserve enclosing callable."
|
||||
}
|
||||
|
||||
query predicate readStepIsLocal(Node n1, Node n2, string msg) {
|
||||
readStep(n1, _, n2) and
|
||||
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
|
||||
msg = "Read step does not preserve enclosing callable."
|
||||
}
|
||||
|
||||
query predicate storeStepIsLocal(Node n1, Node n2, string msg) {
|
||||
storeStep(n1, _, n2) and
|
||||
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
|
||||
msg = "Store step does not preserve enclosing callable."
|
||||
}
|
||||
|
||||
private DataFlowType typeRepr() { result = getNodeType(_) }
|
||||
|
||||
query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
|
||||
t = typeRepr() and
|
||||
not compatibleTypes(t, t) and
|
||||
msg = "Type compatibility predicate is not reflexive."
|
||||
}
|
||||
|
||||
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
|
||||
isUnreachableInCall(n, call) and
|
||||
exists(DataFlowCallable c |
|
||||
c = nodeGetEnclosingCallable(n) and
|
||||
not viableCallable(call) = c
|
||||
) and
|
||||
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
|
||||
}
|
||||
|
||||
query predicate localCallNodes(DataFlowCall call, Node n, string msg) {
|
||||
(
|
||||
n = getAnOutNode(call, _) and
|
||||
msg = "OutNode and call does not share enclosing callable."
|
||||
or
|
||||
n.(ArgumentNode).argumentOf(call, _) and
|
||||
msg = "ArgumentNode and call does not share enclosing callable."
|
||||
) and
|
||||
nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
|
||||
}
|
||||
|
||||
// This predicate helps the compiler forget that in some languages
|
||||
// it is impossible for a result of `getPreUpdateNode` to be an
|
||||
// instance of `PostUpdateNode`.
|
||||
private Node getPre(PostUpdateNode n) {
|
||||
result = n.getPreUpdateNode()
|
||||
or
|
||||
none()
|
||||
}
|
||||
|
||||
query predicate postIsNotPre(PostUpdateNode n, string msg) {
|
||||
getPre(n) = n and
|
||||
msg = "PostUpdateNode should not equal its pre-update node."
|
||||
}
|
||||
|
||||
query predicate postHasUniquePre(PostUpdateNode n, string msg) {
|
||||
not any(ConsistencyConfiguration conf).postHasUniquePreExclude(n) and
|
||||
exists(int c |
|
||||
c = count(n.getPreUpdateNode()) and
|
||||
c != 1 and
|
||||
msg = "PostUpdateNode should have one pre-update node but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniquePostUpdate(Node n, string msg) {
|
||||
not any(ConsistencyConfiguration conf).uniquePostUpdateExclude(n) and
|
||||
1 < strictcount(PostUpdateNode post | post.getPreUpdateNode() = n) and
|
||||
msg = "Node has multiple PostUpdateNodes."
|
||||
}
|
||||
|
||||
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
|
||||
nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
|
||||
msg = "PostUpdateNode does not share callable with its pre-update node."
|
||||
}
|
||||
|
||||
private predicate hasPost(Node n) { exists(PostUpdateNode post | post.getPreUpdateNode() = n) }
|
||||
|
||||
query predicate reverseRead(Node n, string msg) {
|
||||
exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
|
||||
not any(ConsistencyConfiguration conf).reverseReadExclude(n) and
|
||||
msg = "Origin of readStep is missing a PostUpdateNode."
|
||||
}
|
||||
|
||||
query predicate argHasPostUpdate(ArgumentNode n, string msg) {
|
||||
not hasPost(n) and
|
||||
not any(ConsistencyConfiguration c).argHasPostUpdateExclude(n) and
|
||||
msg = "ArgumentNode is missing PostUpdateNode."
|
||||
}
|
||||
|
||||
// This predicate helps the compiler forget that in some languages
|
||||
// it is impossible for a `PostUpdateNode` to be the target of
|
||||
// `simpleLocalFlowStep`.
|
||||
private predicate isPostUpdateNode(Node n) { n instanceof PostUpdateNode or none() }
|
||||
|
||||
query predicate postWithInFlow(Node n, string msg) {
|
||||
isPostUpdateNode(n) and
|
||||
not clearsContent(n, _) and
|
||||
simpleLocalFlowStep(_, n) and
|
||||
not any(ConsistencyConfiguration c).postWithInFlowExclude(n) and
|
||||
msg = "PostUpdateNode should not be the target of local flow."
|
||||
}
|
||||
|
||||
query predicate viableImplInCallContextTooLarge(
|
||||
DataFlowCall call, DataFlowCall ctx, DataFlowCallable callable
|
||||
) {
|
||||
callable = viableImplInCallContext(call, ctx) and
|
||||
not callable = viableCallable(call) and
|
||||
not any(ConsistencyConfiguration c).viableImplInCallContextTooLargeExclude(call, ctx, callable)
|
||||
}
|
||||
|
||||
query predicate uniqueParameterNodeAtPosition(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p, string msg
|
||||
) {
|
||||
not any(ConsistencyConfiguration conf).uniqueParameterNodeAtPositionExclude(c, pos, p) and
|
||||
isParameterNode(p, c, pos) and
|
||||
not exists(unique(Node p0 | isParameterNode(p0, c, pos))) and
|
||||
msg = "Parameters with overlapping positions."
|
||||
}
|
||||
|
||||
query predicate uniqueParameterNodePosition(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p, string msg
|
||||
) {
|
||||
not any(ConsistencyConfiguration conf).uniqueParameterNodePositionExclude(c, pos, p) and
|
||||
isParameterNode(p, c, pos) and
|
||||
not exists(unique(ParameterPosition pos0 | isParameterNode(p, c, pos0))) and
|
||||
msg = "Parameter node with multiple positions."
|
||||
}
|
||||
|
||||
query predicate uniqueContentApprox(Content c, string msg) {
|
||||
not exists(unique(ContentApprox approx | approx = getContentApprox(c))) and
|
||||
msg = "Non-unique content approximation."
|
||||
}
|
||||
|
||||
query predicate identityLocalStep(Node n, string msg) {
|
||||
simpleLocalFlowStep(n, n) and
|
||||
not any(ConsistencyConfiguration c).identityLocalStepExclude(n) and
|
||||
msg = "Node steps to itself"
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
@@ -313,6 +315,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() }
|
||||
|
||||
@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
@@ -313,6 +315,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.DataFlow
|
||||
|
||||
module Private {
|
||||
import DataFlowPrivate
|
||||
import DataFlowDispatch
|
||||
@@ -9,3 +12,16 @@ module Private {
|
||||
module Public {
|
||||
import DataFlowPublic
|
||||
}
|
||||
|
||||
module RubyDataFlow implements InputSig {
|
||||
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, _)
|
||||
)
|
||||
}
|
||||
@@ -137,22 +137,6 @@ module LocalFlow {
|
||||
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom -> nodeTo` is a step from a parameter to a capture entry node for
|
||||
* that parameter.
|
||||
*
|
||||
* This is intended to recover from flow not currently recognised by ordinary capture flow.
|
||||
*/
|
||||
predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::CapturedEntryDefinition def |
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
|
|
||||
nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
or
|
||||
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
|
||||
* involving SSA definition `def`.
|
||||
@@ -203,8 +187,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
|
||||
@@ -245,6 +229,7 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
not this.getExpr() instanceof BlockArgument and
|
||||
not this.getExpr().(Pair).getKey().getConstantValue().isSymbol(_) and
|
||||
not this.getExpr() instanceof HashSplatExpr and
|
||||
not this.getExpr() instanceof SplatExpr and
|
||||
arg.isPositional(i)
|
||||
)
|
||||
or
|
||||
@@ -260,9 +245,11 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
this.getExpr() instanceof HashSplatExpr and
|
||||
arg.isHashSplat()
|
||||
or
|
||||
this = call.getArgument(0) and
|
||||
this.getExpr() instanceof SplatExpr and
|
||||
arg.isSplatAll()
|
||||
exists(int pos |
|
||||
this = call.getArgument(pos) and
|
||||
this.getExpr() instanceof SplatExpr and
|
||||
arg.isSplat(pos)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this expression is the `i`th argument of `c`. */
|
||||
@@ -277,6 +264,53 @@ predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) {
|
||||
not n.getExpr() instanceof ConstantAccess
|
||||
}
|
||||
|
||||
/** Provides logic related to captured variables. */
|
||||
module VariableCapture {
|
||||
class CapturedVariable extends LocalVariable {
|
||||
CapturedVariable() { this.isCaptured() }
|
||||
|
||||
CfgScope getCfgScope() {
|
||||
exists(Scope scope | scope = this.getDeclaringScope() |
|
||||
result = scope
|
||||
or
|
||||
result = scope.(ModuleBase).getCfgScope()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CapturedSsaDefinitionExt extends SsaImpl::DefinitionExt {
|
||||
CapturedSsaDefinitionExt() { this.getSourceVariable() instanceof CapturedVariable }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is control-flow insensitive data-flow from `node1` to `node2`
|
||||
* involving a captured variable. Only used in type tracking.
|
||||
*/
|
||||
predicate flowInsensitiveStep(Node node1, Node node2) {
|
||||
exists(CapturedSsaDefinitionExt def, CapturedVariable v |
|
||||
// From an assignment or implicit initialization of a captured variable to its flow-insensitive node
|
||||
def = node1.(SsaDefinitionExtNode).getDefinitionExt() and
|
||||
def.getSourceVariable() = v and
|
||||
(
|
||||
def instanceof Ssa::WriteDefinition
|
||||
or
|
||||
def instanceof Ssa::SelfDefinition
|
||||
) and
|
||||
node2.(CapturedVariableNode).getVariable() = v
|
||||
or
|
||||
// From a captured variable node to its flow-sensitive capture nodes
|
||||
node1.(CapturedVariableNode).getVariable() = v and
|
||||
def = node2.(SsaDefinitionExtNode).getDefinitionExt() and
|
||||
def.getSourceVariable() = v and
|
||||
(
|
||||
def instanceof Ssa::CapturedCallDefinition
|
||||
or
|
||||
def instanceof Ssa::CapturedEntryDefinition
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A collection of cached types and predicates to be evaluated in the same stage. */
|
||||
cached
|
||||
private module Cached {
|
||||
@@ -288,6 +322,7 @@ private module Cached {
|
||||
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
|
||||
TReturningNode(CfgNodes::ReturningCfgNode n) or
|
||||
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
|
||||
TCapturedVariableNode(VariableCapture::CapturedVariable v) or
|
||||
TNormalParameterNode(Parameter p) {
|
||||
p instanceof SimpleParameter or
|
||||
p instanceof OptionalParameter or
|
||||
@@ -300,6 +335,19 @@ private module Cached {
|
||||
TSynthHashSplatParameterNode(DataFlowCallable c) {
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
|
||||
} or
|
||||
TSynthSplatParameterNode(DataFlowCallable c) {
|
||||
exists(c.asCallable()) and // exclude library callables
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isPositional(_)))
|
||||
} or
|
||||
TSynthSplatArgParameterNode(DataFlowCallable c) {
|
||||
exists(c.asCallable()) and // exclude library callables
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_)))
|
||||
} or
|
||||
TSynthSplatParameterElementNode(DataFlowCallable c, int n) {
|
||||
exists(c.asCallable()) and // exclude library callables
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_))) and
|
||||
n in [0 .. 10]
|
||||
} or
|
||||
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) {
|
||||
// filter out nodes that clearly don't need post-update nodes
|
||||
isNonConstantExpr(n) and
|
||||
@@ -314,11 +362,15 @@ private module Cached {
|
||||
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
|
||||
or
|
||||
c.getAnArgument() instanceof CfgNodes::ExprNodes::PairCfgNode
|
||||
} or
|
||||
TSynthSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
|
||||
exists(Argument arg, ArgumentPosition pos | pos.isPositional(_) | arg.isArgumentOf(c, pos)) and
|
||||
not exists(Argument arg, ArgumentPosition pos | pos.isSplat(_) | arg.isArgumentOf(c, pos))
|
||||
}
|
||||
|
||||
class TSourceParameterNode =
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
|
||||
TSynthHashSplatParameterNode;
|
||||
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode;
|
||||
|
||||
cached
|
||||
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
|
||||
@@ -377,6 +429,8 @@ private module Cached {
|
||||
LocalFlow::localFlowSsaInputFromRead(exprFrom, _, nodeTo) and
|
||||
exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()]
|
||||
)
|
||||
or
|
||||
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
private predicate entrySsaDefinition(SsaDefinitionExtNode n) {
|
||||
@@ -418,8 +472,11 @@ private module Cached {
|
||||
entrySsaDefinition(n) and
|
||||
not LocalFlow::localFlowSsaParamInput(_, n)
|
||||
or
|
||||
// Needed for stores in type tracking
|
||||
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
|
||||
or
|
||||
TypeTrackerSpecific::readStepIntoSourceNode(_, n, _)
|
||||
or
|
||||
TypeTrackerSpecific::readStoreStepIntoSourceNode(_, n, _, _)
|
||||
}
|
||||
|
||||
cached
|
||||
@@ -501,9 +558,7 @@ import Cached
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) {
|
||||
exists(SsaImpl::DefinitionExt def | def = n.(SsaDefinitionExtNode).getDefinitionExt() |
|
||||
not def instanceof Ssa::WriteDefinition
|
||||
)
|
||||
n.(SsaDefinitionExtNode).isHidden()
|
||||
or
|
||||
n = LocalFlow::getParameterDefNode(_)
|
||||
or
|
||||
@@ -514,6 +569,14 @@ predicate nodeIsHidden(Node n) {
|
||||
n instanceof SynthHashSplatParameterNode
|
||||
or
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
or
|
||||
n instanceof SynthSplatParameterNode
|
||||
or
|
||||
n instanceof SynthSplatArgumentNode
|
||||
or
|
||||
n instanceof SynthSplatArgParameterNode
|
||||
or
|
||||
n instanceof SynthSplatParameterElementNode
|
||||
}
|
||||
|
||||
/** An SSA definition, viewed as a node in a data flow graph. */
|
||||
@@ -528,6 +591,13 @@ class SsaDefinitionExtNode extends NodeImpl, TSsaDefinitionExtNode {
|
||||
/** Gets the underlying variable. */
|
||||
Variable getVariable() { result = def.getSourceVariable() }
|
||||
|
||||
/** Holds if this node should be hidden from path explanations. */
|
||||
predicate isHidden() {
|
||||
not def instanceof Ssa::WriteDefinition
|
||||
or
|
||||
isDesugarNode(def.(Ssa::WriteDefinition).getWriteAccess().getExpr())
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = def.getBasicBlock().getScope() }
|
||||
|
||||
override Location getLocationImpl() { result = def.getLocation() }
|
||||
@@ -536,7 +606,7 @@ class SsaDefinitionExtNode extends NodeImpl, TSsaDefinitionExtNode {
|
||||
}
|
||||
|
||||
/** An SSA definition for a `self` variable. */
|
||||
class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode {
|
||||
class SsaSelfDefinitionNode extends SsaDefinitionExtNode {
|
||||
private SelfVariable self;
|
||||
|
||||
SsaSelfDefinitionNode() { self = def.getSourceVariable() }
|
||||
@@ -545,6 +615,22 @@ class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode {
|
||||
Scope getSelfScope() { result = self.getDeclaringScope() }
|
||||
}
|
||||
|
||||
/** A data flow node representing a captured variable. Only used in type tracking. */
|
||||
class CapturedVariableNode extends NodeImpl, TCapturedVariableNode {
|
||||
private VariableCapture::CapturedVariable variable;
|
||||
|
||||
CapturedVariableNode() { this = TCapturedVariableNode(variable) }
|
||||
|
||||
/** Gets the captured variable represented by this node. */
|
||||
VariableCapture::CapturedVariable getVariable() { result = variable }
|
||||
|
||||
override CfgScope getCfgScope() { result = variable.getCfgScope() }
|
||||
|
||||
override Location getLocationImpl() { result = variable.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "captured " + variable.getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A value returning statement, viewed as a node in a data flow graph.
|
||||
*
|
||||
@@ -609,8 +695,12 @@ private module ParameterNodes {
|
||||
parameter = callable.getAParameter().(HashSplatParameter) and
|
||||
pos.isHashSplat()
|
||||
or
|
||||
parameter = callable.getParameter(0).(SplatParameter) and
|
||||
pos.isSplatAll()
|
||||
exists(int n |
|
||||
parameter = callable.getParameter(n).(SplatParameter) and
|
||||
pos.isSplat(n) and
|
||||
// There are no positional parameters after the splat
|
||||
not exists(SimpleParameter p, int m | m > n | p = callable.getParameter(m))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -749,6 +839,129 @@ private module ParameterNodes {
|
||||
final override string toStringImpl() { result = "**kwargs" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic data-flow node to allow flow to positional parameters from a splat argument.
|
||||
*
|
||||
* For example, in the following code:
|
||||
*
|
||||
* ```rb
|
||||
* def foo(x, y); end
|
||||
*
|
||||
* foo(*[a, b])
|
||||
* ```
|
||||
*
|
||||
* We want `a` to flow to `x` and `b` to flow to `y`. We do this by constructing
|
||||
* a `SynthSplatParameterNode` for the method `foo`, and matching the splat argument to this
|
||||
* parameter node via `parameterMatch/2`. We then add read steps from this node to parameters
|
||||
* `x` and `y`, for content at indices 0 and 1 respectively (see `readStep`).
|
||||
*
|
||||
* We don't yet correctly handle cases where the splat argument is not the first argument, e.g. in
|
||||
* ```rb
|
||||
* foo(a, *[b])
|
||||
* ```
|
||||
*/
|
||||
class SynthSplatParameterNode extends ParameterNodeImpl, TSynthSplatParameterNode {
|
||||
private DataFlowCallable callable;
|
||||
|
||||
SynthSplatParameterNode() { this = TSynthSplatParameterNode(callable) }
|
||||
|
||||
/**
|
||||
* Gets a parameter which will contain the value given by `c`, assuming
|
||||
* that the method was called with a single splat argument.
|
||||
* For example, if the synth splat parameter is for the following method
|
||||
*
|
||||
* ```rb
|
||||
* def foo(x, y, a:, *rest)
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* Then `getAParameter(element 0) = x` and `getAParameter(element 1) = y`.
|
||||
*/
|
||||
ParameterNode getAParameter(ContentSet c) {
|
||||
exists(int n |
|
||||
isParameterNode(result, callable, (any(ParameterPosition p | p.isPositional(n)))) and
|
||||
(
|
||||
c = getPositionalContent(n)
|
||||
or
|
||||
c.isSingleton(TUnknownElementContent())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
final override Parameter getParameter() { none() }
|
||||
|
||||
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
c = callable and pos.isSynthSplat()
|
||||
}
|
||||
|
||||
final override CfgScope getCfgScope() { result = callable.asCallable() }
|
||||
|
||||
final override DataFlowCallable getEnclosingCallable() { result = callable }
|
||||
|
||||
final override Location getLocationImpl() { result = callable.getLocation() }
|
||||
|
||||
final override string toStringImpl() { result = "synthetic *args" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that holds all positional arguments passed in a call to `c`.
|
||||
* This is a mirror of the `SynthSplatArgumentNode` on the callable side.
|
||||
* See `SynthSplatArgumentNode` for more information.
|
||||
*/
|
||||
class SynthSplatArgParameterNode extends ParameterNodeImpl, TSynthSplatArgParameterNode {
|
||||
private DataFlowCallable callable;
|
||||
|
||||
SynthSplatArgParameterNode() { this = TSynthSplatArgParameterNode(callable) }
|
||||
|
||||
final override Parameter getParameter() { none() }
|
||||
|
||||
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
c = callable and pos.isSynthArgSplat()
|
||||
}
|
||||
|
||||
final override CfgScope getCfgScope() { result = callable.asCallable() }
|
||||
|
||||
final override DataFlowCallable getEnclosingCallable() { result = callable }
|
||||
|
||||
final override Location getLocationImpl() { result = callable.getLocation() }
|
||||
|
||||
final override string toStringImpl() { result = "synthetic *args" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that holds the content of a specific positional argument.
|
||||
* See `SynthSplatArgumentNode` for more information.
|
||||
*/
|
||||
class SynthSplatParameterElementNode extends NodeImpl, TSynthSplatParameterElementNode {
|
||||
private DataFlowCallable callable;
|
||||
private int pos;
|
||||
|
||||
SynthSplatParameterElementNode() { this = TSynthSplatParameterElementNode(callable, pos) }
|
||||
|
||||
pragma[nomagic]
|
||||
NormalParameterNode getSplatParameterNode(int splatPos) {
|
||||
result
|
||||
.isParameterOf(this.getEnclosingCallable(), any(ParameterPosition p | p.isSplat(splatPos)))
|
||||
}
|
||||
|
||||
int getStorePosition() { result = pos }
|
||||
|
||||
int getReadPosition() {
|
||||
exists(int splatPos |
|
||||
exists(this.getSplatParameterNode(splatPos)) and
|
||||
result = pos + splatPos
|
||||
)
|
||||
}
|
||||
|
||||
final override CfgScope getCfgScope() { result = callable.asCallable() }
|
||||
|
||||
final override DataFlowCallable getEnclosingCallable() { result = callable }
|
||||
|
||||
final override Location getLocationImpl() { result = callable.getLocation() }
|
||||
|
||||
final override string toStringImpl() { result = "synthetic *args[" + pos + "]" }
|
||||
}
|
||||
|
||||
/** A parameter for a library callable with a flow summary. */
|
||||
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
|
||||
private ParameterPosition pos_;
|
||||
@@ -894,6 +1107,51 @@ private module ArgumentNodes {
|
||||
|
||||
override string toStringImpl() { result = "**" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that represents all arguments passed to the call.
|
||||
* We use this to model data flow via splat parameters.
|
||||
* Consider this example:
|
||||
*
|
||||
* ```rb
|
||||
* def foo(x, y, *z)
|
||||
* end
|
||||
*
|
||||
* foo(1, 2, 3, 4)
|
||||
* ```
|
||||
*
|
||||
* 1. We want `3` to flow to `z[0]` and `4` to flow to `z[1]`. We model this by first storing all arguments
|
||||
* in a synthetic argument node `SynthSplatArgumentNode` (see `storeStepCommon`).
|
||||
* 2. We match this to an analogous parameter node `SynthSplatArgParameterNode` on the callee side
|
||||
* (see `parameterMatch`).
|
||||
* 3. For each content element stored in the `SynthSplatArgParameterNode`, we add a read step to a separate
|
||||
* `SynthSplatParameterElementNode`, which is parameterized by the element index (see `readStep`).
|
||||
* 4. Finally, we add store steps from these `SynthSplatParameterElementNode`s to the real splat parameter node
|
||||
* (see `storeStep`).
|
||||
* We only add store steps for elements that will not flow to the earlier positional parameters.
|
||||
* In practice that means we ignore elements at index `<= N`, where `N` is the index of the splat parameter.
|
||||
* For the remaining elements we subtract `N` from their index and store them in the splat parameter.
|
||||
*/
|
||||
class SynthSplatArgumentNode extends ArgumentNode, NodeImpl, TSynthSplatArgumentNode {
|
||||
CfgNodes::ExprNodes::CallCfgNode c;
|
||||
|
||||
SynthSplatArgumentNode() { this = TSynthSplatArgumentNode(c) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this.sourceArgumentOf(call.asCall(), pos)
|
||||
}
|
||||
|
||||
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
|
||||
call = c and
|
||||
pos.isSynthSplat()
|
||||
}
|
||||
|
||||
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
|
||||
|
||||
override Location getLocationImpl() { result = c.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "*" }
|
||||
}
|
||||
}
|
||||
|
||||
import ArgumentNodes
|
||||
@@ -926,7 +1184,7 @@ abstract class SourceReturnNode extends ReturnNode {
|
||||
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
|
||||
@@ -952,7 +1210,7 @@ private module ReturnNodes {
|
||||
}
|
||||
|
||||
override ReturnKind getKindSource() {
|
||||
if n.getNode() instanceof BreakStmt
|
||||
if n.getAstNode() instanceof BreakStmt
|
||||
then result instanceof BreakReturnKind
|
||||
else
|
||||
exists(CfgScope scope | scope = this.getCfgScope() |
|
||||
@@ -1077,13 +1335,7 @@ private module OutNodes {
|
||||
|
||||
import OutNodes
|
||||
|
||||
predicate jumpStep(Node pred, Node succ) {
|
||||
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
or
|
||||
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
or
|
||||
predicate jumpStepTypeTracker(Node pred, Node succ) {
|
||||
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
|
||||
@@ -1092,6 +1344,16 @@ predicate jumpStep(Node pred, Node succ) {
|
||||
any(AdditionalJumpStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
predicate jumpStep(Node pred, Node succ) {
|
||||
jumpStepTypeTracker(pred, succ)
|
||||
or
|
||||
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
or
|
||||
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
}
|
||||
|
||||
private ContentSet getKeywordContent(string name) {
|
||||
exists(ConstantValue::ConstantSymbolValue key |
|
||||
result.isSingleton(TKnownElementContent(key)) and
|
||||
@@ -1099,6 +1361,13 @@ private ContentSet getKeywordContent(string name) {
|
||||
)
|
||||
}
|
||||
|
||||
ContentSet getPositionalContent(int n) {
|
||||
exists(ConstantValue::ConstantIntegerValue i |
|
||||
result.isSingleton(TKnownElementContent(i)) and
|
||||
i.isInt(n)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subset of `storeStep` that should be shared with type-tracking.
|
||||
*/
|
||||
@@ -1122,6 +1391,14 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
|
||||
c.isSingleton(TKnownElementContent(cv))
|
||||
)
|
||||
)
|
||||
or
|
||||
// Wrap all positional arguments in a synthesized splat argument node
|
||||
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos |
|
||||
node2 = TSynthSplatArgumentNode(call) and
|
||||
node1.asExpr().(Argument).isArgumentOf(call, pos)
|
||||
|
|
||||
exists(int n | pos.isPositional(n) and c = getPositionalContent(n))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1155,9 +1432,24 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
||||
node2.(FlowSummaryNode).getSummaryNode())
|
||||
or
|
||||
node1 =
|
||||
any(SynthSplatParameterElementNode elemNode |
|
||||
node2 = elemNode.getSplatParameterNode(_) and
|
||||
c = getPositionalContent(elemNode.getStorePosition())
|
||||
)
|
||||
or
|
||||
storeStepCommon(node1, c, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subset of `readStep` that should be shared with type-tracking.
|
||||
*/
|
||||
predicate readStepCommon(Node node1, ContentSet c, Node node2) {
|
||||
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
|
||||
or
|
||||
node2 = node1.(SynthSplatParameterNode).getAParameter(c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a read step of content `c` from `node1` to `node2`.
|
||||
*/
|
||||
@@ -1185,10 +1477,17 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
))
|
||||
)
|
||||
or
|
||||
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
||||
node2.(FlowSummaryNode).getSummaryNode())
|
||||
or
|
||||
// Read from SynthSplatArgParameterNode into SynthSplatParameterElementNode
|
||||
node2 =
|
||||
any(SynthSplatParameterElementNode e |
|
||||
node1.(SynthSplatArgParameterNode).isParameterOf(e.getEnclosingCallable(), _) and
|
||||
c = getPositionalContent(e.getReadPosition())
|
||||
)
|
||||
or
|
||||
readStepCommon(node1, c, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1232,7 +1531,7 @@ class DataFlowType extends TDataFlowType {
|
||||
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() }
|
||||
@@ -1290,16 +1589,24 @@ private import PostUpdateNodes
|
||||
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends Node {
|
||||
CastNode() {
|
||||
// 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 =
|
||||
any(SsaDefinitionExtNode def |
|
||||
def.getDefinitionExt() instanceof Ssa::WriteDefinition and
|
||||
not def.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -1327,11 +1634,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.
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,6 +239,10 @@ class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl {
|
||||
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) }
|
||||
@@ -337,10 +363,9 @@ private module Cached {
|
||||
source = sink and
|
||||
source instanceof LocalSourceNode
|
||||
or
|
||||
exists(Node mid | hasLocalSource(mid, source) |
|
||||
exists(Node mid |
|
||||
hasLocalSource(mid, source) and
|
||||
localFlowStepTypeTracker(mid, sink)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -359,6 +384,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.
|
||||
*/
|
||||
@@ -387,6 +417,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() }
|
||||
|
||||
@@ -1028,6 +1091,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) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1216,6 +1306,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) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1284,13 +1377,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) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1331,24 +1427,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.
|
||||
*
|
||||
@@ -1356,22 +1434,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).
|
||||
*
|
||||
@@ -1433,7 +1495,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)
|
||||
)
|
||||
}
|
||||
@@ -1455,7 +1517,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,42 +169,6 @@ module Public {
|
||||
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this component used for flow summaries. */
|
||||
private string getComponent(SummaryComponent sc) {
|
||||
result = getComponentSpecific(sc)
|
||||
or
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(string synthetic |
|
||||
sc = TSyntheticGlobalSummaryComponent(synthetic) and
|
||||
result = "SyntheticGlobal[" + synthetic + "]"
|
||||
)
|
||||
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).
|
||||
@@ -329,11 +296,21 @@ module Public {
|
||||
predicate hasProvenance(Provenance provenance) { provenance = "manual" }
|
||||
}
|
||||
|
||||
/** A callable where there is no flow via the callable. */
|
||||
class NeutralCallable extends SummarizedCallableBase {
|
||||
/**
|
||||
* A callable where there is no flow via the callable.
|
||||
*/
|
||||
class NeutralSummaryCallable extends NeutralCallable {
|
||||
NeutralSummaryCallable() { this.getKind() = "summary" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A callable that has a neutral model.
|
||||
*/
|
||||
class NeutralCallable extends NeutralCallableBase {
|
||||
private string kind;
|
||||
private Provenance provenance;
|
||||
|
||||
NeutralCallable() { neutralSummaryElement(this, provenance) }
|
||||
NeutralCallable() { neutralElement(this, kind, provenance) }
|
||||
|
||||
/**
|
||||
* Holds if the neutral is auto generated.
|
||||
@@ -349,6 +326,11 @@ module Public {
|
||||
* Holds if the neutral has provenance `p`.
|
||||
*/
|
||||
predicate hasProvenance(Provenance p) { p = provenance }
|
||||
|
||||
/**
|
||||
* Gets the kind of the neutral.
|
||||
*/
|
||||
string getKind() { result = kind }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1351,6 +1333,11 @@ module Private {
|
||||
/** Gets the string representation of this callable used by `neutral/1`. */
|
||||
abstract string getCallableCsv();
|
||||
|
||||
/**
|
||||
* Gets the kind of the neutral.
|
||||
*/
|
||||
string getKind() { result = super.getKind() }
|
||||
|
||||
string toString() { result = super.toString() }
|
||||
}
|
||||
|
||||
@@ -1382,8 +1369,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
|
||||
)
|
||||
@@ -1391,12 +1378,13 @@ module Private {
|
||||
|
||||
/**
|
||||
* Holds if a neutral model `csv` exists (semi-colon separated format). Used for testing purposes.
|
||||
* The syntax is: "namespace;type;name;signature;provenance"",
|
||||
* The syntax is: "namespace;type;name;signature;kind;provenance"",
|
||||
*/
|
||||
query predicate neutral(string csv) {
|
||||
exists(RelevantNeutralCallable c |
|
||||
csv =
|
||||
c.getCallableCsv() // Callable information
|
||||
+ c.getKind() + ";" // kind
|
||||
+ renderProvenanceNeutral(c) // provenance
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,16 @@ private import FlowSummaryImpl::Private
|
||||
private import FlowSummaryImpl::Public
|
||||
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
|
||||
|
||||
/**
|
||||
* A class of callables that are candidates for flow summary modeling.
|
||||
*/
|
||||
class SummarizedCallableBase = string;
|
||||
|
||||
/**
|
||||
* A class of callables that are candidates for neutral modeling.
|
||||
*/
|
||||
class NeutralCallableBase = string;
|
||||
|
||||
DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
|
||||
|
||||
/** Gets the parameter position representing a callback itself, if any. */
|
||||
@@ -62,11 +70,11 @@ predicate summaryElement(
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a neutral summary model exists for `c` with provenance `provenance`,
|
||||
* which means that there is no flow through `c`.
|
||||
* Holds if a neutral model exists for `c` of kind `kind`
|
||||
* and with provenance `provenance`.
|
||||
* Note. Neutral models have not been implemented for Ruby.
|
||||
*/
|
||||
predicate neutralSummaryElement(FlowSummary::SummarizedCallable c, string provenance) { none() }
|
||||
predicate neutralElement(NeutralCallableBase c, string kind, string provenance) { none() }
|
||||
|
||||
bindingset[arg]
|
||||
private SummaryComponent interpretElementArg(string arg) {
|
||||
@@ -139,8 +147,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 +223,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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Provides Ruby-specific definitions for use in the taint tracking library.
|
||||
*/
|
||||
|
||||
private import codeql.dataflow.TaintTracking
|
||||
private import DataFlowImplSpecific
|
||||
|
||||
module RubyTaintTracking implements InputSig<RubyDataFlow> {
|
||||
import TaintTrackingPrivate
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Provides classes for performing local (intra-procedural) and
|
||||
* global (inter-procedural) taint-tracking analyses.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> implements
|
||||
DataFlowInternal::FullStateConfigSig
|
||||
{
|
||||
import Config
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
Config::isBarrier(node) or defaultTaintSanitizer(node)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
Config::isAdditionalFlowStep(node1, node2) or
|
||||
defaultAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
Config::allowImplicitRead(node, c)
|
||||
or
|
||||
(
|
||||
Config::isSink(node, _) or
|
||||
Config::isAdditionalFlowStep(node, _) or
|
||||
Config::isAdditionalFlowStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a global taint tracking computation.
|
||||
*/
|
||||
module Global<DataFlow::ConfigSig Config> implements DataFlow::GlobalFlowSig {
|
||||
private module Config0 implements DataFlowInternal::FullStateConfigSig {
|
||||
import DataFlowInternal::DefaultState<Config>
|
||||
import Config
|
||||
}
|
||||
|
||||
private module C implements DataFlowInternal::FullStateConfigSig {
|
||||
import AddTaintDefaults<Config0>
|
||||
}
|
||||
|
||||
import DataFlowInternal::Impl<C>
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use `Global` instead. */
|
||||
deprecated module Make<DataFlow::ConfigSig Config> implements DataFlow::GlobalFlowSig {
|
||||
import Global<Config>
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a global taint tracking computation using flow state.
|
||||
*/
|
||||
module GlobalWithState<DataFlow::StateConfigSig Config> implements DataFlow::GlobalFlowSig {
|
||||
private module Config0 implements DataFlowInternal::FullStateConfigSig {
|
||||
import Config
|
||||
}
|
||||
|
||||
private module C implements DataFlowInternal::FullStateConfigSig {
|
||||
import AddTaintDefaults<Config0>
|
||||
}
|
||||
|
||||
import DataFlowInternal::Impl<C>
|
||||
}
|
||||
|
||||
/** DEPRECATED: Use `GlobalWithState` instead. */
|
||||
deprecated module MakeWithState<DataFlow::StateConfigSig Config> implements DataFlow::GlobalFlowSig {
|
||||
import GlobalWithState<Config>
|
||||
}
|
||||
@@ -91,19 +91,19 @@ class Configuration extends TaintTracking::Configuration {
|
||||
// unicode_utils
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("UnicodeUtils").getMethod(["nfkd", "nfc", "nfd", "nfkc"]) and
|
||||
sink = mac.getParameter(0).asSink()
|
||||
sink = mac.getArgument(0).asSink()
|
||||
)
|
||||
or
|
||||
// eprun
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("Eprun").getMethod("normalize") and
|
||||
sink = mac.getParameter(0).asSink()
|
||||
sink = mac.getArgument(0).asSink()
|
||||
)
|
||||
or
|
||||
// unf
|
||||
exists(API::MethodAccessNode mac |
|
||||
mac = API::getTopLevelMember("UNF").getMember("Normalizer").getMethod("normalize") and
|
||||
sink = mac.getParameter(0).asSink()
|
||||
sink = mac.getArgument(0).asSink()
|
||||
)
|
||||
or
|
||||
// ActiveSupport::Multibyte::Chars
|
||||
@@ -113,7 +113,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
.getMember("Multibyte")
|
||||
.getMember("Chars")
|
||||
.getMethod("new")
|
||||
.getCallNode() and
|
||||
.asCall() and
|
||||
n = cn.getAMethodCall("normalize") and
|
||||
sink = cn.getArgument(0)
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,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. */
|
||||
@@ -260,6 +260,7 @@ class RedirectToCall extends MethodCall {
|
||||
this =
|
||||
controller
|
||||
.getSelf()
|
||||
.track()
|
||||
.getAMethodCall(["redirect_to", "redirect_back", "redirect_back_or_to"])
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
@@ -600,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=") }
|
||||
@@ -628,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
|
||||
@@ -673,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`.
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,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
|
||||
@@ -42,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,
|
||||
*
|
||||
@@ -55,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
|
||||
@@ -90,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())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -162,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
|
||||
@@ -175,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
|
||||
/**
|
||||
@@ -241,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,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,
|
||||
DataFlow::SelfParameterNode
|
||||
{
|
||||
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation {
|
||||
private ActiveRecordModelClass cls;
|
||||
|
||||
ActiveRecordModelClassSelfReference() {
|
||||
exists(MethodBase m |
|
||||
m = this.getCallable() 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.getSelfVariable().getDeclaringScope() instanceof SingletonMethod
|
||||
}
|
||||
ActiveRecordModelClassSelfReference() { this = cls.getClassNode().getAnOwnInstanceSelf() }
|
||||
|
||||
final override ActiveRecordModelClass getClass() { result = cls }
|
||||
}
|
||||
@@ -342,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() }
|
||||
@@ -380,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() {
|
||||
@@ -402,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() {
|
||||
@@ -448,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") }
|
||||
}
|
||||
@@ -461,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. */
|
||||
|
||||
106
ruby/ql/lib/codeql/ruby/frameworks/Ldap.qll
Normal file
106
ruby/ql/lib/codeql/ruby/frameworks/Ldap.qll
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Provides modeling for `net-ldap` a ruby library for LDAP.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for `net-ldap` a ruby library for LDAP.
|
||||
*/
|
||||
module NetLdap {
|
||||
/**
|
||||
* Flow summary for `Net::LDAP.new`. This method establishes a connection to a LDAP server.
|
||||
*/
|
||||
private class LdapConnSummary extends SummarizedCallable {
|
||||
LdapConnSummary() { this = "Net::LDAP.new" }
|
||||
|
||||
override MethodCall getACall() { result = any(NetLdapConnection l).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
getValue(this, "encryption").getConstantValue().isStringlikeValue("simple_tls")
|
||||
}
|
||||
|
||||
DataFlow::Node getAuthValue(string arg) {
|
||||
result =
|
||||
this.getKeywordArgument("auth")
|
||||
.(DataFlow::HashLiteralNode)
|
||||
.getElementFromKey(any(Ast::ConstantValue cv | cv.isStringlikeValue(arg)))
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that constructs a LDAP query */
|
||||
private class NetLdapFilter extends LdapConstruction::Range, DataFlow::CallNode {
|
||||
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]) }
|
||||
}
|
||||
|
||||
/** A call considered as a LDAP execution. */
|
||||
private class NetLdapExecution extends LdapExecution::Range, DataFlow::CallNode {
|
||||
NetLdapExecution() { this = any(NetLdapConnection l).getAMethodCall("search") }
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getKeywordArgument(_) }
|
||||
}
|
||||
|
||||
/** A call considered as a LDAP bind. */
|
||||
private class NetLdapBind extends LdapBind::Range, DataFlow::CallNode {
|
||||
private NetLdapConnection l;
|
||||
|
||||
NetLdapBind() { this = l.getAMethodCall("bind") }
|
||||
|
||||
override DataFlow::Node getHost() { result = getValue(l, "host") }
|
||||
|
||||
override DataFlow::Node getPassword() {
|
||||
result = l.getAuthValue("password") or
|
||||
result = l.getAMethodCall("auth").getArgument(1)
|
||||
}
|
||||
|
||||
override predicate usesSsl() { l.usesSsl() }
|
||||
}
|
||||
|
||||
/** LDAP Attribute value */
|
||||
DataFlow::Node getValue(NetLdapConnection l, string attr) {
|
||||
result =
|
||||
[
|
||||
l.getKeywordArgument(attr), l.getAMethodCall(attr).getArgument(0),
|
||||
l.getAMethodCall(attr).getKeywordArgument(attr)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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.getAReturnNode() = 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;
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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`.
|
||||
*/
|
||||
|
||||
@@ -128,7 +128,7 @@ module Request {
|
||||
private import codeql.ruby.frameworks.Rack
|
||||
|
||||
private class RackEnv extends Env {
|
||||
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
|
||||
RackEnv() { this = any(Rack::App::RequestHandler handler).getEnv().getALocalUse() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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`. */
|
||||
|
||||
@@ -64,10 +64,8 @@ class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode
|
||||
|
||||
/** Gets the value that controls certificate validation, if any. */
|
||||
DataFlow::Node getCertificateValidationControllingValue() {
|
||||
exists(DataFlow::CallNode newCall | newCall = connectionNode.getAValueReachableFromSource() |
|
||||
// Check for `ssl_verify_peer: false`
|
||||
result = newCall.getKeywordArgumentIncludeHashArgument("ssl_verify_peer")
|
||||
)
|
||||
result =
|
||||
connectionUse.(DataFlow::CallNode).getKeywordArgumentIncludeHashArgument("ssl_verify_peer")
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
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
|
||||
|
||||
@@ -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%")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* improper LDAP authentication, as well as extension points for adding your own
|
||||
*/
|
||||
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.BarrierGuards
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* improper LDAP authentication, as well as extension points for adding your own
|
||||
*/
|
||||
module ImproperLdapAuth {
|
||||
/** A data flow source for improper LDAP authentication vulnerabilities */
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/** A data flow sink for improper LDAP authentication vulnerabilities */
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/** A sanitizer for improper LDAP authentication vulnerabilities. */
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
*/
|
||||
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
|
||||
/**
|
||||
* An LDAP query execution considered as a flow sink.
|
||||
*/
|
||||
private class LdapBindAsSink extends Sink {
|
||||
LdapBindAsSink() { this = any(LdapBind l).getPassword() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* An inclusion check against an array of constant strings, considered as a
|
||||
* sanitizer-guard.
|
||||
*/
|
||||
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier
|
||||
{ }
|
||||
}
|
||||
21
ruby/ql/lib/codeql/ruby/security/ImproperLdapAuthQuery.qll
Normal file
21
ruby/ql/lib/codeql/ruby/security/ImproperLdapAuthQuery.qll
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* improper LDAP authentication, as well as extension points for adding your own
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import ImproperLdapAuthCustomizations::ImproperLdapAuth
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting improper LDAP authentication vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ImproperLdapAuth" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* LDAP Injections, as well as extension points for adding your own
|
||||
*/
|
||||
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.BarrierGuards
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* LDAP Injections, as well as extension points for adding your own
|
||||
*/
|
||||
module LdapInjection {
|
||||
/** A data flow source for LDAP Injection vulnerabilities */
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/** A data flow sink for LDAP Injection vulnerabilities */
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/** A sanitizer for LDAP Injection vulnerabilities. */
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* Additional taint steps for "LDAP Injection" vulnerabilities.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
attributeArrayTaintStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getMethodName() = "[]" and
|
||||
n1 = filterCall.getArgument(_) and
|
||||
n2 = filterCall
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
*/
|
||||
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
|
||||
/**
|
||||
* An LDAP query execution considered as a flow sink.
|
||||
*/
|
||||
private class LdapExecutionAsSink extends Sink {
|
||||
LdapExecutionAsSink() { this = any(LdapExecution l).getQuery() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* An inclusion check against an array of constant strings, considered as a
|
||||
* sanitizer-guard.
|
||||
*/
|
||||
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
29
ruby/ql/lib/codeql/ruby/security/LdapInjectionQuery.qll
Normal file
29
ruby/ql/lib/codeql/ruby/security/LdapInjectionQuery.qll
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* LDAP Injections, as well as extension points for adding your own
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
|
||||
/** Provides a taint-tracking configuration for detecting LDAP Injections vulnerabilities. */
|
||||
module LdapInjection {
|
||||
import LdapInjectionCustomizations::LdapInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Provides class and predicates to track external data that
|
||||
* may represent malicious xpath query objects.
|
||||
*
|
||||
* This module is intended to be imported into a taint-tracking query.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.BarrierGuards
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
|
||||
/** Models Xpath Injection related classes and functions */
|
||||
module XpathInjection {
|
||||
/** A data flow source for "XPath injection" vulnerabilities. */
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/** A data flow sink for "XPath injection" vulnerabilities */
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/** A sanitizer for "XPath injection" vulnerabilities. */
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
*/
|
||||
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
|
||||
/**
|
||||
* An execution of an XPath expression, considered as a sink.
|
||||
*/
|
||||
private class XPathExecutionAsSink extends Sink {
|
||||
XPathExecutionAsSink() { this = any(XPathExecution e).getXPath() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A construction of an XPath expression, considered as a sink.
|
||||
*/
|
||||
private class XPathConstructionAsSink extends Sink {
|
||||
XPathConstructionAsSink() { this = any(XPathConstruction c).getXPath() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* An inclusion check against an array of constant strings, considered as a
|
||||
* sanitizer-guard.
|
||||
*/
|
||||
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier
|
||||
{ }
|
||||
}
|
||||
27
ruby/ql/lib/codeql/ruby/security/XpathInjectionQuery.qll
Normal file
27
ruby/ql/lib/codeql/ruby/security/XpathInjectionQuery.qll
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `XpathInjection::Configuration` is needed, otherwise
|
||||
* `XpathInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
import XpathInjectionCustomizations::XpathInjection
|
||||
|
||||
/** Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities. */
|
||||
module XpathInjection {
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
import TaintTracking::Global<Config>
|
||||
}
|
||||
328
ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll
Normal file
328
ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll
Normal file
@@ -0,0 +1,328 @@
|
||||
/**
|
||||
* Parts of API graphs that can be shared with other dynamic languages.
|
||||
*
|
||||
* Depends on TypeTrackerSpecific for the corresponding language.
|
||||
*/
|
||||
|
||||
private import codeql.Locations
|
||||
private import codeql.ruby.typetracking.TypeTracker
|
||||
private import TypeTrackerSpecific
|
||||
|
||||
/**
|
||||
* The signature to use when instantiating `ApiGraphShared`.
|
||||
*
|
||||
* The implementor should define a newtype with at least three branches as follows:
|
||||
* ```ql
|
||||
* newtype TApiNode =
|
||||
* MkForwardNode(LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
|
||||
* MkBackwardNode(LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
|
||||
* MkSinkNode(Node node) { ... } or
|
||||
* ...
|
||||
* ```
|
||||
*
|
||||
* The three branches should be exposed through `getForwardNode`, `getBackwardNode`, and `getSinkNode`, respectively.
|
||||
*/
|
||||
signature module ApiGraphSharedSig {
|
||||
/** A node in the API graph. */
|
||||
class ApiNode {
|
||||
/** Gets a string representation of this API node. */
|
||||
string toString();
|
||||
|
||||
/** Gets the location associated with this API node, if any. */
|
||||
Location getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the forward node with the given type-tracking state.
|
||||
*
|
||||
* This node will have outgoing epsilon edges to its type-tracking successors.
|
||||
*/
|
||||
ApiNode getForwardNode(TypeTrackingNode node, TypeTracker t);
|
||||
|
||||
/**
|
||||
* Gets the backward node with the given type-tracking state.
|
||||
*
|
||||
* This node will have outgoing epsilon edges to its type-tracking predecessors.
|
||||
*/
|
||||
ApiNode getBackwardNode(TypeTrackingNode node, TypeTracker t);
|
||||
|
||||
/**
|
||||
* Gets the sink node corresponding to `node`.
|
||||
*
|
||||
* Since sinks are not generally `LocalSourceNode`s, such nodes are materialised separately in order for
|
||||
* the API graph to include representatives for sinks. Note that there is no corresponding case for "source"
|
||||
* nodes as these are represented as forward nodes with initial-state type-trackers.
|
||||
*
|
||||
* Sink nodes have outgoing epsilon edges to the backward nodes corresponding to their local sources.
|
||||
*/
|
||||
ApiNode getSinkNode(Node node);
|
||||
|
||||
/**
|
||||
* Holds if a language-specific epsilon edge `pred -> succ` should be generated.
|
||||
*/
|
||||
predicate specificEpsilonEdge(ApiNode pred, ApiNode succ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parts of API graphs that can be shared between language implementations.
|
||||
*/
|
||||
module ApiGraphShared<ApiGraphSharedSig S> {
|
||||
private import S
|
||||
|
||||
/** Gets a local source of `node`. */
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
TypeTrackingNode getALocalSourceStrict(Node node) { result = node.getALocalSource() }
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* Holds if there is an epsilon edge `pred -> succ`.
|
||||
*
|
||||
* That relation is reflexive, so `fastTC` produces the equivalent of a reflexive, transitive closure.
|
||||
*/
|
||||
pragma[noopt]
|
||||
cached
|
||||
predicate epsilonEdge(ApiNode pred, ApiNode succ) {
|
||||
exists(
|
||||
StepSummary summary, TypeTrackingNode predNode, TypeTracker predState,
|
||||
TypeTrackingNode succNode, TypeTracker succState
|
||||
|
|
||||
StepSummary::stepCall(predNode, succNode, summary)
|
||||
or
|
||||
StepSummary::stepNoCall(predNode, succNode, summary)
|
||||
|
|
||||
pred = getForwardNode(predNode, predState) and
|
||||
succState = StepSummary::append(predState, summary) and
|
||||
succ = getForwardNode(succNode, succState)
|
||||
or
|
||||
succ = getBackwardNode(predNode, predState) and // swap order for backward flow
|
||||
succState = StepSummary::append(predState, summary) and
|
||||
pred = getBackwardNode(succNode, succState) // swap order for backward flow
|
||||
)
|
||||
or
|
||||
exists(Node sink, TypeTrackingNode localSource |
|
||||
pred = getSinkNode(sink) and
|
||||
localSource = getALocalSourceStrict(sink) and
|
||||
succ = getBackwardStartNode(localSource)
|
||||
)
|
||||
or
|
||||
specificEpsilonEdge(pred, succ)
|
||||
or
|
||||
succ instanceof ApiNode and
|
||||
succ = pred
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` can reach `succ` by zero or more epsilon edges.
|
||||
*/
|
||||
cached
|
||||
predicate epsilonStar(ApiNode pred, ApiNode succ) = fastTC(epsilonEdge/2)(pred, succ)
|
||||
|
||||
/** Gets the API node to use when starting forward flow from `source` */
|
||||
cached
|
||||
ApiNode forwardStartNode(TypeTrackingNode source) {
|
||||
result = getForwardNode(source, TypeTracker::end(false))
|
||||
}
|
||||
|
||||
/** Gets the API node to use when starting backward flow from `sink` */
|
||||
cached
|
||||
ApiNode backwardStartNode(TypeTrackingNode sink) {
|
||||
// There is backward flow A->B iff there is forward flow B->A.
|
||||
// The starting point of backward flow corresponds to the end of a forward flow, and vice versa.
|
||||
result = getBackwardNode(sink, TypeTracker::end(_))
|
||||
}
|
||||
|
||||
/** Gets `node` as a data flow source. */
|
||||
cached
|
||||
TypeTrackingNode asSourceCached(ApiNode node) { node = forwardEndNode(result) }
|
||||
|
||||
/** Gets `node` as a data flow sink. */
|
||||
cached
|
||||
Node asSinkCached(ApiNode node) { node = getSinkNode(result) }
|
||||
}
|
||||
|
||||
private import Cached
|
||||
|
||||
/** Gets an API node corresponding to the end of forward-tracking to `localSource`. */
|
||||
pragma[nomagic]
|
||||
private ApiNode forwardEndNode(TypeTrackingNode localSource) {
|
||||
result = getForwardNode(localSource, TypeTracker::end(_))
|
||||
}
|
||||
|
||||
/** Gets an API node corresponding to the end of backtracking to `localSource`. */
|
||||
pragma[nomagic]
|
||||
private ApiNode backwardEndNode(TypeTrackingNode localSource) {
|
||||
result = getBackwardNode(localSource, TypeTracker::end(false))
|
||||
}
|
||||
|
||||
/** Gets a node reachable from `node` by zero or more epsilon edges, including `node` itself. */
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getAnEpsilonSuccessorInline(ApiNode node) { epsilonStar(node, result) }
|
||||
|
||||
/** Gets `node` as a data flow sink. */
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
Node asSinkInline(ApiNode node) { result = asSinkCached(node) }
|
||||
|
||||
/** Gets `node` as a data flow source. */
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
TypeTrackingNode asSourceInline(ApiNode node) { result = asSourceCached(node) }
|
||||
|
||||
/** Gets a value reachable from `source`. */
|
||||
bindingset[source]
|
||||
pragma[inline_late]
|
||||
Node getAValueReachableFromSourceInline(ApiNode source) {
|
||||
exists(TypeTrackingNode src |
|
||||
src = asSourceInline(getAnEpsilonSuccessorInline(source)) and
|
||||
src.flowsTo(pragma[only_bind_into](result))
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a value that can reach `sink`. */
|
||||
bindingset[sink]
|
||||
pragma[inline_late]
|
||||
Node getAValueReachingSinkInline(ApiNode sink) {
|
||||
result = asSinkInline(getAnEpsilonSuccessorInline(sink))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the starting point for forward-tracking at `node`.
|
||||
*
|
||||
* Should be used to obtain the successor of an edge when constructing labelled edges.
|
||||
*/
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getForwardStartNode(Node node) { result = forwardStartNode(node) }
|
||||
|
||||
/**
|
||||
* Gets the starting point of backtracking from `node`.
|
||||
*
|
||||
* Should be used to obtain the successor of an edge when constructing labelled edges.
|
||||
*/
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getBackwardStartNode(Node node) { result = backwardStartNode(node) }
|
||||
|
||||
/**
|
||||
* Gets a possible ending point of forward-tracking at `node`.
|
||||
*
|
||||
* Should be used to obtain the predecessor of an edge when constructing labelled edges.
|
||||
*
|
||||
* This is not backed by a `cached` predicate, and should only be used for materialising `cached`
|
||||
* predicates in the API graph implementation - it should not be called in later stages.
|
||||
*/
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getForwardEndNode(Node node) { result = forwardEndNode(node) }
|
||||
|
||||
/**
|
||||
* Gets a possible ending point backtracking to `node`.
|
||||
*
|
||||
* Should be used to obtain the predecessor of an edge when constructing labelled edges.
|
||||
*
|
||||
* This is not backed by a `cached` predicate, and should only be used for materialising `cached`
|
||||
* predicates in the API graph implementation - it should not be called in later stages.
|
||||
*/
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getBackwardEndNode(Node node) { result = backwardEndNode(node) }
|
||||
|
||||
/**
|
||||
* Gets a possible eding point of forward or backward tracking at `node`.
|
||||
*
|
||||
* Should be used to obtain the predecessor of an edge generated from store or load edges.
|
||||
*/
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getForwardOrBackwardEndNode(Node node) {
|
||||
result = getForwardEndNode(node) or result = getBackwardEndNode(node)
|
||||
}
|
||||
|
||||
/** Gets an API node for tracking forward starting at `node`. This is the implementation of `DataFlow::LocalSourceNode.track()` */
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getNodeForForwardTracking(Node node) { result = forwardStartNode(node) }
|
||||
|
||||
/** Gets an API node for backtracking starting at `node`. The implementation of `DataFlow::Node.backtrack()`. */
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
ApiNode getNodeForBacktracking(Node node) {
|
||||
result = getBackwardStartNode(getALocalSourceStrict(node))
|
||||
}
|
||||
|
||||
/** Parts of the shared module to be re-exported by the user-facing `API` module. */
|
||||
module Public {
|
||||
/**
|
||||
* The signature to use when instantiating the `ExplainFlow` module.
|
||||
*/
|
||||
signature module ExplainFlowSig {
|
||||
/** Holds if `node` should be a source. */
|
||||
predicate isSource(ApiNode node);
|
||||
|
||||
/** Holds if `node` should be a sink. */
|
||||
default predicate isSink(ApiNode node) { any() }
|
||||
|
||||
/** Holds if `node` should be skipped in the generated paths. */
|
||||
default predicate isHidden(ApiNode node) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Module to help debug and visualize the data flows underlying API graphs.
|
||||
*
|
||||
* This module exports the query predicates for a path-problem query, and should be imported
|
||||
* into the top-level of such a query.
|
||||
*
|
||||
* The module argument should specify source and sink API nodes, and the resulting query
|
||||
* will show paths of epsilon edges that go from a source to a sink. Only epsilon edges are visualized.
|
||||
*
|
||||
* To condense the output a bit, paths in which the source and sink are the same node are omitted.
|
||||
*/
|
||||
module ExplainFlow<ExplainFlowSig T> {
|
||||
private import T
|
||||
|
||||
private ApiNode relevantNode() {
|
||||
isSink(result) and
|
||||
result = getAnEpsilonSuccessorInline(any(ApiNode node | isSource(node)))
|
||||
or
|
||||
epsilonEdge(result, relevantNode())
|
||||
}
|
||||
|
||||
/** Holds if `node` is part of the graph to visualize. */
|
||||
query predicate nodes(ApiNode node) { node = relevantNode() and not isHidden(node) }
|
||||
|
||||
private predicate edgeToHiddenNode(ApiNode pred, ApiNode succ) {
|
||||
epsilonEdge(pred, succ) and
|
||||
isHidden(succ) and
|
||||
pred = relevantNode() and
|
||||
succ = relevantNode()
|
||||
}
|
||||
|
||||
/** Holds if `pred -> succ` is an edge in the graph to visualize. */
|
||||
query predicate edges(ApiNode pred, ApiNode succ) {
|
||||
nodes(pred) and
|
||||
nodes(succ) and
|
||||
exists(ApiNode mid |
|
||||
edgeToHiddenNode*(pred, mid) and
|
||||
epsilonEdge(mid, succ)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds for each source/sink pair to visualize in the graph. */
|
||||
query predicate problems(
|
||||
ApiNode location, ApiNode sourceNode, ApiNode sinkNode, string message
|
||||
) {
|
||||
nodes(sourceNode) and
|
||||
nodes(sinkNode) and
|
||||
isSource(sourceNode) and
|
||||
isSink(sinkNode) and
|
||||
sinkNode = getAnEpsilonSuccessorInline(sourceNode) and
|
||||
sourceNode != sinkNode and
|
||||
location = sinkNode and
|
||||
message = "Node flows here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user