Merge branch 'main' into maikypedia/ldap-improper-auth

This commit is contained in:
Maiky
2023-08-03 16:49:30 +02:00
committed by GitHub
3594 changed files with 171595 additions and 111833 deletions

View File

@@ -10,7 +10,7 @@ runs:
uses: actions/cache@v3
with:
path: ruby/extractor-pack
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-extractor-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/scripts/create-extractor-pack.sh', 'ruby/extractor/**/Cargo.lock', 'ruby/actions/create-extractor-pack/action.yml') }}-${{ hashFiles('ruby/extractor/**/*.rs') }}-${{ hashFiles('ruby/codeql-extractor.yml', 'ruby/downgrades', 'ruby/tools', 'ruby/ql/lib/ruby.dbscheme', 'ruby/ql/lib/ruby.dbscheme.stats') }}
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-extractor-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/scripts/create-extractor-pack.sh', 'ruby/extractor/**/Cargo.lock', 'ruby/actions/create-extractor-pack/action.yml') }}-${{ hashFiles('shared/tree-sitter-extractor') }}-${{ hashFiles('ruby/extractor/**/*.rs') }}-${{ hashFiles('ruby/codeql-extractor.yml', 'ruby/downgrades', 'ruby/tools', 'ruby/ql/lib/ruby.dbscheme', 'ruby/ql/lib/ruby.dbscheme.stats') }}
- name: Cache cargo
uses: actions/cache@v3
if: steps.cache-extractor.outputs.cache-hit != 'true'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Sync dbscheme fragments
compatibility: full

View File

@@ -2,3 +2,4 @@ name: codeql/ruby-downgrades
groups: ruby
downgrades: .
library: true
warnOnImplicitThis: true

Binary file not shown.

View File

@@ -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"] }

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -5,12 +5,12 @@ import codeql.ruby.dataflow.internal.DataFlowPrivate
import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency
private class MyConsistencyConfiguration extends ConsistencyConfiguration {
override predicate postWithInFlowExclude(Node n) { n instanceof SummaryNode }
override predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
override predicate argHasPostUpdateExclude(ArgumentNode n) {
n instanceof BlockArgumentNode
or
n instanceof SummaryNode
n instanceof FlowSummaryNode
or
n instanceof SynthHashSplatArgumentNode
or
@@ -33,3 +33,8 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
)
}
}
query predicate multipleToString(Node n, string s) {
s = strictconcat(n.toString(), ",") and
strictcount(n.toString()) > 1
}

View File

@@ -2,3 +2,4 @@ name: codeql/ruby-consistency-queries
groups: [ruby, test, consistency-queries]
dependencies:
codeql/ruby-all: ${workspace}
warnOnImplicitThis: true

View File

@@ -4,3 +4,4 @@ groups:
- examples
dependencies:
codeql/ruby-all: ${workspace}
warnOnImplicitThis: true

View File

@@ -1,3 +1,4 @@
dependencies:
codeql/ruby-all: '*'
codeql/ruby-queries: '*'
warnOnImplicitThis: true

View File

@@ -1,3 +1,63 @@
## 0.7.1
### New Features
* The `DataFlow::StateConfigSig` signature module has gained default implementations for `isBarrier/2` and `isAdditionalFlowStep/4`.
Hence it is no longer needed to provide `none()` implementations of these predicates if they are not needed.
### Major Analysis Improvements
* The API graph library (`codeql.ruby.ApiGraphs`) has been significantly improved, with better support for inheritance,
and data-flow nodes can now be converted to API nodes by calling `.track()` or `.backtrack()` on the node.
API graphs allow for efficient modelling of how a given value is used by the code base, or how values produced by the code base
are consumed by a library. See the documentation for `API::Node` for details and examples.
### Minor Analysis Improvements
* Data flow configurations can now include a predicate `neverSkip(Node node)`
in order to ensure inclusion of certain nodes in the path explanations. The
predicate defaults to the end-points of the additional flow steps provided in
the configuration, which means that such steps now always are visible by
default in path explanations.
* The `'QUERY_STRING'` field of a Rack `env` parameter is now recognized as a source of remote user input.
* Query parameters and cookies from `Rack::Response` objects are recognized as potential sources of remote flow input.
* Calls to `Rack::Utils.parse_query` now propagate taint.
## 0.7.0
### Deprecated APIs
* The `Configuration` taint flow configuration class from `codeql.ruby.security.InsecureDownloadQuery` has been deprecated. Use the `Flow` module instead.
### Minor Analysis Improvements
* More kinds of rack applications are now recognized.
* Rack::Response instances are now recognized as potential responses from rack applications.
* HTTP redirect responses from Rack applications are now recognized as a potential sink for open redirect alerts.
* Additional sinks for `rb/unsafe-deserialization` have been added. This includes various methods from the `yaml` and `plist` gems, which deserialize YAML and Property List data, respectively.
## 0.6.4
No user-facing changes.
## 0.6.3
### Minor Analysis Improvements
* Deleted many deprecated predicates and classes with uppercase `URL`, `XSS`, etc. in their names. Use the PascalCased versions instead.
* Deleted the deprecated `getValueText` predicate from the `Expr`, `StringComponent`, and `ExprCfgNode` classes. Use `getConstantValue` instead.
* Deleted the deprecated `VariableReferencePattern` class, use `ReferencePattern` instead.
* Deleted all deprecated aliases in `StandardLibrary.qll`, use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.
* Support for the `sequel` gem has been added. Method calls that execute queries against a database that may be vulnerable to injection attacks will now be recognized.
* Support for the `mysql2` gem has been added. Method calls that execute queries against an MySQL database that may be vulnerable to injection attacks will now be recognized.
* Support for the `pg` gem has been added. Method calls that execute queries against a PostgreSQL database that may be vulnerable to injection attacks will now be recognized.
## 0.6.2
### Minor Analysis Improvements
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
## 0.6.1
No user-facing changes.

View File

@@ -1,4 +1,5 @@
---
category: minorAnalysis
---
## 0.6.2
### Minor Analysis Improvements
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.

View 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.

View File

@@ -0,0 +1,3 @@
## 0.6.4
No user-facing changes.

View 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.

View 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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.6.1
lastReleaseVersion: 0.7.1

View File

@@ -2,22 +2,13 @@
import files.FileSystem
bindingset[loc]
pragma[inline_late]
private string locationToString(Location loc) {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
loc.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}
/**
* A location as given by a file, a start line, a start column,
* an end line, and an end column.
*
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
class Location extends @location {
class Location extends @location_default {
/** Gets the file for this location. */
File getFile() { locations_default(this, result, _, _, _, _) }
@@ -37,8 +28,14 @@ class Location extends @location {
int getNumLines() { result = this.getEndLine() - this.getStartLine() + 1 }
/** Gets a textual representation of this element. */
pragma[inline]
string toString() { result = locationToString(this) }
bindingset[this]
pragma[inline_late]
string toString() {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
this.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}
/**
* Holds if this element is at the specified location.

File diff suppressed because it is too large Load Diff

View File

@@ -78,6 +78,19 @@ module SqlExecution {
}
}
/**
* A data-flow node that performs SQL sanitization.
*/
class SqlSanitization extends DataFlow::Node instanceof SqlSanitization::Range { }
/** Provides a class for modeling new SQL sanitization APIs. */
module SqlSanitization {
/**
* A data-flow node that performs SQL sanitization.
*/
abstract class Range extends DataFlow::Node { }
}
/**
* A data-flow node that executes a regular expression.
*
@@ -687,9 +700,7 @@ module Http {
* Gets a node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
deprecated DataFlow::Node getURL() {
result = super.getURL() or result = Request::Range.super.getAUrlPart()
}
deprecated DataFlow::Node getURL() { result = Request::Range.super.getAUrlPart() }
/**
* Holds if this request is made using a mode that disables SSL/TLS
@@ -715,14 +726,6 @@ module Http {
/** Gets a node which returns the body of the response */
abstract DataFlow::Node getResponseBody();
/**
* DEPRECATED: overwrite `getAUrlPart` instead.
*
* Gets a node that contributes to the URL of the request.
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
*/
deprecated DataFlow::Node getURL() { none() }
/**
* DEPRECATED: override `disablesCertificateValidation/2` instead.
*
@@ -840,6 +843,58 @@ module XmlParserCall {
}
}
/**
* A data-flow node that constructs an XPath expression.
*
* If it is important that the XPath expression is indeed executed, then use `XPathExecution`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `XPathConstruction::Range` instead.
*/
class XPathConstruction extends DataFlow::Node instanceof XPathConstruction::Range {
/** Gets the argument that specifies the XPath expressions to be constructed. */
DataFlow::Node getXPath() { result = super.getXPath() }
}
/** Provides a class for modeling new XPath construction APIs. */
module XPathConstruction {
/**
* A data-flow node that constructs an XPath expression.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XPathConstruction` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the XPath expressions to be constructed. */
abstract DataFlow::Node getXPath();
}
}
/**
* A data-flow node that executes an XPath expression.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `XPathExecution::Range` instead.
*/
class XPathExecution extends DataFlow::Node instanceof XPathExecution::Range {
/** Gets the argument that specifies the XPath expressions to be executed. */
DataFlow::Node getXPath() { result = super.getXPath() }
}
/** Provides a class for modeling new XPath execution APIs. */
module XPathExecution {
/**
* A data-flow node that executes an XPath expression.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XPathExecution` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the XPath expressions to be executed. */
abstract DataFlow::Node getXPath();
}
}
/**
* A data-flow node that may represent a database object in an ORM system.
*
@@ -1191,7 +1246,7 @@ module LdapExecution {
* extend `LdapExecution` instead.
*/
abstract class Range extends DataFlow::Node {
//** Gets the argument that specifies the query to be executed. */
/** Gets the argument that specifies the query to be executed. */
abstract DataFlow::Node getQuery();
}
}

View File

@@ -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
}

View File

@@ -32,4 +32,8 @@ private import codeql.ruby.frameworks.Slim
private import codeql.ruby.frameworks.Sinatra
private import codeql.ruby.frameworks.Twirp
private import codeql.ruby.frameworks.Sqlite3
private import codeql.ruby.frameworks.Ldap
private import codeql.ruby.frameworks.Mysql2
private import codeql.ruby.frameworks.Pg
private import codeql.ruby.frameworks.Yaml
private import codeql.ruby.frameworks.Sequel
private import codeql.ruby.frameworks.Ldap

View File

@@ -11,13 +11,6 @@ private import internal.TreeSitter
* This is the root QL class for all expressions.
*/
class Expr extends Stmt, TExpr {
/**
* DEPRECATED: Use `getConstantValue` instead.
*
* Gets the textual (constant) value of this expression, if any.
*/
deprecated string getValueText() { result = this.getConstantValue().toString() }
/** Gets the constant value of this expression, if any. */
ConstantValue getConstantValue() { result = getConstantValueExpr(this) }
}

View File

@@ -165,14 +165,6 @@ class FileLiteral extends Literal instanceof FileLiteralImpl {
* `StringEscapeSequenceComponent`, or `StringInterpolationComponent`.
*/
class StringComponent extends AstNode instanceof StringComponentImpl {
/**
* DEPRECATED: Use `getConstantValue` instead.
*
* Gets the source text for this string component. Has no result if this is
* a `StringInterpolationComponent`.
*/
deprecated string getValueText() { result = this.getConstantValue().toString() }
/** Gets the constant value of this string component, if any. */
ConstantValue::ConstantStringValue getConstantValue() { result = TString(super.getValue()) }
}
@@ -218,8 +210,6 @@ class StringInterpolationComponent extends StringComponent, StmtSequence instanc
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
deprecated final override string getValueText() { none() }
final override ConstantValue::ConstantStringValue getConstantValue() {
result = StmtSequence.super.getConstantValue()
}
@@ -267,8 +257,6 @@ class RegExpInterpolationComponent extends RegExpComponent, StmtSequence instanc
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
deprecated final override string getValueText() { none() }
final override ConstantValue::ConstantStringValue getConstantValue() {
result = StmtSequence.super.getConstantValue()
}

View File

@@ -363,19 +363,3 @@ class ReferencePattern extends CasePattern, TReferencePattern {
pred = "getExpr" and result = this.getExpr()
}
}
/**
* DEPRECATED: Use `ReferencePattern` instead.
*
* A variable reference in a pattern, i.e. `^x` in the following example:
* ```rb
* x = 10
* case expr
* in ^x then puts "ok"
* end
* ```
*/
deprecated class VariableReferencePattern extends ReferencePattern, TVariableReferencePattern {
/** Gets the variable access corresponding to this variable reference pattern. */
final VariableReadAccess getVariableAccess() { result = this.getExpr() }
}

View File

@@ -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() }

View File

@@ -121,13 +121,15 @@ private Ruby::AstNode getSuperParent(Ruby::Super sup) {
result = sup
or
result = getSuperParent(sup).getParent() and
not result instanceof Ruby::Method
not result instanceof Ruby::Method and
not result instanceof Ruby::SingletonMethod
}
private string getSuperMethodName(Ruby::Super sup) {
exists(Ruby::Method meth |
meth = getSuperParent(sup).getParent() and
exists(Ruby::AstNode meth | meth = getSuperParent(sup).getParent() |
result = any(Method c | toGenerated(c) = meth).getName()
or
result = any(SingletonMethod c | toGenerated(c) = meth).getName()
)
}

View File

@@ -214,7 +214,7 @@ private module Propagation {
any(StringComponentCfgNode c |
isString(c, result)
or
result = c.getNode().(StringComponentImpl).getValue()
result = c.getAstNode().(StringComponentImpl).getValue()
)
}

View File

@@ -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)
}

View File

@@ -6,62 +6,26 @@ private import codeql.ruby.dataflow.SSA
private import codeql.ruby.ast.internal.Constant
private import codeql.ruby.ast.internal.Literal
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import internal.ControlFlowGraphImpl as CfgImpl
private import internal.Splitting
/** An entry node for a given scope. */
class EntryNode extends CfgNode, TEntryNode {
class EntryNode extends CfgNode, CfgImpl::EntryNode {
override string getAPrimaryQlClass() { result = "EntryNode" }
private CfgScope scope;
EntryNode() { this = TEntryNode(scope) }
final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() }
final override Location getLocation() { result = scope.getLocation() }
final override string toString() { result = "enter " + scope }
}
/** An exit node for a given scope, annotated with the type of exit. */
class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
class AnnotatedExitNode extends CfgNode, CfgImpl::AnnotatedExitNode {
override string getAPrimaryQlClass() { result = "AnnotatedExitNode" }
private CfgScope scope;
private boolean normal;
AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
/** Holds if this node represent a normal exit. */
final predicate isNormal() { normal = true }
final override AnnotatedExitBasicBlock getBasicBlock() { result = super.getBasicBlock() }
final override Location getLocation() { result = scope.getLocation() }
final override string toString() {
exists(string s |
normal = true and s = "normal"
or
normal = false and s = "abnormal"
|
result = "exit " + scope + " (" + s + ")"
)
}
}
/** An exit node for a given scope. */
class ExitNode extends CfgNode, TExitNode {
class ExitNode extends CfgNode, CfgImpl::ExitNode {
override string getAPrimaryQlClass() { result = "ExitNode" }
private CfgScope scope;
ExitNode() { this = TExitNode(scope) }
final override Location getLocation() { result = scope.getLocation() }
final override string toString() { result = "exit " + scope }
}
/**
@@ -71,55 +35,22 @@ class ExitNode extends CfgNode, TExitNode {
* (dead) code or not important for control flow, and multiple when there are different
* splits for the AST node.
*/
class AstCfgNode extends CfgNode, TElementNode {
class AstCfgNode extends CfgNode, CfgImpl::AstCfgNode {
/** Gets the name of the primary QL class for this node. */
override string getAPrimaryQlClass() { result = "AstCfgNode" }
private Splits splits;
AstNode e;
AstCfgNode() { this = TElementNode(_, e, splits) }
final override AstNode getNode() { result = e }
override Location getLocation() { result = e.getLocation() }
final override string toString() {
exists(string s | s = e.toString() |
result = "[" + this.getSplitsString() + "] " + s
or
not exists(this.getSplitsString()) and result = s
)
}
/** Gets a comma-separated list of strings for each split in this node, if any. */
final string getSplitsString() {
result = splits.toString() and
result != ""
}
/** Gets a split for this control flow node, if any. */
final Split getASplit() { result = splits.getASplit() }
}
/** A control-flow node that wraps an AST expression. */
class ExprCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "ExprCfgNode" }
override Expr e;
Expr e;
ExprCfgNode() { e = this.getNode() }
ExprCfgNode() { e = this.getAstNode() }
/** Gets the underlying expression. */
Expr getExpr() { result = e }
/**
* DEPRECATED: Use `getConstantValue` instead.
*
* Gets the textual (constant) value of this expression, if any.
*/
deprecated string getValueText() { result = this.getConstantValue().toString() }
/** Gets the constant value of this expression, if any. */
ConstantValue getConstantValue() { result = getConstantValue(this) }
}
@@ -130,12 +61,12 @@ class ReturningCfgNode extends AstCfgNode {
ReturningStmt s;
ReturningCfgNode() { s = this.getNode() }
ReturningCfgNode() { s = this.getAstNode() }
/** Gets the node of the returned value, if any. */
ExprCfgNode getReturnedValueNode() {
result = this.getAPredecessor() and
result.getNode() = s.getValue()
result.getAstNode() = s.getValue()
}
}
@@ -143,17 +74,19 @@ class ReturningCfgNode extends AstCfgNode {
class StringComponentCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "StringComponentCfgNode" }
StringComponentCfgNode() { this.getNode() instanceof StringComponent }
StringComponentCfgNode() { this.getAstNode() instanceof StringComponent }
/** Gets the constant value of this string component. */
ConstantValue getConstantValue() { result = this.getNode().(StringComponent).getConstantValue() }
ConstantValue getConstantValue() {
result = this.getAstNode().(StringComponent).getConstantValue()
}
}
/** A control-flow node that wraps a `RegExpComponent` AST expression. */
class RegExpComponentCfgNode extends StringComponentCfgNode {
override string getAPrimaryQlClass() { result = "RegExpComponentCfgNode" }
RegExpComponentCfgNode() { e instanceof RegExpComponent }
RegExpComponentCfgNode() { this.getAstNode() instanceof RegExpComponent }
}
private AstNode desugar(AstNode n) {
@@ -186,7 +119,7 @@ abstract private class ChildMapping extends AstNode {
cached
predicate hasCfgChild(AstNode child, CfgNode cfn, CfgNode cfnChild) {
this.reachesBasicBlock(child, cfn, cfnChild.getBasicBlock()) and
cfnChild.getNode() = desugar(child)
cfnChild.getAstNode() = desugar(child)
}
}
@@ -203,7 +136,7 @@ abstract private class ExprChildMapping extends Expr, ChildMapping {
exists(BasicBlock mid |
this.reachesBasicBlock(child, cfn, mid) and
bb = mid.getAPredecessor() and
not mid.getANode().getNode() = child
not mid.getANode().getAstNode() = child
)
}
}
@@ -217,13 +150,13 @@ abstract private class NonExprChildMapping extends ChildMapping {
pragma[nomagic]
override predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb) {
this.relevantChild(child) and
cfn.getNode() = this and
cfn.getAstNode() = this and
bb.getANode() = cfn
or
exists(BasicBlock mid |
this.reachesBasicBlock(child, cfn, mid) and
bb = mid.getASuccessor() and
not mid.getANode().getNode() = child
not mid.getANode().getAstNode() = child
)
}
}
@@ -447,9 +380,11 @@ module ExprNodes {
/** A control-flow node that wraps an `InClause` AST expression. */
class InClauseCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "InClauseCfgNode" }
private InClauseChildMapping e;
override InClauseChildMapping e;
InClauseCfgNode() { e = this.getAstNode() }
override string getAPrimaryQlClass() { result = "InClauseCfgNode" }
/** Gets the pattern in this `in`-clause. */
final AstCfgNode getPattern() { e.hasCfgChild(e.getPattern(), this, result) }
@@ -467,56 +402,60 @@ module ExprNodes {
predicate patternReachesBasicBlock(int i, CfgNode cfnPattern, BasicBlock bb) {
exists(Expr pattern |
pattern = this.getPattern(i) and
cfnPattern.getNode() = pattern and
cfnPattern.getAstNode() = pattern and
bb.getANode() = cfnPattern
)
or
exists(BasicBlock mid |
this.patternReachesBasicBlock(i, cfnPattern, mid) and
bb = mid.getASuccessor() and
not mid.getANode().getNode() = this
not mid.getANode().getAstNode() = this
)
}
predicate bodyReachesBasicBlock(CfgNode cfnBody, BasicBlock bb) {
exists(Stmt body |
body = this.getBody() and
cfnBody.getNode() = body and
cfnBody.getAstNode() = body and
bb.getANode() = cfnBody
)
or
exists(BasicBlock mid |
this.bodyReachesBasicBlock(cfnBody, mid) and
bb = mid.getAPredecessor() and
not mid.getANode().getNode() = this
not mid.getANode().getAstNode() = this
)
}
}
/** A control-flow node that wraps a `WhenClause` AST expression. */
class WhenClauseCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "WhenClauseCfgNode" }
private WhenClauseChildMapping e;
override WhenClauseChildMapping e;
WhenClauseCfgNode() { e = this.getAstNode() }
override string getAPrimaryQlClass() { result = "WhenClauseCfgNode" }
/** Gets the body of this `when`-clause. */
final ExprCfgNode getBody() {
result.getNode() = desugar(e.getBody()) and
result.getAstNode() = desugar(e.getBody()) and
e.bodyReachesBasicBlock(result, this.getBasicBlock())
}
/** Gets the `i`th pattern this `when`-clause. */
final ExprCfgNode getPattern(int i) {
result.getNode() = desugar(e.getPattern(i)) and
result.getAstNode() = desugar(e.getPattern(i)) and
e.patternReachesBasicBlock(i, result, this.getBasicBlock())
}
}
/** A control-flow node that wraps a `CasePattern`. */
class CasePatternCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "CasePatternCfgNode" }
CasePattern e;
override CasePattern e;
CasePatternCfgNode() { e = this.getAstNode() }
override string getAPrimaryQlClass() { result = "CasePatternCfgNode" }
}
private class ArrayPatternChildMapping extends NonExprChildMapping, ArrayPattern {
@@ -878,7 +817,7 @@ module ExprNodes {
/** A control-flow node that wraps an `InstanceVariableReadAccess` AST expression. */
class InstanceVariableReadAccessCfgNode extends InstanceVariableAccessCfgNode {
InstanceVariableReadAccessCfgNode() { this.getNode() instanceof InstanceVariableReadAccess }
InstanceVariableReadAccessCfgNode() { this.getAstNode() instanceof InstanceVariableReadAccess }
override string getAPrimaryQlClass() { result = "InstanceVariableReadAccessCfgNode" }
@@ -887,7 +826,9 @@ module ExprNodes {
/** A control-flow node that wraps an `InstanceVariableWriteAccess` AST expression. */
class InstanceVariableWriteAccessCfgNode extends InstanceVariableAccessCfgNode {
InstanceVariableWriteAccessCfgNode() { this.getNode() instanceof InstanceVariableWriteAccess }
InstanceVariableWriteAccessCfgNode() {
this.getAstNode() instanceof InstanceVariableWriteAccess
}
override string getAPrimaryQlClass() { result = "InstanceVariableWriteAccessCfgNode" }
@@ -898,7 +839,9 @@ module ExprNodes {
class StringInterpolationComponentCfgNode extends StringComponentCfgNode, StmtSequenceCfgNode {
override string getAPrimaryQlClass() { result = "StringInterpolationComponentCfgNode" }
StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent }
StringInterpolationComponentCfgNode() {
this.getAstNode() instanceof StringInterpolationComponent
}
final override ConstantValue getConstantValue() {
result = StmtSequenceCfgNode.super.getConstantValue()
@@ -909,7 +852,9 @@ module ExprNodes {
class RegExpInterpolationComponentCfgNode extends RegExpComponentCfgNode, StmtSequenceCfgNode {
override string getAPrimaryQlClass() { result = "RegExpInterpolationComponentCfgNode" }
RegExpInterpolationComponentCfgNode() { this.getNode() instanceof RegExpInterpolationComponent }
RegExpInterpolationComponentCfgNode() {
this.getAstNode() instanceof RegExpInterpolationComponent
}
final override ConstantValue getConstantValue() {
result = StmtSequenceCfgNode.super.getConstantValue()
@@ -936,10 +881,10 @@ module ExprNodes {
}
/** A control-flow node that wraps a `StringLiteral` AST expression. */
class StringLiteralCfgNode extends ExprCfgNode {
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
class StringLiteralCfgNode extends StringlikeLiteralCfgNode {
StringLiteralCfgNode() { e instanceof StringLiteral }
override StringLiteral e;
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
final override StringLiteral getExpr() { result = super.getExpr() }
}

View File

@@ -3,7 +3,7 @@
private import codeql.ruby.AST
private import codeql.ruby.controlflow.BasicBlocks
private import SuccessorTypes
private import internal.ControlFlowGraphImpl
private import internal.ControlFlowGraphImpl as CfgImpl
private import internal.Splitting
private import internal.Completion
@@ -15,12 +15,12 @@ private import internal.Completion
* Note that module declarations are not themselves CFG scopes, as they are part of
* the CFG of the enclosing top-level or callable.
*/
class CfgScope extends Scope instanceof CfgScopeImpl {
class CfgScope extends Scope instanceof CfgImpl::CfgScopeImpl {
/** Gets the CFG scope that this scope is nested under, if any. */
final CfgScope getOuterCfgScope() {
exists(AstNode parent |
parent = this.getParent() and
result = getCfgScope(parent)
result = CfgImpl::getCfgScope(parent)
)
}
}
@@ -33,33 +33,18 @@ class CfgScope extends Scope instanceof CfgScopeImpl {
*
* Only nodes that can be reached from an entry point are included in the CFG.
*/
class CfgNode extends TCfgNode {
class CfgNode extends CfgImpl::Node {
/** Gets the name of the primary QL class for this node. */
string getAPrimaryQlClass() { none() }
/** Gets a textual representation of this control flow node. */
string toString() { none() }
/** Gets the AST node that this node corresponds to, if any. */
AstNode getNode() { none() }
/** Gets the location of this control flow node. */
Location getLocation() { none() }
/** Gets the file of this control flow node. */
final File getFile() { result = this.getLocation().getFile() }
/** Holds if this control flow node has conditional successors. */
final predicate isCondition() { exists(this.getASuccessor(any(ConditionalSuccessor bs))) }
/** Gets the scope of this node. */
final CfgScope getScope() { result = getNodeCfgScope(this) }
/** Gets the basic block that this control flow node belongs to. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** DEPRECATED: Use `getAstNode` instead. */
deprecated AstNode getNode() { result = this.getAstNode() }
/** Gets a successor node of a given type, if any. */
final CfgNode getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
final CfgNode getASuccessor(SuccessorType t) { result = super.getASuccessor(t) }
/** Gets an immediate successor, if any. */
final CfgNode getASuccessor() { result = this.getASuccessor(_) }
@@ -70,15 +55,12 @@ class CfgNode extends TCfgNode {
/** Gets an immediate predecessor, if any. */
final CfgNode getAPredecessor() { result = this.getAPredecessor(_) }
/** Holds if this node has more than one predecessor. */
final predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
/** Holds if this node has more than one successor. */
final predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
/** Gets the basic block that this control flow node belongs to. */
BasicBlock getBasicBlock() { result.getANode() = this }
}
/** The type of a control flow successor. */
class SuccessorType extends TSuccessorType {
class SuccessorType extends CfgImpl::TSuccessorType {
/** Gets a textual representation of successor type. */
string toString() { none() }
}
@@ -86,7 +68,7 @@ class SuccessorType extends TSuccessorType {
/** Provides different types of control flow successor types. */
module SuccessorTypes {
/** A normal control flow successor. */
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
class NormalSuccessor extends SuccessorType, CfgImpl::TSuccessorSuccessor {
final override string toString() { result = "successor" }
}
@@ -99,9 +81,9 @@ module SuccessorTypes {
boolean value;
ConditionalSuccessor() {
this = TBooleanSuccessor(value) or
this = TEmptinessSuccessor(value) or
this = TMatchingSuccessor(value)
this = CfgImpl::TBooleanSuccessor(value) or
this = CfgImpl::TEmptinessSuccessor(value) or
this = CfgImpl::TMatchingSuccessor(value)
}
/** Gets the Boolean value of this successor. */
@@ -125,7 +107,7 @@ module SuccessorTypes {
*
* `x >= 0` has both a `true` successor and a `false` successor.
*/
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { }
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor { }
/**
* An emptiness control flow successor.
@@ -158,7 +140,7 @@ module SuccessorTypes {
* \___/
* ```
*/
class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor {
class EmptinessSuccessor extends ConditionalSuccessor, CfgImpl::TEmptinessSuccessor {
override string toString() { if value = true then result = "empty" else result = "non-empty" }
}
@@ -189,7 +171,7 @@ module SuccessorTypes {
* puts "one" puts "not one"
* ```
*/
class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor {
class MatchingSuccessor extends ConditionalSuccessor, CfgImpl::TMatchingSuccessor {
override string toString() { if value = true then result = "match" else result = "no-match" }
}
@@ -207,7 +189,7 @@ module SuccessorTypes {
* The exit node of `sum` is a `return` successor of the `return x + y`
* statement.
*/
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
class ReturnSuccessor extends SuccessorType, CfgImpl::TReturnSuccessor {
final override string toString() { result = "return" }
}
@@ -230,7 +212,7 @@ module SuccessorTypes {
*
* The node `puts "done"` is `break` successor of the node `break`.
*/
class BreakSuccessor extends SuccessorType, TBreakSuccessor {
class BreakSuccessor extends SuccessorType, CfgImpl::TBreakSuccessor {
final override string toString() { result = "break" }
}
@@ -253,7 +235,7 @@ module SuccessorTypes {
*
* The node `x >= 0` is `next` successor of the node `next`.
*/
class NextSuccessor extends SuccessorType, TNextSuccessor {
class NextSuccessor extends SuccessorType, CfgImpl::TNextSuccessor {
final override string toString() { result = "next" }
}
@@ -278,7 +260,7 @@ module SuccessorTypes {
*
* The node `x -= 1` is `redo` successor of the node `redo`.
*/
class RedoSuccessor extends SuccessorType, TRedoSuccessor {
class RedoSuccessor extends SuccessorType, CfgImpl::TRedoSuccessor {
final override string toString() { result = "redo" }
}
@@ -302,7 +284,7 @@ module SuccessorTypes {
*
* The node `puts "Retry"` is `retry` successor of the node `retry`.
*/
class RetrySuccessor extends SuccessorType, TRetrySuccessor {
class RetrySuccessor extends SuccessorType, CfgImpl::TRetrySuccessor {
final override string toString() { result = "retry" }
}
@@ -323,7 +305,7 @@ module SuccessorTypes {
* The exit node of `m` is an exceptional successor of the node
* `raise "x > 2"`.
*/
class RaiseSuccessor extends SuccessorType, TRaiseSuccessor {
class RaiseSuccessor extends SuccessorType, CfgImpl::TRaiseSuccessor {
final override string toString() { result = "raise" }
}
@@ -344,7 +326,7 @@ module SuccessorTypes {
* The exit node of `m` is an exit successor of the node
* `exit 1`.
*/
class ExitSuccessor extends SuccessorType, TExitSuccessor {
class ExitSuccessor extends SuccessorType, CfgImpl::TExitSuccessor {
final override string toString() { result = "exit" }
}
}

View File

@@ -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()
}
/**

View File

@@ -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;

View File

@@ -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()
)
}
}

View File

@@ -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;
}

View File

@@ -8,6 +8,26 @@ private import FlowSummaryImpl as FlowSummaryImpl
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.SSA
private import codeql.util.Boolean
private import codeql.util.Unit
/**
* A `LocalSourceNode` for a `self` variable. This is the implicit `self`
* parameter, when it exists, otherwise the implicit SSA entry definition.
*/
private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
private SelfVariable self;
SelfLocalSourceNode() {
self = this.(SelfParameterNodeImpl).getSelfVariable()
or
self = this.(SsaSelfDefinitionNode).getVariable() and
not LocalFlow::localFlowSsaParamInput(_, this)
}
/** Gets the `self` variable. */
SelfVariable getVariable() { result = self }
}
newtype TReturnKind =
TNormalReturnKind() or
@@ -133,12 +153,12 @@ class DataFlowCall extends TDataFlowCall {
*/
class SummaryCall extends DataFlowCall, TSummaryCall {
private FlowSummaryImpl::Public::SummarizedCallable c;
private DataFlow::Node receiver;
private FlowSummaryImpl::Private::SummaryNode receiver;
SummaryCall() { this = TSummaryCall(c, receiver) }
/** Gets the data flow node that this call targets. */
DataFlow::Node getReceiver() { result = receiver }
FlowSummaryImpl::Private::SummaryNode getReceiver() { result = receiver }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
@@ -244,7 +264,7 @@ private predicate selfInToplevel(SelfVariable self, Module m) {
private predicate asModulePattern(SsaDefinitionExtNode def, Module m) {
exists(AsPattern ap |
m = resolveConstantReadAccess(ap.getPattern()) and
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getNode() =
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getAstNode() =
ap.getVariableAccess()
)
}
@@ -316,7 +336,7 @@ private predicate extendCall(DataFlow::ExprNode receiver, Module m) {
exists(DataFlow::CallNode extendCall |
extendCall.getMethodName() = "extend" and
exists(DataFlow::LocalSourceNode sourceNode | sourceNode.flowsTo(extendCall.getArgument(_)) |
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m) or
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) or
m = resolveConstantReadAccess(sourceNode.asExpr().getExpr())
) and
receiver = extendCall.getReceiver()
@@ -329,7 +349,7 @@ private predicate extendCallModule(Module m, Module n) {
exists(DataFlow::LocalSourceNode receiver, DataFlow::ExprNode e |
receiver.flowsTo(e) and extendCall(e, n)
|
selfInModule(receiver.(SsaSelfDefinitionNode).getVariable(), m) or
selfInModule(receiver.(SelfLocalSourceNode).getVariable(), m) or
m = resolveConstantReadAccess(receiver.asExpr().getExpr())
)
}
@@ -357,7 +377,9 @@ private module Cached {
cached
newtype TDataFlowCall =
TNormalCall(CfgNodes::ExprNodes::CallCfgNode c) or
TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, DataFlow::Node receiver) {
TSummaryCall(
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNode receiver
) {
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
}
@@ -453,35 +475,28 @@ private module Cached {
import Cached
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccess(Module m, TypeTracker t) {
t.start() and m = resolveConstantReadAccess(result.asExpr().getExpr())
or
exists(TypeTracker t2, StepSummary summary |
result = trackModuleAccessRec(m, t2, summary) and t = t2.append(summary)
)
private predicate isNotSelf(DataFlow::Node n) { not n instanceof SelfParameterNodeImpl }
private module TrackModuleInput implements CallGraphConstruction::Simple::InputSig {
class State = Module;
predicate start(DataFlow::Node start, Module m) {
m = resolveConstantReadAccess(start.asExpr().getExpr())
}
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
/**
* We exclude steps into `self` parameters, and instead rely on the type of the
* enclosing module.
*/
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccessRec(Module m, TypeTracker t, StepSummary summary) {
StepSummary::step(trackModuleAccess(m, t), result, summary) and
not result instanceof SelfParameterNode
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
result = trackModuleAccess(m, TypeTracker::end())
}
predicate trackModuleAccess = CallGraphConstruction::Simple::Make<TrackModuleInput>::track/1;
pragma[nomagic]
private predicate hasUserDefinedNew(Module m) {
exists(DataFlow::MethodNode method |
// not `getAnAncestor` because singleton methods cannot be included
singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturningNode())
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturnNode())
)
}
@@ -502,153 +517,174 @@ private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
exact = true
or
// `self.new` inside a module
selfInModule(sourceNode.(SsaSelfDefinitionNode).getVariable(), m) and
selfInModule(sourceNode.(SelfLocalSourceNode).getVariable(), m) and
exact = true
or
// `self.new` inside a singleton method
exists(MethodBase caller |
selfInMethod(sourceNode.(SsaSelfDefinitionNode).getVariable(), caller, m) and
selfInMethod(sourceNode.(SelfLocalSourceNode).getVariable(), caller, m) and
singletonMethod(caller, _, _) and
exact = false
)
)
}
/** Holds if `n` is an instance of type `tp`. */
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
n.asExpr().getExpr() instanceof NilLiteral and
tp = TResolved("NilClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
tp = TResolved("FalseClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
tp = TResolved("TrueClass") and
exact = true
or
n.asExpr().getExpr() instanceof IntegerLiteral and
tp = TResolved("Integer") and
exact = true
or
n.asExpr().getExpr() instanceof FloatLiteral and
tp = TResolved("Float") and
exact = true
or
n.asExpr().getExpr() instanceof RationalLiteral and
tp = TResolved("Rational") and
exact = true
or
n.asExpr().getExpr() instanceof ComplexLiteral and
tp = TResolved("Complex") and
exact = true
or
n.asExpr().getExpr() instanceof StringlikeLiteral and
tp = TResolved("String") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
tp = TResolved("Array") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
tp = TResolved("Hash") and
exact = true
or
n.asExpr().getExpr() instanceof MethodBase and
tp = TResolved("Symbol") and
exact = true
or
n.asParameter() instanceof BlockParameter and
tp = TResolved("Proc") and
exact = true
or
n.asExpr().getExpr() instanceof Lambda and
tp = TResolved("Proc") and
exact = true
or
isStandardNewCall(n.asExpr(), tp, exact)
or
// `self` reference in method or top-level (but not in module or singleton method,
// where instance methods cannot be called; only singleton methods)
n =
any(SsaSelfDefinitionNode self |
exists(MethodBase m |
selfInMethod(self.getVariable(), m, tp) and
not m instanceof SingletonMethod and
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
)
or
selfInToplevel(self.getVariable(), tp) and
exact = true
)
or
// `in C => c then c.foo`
asModulePattern(n, tp) and
exact = false
or
// `case object when C then object.foo`
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
exact = false
}
pragma[nomagic]
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
t.start() and
(
isInstance(result, tp, exact)
or
exists(Module m |
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
exact = true
|
// needed for e.g. `C.new`
m = resolveConstantReadAccess(result.asExpr().getExpr())
or
// needed for e.g. `self.include`
selfInModule(result.(SsaSelfDefinitionNode).getVariable(), m)
or
// needed for e.g. `self.puts`
selfInMethod(result.(SsaSelfDefinitionNode).getVariable(), any(SingletonMethod sm), m)
)
)
or
exists(TypeTracker t2, StepSummary summary |
result = trackInstanceRec(tp, t2, exact, summary) and t = t2.append(summary)
)
}
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
localFlowStepTypeTracker(nodeFrom, nodeTo) and
summary.toString() = "level"
}
pragma[nomagic]
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
}
/**
* We exclude steps into `self` parameters and type checked variables. For those,
* we instead rely on the type of the enclosing module resp. the type being checked
* against, and apply an open-world assumption when determining possible dispatch
* targets.
*/
pragma[nomagic]
private DataFlow::Node trackInstanceRec(Module tp, TypeTracker t, boolean exact, StepSummary summary) {
exists(DataFlow::Node mid | mid = trackInstance(tp, exact, t) |
StepSummary::smallstep(mid, result, summary) and
not result instanceof SelfParameterNode
private module TrackInstanceInput implements CallGraphConstruction::InputSig {
pragma[nomagic]
private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) {
n.asExpr().getExpr() instanceof NilLiteral and
tp = TResolved("NilClass") and
exact = true
or
localFlowStep(mid, result, summary) and
not hasAdjacentTypeCheckedReads(result)
)
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
tp = TResolved("FalseClass") and
exact = true
or
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
tp = TResolved("TrueClass") and
exact = true
or
n.asExpr().getExpr() instanceof IntegerLiteral and
tp = TResolved("Integer") and
exact = true
or
n.asExpr().getExpr() instanceof FloatLiteral and
tp = TResolved("Float") and
exact = true
or
n.asExpr().getExpr() instanceof RationalLiteral and
tp = TResolved("Rational") and
exact = true
or
n.asExpr().getExpr() instanceof ComplexLiteral and
tp = TResolved("Complex") and
exact = true
or
n.asExpr().getExpr() instanceof StringlikeLiteral and
tp = TResolved("String") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
tp = TResolved("Array") and
exact = true
or
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
tp = TResolved("Hash") and
exact = true
or
n.asExpr().getExpr() instanceof MethodBase and
tp = TResolved("Symbol") and
exact = true
or
n.asParameter() instanceof BlockParameter and
tp = TResolved("Proc") and
exact = true
or
n.asExpr().getExpr() instanceof Lambda and
tp = TResolved("Proc") and
exact = true
or
// `self` reference in method or top-level (but not in module or singleton method,
// where instance methods cannot be called; only singleton methods)
n =
any(SelfLocalSourceNode self |
exists(MethodBase m |
selfInMethod(self.getVariable(), m, tp) and
not m instanceof SingletonMethod and
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
)
or
selfInToplevel(self.getVariable(), tp) and
exact = true
)
or
// `in C => c then c.foo`
asModulePattern(n, tp) and
exact = false
or
// `case object when C then object.foo`
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
exact = false
}
pragma[nomagic]
private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) {
isStandardNewCall(n.asExpr(), tp, exact)
}
/** Holds if `n` is an instance of type `tp`. */
pragma[inline]
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
isInstanceNoCall(n, tp, exact)
or
isInstanceCall(n, tp, exact)
}
pragma[nomagic]
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
}
newtype State = additional MkState(Module m, Boolean exact)
predicate start(DataFlow::Node start, State state) {
exists(Module tp, boolean exact | state = MkState(tp, exact) |
isInstance(start, tp, exact)
or
exists(Module m |
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
exact = true
|
// needed for e.g. `C.new`
m = resolveConstantReadAccess(start.asExpr().getExpr())
or
// needed for e.g. `self.include`
selfInModule(start.(SelfLocalSourceNode).getVariable(), m)
or
// needed for e.g. `self.puts`
selfInMethod(start.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
)
)
}
pragma[nomagic]
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
// We exclude steps into `self` parameters. For those, we instead rely on the type of
// the enclosing module
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) and
isNotSelf(nodeTo)
or
// We exclude steps into type checked variables. For those, we instead rely on the
// type being checked against
localFlowStep(nodeFrom, nodeTo, summary) and
not hasAdjacentTypeCheckedReads(nodeTo)
}
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
}
class StateProj = Unit;
Unit stateProj(State state) { exists(state) and exists(result) }
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n, Unit u) {
n instanceof SelfParameterNodeImpl and
exists(u)
}
}
pragma[nomagic]
private DataFlow::Node trackInstance(Module tp, boolean exact) {
result = trackInstance(tp, exact, TypeTracker::end())
result =
CallGraphConstruction::Make<TrackInstanceInput>::track(TrackInstanceInput::MkState(tp, exact))
}
pragma[nomagic]
@@ -689,30 +725,17 @@ private CfgScope getTargetInstance(RelevantCall call, string method) {
)
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
t.start() and result.asExpr().getExpr() = block
or
exists(TypeTracker t2, StepSummary summary |
result = trackBlockRec(block, t2, summary) and
t = t2.append(summary)
)
private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig {
class State = Block;
predicate start(DataFlow::Node start, Block block) { start.asExpr().getExpr() = block }
// We exclude steps into `self` parameters, and instead rely on the type of the
// enclosing module
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
}
/**
* We exclude steps into `self` parameters, which may happen when the code
* base contains implementations of `call`.
*/
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) {
StepSummary::step(trackBlock(block, t), result, summary) and
not result instanceof SelfParameterNode
}
pragma[nomagic]
private DataFlow::LocalSourceNode trackBlock(Block block) {
result = trackBlock(block, TypeTracker::end())
}
private predicate trackBlock = CallGraphConstruction::Simple::Make<TrackBlockInput>::track/1;
/** Holds if `m` is a singleton method named `name`, defined on `object. */
private predicate singletonMethod(MethodBase m, string name, Expr object) {
@@ -879,98 +902,104 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object)
)
}
/**
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
*
* This is only used for tracking singleton methods, where we want to be able
* to handle cases like
*
* ```rb
* def add_singleton x
* def x.foo; end
* end
*
* y = add_singleton C.new
* y.foo
* ```
*
* and
*
* ```rb
* class C
* def add_singleton_to_self
* def self.foo; end
* end
* end
*
* y = C.new
* y.add_singleton_to_self
* y.foo
* ```
*/
pragma[nomagic]
private predicate paramReturnFlow(
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
) {
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
TypeTrackerSpecific::callStep(call, arg, p) and
nodeTo.getPreUpdateNode() = arg and
summary.toString() = "return" and
(
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruction::InputSig {
/**
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
*
* This is only used for tracking singleton methods, where we want to be able
* to handle cases like
*
* ```rb
* def add_singleton x
* def x.foo; end
* end
*
* y = add_singleton C.new
* y.foo
* ```
*
* and
*
* ```rb
* class C
* def add_singleton_to_self
* def self.foo; end
* end
* end
*
* y = C.new
* y.add_singleton_to_self
* y.foo
* ```
*/
pragma[nomagic]
private predicate paramReturnFlow(
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
) {
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
TypeTrackerSpecific::callStep(call, arg, p) and
nodeTo.getPreUpdateNode() = arg and
summary.toString() = "return" and
(
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
or
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
singletonMethodOnInstance(_, _, nodeFromPreExpr)
)
|
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
or
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
singletonMethodOnInstance(_, _, nodeFromPreExpr)
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess()
)
|
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
or
nodeFromPreExpr = p.(SelfParameterNode).getSelfVariable().getAnAccess()
)
}
}
pragma[nomagic]
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name, TypeTracker t) {
t.start() and
singletonMethodOnInstance(method, name, result.asExpr().getExpr())
or
exists(TypeTracker t2, StepSummary summary |
result = trackSingletonMethodOnInstanceRec(method, name, t2, summary) and
t = t2.append(summary) and
// Stop flow at redefinitions.
//
// Example:
// ```rb
// def x.foo; end
// def x.foo; end
// x.foo # <- we want to resolve this call to the second definition only
// ```
not singletonMethodOnInstance(_, name, result.asExpr().getExpr())
)
}
class State = MethodBase;
pragma[nomagic]
private DataFlow::Node trackSingletonMethodOnInstanceRec(
MethodBase method, string name, TypeTracker t, StepSummary summary
) {
exists(DataFlow::Node mid | mid = trackSingletonMethodOnInstance(method, name, t) |
StepSummary::smallstep(mid, result, summary)
predicate start(DataFlow::Node start, MethodBase method) {
singletonMethodOnInstance(method, _, start.asExpr().getExpr())
}
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary)
or
paramReturnFlow(mid, result, summary)
localFlowStep(nodeFrom, nodeTo, summary)
}
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
or
localFlowStep(mid, result, summary)
)
paramReturnFlow(nodeFrom, nodeTo, summary)
}
class StateProj extends string {
StateProj() { singletonMethodOnInstance(_, this, _) }
}
StateProj stateProj(MethodBase method) { singletonMethodOnInstance(method, result, _) }
// Stop flow at redefinitions.
//
// Example:
// ```rb
// def x.foo; end
// def x.foo; end
// x.foo # <- we want to resolve this call to the second definition only
// ```
predicate filter(DataFlow::Node n, StateProj name) {
singletonMethodOnInstance(_, name, n.asExpr().getExpr())
}
}
pragma[nomagic]
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name) {
result = trackSingletonMethodOnInstance(method, name, TypeTracker::end())
result = CallGraphConstruction::Make<TrackSingletonMethodOnInstanceInput>::track(method) and
singletonMethodOnInstance(method, name, _)
}
/** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */
pragma[nomagic]
private predicate selfInModuleFlowsToMethodCallReceiver(RelevantCall call, Module m, string method) {
exists(SsaSelfDefinitionNode self |
exists(SelfLocalSourceNode self |
flowsToMethodCallReceiver(call, self, method) and
selfInModule(self.getVariable(), m)
)
@@ -984,7 +1013,7 @@ pragma[nomagic]
private predicate selfInSingletonMethodFlowsToMethodCallReceiver(
RelevantCall call, Module m, string method
) {
exists(SsaSelfDefinitionNode self, MethodBase caller |
exists(SelfLocalSourceNode self, MethodBase caller |
flowsToMethodCallReceiver(call, self, method) and
selfInMethod(self.getVariable(), caller, m) and
singletonMethod(caller, _, _)
@@ -1062,10 +1091,13 @@ private CfgScope getTargetSingleton(RelevantCall call, string method) {
*/
pragma[nomagic]
private predicate argMustFlowToReceiver(
RelevantCall ctx, DataFlow::LocalSourceNode source, DataFlow::Node arg,
SsaDefinitionExtNode paramDef, RelevantCall call, Callable encl, string name
RelevantCall ctx, DataFlow::LocalSourceNode source, DataFlow::Node arg, RelevantCall call,
Callable encl, string name
) {
exists(ParameterNodeImpl p, ParameterPosition ppos, ArgumentPosition apos |
exists(
ParameterNodeImpl p, SsaDefinitionExtNode paramDef, ParameterPosition ppos,
ArgumentPosition apos
|
// the receiver of `call` references `p`
exists(DataFlow::Node receiver |
LocalFlow::localFlowSsaParamInput(p, paramDef) and
@@ -1106,7 +1138,7 @@ private predicate mayBenefitFromCallContextInitialize(
RelevantCall ctx, RelevantCall new, DataFlow::Node arg, Callable encl, Module tp, string name
) {
exists(DataFlow::LocalSourceNode source |
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, new, encl, "new") and
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, new, encl, "new") and
source = trackModuleAccess(tp) and
name = "initialize" and
exists(lookupMethod(tp, name))
@@ -1127,7 +1159,7 @@ private predicate mayBenefitFromCallContextInstance(
string name
) {
exists(DataFlow::LocalSourceNode source |
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, _, call, encl,
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), arg, call, encl,
pragma[only_bind_into](name)) and
source = trackInstance(tp, exact) and
exists(lookupMethod(tp, pragma[only_bind_into](name)))
@@ -1148,7 +1180,7 @@ private predicate mayBenefitFromCallContextSingleton(
string name
) {
exists(DataFlow::LocalSourceNode source |
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), pragma[only_bind_into](arg), _, call,
argMustFlowToReceiver(ctx, pragma[only_bind_into](source), pragma[only_bind_into](arg), call,
encl, pragma[only_bind_into](name)) and
exists(lookupSingletonMethod(tp, pragma[only_bind_into](name), exact))
|
@@ -1216,13 +1248,10 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
or
// `ctx` cannot provide a type bound, and the receiver of the call is `self`;
// in this case, still apply an open-world assumption
exists(
RelevantCall call0, RelevantCall ctx0, DataFlow::Node arg, SsaSelfDefinitionNode self,
string name
|
exists(RelevantCall call0, RelevantCall ctx0, DataFlow::Node arg, string name |
call0 = call.asCall() and
ctx0 = ctx.asCall() and
argMustFlowToReceiver(ctx0, _, arg, self, call0, _, name) and
argMustFlowToReceiver(ctx0, _, arg, call0, _, name) and
not mayBenefitFromCallContextInitialize(ctx0, call0, arg, _, _, _) and
not mayBenefitFromCallContextInstance(ctx0, call0, arg, _, _, _, name) and
not mayBenefitFromCallContextSingleton(ctx0, call0, arg, _, _, _, name) and
@@ -1230,7 +1259,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
)
or
// library calls should always be able to resolve
argMustFlowToReceiver(ctx.asCall(), _, _, _, call.asCall(), _, _) and
argMustFlowToReceiver(ctx.asCall(), _, _, call.asCall(), _, _) and
result = viableLibraryCallable(call)
)
}
@@ -1387,6 +1416,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
* This is a temporary hook to support technical debt in the Go language; do not use.
*/
pragma[inline]
predicate golangSpecificParamArgFilter(DataFlowCall call, DataFlow::Node p, ArgumentNode arg) {
predicate golangSpecificParamArgFilter(
DataFlowCall call, DataFlow::ParameterNode p, ArgumentNode arg
) {
any()
}

File diff suppressed because it is too large Load Diff

View File

@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -313,6 +313,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -1,6 +1,9 @@
/**
* Provides Ruby-specific definitions for use in the data flow library.
*/
private import codeql.dataflow.DataFlowParameter
module Private {
import DataFlowPrivate
import DataFlowDispatch
@@ -9,3 +12,16 @@ module Private {
module Public {
import DataFlowPublic
}
module RubyDataFlow implements DataFlowParameter {
import Private
import Public
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
Private::isParameterNode(p, c, pos)
}
predicate neverSkipInPathGraph = Private::neverSkipInPathGraph/1;
Node exprNode(DataFlowExpr e) { result = Public::exprNode(e) }
}

View File

@@ -12,7 +12,7 @@ private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import codeql.ruby.frameworks.data.ModelsAsData
/** Gets the callable in which this node occurs. */
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.(NodeImpl).getEnclosingCallable() }
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
predicate isParameterNode(ParameterNodeImpl p, DataFlowCallable c, ParameterPosition pos) {
@@ -118,7 +118,7 @@ module LocalFlow {
/** Gets the SSA definition node corresponding to parameter `p`. */
SsaDefinitionExtNode getParameterDefNode(NamedParameter p) {
exists(BasicBlock bb, int i |
bb.getNode(i).getNode() = p.getDefiningAccess() and
bb.getNode(i).getAstNode() = p.getDefiningAccess() and
result.getDefinitionExt().definesAt(_, bb, i, _)
)
}
@@ -131,10 +131,10 @@ module LocalFlow {
/**
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
*/
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
predicate localFlowSsaParamInput(Node nodeFrom, SsaDefinitionExtNode nodeTo) {
nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter())
or
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
}
/**
@@ -143,13 +143,13 @@ module LocalFlow {
*
* This is intended to recover from flow not currently recognised by ordinary capture flow.
*/
predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) {
predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) {
exists(Ssa::CapturedEntryDefinition def |
nodeFrom.asParameter().(NamedParameter).getVariable() = def.getSourceVariable()
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
or
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
)
}
@@ -163,7 +163,7 @@ module LocalFlow {
/**
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
* SSA definition `def`.
* some SSA definition.
*/
private predicate localSsaFlowStep(Node nodeFrom, Node nodeTo) {
exists(SsaImpl::DefinitionExt def |
@@ -181,6 +181,8 @@ module LocalFlow {
// Flow into phi (read) SSA definition node from def
localFlowSsaInputFromDef(nodeFrom, def, nodeTo)
)
or
localFlowSsaParamInput(nodeFrom, nodeTo)
// TODO
// or
// // Flow into uncertain SSA definition
@@ -201,8 +203,8 @@ module LocalFlow {
exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n |
nodeFrom = n and
exprTo = nodeTo.asExpr() and
n.getReturningNode().getNode() instanceof BreakStmt and
exprTo.getNode() instanceof Loop and
n.getReturningNode().getAstNode() instanceof BreakStmt and
exprTo.getAstNode() instanceof Loop and
nodeTo.asExpr().getAPredecessor(any(SuccessorTypes::BreakSuccessor s)) = n.getReturningNode()
)
or
@@ -222,6 +224,13 @@ module LocalFlow {
op.getExpr() instanceof BinaryLogicalOperation and
nodeFrom.asExpr() = op.getAnOperand()
)
or
nodeTo.(ParameterNodeImpl).getParameter() =
any(NamedParameter p |
p.(OptionalParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
or
p.(KeywordParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
)
}
}
@@ -278,12 +287,6 @@ private module Cached {
newtype TNode =
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
TReturningNode(CfgNodes::ReturningCfgNode n) or
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
exists(ReturningNode ret |
ret.(NodeImpl).getCfgScope() = scope and
ret.getKind() = kind
)
} or
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
TNormalParameterNode(Parameter p) {
p instanceof SimpleParameter or
@@ -306,30 +309,16 @@ private module Cached {
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
)
} or
TSummaryNode(
FlowSummaryImpl::Public::SummarizedCallable c,
FlowSummaryImpl::Private::SummaryNodeState state
) {
FlowSummaryImpl::Private::summaryNodeRange(c, state)
} or
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
} or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TSynthHashSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
or
c.getAnArgument() instanceof CfgNodes::ExprNodes::PairCfgNode
}
class TParameterNode =
class TSourceParameterNode =
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
TSynthHashSplatParameterNode or TSummaryParameterNode;
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
or
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
}
TSynthHashSplatParameterNode;
cached
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
@@ -345,12 +334,6 @@ private module Cached {
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
or
@@ -364,7 +347,8 @@ private module Cached {
exprFrom = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()
)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), true)
}
/** This is the local flow predicate that is exposed. */
@@ -372,10 +356,6 @@ private module Cached {
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
or
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
or
// Simple flow through library code is included in the exposed local
@@ -385,19 +365,11 @@ private module Cached {
/**
* This is the local flow predicate that is used in type tracking.
*
* This needs to exclude `localFlowSsaParamInput` due to a performance trick
* in type tracking, where such steps are treated as call steps.
*/
cached
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
or
exists(NamedParameter p |
defaultValueFlow(p, nodeFrom) and
nodeTo = LocalFlow::getParameterDefNode(p)
)
or
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
or
// Flow into phi node from read
@@ -433,18 +405,18 @@ private module Cached {
cached
predicate isLocalSourceNode(Node n) {
n instanceof TParameterNode
n instanceof TSourceParameterNode
or
n instanceof SummaryParameterNode
or
// Expressions that can't be reached from another entry definition or expression
n instanceof ExprNode and
not reachedFromExprOrEntrySsaDef(n)
or
// Ensure all entry SSA definitions are local sources -- for parameters, this
// is needed by type tracking
entrySsaDefinition(n)
or
// Needed for flow out in type tracking
n instanceof SynthReturnNode
// Ensure all entry SSA definitions are local sources, except those that correspond
// to parameters (which are themselves local sources)
entrySsaDefinition(n) and
not LocalFlow::localFlowSsaParamInput(_, n)
or
// Needed for stores in type tracking
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
@@ -506,7 +478,7 @@ private module Cached {
*/
cached
predicate exprNodeReturnedFromCached(ExprNode e, Callable c) {
exists(ReturningNode r |
exists(ReturnNode r |
nodeGetEnclosingCallable(r).asCallable() = c and
(
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
@@ -537,11 +509,7 @@ predicate nodeIsHidden(Node n) {
or
isDesugarNode(n.(ExprNode).getExprNode().getExpr())
or
n instanceof SummaryNode
or
n instanceof SummaryParameterNode
or
n instanceof SynthReturnNode
n instanceof FlowSummaryNode
or
n instanceof SynthHashSplatParameterNode
or
@@ -657,10 +625,10 @@ private module ParameterNodes {
* The value of the `self` parameter at function entry, viewed as a node in a data
* flow graph.
*/
class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode {
class SelfParameterNodeImpl extends ParameterNodeImpl, TSelfParameterNode {
private MethodBase method;
SelfParameterNode() { this = TSelfParameterNode(method) }
SelfParameterNodeImpl() { this = TSelfParameterNode(method) }
final MethodBase getMethod() { result = method }
@@ -782,47 +750,41 @@ private module ParameterNodes {
}
/** A parameter for a library callable with a flow summary. */
class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
private FlowSummaryImpl::Public::SummarizedCallable sc;
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
private ParameterPosition pos_;
SummaryParameterNode() { this = TSummaryParameterNode(sc, pos_) }
SummaryParameterNode() {
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), pos_)
}
override Parameter getParameter() { none() }
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
sc = c.asLibraryCallable() and pos = pos_
this.getSummarizedCallable() = c.asLibraryCallable() and pos = pos_
}
override CfgScope getCfgScope() { none() }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }
override EmptyLocation getLocationImpl() { any() }
override string toStringImpl() { result = "parameter " + pos_ + " of " + sc }
}
}
import ParameterNodes
/** A data-flow node used to model flow summaries. */
class SummaryNode extends NodeImpl, TSummaryNode {
FlowSummaryImpl::Public::SummarizedCallable c;
FlowSummaryImpl::Private::SummaryNodeState state;
SummaryNode() { this = TSummaryNode(c, state) }
class FlowSummaryNode extends NodeImpl, TFlowSummaryNode {
FlowSummaryImpl::Private::SummaryNode getSummaryNode() { this = TFlowSummaryNode(result) }
/** Gets the summarized callable that this node belongs to. */
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = c }
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() {
result = this.getSummaryNode().getSummarizedCallable()
}
override CfgScope getCfgScope() { none() }
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
override DataFlowCallable getEnclosingCallable() {
result.asLibraryCallable() = this.getSummarizedCallable()
}
override EmptyLocation getLocationImpl() { any() }
override string toStringImpl() { result = "[summary] " + state + " in " + c }
override string toStringImpl() { result = this.getSummaryNode().toString() }
}
/** A data-flow node that represents a call argument. */
@@ -882,15 +844,20 @@ private module ArgumentNodes {
}
}
private class SummaryArgumentNode extends SummaryNode, ArgumentNode {
SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) }
private class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
private DataFlowCall call_;
private ArgumentPosition pos_;
SummaryArgumentNode() {
FlowSummaryImpl::Private::summaryArgumentNode(call_, this.getSummaryNode(), pos_)
}
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
none()
}
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
call = call_ and pos = pos_
}
}
@@ -936,22 +903,30 @@ private class NewCall extends DataFlowCall {
NewCall() { this.asCall().getExpr().(MethodCall).getMethodName() = "new" }
}
/** A data-flow node that represents a value syntactically returned by a callable. */
abstract class ReturningNode extends Node {
/** Gets the kind of this return node. */
abstract ReturnKind getKind();
}
/** A data-flow node that represents a value returned by a callable. */
abstract class ReturnNode extends Node {
/** Gets the kind of this return node. */
abstract ReturnKind getKind();
}
/** A data-flow node that represents a value returned by a callable. */
abstract class SourceReturnNode extends ReturnNode {
/** Gets the kind of this return node. */
abstract ReturnKind getKindSource(); // only exists to avoid spurious negative recursion
final override ReturnKind getKind() { result = this.getKindSource() }
pragma[nomagic]
predicate hasKind(ReturnKind kind, CfgScope scope) {
kind = this.getKindSource() and
scope = this.(NodeImpl).getCfgScope()
}
}
private module ReturnNodes {
private predicate isValid(CfgNodes::ReturningCfgNode node) {
exists(ReturningStmt stmt, Callable scope |
stmt = node.getNode() and
stmt = node.getAstNode() and
scope = node.getScope()
|
stmt instanceof ReturnStmt and
@@ -969,15 +944,15 @@ private module ReturnNodes {
* A data-flow node that represents an expression explicitly returned by
* a callable.
*/
class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
class ExplicitReturnNode extends SourceReturnNode, ReturningStatementNode {
ExplicitReturnNode() {
isValid(n) and
n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
n.getScope() instanceof Callable
}
override ReturnKind getKind() {
if n.getNode() instanceof BreakStmt
override ReturnKind getKindSource() {
if n.getAstNode() instanceof BreakStmt
then result instanceof BreakReturnKind
else
exists(CfgScope scope | scope = this.getCfgScope() |
@@ -1005,10 +980,10 @@ private module ReturnNodes {
* a callable. An implicit return happens when an expression can be the
* last thing that is evaluated in the body of the callable.
*/
class ExprReturnNode extends ReturningNode, ExprNode {
class ExprReturnNode extends SourceReturnNode, ExprNode {
ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) }
override ReturnKind getKind() {
override ReturnKind getKindSource() {
exists(CfgScope scope | scope = this.(NodeImpl).getCfgScope() |
if isUserDefinedNew(scope)
then result instanceof NewReturnKind
@@ -1033,7 +1008,7 @@ private module ReturnNodes {
* the implicit `self` reference in `@x` will return data stored in the field
* `x` out to the call `C.new`.
*/
class InitializeReturnNode extends ExprPostUpdateNode, ReturningNode {
class InitializeReturnNode extends ExprPostUpdateNode, ReturnNode {
InitializeReturnNode() {
exists(Method initialize |
this.getCfgScope() = initialize and
@@ -1046,44 +1021,16 @@ private module ReturnNodes {
override ReturnKind getKind() { result instanceof NewReturnKind }
}
/**
* A synthetic data-flow node for joining flow from different syntactic
* returns into a single node.
*
* This node only exists to avoid computing the product of a large fan-in
* with a large fan-out.
*/
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
private CfgScope scope;
private ReturnKind kind;
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
/** Gets a syntactic return node that flows into this synthetic node. */
ReturningNode getAnInput() {
result.(NodeImpl).getCfgScope() = scope and
result.getKind() = kind
}
override ReturnKind getKind() { result = kind }
override CfgScope getCfgScope() { result = scope }
override Location getLocationImpl() { result = scope.getLocation() }
override string toStringImpl() { result = "return " + kind + " in " + scope }
}
private class SummaryReturnNode extends SummaryNode, ReturnNode {
private class SummaryReturnNode extends FlowSummaryNode, ReturnNode {
private ReturnKind rk;
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) }
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this.getSummaryNode(), rk) }
override ReturnKind getKind() {
result = rk
or
exists(NewCall new |
TLibraryCallable(c) = viableLibraryCallable(new) and
TLibraryCallable(this.getSummarizedCallable()) = viableLibraryCallable(new) and
result instanceof NewReturnKind
)
}
@@ -1116,12 +1063,15 @@ private module OutNodes {
}
}
private class SummaryOutNode extends SummaryNode, OutNode {
SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) }
private class SummaryOutNode extends FlowSummaryNode, OutNode {
private DataFlowCall call;
private ReturnKind kind_;
override DataFlowCall getCall(ReturnKind kind) {
FlowSummaryImpl::Private::summaryOutNode(result, this, kind)
SummaryOutNode() {
FlowSummaryImpl::Private::summaryOutNode(call, this.getSummaryNode(), kind_)
}
override DataFlowCall getCall(ReturnKind kind) { result = call and kind = kind_ }
}
}
@@ -1136,7 +1086,8 @@ predicate jumpStep(Node pred, Node succ) {
or
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
or
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred, succ)
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
succ.(FlowSummaryNode).getSummaryNode())
or
any(AdditionalJumpStep s).step(pred, succ)
}
@@ -1201,7 +1152,8 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
))
).getReceiver()
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
or
storeStepCommon(node1, c, node2)
}
@@ -1235,7 +1187,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
or
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
}
/**
@@ -1244,7 +1197,7 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, ContentSet c) {
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
or
// Filter out keyword arguments that are part of the method signature from
// the hash-splat parameter
@@ -1265,7 +1218,7 @@ predicate clearsContent(Node n, ContentSet c) {
* at node `n`.
*/
predicate expectsContent(Node n, ContentSet c) {
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n, c)
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
}
private newtype TDataFlowType =
@@ -1276,8 +1229,10 @@ class DataFlowType extends TDataFlowType {
string toString() { result = "" }
}
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
/** Gets the type of `n` used for type pruning. */
DataFlowType getNodeType(NodeImpl n) { result = TTodoDataFlowType() and exists(n) }
DataFlowType getNodeType(Node n) { result = TTodoDataFlowType() and exists(n) }
/** Gets a string representation of a `DataFlowType`. */
string ppReprType(DataFlowType t) { none() }
@@ -1320,10 +1275,12 @@ private module PostUpdateNodes {
override string toStringImpl() { result = "[post] " + e.toString() }
}
private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNodeImpl {
private Node pre;
private class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNodeImpl {
private FlowSummaryNode pre;
SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) }
SummaryPostUpdateNode() {
FlowSummaryImpl::Private::summaryPostUpdateNode(this.getSummaryNode(), pre.getSummaryNode())
}
override Node getPreUpdateNode() { result = pre }
}
@@ -1333,19 +1290,20 @@ private import PostUpdateNodes
/** A node that performs a type cast. */
class CastNode extends Node {
CastNode() {
// ensure that actual return nodes are included in the path graph
this instanceof ReturningNode
or
// ensure that all variable assignments are included in the path graph
this.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition
}
CastNode() { none() }
}
/**
* Holds if `n` should never be skipped over in the `PathGraph` and in path
* explanations.
*/
predicate neverSkipInPathGraph(Node n) {
// ensure that all variable assignments are included in the path graph
n.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition
}
class DataFlowExpr = CfgNodes::ExprCfgNode;
int accessPathLimit() { result = 5 }
/**
* Holds if access paths with `c` at their head always should be tracked at high
* precision. This disables adaptive access path precision for such access paths.
@@ -1373,11 +1331,20 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
creation.asExpr() =
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
c.asCallable() = mc.getBlock().getExpr() and
mc.getExpr().getMethodName() = ["lambda", "proc"]
isProcCreationCall(mc.getExpr())
)
)
}
/** Holds if `call` is a call to `lambda`, `proc`, or `Proc.new` */
pragma[nomagic]
private predicate isProcCreationCall(MethodCall call) {
call.getMethodName() = ["proc", "lambda"]
or
call.getMethodName() = "new" and
call.getReceiver().(ConstantReadAccess).getAQualifiedName() = "Proc"
}
/**
* Holds if `call` is a from-source lambda call of kind `kind` where `receiver`
* is the lambda expression.
@@ -1401,7 +1368,7 @@ predicate lambdaSourceCall(CfgNodes::ExprNodes::CallCfgNode call, LambdaCallKind
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
lambdaSourceCall(call.asCall(), kind, receiver)
or
receiver = call.(SummaryCall).getReceiver() and
receiver.(FlowSummaryNode).getSummaryNode() = call.(SummaryCall).getReceiver() and
if receiver.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pos | pos.isBlock()))
then kind = TYieldCallKind()
else kind = TLambdaCallKind()

View File

@@ -6,12 +6,17 @@ private import codeql.ruby.typetracking.TypeTracker
private import codeql.ruby.dataflow.SSA
private import FlowSummaryImpl as FlowSummaryImpl
private import SsaImpl as SsaImpl
private import codeql.ruby.ApiGraphs
/**
* An element, viewed as a node in a data flow graph. Either an expression
* (`ExprNode`) or a parameter (`ParameterNode`).
*/
class Node extends TNode {
/** Starts backtracking from this node using API graphs. */
pragma[inline]
API::Node backtrack() { result = API::Internal::getNodeForBacktracking(this) }
/** Gets the expression corresponding to this node, if any. */
CfgNodes::ExprCfgNode asExpr() { result = this.(ExprNode).getExprNode() }
@@ -76,6 +81,11 @@ class Node extends TNode {
result.asCallableAstNode() = c.asCallable()
)
}
/** Gets the enclosing method, if any. */
MethodNode getEnclosingMethod() {
result.asCallableAstNode() = this.asExpr().getExpr().getEnclosingMethod()
}
}
/** A data-flow node corresponding to a call in the control-flow graph. */
@@ -144,6 +154,18 @@ class CallNode extends LocalSourceNode, ExprNode {
result.asExpr() = pair.getValue()
)
}
/**
* Gets a potential target of this call, if any.
*/
final CallableNode getATarget() {
result.asCallableAstNode() = this.asExpr().getExpr().(Call).getATarget()
}
/**
* Holds if this is a `super` call.
*/
final predicate isSuperCall() { this.asExpr().getExpr() instanceof SuperCall }
}
/**
@@ -191,20 +213,36 @@ class ExprNode extends Node, TExprNode {
* The value of a parameter at function entry, viewed as a node in a data
* flow graph.
*/
class ParameterNode extends LocalSourceNode, TParameterNode instanceof ParameterNodeImpl {
class ParameterNode extends LocalSourceNode instanceof ParameterNodeImpl {
/** Gets the parameter corresponding to this node, if any. */
final Parameter getParameter() { result = super.getParameter() }
/** Gets the callable that this parameter belongs to. */
final Callable getCallable() { result = super.getCfgScope() }
/** Gets the name of the parameter, if any. */
final string getName() { result = this.getParameter().(NamedParameter).getName() }
}
/**
* The value of an implicit `self` parameter at function entry, viewed as a node in a data
* flow graph.
*/
class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl {
/** Gets the underlying `self` variable. */
final SelfVariable getSelfVariable() { result = super.getSelfVariable() }
}
/**
* A data-flow node that is a source of local flow.
*/
class LocalSourceNode extends Node {
LocalSourceNode() { isLocalSourceNode(this) }
/** Starts tracking this node forward using API graphs. */
pragma[inline]
API::Node track() { result = API::Internal::getNodeForForwardTracking(this) }
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
pragma[inline]
predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) }
@@ -328,9 +366,6 @@ private module Cached {
exists(Node mid | hasLocalSource(mid, source) |
localFlowStepTypeTracker(mid, sink)
or
// Explicitly include the SSA param input step as type-tracking omits this step.
LocalFlow::localFlowSsaParamInput(mid, sink)
or
LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
)
}
@@ -350,6 +385,11 @@ private module Cached {
)
}
cached
predicate methodHasSuperCall(MethodNode method, CallNode call) {
call.isSuperCall() and method = call.getEnclosingMethod()
}
/**
* A place in which a named constant can be looked up during constant lookup.
*/
@@ -378,6 +418,39 @@ private module Cached {
result.asExpr().getExpr() = access
}
/**
* Gets a module for which `constRef` is the reference to an ancestor module.
*
* For example, `M` is the ancestry target of `C` in the following examples:
* ```rb
* class M < C {}
*
* module M
* include C
* end
*
* module M
* prepend C
* end
* ```
*/
private ModuleNode getAncestryTarget(ConstRef constRef) { result.getAnAncestorExpr() = constRef }
/**
* Gets a scope in which a constant lookup may access the contents of the module referenced by `constRef`.
*/
cached
TConstLookupScope getATargetScope(ConstRef constRef) {
result = MkAncestorLookup(getAncestryTarget(constRef).getAnImmediateDescendent*())
or
constRef.asConstantAccess() = any(ConstantAccess ac).getScopeExpr() and
result = MkQualifiedLookup(constRef.asConstantAccess())
or
result = MkNestedLookup(getAncestryTarget(constRef))
or
result = MkExactLookup(constRef.asConstantAccess().(Namespace).getModule())
}
cached
predicate forceCachingInSameStage() { any() }
@@ -1019,6 +1092,33 @@ class ModuleNode instanceof Module {
* this predicate.
*/
ModuleNode getNestedModule(string name) { result = super.getNestedModule(name) }
/**
* Starts tracking the module object using API graphs.
*
* Concretely, this tracks forward from the following starting points:
* - A constant access that resolves to this module.
* - `self` in the module scope or in a singleton method of the module.
* - A call to `self.class` in an instance method of this module or an ancestor module.
*/
bindingset[this]
pragma[inline]
API::Node trackModule() { result = API::Internal::getModuleNode(this) }
/**
* Starts tracking instances of this module forward using API graphs.
*
* Concretely, this tracks forward from the following starting points:
* - `self` in instance methods of this module and ancestor modules
* - Calls to `new` on the module object
*
* Note that this includes references to `self` in ancestor modules, but not in descendent modules.
* This is usually the desired behavior, particularly if this module was itself found using
* a call to `getADescendentModule()`.
*/
bindingset[this]
pragma[inline]
API::Node trackInstance() { result = API::Internal::getModuleInstance(this) }
}
/**
@@ -1176,19 +1276,15 @@ class CallableNode extends StmtSequenceNode {
result = this.getBlockParameter().getAMethodCall("call")
}
/**
* Gets the canonical return node from this callable.
*
* Each callable has exactly one such node, and its location may not correspond
* to any particular return site - consider using `getAReturningNode` to get nodes
* whose locations correspond to return sites.
*/
Node getReturn() { result.(SynthReturnNode).getCfgScope() = callable }
/**
* Gets a data flow node whose value is about to be returned by this callable.
*/
Node getAReturningNode() { result = this.getReturn().(SynthReturnNode).getAnInput() }
Node getAReturnNode() { result.(ReturnNode).(NodeImpl).getCfgScope() = callable }
/**
* DEPRECATED. Use `getAReturnNode` instead.
*/
deprecated Node getAReturningNode() { result = this.getAReturnNode() }
}
/**
@@ -1211,6 +1307,9 @@ class MethodNode extends CallableNode {
/** Holds if this method is protected. */
predicate isProtected() { this.asCallableAstNode().isProtected() }
/** Gets a `super` call in this method. */
CallNode getASuperCall() { methodHasSuperCall(this, result) }
}
/**
@@ -1279,13 +1378,16 @@ class HashLiteralNode extends LocalSourceNode, ExprNode {
* into calls to `Array.[]`, so this includes both desugared calls as well as
* explicit calls.
*/
class ArrayLiteralNode extends LocalSourceNode, ExprNode {
class ArrayLiteralNode extends LocalSourceNode, CallNode {
ArrayLiteralNode() { super.getExprNode() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode }
/**
* Gets an element of the array.
*/
Node getAnElement() { result = this.(CallNode).getPositionalArgument(_) }
Node getAnElement() { result = this.getElement(_) }
/** Gets the `i`th element of the array. */
Node getElement(int i) { result = this.getPositionalArgument(i) }
}
/**
@@ -1326,24 +1428,6 @@ class ConstRef extends LocalSourceNode {
not exists(access.getScopeExpr())
}
/**
* Gets a module for which this constant is the reference to an ancestor module.
*
* For example, `M` is the ancestry target of `C` in the following examples:
* ```rb
* class M < C {}
*
* module M
* include C
* end
*
* module M
* prepend C
* end
* ```
*/
private ModuleNode getAncestryTarget() { result.getAnAncestorExpr() = this }
/**
* Gets the known target module.
*
@@ -1351,22 +1435,6 @@ class ConstRef extends LocalSourceNode {
*/
private Module getExactTarget() { result.getAnImmediateReference() = access }
/**
* Gets a scope in which a constant lookup may access the contents of the module referenced by this constant.
*/
cached
private TConstLookupScope getATargetScope() {
forceCachingInSameStage() and
result = MkAncestorLookup(this.getAncestryTarget().getAnImmediateDescendent*())
or
access = any(ConstantAccess ac).getScopeExpr() and
result = MkQualifiedLookup(access)
or
result = MkNestedLookup(this.getAncestryTarget())
or
result = MkExactLookup(access.(Namespace).getModule())
}
/**
* Gets the scope expression, or the immediately enclosing `Namespace` (skipping over singleton classes).
*
@@ -1428,7 +1496,7 @@ class ConstRef extends LocalSourceNode {
pragma[inline]
ConstRef getConstant(string name) {
exists(TConstLookupScope scope |
pragma[only_bind_into](scope) = pragma[only_bind_out](this).getATargetScope() and
pragma[only_bind_into](scope) = getATargetScope(pragma[only_bind_out](this)) and
result.accesses(pragma[only_bind_out](scope), name)
)
}
@@ -1450,7 +1518,9 @@ class ConstRef extends LocalSourceNode {
* end
* ```
*/
ModuleNode getADescendentModule() { MkAncestorLookup(result) = this.getATargetScope() }
bindingset[this]
pragma[inline_late]
ModuleNode getADescendentModule() { MkAncestorLookup(result) = getATargetScope(this) }
}
/**

View File

@@ -23,29 +23,30 @@ module Public {
* content type, or a return kind.
*/
class SummaryComponent extends TSummaryComponent {
/** Gets a textual representation of this summary component. */
string toString() {
exists(ContentSet c | this = TContentSummaryComponent(c) and result = c.toString())
or
exists(ContentSet c | this = TWithoutContentSummaryComponent(c) and result = "without " + c)
or
exists(ContentSet c | this = TWithContentSummaryComponent(c) and result = "with " + c)
/** Gets a textual representation of this component used for MaD models. */
string getMadRepresentation() {
result = getMadRepresentationSpecific(this)
or
exists(ArgumentPosition pos |
this = TParameterSummaryComponent(pos) and result = "parameter " + pos
this = TParameterSummaryComponent(pos) and
result = "Parameter[" + getArgumentPosition(pos) + "]"
)
or
exists(ParameterPosition pos |
this = TArgumentSummaryComponent(pos) and result = "argument " + pos
this = TArgumentSummaryComponent(pos) and
result = "Argument[" + getParameterPosition(pos) + "]"
)
or
exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")")
or
exists(SummaryComponent::SyntheticGlobal sg |
this = TSyntheticGlobalSummaryComponent(sg) and
result = "synthetic global (" + sg + ")"
exists(string synthetic |
this = TSyntheticGlobalSummaryComponent(synthetic) and
result = "SyntheticGlobal[" + synthetic + "]"
)
or
this = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
}
/** Gets a textual representation of this summary component. */
string toString() { result = this.getMadRepresentation() }
}
/** Provides predicates for constructing summary components. */
@@ -110,7 +111,6 @@ module Public {
}
/** Gets the stack obtained by dropping the first `i` elements, if any. */
pragma[assume_small_delta]
SummaryComponentStack drop(int i) {
i = 0 and result = this
or
@@ -125,19 +125,22 @@ module Public {
this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom()
}
/** Gets a textual representation of this stack. */
string toString() {
/** Gets a textual representation of this stack used for MaD models. */
string getMadRepresentation() {
exists(SummaryComponent head, SummaryComponentStack tail |
head = this.head() and
tail = this.tail() and
result = tail + "." + head
result = tail.getMadRepresentation() + "." + head.getMadRepresentation()
)
or
exists(SummaryComponent c |
this = TSingletonSummaryComponentStack(c) and
result = c.toString()
result = c.getMadRepresentation()
)
}
/** Gets a textual representation of this stack. */
string toString() { result = this.getMadRepresentation() }
}
/** Provides predicates for constructing stacks of summary components. */
@@ -166,44 +169,6 @@ module Public {
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
}
private predicate noComponentSpecific(SummaryComponent sc) {
not exists(getComponentSpecific(sc))
}
/** Gets a textual representation of this component used for flow summaries. */
private string getComponent(SummaryComponent sc) {
result = getComponentSpecific(sc)
or
noComponentSpecific(sc) and
(
exists(ArgumentPosition pos |
sc = TParameterSummaryComponent(pos) and
result = "Parameter[" + getArgumentPosition(pos) + "]"
)
or
exists(ParameterPosition pos |
sc = TArgumentSummaryComponent(pos) and
result = "Argument[" + getParameterPosition(pos) + "]"
)
or
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
)
}
/** Gets a textual representation of this stack used for flow summaries. */
string getComponentStack(SummaryComponentStack stack) {
exists(SummaryComponent head, SummaryComponentStack tail |
head = stack.head() and
tail = stack.tail() and
result = getComponentStack(tail) + "." + getComponent(head)
)
or
exists(SummaryComponent c |
stack = TSingletonSummaryComponentStack(c) and
result = getComponent(c)
)
}
/**
* A class that exists for QL technical reasons only (the IPA type used
* to represent component stacks needs to be bounded).
@@ -512,6 +477,9 @@ module Private {
or
// Add the post-update node corresponding to the requested argument node
outputState(c, s) and isCallbackParameter(s)
or
// Add the parameter node for parameter side-effects
outputState(c, s) and s = SummaryComponentStack::argument(_)
}
private newtype TSummaryNodeState =
@@ -537,7 +505,7 @@ module Private {
* this state represents that the components in `s` _remain to be written_ to
* the output.
*/
class SummaryNodeState extends TSummaryNodeState {
private class SummaryNodeState extends TSummaryNodeState {
/** Holds if this state is a valid input state for `c`. */
pragma[nomagic]
predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
@@ -566,6 +534,42 @@ module Private {
}
}
private newtype TSummaryNode =
TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) {
summaryNodeRange(c, state)
} or
TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) {
summaryParameterNodeRange(c, pos)
}
abstract class SummaryNode extends TSummaryNode {
abstract string toString();
abstract SummarizedCallable getSummarizedCallable();
}
private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode {
private SummarizedCallable c;
private SummaryNodeState state;
SummaryInternalNode() { this = TSummaryInternalNode(c, state) }
override string toString() { result = "[summary] " + state + " in " + c }
override SummarizedCallable getSummarizedCallable() { result = c }
}
private class SummaryParamNode extends SummaryNode, TSummaryParameterNode {
private SummarizedCallable c;
private ParameterPosition pos;
SummaryParamNode() { this = TSummaryParameterNode(c, pos) }
override string toString() { result = "[summary param] " + pos + " in " + c }
override SummarizedCallable getSummarizedCallable() { result = c }
}
/**
* Holds if `state` represents having read from a parameter at position
* `pos` in `c`. In this case we are not synthesizing a data-flow node,
@@ -581,7 +585,7 @@ module Private {
* Holds if a synthesized summary node is needed for the state `state` in summarized
* callable `c`.
*/
predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
private predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
state.isInputState(c, _) and
not parameterReadState(c, state, _)
or
@@ -589,22 +593,22 @@ module Private {
}
pragma[noinline]
private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
private SummaryNode summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
exists(SummaryNodeState state | state.isInputState(c, s) |
result = summaryNode(c, state)
result = TSummaryInternalNode(c, state)
or
exists(ParameterPosition pos |
parameterReadState(c, state, pos) and
result.(ParamNode).isParameterOf(inject(c), pos)
result = TSummaryParameterNode(c, pos)
)
)
}
pragma[noinline]
private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
private SummaryNode summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
exists(SummaryNodeState state |
state.isOutputState(c, s) and
result = summaryNode(c, state)
result = TSummaryInternalNode(c, state)
)
}
@@ -612,12 +616,14 @@ module Private {
* Holds if a write targets `post`, which is a post-update node for a
* parameter at position `pos` in `c`.
*/
private predicate isParameterPostUpdate(Node post, SummarizedCallable c, ParameterPosition pos) {
private predicate isParameterPostUpdate(
SummaryNode post, SummarizedCallable c, ParameterPosition pos
) {
post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos))
}
/** Holds if a parameter node at position `pos` is required for `c`. */
predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) {
private predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) {
parameterReadState(c, _, pos)
or
// Same as `isParameterPostUpdate(_, c, pos)`, but can be used in a negative context
@@ -625,7 +631,7 @@ module Private {
}
private predicate callbackOutput(
SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ReturnKind rk
) {
any(SummaryNodeState state).isInputState(c, s) and
s.head() = TReturnSummaryComponent(rk) and
@@ -633,7 +639,7 @@ module Private {
}
private predicate callbackInput(
SummarizedCallable c, SummaryComponentStack s, Node receiver, ArgumentPosition pos
SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ArgumentPosition pos
) {
any(SummaryNodeState state).isOutputState(c, s) and
s.head() = TParameterSummaryComponent(pos) and
@@ -641,7 +647,7 @@ module Private {
}
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
predicate summaryCallbackRange(SummarizedCallable c, Node receiver) {
predicate summaryCallbackRange(SummarizedCallable c, SummaryNode receiver) {
callbackOutput(c, _, receiver, _)
or
callbackInput(c, _, receiver, _)
@@ -654,10 +660,10 @@ module Private {
* `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and
* `getCallbackReturnType()`.
*/
DataFlowType summaryNodeType(Node n) {
exists(Node pre |
DataFlowType summaryNodeType(SummaryNode n) {
exists(SummaryNode pre |
summaryPostUpdateNode(n, pre) and
result = getNodeType(pre)
result = summaryNodeType(pre)
)
or
exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
@@ -669,12 +675,12 @@ module Private {
)
or
head = TWithoutContentSummaryComponent(_) and
result = getNodeType(summaryNodeInputState(c, s.tail()))
result = summaryNodeType(summaryNodeInputState(c, s.tail()))
or
exists(ReturnKind rk |
head = TReturnSummaryComponent(rk) and
result =
getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
getCallbackReturnType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.tail())), rk)
)
or
@@ -682,6 +688,11 @@ module Private {
head = TSyntheticGlobalSummaryComponent(sg) and
result = getSyntheticGlobalType(sg)
)
or
exists(ParameterPosition pos |
head = TArgumentSummaryComponent(pos) and
result = getParameterType(c, pos)
)
)
or
n = summaryNodeOutputState(c, s) and
@@ -698,7 +709,7 @@ module Private {
or
exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) |
result =
getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
getCallbackParameterType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.tail())), pos)
)
or
@@ -710,9 +721,14 @@ module Private {
)
}
/** Holds if summary node `p` is a parameter with position `pos`. */
predicate summaryParameterNode(SummaryNode p, ParameterPosition pos) {
p = TSummaryParameterNode(_, pos)
}
/** Holds if summary node `out` contains output of kind `rk` from call `c`. */
predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) {
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
predicate summaryOutNode(DataFlowCall c, SummaryNode out, ReturnKind rk) {
exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver |
callbackOutput(callable, s, receiver, rk) and
out = summaryNodeInputState(callable, s) and
c = summaryDataFlowCall(receiver)
@@ -720,8 +736,8 @@ module Private {
}
/** Holds if summary node `arg` is at position `pos` in the call `c`. */
predicate summaryArgumentNode(DataFlowCall c, Node arg, ArgumentPosition pos) {
exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
predicate summaryArgumentNode(DataFlowCall c, SummaryNode arg, ArgumentPosition pos) {
exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver |
callbackInput(callable, s, receiver, pos) and
arg = summaryNodeOutputState(callable, s) and
c = summaryDataFlowCall(receiver)
@@ -729,10 +745,10 @@ module Private {
}
/** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
predicate summaryPostUpdateNode(Node post, Node pre) {
predicate summaryPostUpdateNode(SummaryNode post, SummaryNode pre) {
exists(SummarizedCallable c, ParameterPosition pos |
isParameterPostUpdate(post, c, pos) and
pre.(ParamNode).isParameterOf(inject(c), pos)
pre = TSummaryParameterNode(c, pos)
)
or
exists(SummarizedCallable callable, SummaryComponentStack s |
@@ -743,7 +759,7 @@ module Private {
}
/** Holds if summary node `ret` is a return node of kind `rk`. */
predicate summaryReturnNode(Node ret, ReturnKind rk) {
predicate summaryReturnNode(SummaryNode ret, ReturnKind rk) {
exists(SummaryComponentStack s |
ret = summaryNodeOutputState(_, s) and
s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk))
@@ -755,7 +771,9 @@ module Private {
* node, and back out to `p`.
*/
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(inject(c), ppos) |
exists(SummarizedCallable c, ParameterPosition ppos |
p.isParameterOf(inject(c), pragma[only_bind_into](ppos))
|
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
summary(c, inputContents, outputContents, _) and
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and
@@ -770,7 +788,7 @@ module Private {
* Holds if there is a local step from `pred` to `succ`, which is synthesized
* from a flow summary.
*/
predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) {
predicate summaryLocalStep(SummaryNode pred, SummaryNode succ, boolean preservesValue) {
exists(
SummarizedCallable c, SummaryComponentStack inputContents,
SummaryComponentStack outputContents
@@ -796,7 +814,7 @@ module Private {
* Holds if there is a read step of content `c` from `pred` to `succ`, which
* is synthesized from a flow summary.
*/
predicate summaryReadStep(Node pred, ContentSet c, Node succ) {
predicate summaryReadStep(SummaryNode pred, ContentSet c, SummaryNode succ) {
exists(SummarizedCallable sc, SummaryComponentStack s |
pred = summaryNodeInputState(sc, s.tail()) and
succ = summaryNodeInputState(sc, s) and
@@ -808,7 +826,7 @@ module Private {
* Holds if there is a store step of content `c` from `pred` to `succ`, which
* is synthesized from a flow summary.
*/
predicate summaryStoreStep(Node pred, ContentSet c, Node succ) {
predicate summaryStoreStep(SummaryNode pred, ContentSet c, SummaryNode succ) {
exists(SummarizedCallable sc, SummaryComponentStack s |
pred = summaryNodeOutputState(sc, s) and
succ = summaryNodeOutputState(sc, s.tail()) and
@@ -820,7 +838,7 @@ module Private {
* Holds if there is a jump step from `pred` to `succ`, which is synthesized
* from a flow summary.
*/
predicate summaryJumpStep(Node pred, Node succ) {
predicate summaryJumpStep(SummaryNode pred, SummaryNode succ) {
exists(SummaryComponentStack s |
s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and
pred = summaryNodeOutputState(_, s) and
@@ -847,9 +865,9 @@ module Private {
* `a` on line 2 to the post-update node for `a` on that line (via an intermediate
* node where field `b` is cleared).
*/
predicate summaryClearsContent(Node n, ContentSet c) {
predicate summaryClearsContent(SummaryNode n, ContentSet c) {
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
n = summaryNode(sc, state) and
n = TSummaryInternalNode(sc, state) and
state.isInputState(sc, stack) and
stack.head() = SummaryComponent::withoutContent(c)
)
@@ -859,9 +877,9 @@ module Private {
* Holds if the value that is being tracked is expected to be stored inside
* content `c` at `n`.
*/
predicate summaryExpectsContent(Node n, ContentSet c) {
predicate summaryExpectsContent(SummaryNode n, ContentSet c) {
exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack |
n = summaryNode(sc, state) and
n = TSummaryInternalNode(sc, state) and
state.isInputState(sc, stack) and
stack.head() = SummaryComponent::withContent(c)
)
@@ -869,17 +887,17 @@ module Private {
pragma[noinline]
private predicate viableParam(
DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, ParamNode p
DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, SummaryParamNode p
) {
exists(DataFlowCallable c |
c = inject(sc) and
p.isParameterOf(c, ppos) and
p = TSummaryParameterNode(sc, ppos) and
c = viableCallable(call)
)
}
pragma[nomagic]
private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg, SummarizedCallable sc) {
private SummaryParamNode summaryArgParam(DataFlowCall call, ArgNode arg, SummarizedCallable sc) {
exists(ParameterPosition ppos |
argumentPositionMatch(call, arg, ppos) and
viableParam(call, sc, ppos, result)
@@ -891,12 +909,12 @@ module Private {
* local steps. `clearsOrExpects` records whether any node on the path from `p` to
* `n` either clears or expects contents.
*/
private predicate paramReachesLocal(ParamNode p, Node n, boolean clearsOrExpects) {
private predicate paramReachesLocal(SummaryParamNode p, SummaryNode n, boolean clearsOrExpects) {
viableParam(_, _, _, p) and
n = p and
clearsOrExpects = false
or
exists(Node mid, boolean clearsOrExpectsMid |
exists(SummaryNode mid, boolean clearsOrExpectsMid |
paramReachesLocal(p, mid, clearsOrExpectsMid) and
summaryLocalStep(mid, n, true) and
if
@@ -916,21 +934,33 @@ module Private {
*/
pragma[nomagic]
predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) {
exists(ParamNode p, ParameterPosition ppos, Node ret |
exists(SummaryParamNode p, ParameterPosition ppos, SummaryNode ret |
paramReachesLocal(p, ret, true) and
p = summaryArgParam0(_, arg, sc) and
p.isParameterOf(_, pragma[only_bind_into](ppos)) and
p = summaryArgParam(_, arg, sc) and
p = TSummaryParameterNode(_, pragma[only_bind_into](ppos)) and
isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos))
)
}
pragma[nomagic]
private predicate summaryReturnNodeExt(SummaryNode ret, ReturnKindExt rk) {
summaryReturnNode(ret, rk.(ValueReturnKind).getKind())
or
exists(SummaryParamNode p, SummaryNode pre, ParameterPosition pos |
paramReachesLocal(p, pre, _) and
summaryPostUpdateNode(ret, pre) and
p = TSummaryParameterNode(_, pos) and
rk.(ParamUpdateReturnKind).getPosition() = pos
)
}
bindingset[ret]
private ParamNode summaryArgParam(
ArgNode arg, ReturnNodeExt ret, OutNodeExt out, SummarizedCallable sc
private SummaryParamNode summaryArgParamRetOut(
ArgNode arg, SummaryNode ret, OutNodeExt out, SummarizedCallable sc
) {
exists(DataFlowCall call, ReturnKindExt rk |
result = summaryArgParam0(call, arg, sc) and
ret.getKind() = pragma[only_bind_into](rk) and
result = summaryArgParam(call, arg, sc) and
summaryReturnNodeExt(ret, pragma[only_bind_into](rk)) and
out = pragma[only_bind_into](rk).getAnOutNode(call)
)
}
@@ -943,9 +973,9 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) {
exists(ReturnKind rk, ReturnNode ret, DataFlowCall call |
summaryLocalStep(summaryArgParam0(call, arg, sc), ret, true) and
ret.getKind() = pragma[only_bind_into](rk) and
exists(ReturnKind rk, SummaryNode ret, DataFlowCall call |
summaryLocalStep(summaryArgParam(call, arg, sc), ret, true) and
summaryReturnNode(ret, pragma[only_bind_into](rk)) and
out = getAnOutNode(call, pragma[only_bind_into](rk))
)
}
@@ -958,7 +988,9 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) {
exists(ReturnNodeExt ret | summaryLocalStep(summaryArgParam(arg, ret, out, sc), ret, false))
exists(SummaryNode ret |
summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), ret, false)
)
}
/**
@@ -969,8 +1001,8 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
exists(Node mid, ReturnNodeExt ret |
summaryReadStep(summaryArgParam(arg, ret, out, sc), c, mid) and
exists(SummaryNode mid, SummaryNode ret |
summaryReadStep(summaryArgParamRetOut(arg, ret, out, sc), c, mid) and
summaryLocalStep(mid, ret, _)
)
}
@@ -983,8 +1015,8 @@ module Private {
* be useful to include in the exposed local data-flow/taint-tracking relations.
*/
predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) {
exists(Node mid, ReturnNodeExt ret |
summaryLocalStep(summaryArgParam(arg, ret, out, sc), mid, _) and
exists(SummaryNode mid, SummaryNode ret |
summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), mid, _) and
summaryStoreStep(mid, c, ret)
)
}
@@ -1317,8 +1349,8 @@ module Private {
c.relevantSummary(input, output, preservesValue) and
csv =
c.getCallableCsv() // Callable information
+ getComponentStack(input) + ";" // input
+ getComponentStack(output) + ";" // output
+ input.getMadRepresentation() + ";" // input
+ output.getMadRepresentation() + ";" // output
+ renderKind(preservesValue) + ";" // kind
+ renderProvenance(c) // provenance
)
@@ -1351,11 +1383,11 @@ module Private {
}
private newtype TNodeOrCall =
MkNode(Node n) {
MkNode(SummaryNode n) {
exists(RelevantSummarizedCallable c |
n = summaryNode(c, _)
n = TSummaryInternalNode(c, _)
or
n.(ParamNode).isParameterOf(inject(c), _)
n = TSummaryParameterNode(c, _)
)
} or
MkCall(DataFlowCall call) {
@@ -1364,7 +1396,7 @@ module Private {
}
private class NodeOrCall extends TNodeOrCall {
Node asNode() { this = MkNode(result) }
SummaryNode asNode() { this = MkNode(result) }
DataFlowCall asCall() { this = MkCall(result) }
@@ -1384,9 +1416,11 @@ module Private {
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.asNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
or
this.asCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
filepath = "" and
startline = 0 and
startcolumn = 0 and
endline = 0 and
endcolumn = 0
}
}

View File

@@ -18,15 +18,15 @@ DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
/** Gets the parameter position representing a callback itself, if any. */
ArgumentPosition callbackSelfParameterPosition() { none() } // disables implicit summary flow to `self` for callbacks
/** Gets the synthesized summary data-flow node for the given values. */
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = TSummaryNode(c, state) }
/** Gets the synthesized data-flow call for `receiver`. */
SummaryCall summaryDataFlowCall(Node receiver) { receiver = result.getReceiver() }
SummaryCall summaryDataFlowCall(SummaryNode receiver) { receiver = result.getReceiver() }
/** Gets the type of content `c`. */
DataFlowType getContentType(ContentSet c) { any() }
/** Gets the type of the parameter at the given position. */
DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) { any() }
/** Gets the return type of kind `rk` for callable `c`. */
bindingset[c, rk]
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() }
@@ -139,8 +139,53 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
)
}
/** Gets the textual representation of a summary component in the format used for flow summaries. */
string getComponentSpecific(SummaryComponent sc) { none() }
private string getContentSpecific(Content c) {
exists(string name | c = TFieldContent(name) and result = "Field[" + name + "]")
or
exists(ConstantValue cv |
c = TKnownElementContent(cv) and result = "Element[" + cv.serialize() + "!]"
)
or
c = TUnknownElementContent() and result = "Element[?]"
}
private string getContentSetSpecific(ContentSet cs) {
exists(Content c | cs = TSingletonContent(c) and result = getContentSpecific(c))
or
cs = TAnyElementContent() and result = "Element[any]"
or
exists(Content::KnownElementContent kec |
cs = TKnownOrUnknownElementContent(kec) and
result = "Element[" + kec.getIndex().serialize() + "]"
)
or
exists(int lower, boolean includeUnknown, string unknown |
cs = TElementLowerBoundContent(lower, includeUnknown) and
(if includeUnknown = true then unknown = "" else unknown = "!") and
result = "Element[" + lower + ".." + unknown + "]"
)
}
/** Gets the textual representation of a summary component in the format used for MaD models. */
string getMadRepresentationSpecific(SummaryComponent sc) {
exists(ContentSet cs | sc = TContentSummaryComponent(cs) and result = getContentSetSpecific(cs))
or
exists(ContentSet cs |
sc = TWithoutContentSummaryComponent(cs) and
result = "WithoutElement[" + getContentSetSpecific(cs) + "]"
)
or
exists(ContentSet cs |
sc = TWithContentSummaryComponent(cs) and
result = "WithElement[" + getContentSetSpecific(cs) + "]"
)
or
exists(ReturnKind rk |
sc = TReturnSummaryComponent(rk) and
not rk = getReturnValueKind() and
result = "ReturnValue[" + rk + "]"
)
}
/** Gets the textual representation of a parameter position in the format used for flow summaries. */
string getParameterPosition(ParameterPosition pos) {
@@ -170,6 +215,9 @@ string getParameterPosition(ParameterPosition pos) {
or
pos.isAnyNamed() and
result = "any-named"
or
pos.isHashSplat() and
result = "hash-splat"
}
/** Gets the textual representation of an argument position in the format used for flow summaries. */

View File

@@ -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()
)
}

View File

@@ -96,7 +96,8 @@ private module Cached {
)
)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
nodeTo.(FlowSummaryNode).getSummaryNode(), false)
or
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo)
or
@@ -112,6 +113,13 @@ private module Cached {
)
}
cached
predicate summaryThroughStepTaint(
DataFlow::Node arg, DataFlow::Node out, FlowSummaryImpl::Public::SummarizedCallable sc
) {
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(arg, out, sc)
}
/**
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
* (intra-procedural) step.
@@ -122,7 +130,7 @@ private module Cached {
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(nodeFrom, nodeTo, _)
summaryThroughStepTaint(nodeFrom, nodeTo, _)
}
}

View File

@@ -0,0 +1,29 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* "Unicode transformation"
* vulnerabilities, as well as extension points for adding your own.
*/
private import ruby
/**
* Provides default sources, sinks and sanitizers for detecting
* "Unicode transformation"
* vulnerabilities, as well as extension points for adding your own.
*/
module UnicodeBypassValidation {
/**
* A data flow source for "Unicode transformation" vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for "Unicode transformation" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for "Unicode transformation" vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
}

View File

@@ -0,0 +1,123 @@
/**
* Provides a taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
*/
private import ruby
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.Concepts
private import codeql.ruby.TaintTracking
private import codeql.ruby.ApiGraphs
import UnicodeBypassValidationCustomizations::UnicodeBypassValidation
/** A state signifying that a logical validation has not been performed. */
class PreValidation extends DataFlow::FlowState {
PreValidation() { this = "PreValidation" }
}
/** A state signifying that a logical validation has been performed. */
class PostValidation extends DataFlow::FlowState {
PostValidation() { this = "PostValidation" }
}
/**
* A taint-tracking configuration for detecting "Unicode transformation mishandling" vulnerabilities.
*
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "UnicodeBypassValidation" }
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
source instanceof RemoteFlowSource and state instanceof PreValidation
}
override predicate isAdditionalTaintStep(
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
DataFlow::FlowState stateTo
) {
(
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
or
exists(RegexExecution re | nodeFrom = re.getString() and nodeTo = re)
or
// String Manipulation Method Calls
// https://ruby-doc.org/core-2.7.0/String.html
exists(DataFlow::CallNode cn |
cn.getMethodName() =
[
[
"ljust", "lstrip", "succ", "next", "rjust", "capitalize", "chomp", "gsub", "chop",
"downcase", "swapcase", "uprcase", "scrub", "slice", "squeeze", "strip", "sub",
"tr", "tr_s", "reverse"
] + ["", "!"], "concat", "dump", "each_line", "replace", "insert", "inspect", "lines",
"partition", "prepend", "replace", "rpartition", "scan", "split", "undump",
"unpack" + ["", "1"]
] and
nodeFrom = cn.getReceiver() and
nodeTo = cn
)
or
exists(DataFlow::CallNode cn |
cn.getMethodName() =
[
"casecmp" + ["", "?"], "center", "count", "each_char", "index", "rindex", "sum",
["delete", "delete_prefix", "delete_suffix"] + ["", "!"],
["start_with", "end_with" + "eql", "include"] + ["?", "!"], "match" + ["", "?"],
] and
nodeFrom = cn.getReceiver() and
nodeTo = nodeFrom
)
or
exists(DataFlow::CallNode cn |
cn = API::getTopLevelMember("CGI").getAMethodCall("escapeHTML") and
nodeFrom = cn.getArgument(0) and
nodeTo = cn
)
) and
stateFrom instanceof PreValidation and
stateTo instanceof PostValidation
}
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
(
exists(DataFlow::CallNode cn |
cn.getMethodName() = "unicode_normalize" and
cn.getArgument(0).getConstantValue().getSymbol() = ["nfkc", "nfc", "nfkd", "nfd"] and
sink = cn.getReceiver()
)
or
// unicode_utils
exists(API::MethodAccessNode mac |
mac = API::getTopLevelMember("UnicodeUtils").getMethod(["nfkd", "nfc", "nfd", "nfkc"]) and
sink = mac.getArgument(0).asSink()
)
or
// eprun
exists(API::MethodAccessNode mac |
mac = API::getTopLevelMember("Eprun").getMethod("normalize") and
sink = mac.getArgument(0).asSink()
)
or
// unf
exists(API::MethodAccessNode mac |
mac = API::getTopLevelMember("UNF").getMember("Normalizer").getMethod("normalize") and
sink = mac.getArgument(0).asSink()
)
or
// ActiveSupport::Multibyte::Chars
exists(DataFlow::CallNode cn, DataFlow::CallNode n |
cn =
API::getTopLevelMember("ActiveSupport")
.getMember("Multibyte")
.getMember("Chars")
.getMethod("new")
.asCall() and
n = cn.getAMethodCall("normalize") and
sink = cn.getArgument(0)
)
) and
state instanceof PostValidation
}
}

View File

@@ -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()

View File

@@ -83,8 +83,8 @@ class ActionControllerClass extends DataFlow::ClassNode {
}
}
private DataFlow::LocalSourceNode actionControllerInstance() {
result = any(ActionControllerClass cls).getSelf()
private API::Node actionControllerInstance() {
result = any(ActionControllerClass cls).getSelf().track()
}
/**
@@ -204,149 +204,6 @@ private class ActionControllerParamsCall extends ParamsCallImpl {
}
}
/** Modeling for `ActionDispatch::Request`. */
private module Request {
/**
* A call to `request` from within a controller. This is an instance of
* `ActionDispatch::Request`.
*/
private class RequestNode extends DataFlow::CallNode {
RequestNode() { this = actionControllerInstance().getAMethodCall("request") }
}
/**
* A method call on `request`.
*/
private class RequestMethodCall extends DataFlow::CallNode {
RequestMethodCall() {
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
}
}
abstract private class RequestInputAccess extends RequestMethodCall,
Http::Server::RequestInputAccess::Range
{
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
}
/**
* A method call on `request` which returns request parameters.
*/
private class ParametersCall extends RequestInputAccess {
ParametersCall() {
this.getMethodName() =
[
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
"filtered_parameters"
]
}
override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}
/** A method call on `request` which returns part or all of the request path. */
private class PathCall extends RequestInputAccess {
PathCall() {
this.getMethodName() =
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
}
/** A method call on `request` which returns a specific request header. */
private class HeadersCall extends RequestInputAccess {
HeadersCall() {
this.getMethodName() =
[
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
"host_authority", "content_type", "host", "hostname", "accept_encoding",
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
]
or
// Request headers are prefixed with `HTTP_` to distinguish them from
// "headers" supplied by Rack middleware.
this.getMethodName() = ["get_header", "fetch_header"] and
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
// TODO: each_header
/**
* A method call on `request` which returns part or all of the host.
* This can be influenced by headers such as Host and X-Forwarded-Host.
*/
private class HostCall extends RequestInputAccess {
HostCall() {
this.getMethodName() =
[
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
"forwarded_host", "port", "forwarded_port"
]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/**
* A method call on `request` which is influenced by one or more request
* headers.
*/
private class HeaderTaintedCall extends RequestInputAccess {
HeaderTaintedCall() {
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/** A method call on `request` which returns the request body. */
private class BodyCall extends RequestInputAccess {
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
}
private module Env {
abstract private class Env extends DataFlow::LocalSourceNode { }
/**
* A method call on `request` which returns the rack env.
* This is a hash containing all the information about the request. Values
* under keys starting with `HTTP_` are user-controlled.
*/
private class RequestEnvCall extends DataFlow::CallNode, Env {
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
}
private import codeql.ruby.frameworks.Rack
private class RackEnv extends Env {
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
}
/**
* A read of a user-controlled parameter from the request env.
*/
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
EnvHttpAccess() {
this = any(Env c).getAMethodCall("[]") and
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
)
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
}
}
}
/** A call to `render` from within a controller. */
private class ActionControllerRenderCall extends RenderCallImpl {
ActionControllerRenderCall() {
@@ -365,19 +222,19 @@ private class ActionControllerRenderToCall extends RenderToCallImpl {
}
}
pragma[nomagic]
private DataFlow::CallNode renderCall() {
// ActionController#render is an alias for ActionController::Renderer#render
result =
[
any(ActionControllerClass c).trackModule().getAMethodCall("render"),
any(ActionControllerClass c).trackModule().getReturn("renderer").getAMethodCall("render")
]
}
/** A call to `ActionController::Renderer#render`. */
private class RendererRenderCall extends RenderCallImpl {
RendererRenderCall() {
this =
[
// ActionController#render is an alias for ActionController::Renderer#render
any(ActionControllerClass c).getAnImmediateReference().getAMethodCall("render"),
any(ActionControllerClass c)
.getAnImmediateReference()
.getAMethodCall("renderer")
.getAMethodCall("render")
].asExpr().getExpr()
}
RendererRenderCall() { this = renderCall().asExpr().getExpr() }
}
/** A call to `html_escape` from within a controller. */
@@ -403,6 +260,7 @@ class RedirectToCall extends MethodCall {
this =
controller
.getSelf()
.track()
.getAMethodCall(["redirect_to", "redirect_back", "redirect_back_or_to"])
.asExpr()
.getExpr()
@@ -743,9 +601,7 @@ private module ParamsSummaries {
* response.
*/
private module Response {
DataFlow::LocalSourceNode response() {
result = actionControllerInstance().getAMethodCall("response")
}
API::Node response() { result = actionControllerInstance().getReturn("response") }
class BodyWrite extends DataFlow::CallNode, Http::Server::HttpResponse::Range {
BodyWrite() { this = response().getAMethodCall("body=") }
@@ -771,7 +627,7 @@ private module Response {
HeaderWrite() {
// response.header[key] = val
// response.headers[key] = val
this = response().getAMethodCall(["header", "headers"]).getAMethodCall("[]=")
this = response().getReturn(["header", "headers"]).getAMethodCall("[]=")
or
// response.set_header(key) = val
// response[header] = val
@@ -816,18 +672,12 @@ private module Response {
}
}
private class ActionControllerLoggerInstance extends DataFlow::Node {
ActionControllerLoggerInstance() {
this = actionControllerInstance().getAMethodCall("logger")
or
any(ActionControllerLoggerInstance i).(DataFlow::LocalSourceNode).flowsTo(this)
}
}
private class ActionControllerLoggingCall extends DataFlow::CallNode, Logging::Range {
ActionControllerLoggingCall() {
this.getReceiver() instanceof ActionControllerLoggerInstance and
this.getMethodName() = ["debug", "error", "fatal", "info", "unknown", "warn"]
this =
actionControllerInstance()
.getReturn("logger")
.getAMethodCall(["debug", "error", "fatal", "info", "unknown", "warn"])
}
// Note: this is identical to the definition `stdlib.Logger.LoggerInfoStyleCall`.

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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() }
}
}

View File

@@ -7,7 +7,6 @@ private import codeql.ruby.Concepts
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.dataflow.internal.DataFlowPrivate
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.Stdlib
private import codeql.ruby.frameworks.Core
@@ -31,10 +30,8 @@ private predicate isBuiltInMethodForActiveRecordModelInstance(string methodName)
methodName = objectInstanceMethodName()
}
private API::Node activeRecordClassApiNode() {
private API::Node activeRecordBaseClass() {
result =
// class Foo < ActiveRecord::Base
// class Bar < Foo
[
API::getTopLevelMember("ActiveRecord").getMember("Base"),
// In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we
@@ -43,6 +40,46 @@ private API::Node activeRecordClassApiNode() {
]
}
/**
* Gets an object with methods from the ActiveRecord query interface.
*/
private API::Node activeRecordQueryBuilder() {
result = activeRecordBaseClass()
or
result = activeRecordBaseClass().getInstance()
or
// Assume any method call might return an ActiveRecord::Relation
// These are dynamically generated
result = activeRecordQueryBuilderMethodAccess(_).getReturn()
}
/** Gets a call targeting the ActiveRecord query interface. */
private API::MethodAccessNode activeRecordQueryBuilderMethodAccess(string name) {
result = activeRecordQueryBuilder().getMethod(name) and
// Due to the heuristic tracking of query builder objects, add a restriction for methods with a known call target
not isUnlikelyExternalCall(result)
}
/** Gets a call targeting the ActiveRecord query interface. */
private DataFlow::CallNode activeRecordQueryBuilderCall(string name) {
result = activeRecordQueryBuilderMethodAccess(name).asCall()
}
/**
* Holds if `call` is unlikely to call into an external library, since it has a possible
* call target in its enclosing module.
*/
private predicate isUnlikelyExternalCall(API::MethodAccessNode node) {
exists(DataFlow::ModuleNode mod, DataFlow::CallNode call | call = node.asCall() |
call.getATarget() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()] and
call.getEnclosingMethod() = [mod.getAnOwnSingletonMethod(), mod.getAnOwnInstanceMethod()]
)
}
private API::Node activeRecordConnectionInstance() {
result = activeRecordBaseClass().getReturn("connection")
}
/**
* A `ClassDeclaration` for a class that inherits from `ActiveRecord::Base`. For example,
*
@@ -56,20 +93,19 @@ private API::Node activeRecordClassApiNode() {
* ```
*/
class ActiveRecordModelClass extends ClassDeclaration {
private DataFlow::ClassNode cls;
ActiveRecordModelClass() {
this.getSuperclassExpr() =
activeRecordClassApiNode().getASubclass().getAValueReachableFromSource().asExpr().getExpr()
cls = activeRecordBaseClass().getADescendentModule() and this = cls.getADeclaration()
}
// Gets the class declaration for this class and all of its super classes
private ModuleBase getAllClassDeclarations() {
result = this.getModule().getSuperClass*().getADeclaration()
}
private ModuleBase getAllClassDeclarations() { result = cls.getAnAncestor().getADeclaration() }
/**
* Gets methods defined in this class that may access a field from the database.
*/
Method getAPotentialFieldAccessMethod() {
deprecated Method getAPotentialFieldAccessMethod() {
// It's a method on this class or one of its super classes
result = this.getAllClassDeclarations().getAMethod() and
// There is a value that can be returned by this method which may include field data
@@ -91,58 +127,84 @@ class ActiveRecordModelClass extends ClassDeclaration {
)
)
}
/** Gets the class as a `DataFlow::ClassNode`. */
DataFlow::ClassNode getClassNode() { result = cls }
}
/** A class method call whose receiver is an `ActiveRecordModelClass`. */
class ActiveRecordModelClassMethodCall extends MethodCall {
private ActiveRecordModelClass recvCls;
/**
* Gets a potential reference to an ActiveRecord class object.
*/
deprecated private API::Node getAnActiveRecordModelClassRef() {
result = any(ActiveRecordModelClass cls).getClassNode().trackModule()
or
// For methods with an unknown call target, assume this might be a database field, thus returning another ActiveRecord object.
// In this case we do not know which class it belongs to, which is why this predicate can't associate the reference with a specific class.
result = getAnUnknownActiveRecordModelClassCall().getReturn()
}
/**
* Gets a call performed on an ActiveRecord class object, without a known call target in the codebase.
*/
deprecated private API::MethodAccessNode getAnUnknownActiveRecordModelClassCall() {
result = getAnActiveRecordModelClassRef().getMethod(_) and
result.asCall().asExpr().getExpr() instanceof UnknownMethodCall
}
/**
* DEPRECATED. Use `ActiveRecordModelClass.getClassNode().trackModule().getMethod()` instead.
*
* A class method call whose receiver is an `ActiveRecordModelClass`.
*/
deprecated class ActiveRecordModelClassMethodCall extends MethodCall {
ActiveRecordModelClassMethodCall() {
// e.g. Foo.where(...)
recvCls.getModule() = this.getReceiver().(ConstantReadAccess).getModule()
or
// e.g. Foo.joins(:bars).where(...)
recvCls = this.getReceiver().(ActiveRecordModelClassMethodCall).getReceiverClass()
or
// e.g. self.where(...) within an ActiveRecordModelClass
this.getReceiver() instanceof SelfVariableAccess and
this.getEnclosingModule() = recvCls
this = getAnUnknownActiveRecordModelClassCall().asCall().asExpr().getExpr()
}
/** The `ActiveRecordModelClass` of the receiver of this method. */
ActiveRecordModelClass getReceiverClass() { result = recvCls }
/** Gets the `ActiveRecordModelClass` of the receiver of this method, if it can be determined. */
ActiveRecordModelClass getReceiverClass() {
this = result.getClassNode().trackModule().getMethod(_).asCall().asExpr().getExpr()
}
}
private Expr sqlFragmentArgument(MethodCall call) {
exists(string methodName |
methodName = call.getMethodName() and
(
methodName =
[
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
"group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where",
"rewhere", "select", "reselect", "update_all"
] and
result = call.getArgument(0)
or
methodName = "calculate" and result = call.getArgument(1)
or
methodName in ["average", "count", "maximum", "minimum", "sum", "count_by_sql"] and
result = call.getArgument(0)
or
// This format was supported until Rails 2.3.8
methodName = ["all", "find", "first", "last"] and
result = call.getKeywordArgument("conditions")
or
methodName = "reload" and
result = call.getKeywordArgument("lock")
or
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
// SQLi if user supplied input is passed in as an argument.
methodName = "annotate" and
result = call.getArgument(_)
)
private predicate sqlFragmentArgumentInner(DataFlow::CallNode call, DataFlow::Node sink) {
call =
activeRecordQueryBuilderCall([
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
"group", "having", "joins", "lock", "not", "order", "reorder", "pluck", "where", "rewhere",
"select", "reselect", "update_all"
]) and
sink = call.getArgument(0)
or
call = activeRecordQueryBuilderCall("calculate") and
sink = call.getArgument(1)
or
call =
activeRecordQueryBuilderCall(["average", "count", "maximum", "minimum", "sum", "count_by_sql"]) and
sink = call.getArgument(0)
or
// This format was supported until Rails 2.3.8
call = activeRecordQueryBuilderCall(["all", "find", "first", "last"]) and
sink = call.getKeywordArgument("conditions")
or
call = activeRecordQueryBuilderCall("reload") and
sink = call.getKeywordArgument("lock")
or
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
// SQLi if user supplied input is passed in as an argument.
call = activeRecordQueryBuilderCall("annotate") and
sink = call.getArgument(_)
or
call = activeRecordConnectionInstance().getAMethodCall("execute") and
sink = call.getArgument(0)
}
private predicate sqlFragmentArgument(DataFlow::CallNode call, DataFlow::Node sink) {
exists(DataFlow::Node arg |
sqlFragmentArgumentInner(call, arg) and
sink = [arg, arg.(DataFlow::ArrayLiteralNode).getElement(0)] and
unsafeSqlExpr(sink.asExpr().getExpr())
)
}
@@ -163,6 +225,8 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
}
/**
* DEPRECATED. Use the `SqlExecution` concept or `ActiveRecordSqlExecutionRange`.
*
* A method call that may result in executing unintended user-controlled SQL
* queries if the `getSqlFragmentSinkArgument()` expression is tainted by
* unsanitized user-controlled input. For example, supposing that `User` is an
@@ -176,55 +240,32 @@ private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
* as `"') OR 1=1 --"` could result in the application looking up all users
* rather than just one with a matching name.
*/
class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
// The SQL fragment argument itself
private Expr sqlFragmentExpr;
deprecated class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
private DataFlow::CallNode call;
PotentiallyUnsafeSqlExecutingMethodCall() {
exists(Expr arg |
arg = sqlFragmentArgument(this) and
unsafeSqlExpr(sqlFragmentExpr) and
(
sqlFragmentExpr = arg
or
sqlFragmentExpr = arg.(ArrayLiteral).getElement(0)
) and
// Check that method has not been overridden
not exists(SingletonMethod m |
m.getName() = this.getMethodName() and
m.getOuterScope() = this.getReceiverClass()
)
)
call.asExpr().getExpr() = this and sqlFragmentArgument(call, _)
}
/**
* Gets the SQL fragment argument of this method call.
*/
Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr }
Expr getSqlFragmentSinkArgument() {
exists(DataFlow::Node sink |
sqlFragmentArgument(call, sink) and result = sink.asExpr().getExpr()
)
}
}
/**
* An `SqlExecution::Range` for an argument to a
* `PotentiallyUnsafeSqlExecutingMethodCall` that may be vulnerable to being
* controlled by user input.
* A SQL execution arising from a call to the ActiveRecord library.
*/
class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
ActiveRecordSqlExecutionRange() {
exists(PotentiallyUnsafeSqlExecutingMethodCall mc |
this.asExpr().getNode() = mc.getSqlFragmentSinkArgument()
)
or
this = activeRecordConnectionInstance().getAMethodCall("execute").getArgument(0) and
unsafeSqlExpr(this.asExpr().getExpr())
}
ActiveRecordSqlExecutionRange() { sqlFragmentArgument(_, this) }
override DataFlow::Node getSql() { result = this }
}
private API::Node activeRecordConnectionInstance() {
result = activeRecordClassApiNode().getReturn("connection")
}
// TODO: model `ActiveRecord` sanitizers
// https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html
/**
@@ -242,15 +283,8 @@ abstract class ActiveRecordModelInstantiation extends OrmInstantiation::Range,
override predicate methodCallMayAccessField(string methodName) {
// The method is not a built-in, and...
not isBuiltInMethodForActiveRecordModelInstance(methodName) and
(
// ...There is no matching method definition in the class, or...
not exists(this.getClass().getMethod(methodName))
or
// ...the called method can access a field.
exists(Method m | m = this.getClass().getAPotentialFieldAccessMethod() |
m.getName() = methodName
)
)
// ...There is no matching method definition in the class
not exists(this.getClass().getMethod(methodName))
}
}
@@ -318,21 +352,10 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation
}
// A `self` reference that may resolve to an active record model object
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation,
SsaSelfDefinitionNode
{
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation {
private ActiveRecordModelClass cls;
ActiveRecordModelClassSelfReference() {
exists(MethodBase m |
m = this.getCfgScope() and
m.getEnclosingModule() = cls and
m = cls.getAMethod()
) and
// In a singleton method, `self` refers to the class itself rather than an
// instance of that class
not this.getSelfScope() instanceof SingletonMethod
}
ActiveRecordModelClassSelfReference() { this = cls.getClassNode().getAnOwnInstanceSelf() }
final override ActiveRecordModelClass getClass() { result = cls }
}
@@ -343,7 +366,7 @@ private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInsta
class ActiveRecordInstance extends DataFlow::Node {
private ActiveRecordModelInstantiation instantiation;
ActiveRecordInstance() { this = instantiation or instantiation.flowsTo(this) }
ActiveRecordInstance() { this = instantiation.track().getAValueReachableFromSource() }
/** Gets the `ActiveRecordModelClass` that this is an instance of. */
ActiveRecordModelClass getClass() { result = instantiation.getClass() }
@@ -381,12 +404,12 @@ private module Persistence {
/** A call to e.g. `User.create(name: "foo")` */
private class CreateLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
CreateLikeCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() =
[
"create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by",
"find_or_create_by!", "insert", "insert!"
]
this =
activeRecordBaseClass()
.getAMethodCall([
"create", "create!", "create_or_find_by", "create_or_find_by!", "find_or_create_by",
"find_or_create_by!", "insert", "insert!"
])
}
override DataFlow::Node getValue() {
@@ -403,8 +426,7 @@ private module Persistence {
/** A call to e.g. `User.update(1, name: "foo")` */
private class UpdateLikeClassMethodCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
UpdateLikeClassMethodCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = ["update", "update!", "upsert"]
this = activeRecordBaseClass().getAMethodCall(["update", "update!", "upsert"])
}
override DataFlow::Node getValue() {
@@ -449,10 +471,7 @@ private module Persistence {
* ```
*/
private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
TouchAllCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = "touch_all"
}
TouchAllCall() { this = activeRecordQueryBuilderCall("touch_all") }
override DataFlow::Node getValue() { result = this.getKeywordArgument("time") }
}
@@ -462,8 +481,7 @@ private module Persistence {
private ExprNodes::ArrayLiteralCfgNode arr;
InsertAllLikeCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = ["insert_all", "insert_all!", "upsert_all"] and
this = activeRecordBaseClass().getAMethodCall(["insert_all", "insert_all!", "upsert_all"]) and
arr = this.getArgument(0).asExpr()
}

View File

@@ -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
}
}

View File

@@ -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. */

View File

@@ -14,8 +14,8 @@ module NetLdap {
/**
* Flow summary for `Net::LDAP.new`. This method establishes a connection to a LDAP server.
*/
private class LdapSummary extends SummarizedCallable {
LdapSummary() { this = "Net::LDAP.new" }
private class LdapConnSummary extends SummarizedCallable {
LdapConnSummary() { this = "Net::LDAP.new" }
override MethodCall getACall() { result = any(NetLdapConnection l).asExpr().getExpr() }
@@ -24,11 +24,25 @@ module NetLdap {
}
}
/**
* Flow summary for `Net::LDAP.Filter`.
*/
private class LdapFilterSummary extends SummarizedCallable {
LdapFilterSummary() { this = "Net::LDAP::Filter" }
override MethodCall getACall() { result = any(NetLdapFilter l).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = ["Argument[0]", "Argument[1]"] and output = "ReturnValue" and preservesValue = false
}
}
/** Net LDAP Api Node */
private API::Node ldap() { result = API::getTopLevelMember("Net").getMember("LDAP") }
/** A call that establishes a LDAP Connection */
private class NetLdapConnection extends DataFlow::CallNode {
NetLdapConnection() { this in [ldap().getAnInstantiation(), ldap().getAMethodCall(["open"])] }
predicate usesSsl() {
@@ -41,11 +55,21 @@ module NetLdap {
DataFlow::Node getAuthValue(string arg) {
result = this.getKeywordArgument("auth").(DataFlow::CallNode).getKeywordArgument(arg)
}
}
/** A call that constructs a LDAP query */
private class NetLdapFilter extends LdapConstruction::Range, DataFlow::CallNode {
NetLdapFilter() { this = any(ldap().getMember("Filter").getAMethodCall(["eq"])) }
NetLdapFilter() {
this =
any(ldap()
.getMember("Filter")
.getAMethodCall([
"begins", "bineq", "contains", "ends", "eq", "equals", "ex", "ge", "le", "ne",
"present"
])
)
}
override DataFlow::Node getQuery() { result = this.getArgument([0, 1]) }
}

View File

@@ -0,0 +1,73 @@
/**
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
*/
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.Concepts
/**
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
*/
module Mysql2 {
/**
* Flow summary for `Mysql2::Client.new()`.
*/
private class SqlSummary extends SummarizedCallable {
SqlSummary() { this = "Mysql2::Client.new()" }
override MethodCall getACall() { result = any(Mysql2Connection c).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
/** A call to Mysql2::Client.new() is used to establish a connection to a MySql database. */
private class Mysql2Connection extends DataFlow::CallNode {
Mysql2Connection() {
this = API::getTopLevelMember("Mysql2").getMember("Client").getAnInstantiation()
}
}
/** A call that executes SQL statements against a MySQL database. */
private class Mysql2Execution extends SqlExecution::Range, DataFlow::CallNode {
private DataFlow::Node query;
Mysql2Execution() {
exists(Mysql2Connection mysql2Connection |
this = mysql2Connection.getAMethodCall("query") and query = this.getArgument(0)
or
exists(DataFlow::CallNode prepareCall |
prepareCall = mysql2Connection.getAMethodCall("prepare") and
query = prepareCall.getArgument(0) and
this = prepareCall.getAMethodCall("execute")
)
)
}
override DataFlow::Node getSql() { result = query }
}
/**
* A call to `Mysql2::Client.escape`, considered as a sanitizer for SQL statements.
*/
private class Mysql2EscapeSanitization extends SqlSanitization::Range {
Mysql2EscapeSanitization() {
this = API::getTopLevelMember("Mysql2").getMember("Client").getAMethodCall("escape")
}
}
/**
* Flow summary for `Mysql2::Client.escape()`.
*/
private class EscapeSummary extends SummarizedCallable {
EscapeSummary() { this = "Mysql2::Client.escape()" }
override MethodCall getACall() { result = any(Mysql2EscapeSanitization c).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
}

View File

@@ -0,0 +1,77 @@
/**
* Provides modeling for Pg, a Ruby library (gem) for interacting with PostgreSQL databases.
*/
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.Concepts
/**
* Provides modeling for Pg, a Ruby library (gem) for interacting with PostgreSQL databases.
*/
module Pg {
/**
* Flow summary for `PG.new()`. This method initializes a database connection.
*/
private class SqlSummary extends SummarizedCallable {
SqlSummary() { this = "PG.new()" }
override MethodCall getACall() { result = any(PgConnection c).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
/** A call to PG::Connection.open() is used to establish a connection to a PostgreSQL database. */
private class PgConnection extends DataFlow::CallNode {
PgConnection() {
this =
API::getTopLevelMember("PG")
.getMember("Connection")
.getAMethodCall(["open", "new", "connect_start"])
or
this = API::getTopLevelMember("PG").getAnInstantiation()
}
}
/** A call that prepares an SQL statement to be executed later. */
private class PgPrepareCall extends SqlConstruction::Range, DataFlow::CallNode {
private DataFlow::Node query;
private PgConnection pgConnection;
private string queryName;
PgPrepareCall() {
this = pgConnection.getAMethodCall("prepare") and
queryName = this.getArgument(0).getConstantValue().getStringlikeValue() and
query = this.getArgument(1)
}
PgConnection getConnection() { result = pgConnection }
string getQueryName() { result = queryName }
override DataFlow::Node getSql() { result = query }
}
/** A call that executes SQL statements against a PostgreSQL database. */
private class PgExecution extends SqlExecution::Range, DataFlow::CallNode {
private DataFlow::Node query;
PgExecution() {
exists(PgConnection pgConnection |
this =
pgConnection.getAMethodCall(["exec", "async_exec", "exec_params", "async_exec_params"]) and
query = this.getArgument(0)
or
exists(PgPrepareCall prepareCall |
pgConnection = prepareCall.getConnection() and
this.getArgument(0).getConstantValue().isStringlikeValue(prepareCall.getQueryName()) and
query = prepareCall.getSql()
)
)
}
override DataFlow::Node getSql() { result = query }
}
}

View File

@@ -2,47 +2,15 @@
* Provides modeling for the Rack library.
*/
private import codeql.ruby.controlflow.CfgNodes::ExprNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.typetracking.TypeTracker
/**
* Provides modeling for the Rack library.
*/
module Rack {
/**
* A class that may be a rack application.
* This is a class that has a `call` method that takes a single argument
* (traditionally called `env`) and returns a rack-compatible response.
*/
class AppCandidate extends DataFlow::ClassNode {
private DataFlow::MethodNode call;
import rack.internal.App
import rack.internal.Request
import rack.internal.Response::Public as Response
import rack.internal.Utils
AppCandidate() {
call = this.getInstanceMethod("call") and
call.getNumberOfParameters() = 1 and
call.getReturn() = trackRackResponse()
}
/**
* Gets the environment of the request, which is the lone parameter to the `call` method.
*/
DataFlow::ParameterNode getEnv() { result = call.getParameter(0) }
}
private predicate isRackResponse(DataFlow::Node r) {
// [status, headers, body]
r.asExpr().(ArrayLiteralCfgNode).getNumberOfArguments() = 3
}
private DataFlow::LocalSourceNode trackRackResponse(TypeTracker t) {
t.start() and
isRackResponse(result)
or
exists(TypeTracker t2 | result = trackRackResponse(t2).track(t2, t))
}
private DataFlow::Node trackRackResponse() {
trackRackResponse(TypeTracker::end()).flowsTo(result)
}
/** DEPRECATED: Alias for App::AppCandidate */
deprecated class AppCandidate = App::AppCandidate;
}

View File

@@ -0,0 +1,71 @@
/**
* Provides modeling for `Sequel`, the database toolkit for Ruby.
* https://github.com/jeremyevans/sequel
*/
private import ruby
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.Concepts
/**
* Provides modeling for `Sequel`, the database toolkit for Ruby.
* https://github.com/jeremyevans/sequel
*/
module Sequel {
/** Flow Summary for `Sequel`. */
private class SqlSummary extends SummarizedCallable {
SqlSummary() { this = "Sequel.connect" }
override MethodCall getACall() { result = any(SequelConnection c).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
/** A call to establish a connection to a database */
private class SequelConnection extends DataFlow::CallNode {
SequelConnection() {
this =
API::getTopLevelMember("Sequel").getAMethodCall(["connect", "sqlite", "mysql2", "jdbc"])
}
}
/** A call that constructs SQL statements */
private class SequelConstruction extends SqlConstruction::Range, DataFlow::CallNode {
DataFlow::Node query;
SequelConstruction() {
this = API::getTopLevelMember("Sequel").getAMethodCall("cast") and query = this.getArgument(1)
or
this = API::getTopLevelMember("Sequel").getAMethodCall("function") and
query = this.getArgument(0)
}
override DataFlow::Node getSql() { result = query }
}
/** A call that executes SQL statements against a database */
private class SequelExecution extends SqlExecution::Range, DataFlow::CallNode {
SequelExecution() {
exists(SequelConnection sequelConnection |
this =
sequelConnection
.getAMethodCall([
"execute", "execute_ddl", "execute_dui", "execute_insert", "run", "<<", "fetch",
"fetch_rows", "[]", "log_connection_yield"
]) or
this =
sequelConnection
.getAMethodCall("dataset")
.getAMethodCall([
"with_sql", "with_sql_all", "with_sql_delete", "with_sql_each", "with_sql_first",
"with_sql_insert", "with_sql_single_value", "with_sql_update"
])
)
}
override DataFlow::Node getSql() { result = this.getArgument(0) }
}
}

View File

@@ -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 + ")"
)
}
/**

View File

@@ -15,21 +15,20 @@ private import codeql.ruby.Concepts
* https://github.com/sparklemotion/sqlite3-ruby
*/
module Sqlite3 {
private API::Node databaseConst() {
result = API::getTopLevelMember("SQLite3").getMember("Database")
}
private API::Node dbInstance() {
result = databaseConst().getInstance()
or
// e.g. SQLite3::Database.new("foo.db") |db| { db.some_method }
result = databaseConst().getMethod("new").getBlock().getParameter(0)
}
/** Gets a method call with a receiver that is a database instance. */
private DataFlow::CallNode getADatabaseMethodCall(string methodName) {
exists(API::Node dbInstance |
dbInstance = API::getTopLevelMember("SQLite3").getMember("Database").getInstance() and
(
result = dbInstance.getAMethodCall(methodName)
or
// e.g. SQLite3::Database.new("foo.db") |db| { db.some_method }
exists(DataFlow::BlockNode block |
result.getMethodName() = methodName and
block = dbInstance.getAValueReachableFromSource().(DataFlow::CallNode).getBlock() and
block.getParameter(0).flowsTo(result.getReceiver())
)
)
)
result = dbInstance().getAMethodCall(methodName)
}
/** A prepared but unexecuted SQL statement. */
@@ -48,7 +47,7 @@ module Sqlite3 {
this.getMethodName() = ["columns", "execute", "execute!", "get_metadata", "types"]
}
override DataFlow::Node getSql() { result = stmt.getReceiver() }
override DataFlow::Node getSql() { result = stmt.getSql() }
}
/** Gets the name of a method called against a database that executes an SQL statement. */
@@ -77,4 +76,26 @@ module Sqlite3 {
override DataFlow::Node getSql() { result = this.getArgument(0) }
}
/**
* A call to `SQLite3::Database.quote`, considered as a sanitizer for SQL statements.
*/
private class SQLite3QuoteSanitization extends SqlSanitization {
SQLite3QuoteSanitization() {
this = API::getTopLevelMember("SQLite3").getMember("Database").getAMethodCall("quote")
}
}
/**
* Flow summary for `SQLite3::Database.quote()`.
*/
private class QuoteSummary extends SummarizedCallable {
QuoteSummary() { this = "SQLite3::Database.quote()" }
override MethodCall getACall() { result = any(SQLite3QuoteSanitization c).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
}

View File

@@ -1,97 +0,0 @@
/**
* This module is deprecated, and exists as a shim to support any existing code that relies on it.
* New code should use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.
*/
private import codeql.ruby.frameworks.Core as Core
private import codeql.ruby.frameworks.Stdlib as Stdlib
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class SubshellLiteralExecution = Core::SubshellLiteralExecution;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class SubshellHeredocExecution = Core::SubshellHeredocExecution;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class BasicObjectInstanceMethodCall = Core::BasicObjectInstanceMethodCall;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated predicate basicObjectInstanceMethodName = Core::basicObjectInstanceMethodName/0;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class InstanceEvalCallCodeExecution = Core::InstanceEvalCallCodeExecution;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class ObjectInstanceMethodCall = Core::ObjectInstanceMethodCall;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated predicate objectInstanceMethodName = Core::objectInstanceMethodName/0;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class KernelMethodCall = Core::KernelMethodCall;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class KernelSystemCall = Core::KernelSystemCall;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class KernelExecCall = Core::KernelExecCall;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class KernelSpawnCall = Core::KernelSpawnCall;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class EvalCallCodeExecution = Core::EvalCallCodeExecution;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated class SendCallCodeExecution = Core::SendCallCodeExecution;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated module Module = Core::Module;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated module Array = Core::Array;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated module Regexp = Core::Regexp;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated module Open3 = Stdlib::Open3;
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
*/
deprecated module Logger = Stdlib::Logger;

View File

@@ -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" }

View File

@@ -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`.
*/

View File

@@ -0,0 +1,35 @@
/**
* Provides modeling for the `YAML` and `Psych` libraries.
*/
private import codeql.ruby.dataflow.FlowSteps
private import codeql.ruby.DataFlow
private import codeql.ruby.ApiGraphs
/**
* A taint step related to the result of `YAML.parse` calls, or similar.
* In the following example, this step will propagate taint from
* `source` to `sink`:
*
* ```rb
* x = source
* result = YAML.parse(x)
* sink result.to_ruby # Unsafe call
* ```
*/
private class YamlParseStep extends AdditionalTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode yamlParserMethod |
succ = yamlParserMethod.getAMethodCall("to_ruby") and
(
yamlParserMethod = yamlNode().getAMethodCall(["parse", "parse_stream"]) and
pred = [yamlParserMethod.getArgument(0), yamlParserMethod.getKeywordArgument("yaml")]
or
yamlParserMethod = yamlNode().getAMethodCall("parse_file") and
pred = [yamlParserMethod.getArgument(0), yamlParserMethod.getKeywordArgument("filename")]
)
)
}
}
private API::Node yamlNode() { result = API::getTopLevelMember(["YAML", "Psych"]) }

View File

@@ -412,9 +412,7 @@ module Filters {
/**
* Holds if `n` is the self parameter of method `m`.
*/
private predicate selfParameter(DataFlowPrivate::SelfParameterNode n, Method m) {
m = n.getMethod()
}
private predicate selfParameter(DataFlow::SelfParameterNode n, Method m) { m = n.getCallable() }
/**
* A class defining additional jump steps arising from filters.

View File

@@ -0,0 +1,46 @@
/**
* Models MIME type handling using the `ActionDispatch` library, which is part of Rails.
*/
private import codeql.ruby.Regexp as RE
private import codeql.ruby.frameworks.data.ModelsAsData
/**
* Models MIME type handling using the `ActionDispatch` library, which is part of Rails.
*/
module Mime {
/**
* Type summaries for the `Mime::Type` class, i.e. method calls that produce new
* `Mime::Type` instances.
*/
private class MimeTypeTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// type1;type2;path
row =
[
// Mime[type] : Mime::Type (omitted)
// Method names with brackets like [] cannot be represented in MaD.
// Mime.fetch(type) : Mime::Type
"Mime::Type;Mime!;Method[fetch].ReturnValue",
// Mime::Type.lookup(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[lookup].ReturnValue",
// Mime::Type.lookup_by_extension(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[lookup_by_extension].ReturnValue",
// Mime::Type.register(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[register].ReturnValue",
// Mime::Type.register_alias(str) : Mime::Type
"Mime::Type;Mime::Type!;Method[register_alias].ReturnValue",
]
}
}
/**
* An argument to `Mime::Type#match?`, which is converted to a RegExp via
* `Regexp.new`.
*/
class MimeTypeMatchRegExpInterpretation extends RE::RegExpInterpretation::Range {
MimeTypeMatchRegExpInterpretation() {
this = ModelOutput::getATypeNode("Mime::Type").getAMethodCall(["match?", "=~"]).getArgument(0)
}
}
}

View File

@@ -0,0 +1,150 @@
/** Modeling for `ActionDispatch::Request`. */
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.ActionController
/** Modeling for `ActionDispatch::Request`. */
module Request {
/**
* A method call against an `ActionDispatch::Request` instance.
*/
private class RequestMethodCall extends DataFlow::CallNode {
RequestMethodCall() {
any(ActionControllerClass cls)
.getSelf()
.getAMethodCall("request")
.(DataFlow::LocalSourceNode)
.flowsTo(this.getReceiver()) or
this =
API::getTopLevelMember("ActionDispatch")
.getMember("Request")
.getInstance()
.getAMethodCall(_)
}
}
abstract private class RequestInputAccess extends RequestMethodCall,
Http::Server::RequestInputAccess::Range
{
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
}
/**
* A method call on `request` which returns request parameters.
*/
private class ParametersCall extends RequestInputAccess {
ParametersCall() {
this.getMethodName() =
[
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
"filtered_parameters"
]
}
override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}
/** A method call on `request` which returns part or all of the request path. */
private class PathCall extends RequestInputAccess {
PathCall() {
this.getMethodName() =
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
}
/** A method call on `request` which returns a specific request header. */
private class HeadersCall extends RequestInputAccess {
HeadersCall() {
this.getMethodName() =
[
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
"host_authority", "content_type", "host", "hostname", "accept_encoding",
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
]
or
// Request headers are prefixed with `HTTP_` to distinguish them from
// "headers" supplied by Rack middleware.
this.getMethodName() = ["get_header", "fetch_header"] and
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
// TODO: each_header
/**
* A method call on `request` which returns part or all of the host.
* This can be influenced by headers such as Host and X-Forwarded-Host.
*/
private class HostCall extends RequestInputAccess {
HostCall() {
this.getMethodName() =
[
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
"forwarded_host", "port", "forwarded_port"
]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/**
* A method call on `request` which is influenced by one or more request
* headers.
*/
private class HeaderTaintedCall extends RequestInputAccess {
HeaderTaintedCall() {
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/** A method call on `request` which returns the request body. */
private class BodyCall extends RequestInputAccess {
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
}
private module Env {
abstract private class Env extends DataFlow::LocalSourceNode { }
/**
* A method call on `request` which returns the rack env.
* This is a hash containing all the information about the request. Values
* under keys starting with `HTTP_` are user-controlled.
*/
private class RequestEnvCall extends DataFlow::CallNode, Env {
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
}
private import codeql.ruby.frameworks.Rack
private class RackEnv extends Env {
RackEnv() { this = any(Rack::App::RequestHandler handler).getEnv().getALocalUse() }
}
/**
* A read of a user-controlled parameter from the request env.
*/
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
EnvHttpAccess() {
this = any(Env c).getAMethodCall("[]") and
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
)
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
}
}
}

View File

@@ -0,0 +1,961 @@
/**
* Models routing configuration specified using the `ActionDispatch` library, which is part of Rails.
*/
private import codeql.ruby.AST
/**
* Models routing configuration specified using the `ActionDispatch` library, which is part of Rails.
*/
module Routing {
/**
* A block that defines some routes.
* Route blocks can contribute to the path or controller namespace of their child routes.
* For example, in the block below
* ```rb
* scope path: "/admin" do
* get "/dashboard", to: "admin_dashboard#show"
* end
* ```
* the route defined by the call to `get` has the full path `/admin/dashboard`.
* We track these contributions via `getPathComponent` and `getControllerComponent`.
*/
abstract private class RouteBlock extends TRouteBlock {
/**
* Gets the name of a primary CodeQL class to which this route block belongs.
*/
string getAPrimaryQlClass() { result = "RouteBlock" }
/**
* Gets a string representation of this route block.
*/
string toString() { none() }
/**
* Gets a `Stmt` within this route block.
*/
abstract Stmt getAStmt();
/**
* Gets the parent of this route block, if one exists.
*/
abstract RouteBlock getParent();
/**
* Gets the `n`th parent of this route block.
* The zeroth parent is this block, the first parent is the direct parent of this block, etc.
*/
RouteBlock getParent(int n) {
if n = 0 then result = this else result = this.getParent().getParent(n - 1)
}
/**
* Gets the component of the path defined by this block, if it exists.
*/
abstract string getPathComponent();
/**
* Gets the component of the controller namespace defined by this block, if it exists.
*/
abstract string getControllerComponent();
/**
* Gets the location of this route block.
*/
abstract Location getLocation();
}
/**
* A route block that is not the top-level block.
* This block will always have a parent.
*/
abstract private class NestedRouteBlock extends RouteBlock {
RouteBlock parent;
override RouteBlock getParent() { result = parent }
override string getAPrimaryQlClass() { result = "NestedRouteBlock" }
}
/**
* A top-level routes block.
* ```rb
* Rails.application.routes.draw do
* ...
* end
* ```
*/
private class TopLevelRouteBlock extends RouteBlock, TTopLevelRouteBlock {
MethodCall call;
// Routing blocks create scopes which define the namespace for controllers and paths,
// though they can be overridden in various ways.
// The namespaces can differ, so we track them separately.
Block block;
TopLevelRouteBlock() { this = TTopLevelRouteBlock(_, call, block) }
override string getAPrimaryQlClass() { result = "TopLevelRouteBlock" }
Block getBlock() { result = block }
override Stmt getAStmt() { result = block.getAStmt() }
override RouteBlock getParent() { none() }
override string toString() { result = call.toString() }
override Location getLocation() { result = call.getLocation() }
override string getPathComponent() { none() }
override string getControllerComponent() { none() }
}
/**
* A route block defined by a call to `constraints`.
* ```rb
* constraints(foo: /some_regex/) do
* get "/posts/:foo", to "posts#something"
* end
* ```
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-constraints
*/
private class ConstraintsRouteBlock extends NestedRouteBlock, TConstraintsRouteBlock {
private Block block;
private MethodCall call;
ConstraintsRouteBlock() { this = TConstraintsRouteBlock(parent, call, block) }
override string getAPrimaryQlClass() { result = "ConstraintsRouteBlock" }
override Stmt getAStmt() { result = block.getAStmt() }
override string getPathComponent() { result = "" }
override string getControllerComponent() { result = "" }
override string toString() { result = call.toString() }
override Location getLocation() { result = call.getLocation() }
}
/**
* A route block defined by a call to `scope`.
* ```rb
* scope(path: "/some_path", module: "some_module") do
* get "/posts/:foo", to "posts#something"
* end
* ```
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-scope
*/
private class ScopeRouteBlock extends NestedRouteBlock, TScopeRouteBlock {
private MethodCall call;
private Block block;
ScopeRouteBlock() { this = TScopeRouteBlock(parent, call, block) }
override string getAPrimaryQlClass() { result = "ScopeRouteBlock" }
override Stmt getAStmt() { result = block.getAStmt() }
override string toString() { result = call.toString() }
override Location getLocation() { result = call.getLocation() }
override string getPathComponent() {
call.getKeywordArgument("path").getConstantValue().isStringlikeValue(result)
or
not exists(call.getKeywordArgument("path")) and
call.getArgument(0).getConstantValue().isStringlikeValue(result)
}
override string getControllerComponent() {
call.getKeywordArgument(["controller", "module"]).getConstantValue().isStringlikeValue(result)
}
}
/**
* A route block defined by a call to `resources`.
* ```rb
* resources :articles do
* get "/comments", to "comments#index"
* end
* ```
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources
*/
private class ResourcesRouteBlock extends NestedRouteBlock, TResourcesRouteBlock {
private MethodCall call;
private Block block;
ResourcesRouteBlock() { this = TResourcesRouteBlock(parent, call, block) }
override string getAPrimaryQlClass() { result = "ResourcesRouteBlock" }
override Stmt getAStmt() { result = block.getAStmt() }
/**
* Gets the `resources` call that gives rise to this route block.
*/
MethodCall getDefiningMethodCall() { result = call }
override string getPathComponent() {
exists(string resource | call.getArgument(0).getConstantValue().isStringlikeValue(resource) |
result = resource + "/:" + singularize(resource) + "_id"
)
}
override string getControllerComponent() { result = "" }
override string toString() { result = call.toString() }
override Location getLocation() { result = call.getLocation() }
}
/**
* A route block that is guarded by a conditional statement.
* For example:
* ```rb
* if Rails.env.test?
* get "/foo/bar", to: "foo#bar"
* end
* ```
* We ignore the condition and analyze both branches to obtain as
* much routing information as possible.
*/
private class ConditionalRouteBlock extends NestedRouteBlock, TConditionalRouteBlock {
private ConditionalExpr e;
ConditionalRouteBlock() { this = TConditionalRouteBlock(parent, e) }
override string getAPrimaryQlClass() { result = "ConditionalRouteBlock" }
override Stmt getAStmt() { result = e.getBranch(_).(StmtSequence).getAStmt() }
override string getPathComponent() { none() }
override string getControllerComponent() { none() }
override string toString() { result = e.toString() }
override Location getLocation() { result = e.getLocation() }
}
/**
* A route block defined by a call to `namespace`.
* ```rb
* namespace :admin do
* resources :posts
* end
* ```
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-namespace
*/
private class NamespaceRouteBlock extends NestedRouteBlock, TNamespaceRouteBlock {
private MethodCall call;
private Block block;
NamespaceRouteBlock() { this = TNamespaceRouteBlock(parent, call, block) }
override Stmt getAStmt() { result = block.getAStmt() }
override string getPathComponent() { result = this.getNamespace() }
override string getControllerComponent() { result = this.getNamespace() }
private string getNamespace() {
call.getArgument(0).getConstantValue().isStringlikeValue(result)
}
override string toString() { result = call.toString() }
override Location getLocation() { result = call.getLocation() }
}
/**
* A route configuration. This defines a combination of HTTP method and URL
* path which should be routed to a particular controller-action pair.
* This can arise from an explicit call to a routing method, for example:
* ```rb
* get "/photos", to: "photos#index"
* ```
* or via a convenience method like `resources`, which defines multiple routes at once:
* ```rb
* resources :photos
* ```
*/
class Route extends TRoute instanceof RouteImpl {
/**
* Gets the name of a primary CodeQL class to which this route belongs.
*/
string getAPrimaryQlClass() { result = "Route" }
/** Gets a string representation of this route. */
string toString() { result = super.toString() }
/**
* Gets the location of the method call that defines this route.
*/
Location getLocation() { result = super.getLocation() }
/**
* Gets the full controller targeted by this route.
*/
string getController() { result = super.getController() }
/**
* Gets the action targeted by this route.
*/
string getAction() { result = super.getAction() }
/**
* Gets the HTTP method of this route.
* The result is one of [get, post, put, patch, delete].
*/
string getHttpMethod() { result = super.getHttpMethod() }
/**
* Gets the full path of the route.
*/
string getPath() { result = super.getPath() }
/**
* Get a URL capture. This is a wildcard URL segment whose value is placed in `params`.
* For example, in
* ```ruby
* get "/foo/:bar/baz", to: "users#index"
* ```
* the capture is `:bar`.
*/
string getACapture() { result = super.getACapture() }
}
/**
* The implementation of `Route`.
* This class is abstract and is thus kept private so we don't expose it to
* users.
* Extend this class to add new instances of routes.
*/
abstract private class RouteImpl extends TRoute {
/**
* Gets the name of a primary CodeQL class to which this route belongs.
*/
string getAPrimaryQlClass() { result = "RouteImpl" }
MethodCall method;
/** Gets a string representation of this route. */
string toString() { result = method.toString() }
/**
* Gets the location of the method call that defines this route.
*/
Location getLocation() { result = method.getLocation() }
/**
* Gets the method call that defines this route.
*/
MethodCall getDefiningMethodCall() { result = method }
/**
* Get the last component of the path. For example, in
* ```rb
* get "/photos", to: "photos#index"
* ```
* this is `/photos`.
* If the string has any interpolations, this predicate will have no result.
*/
abstract string getLastPathComponent();
/**
* Gets the HTTP method of this route.
* The result is one of [get, post, put, patch, delete].
*/
abstract string getHttpMethod();
/**
* Gets the last controller component.
* This is the controller specified in the route itself.
*/
abstract string getLastControllerComponent();
/**
* Gets a component of the controller.
* This behaves identically to `getPathComponent`, but for controller information.
*/
string getControllerComponent(int n) {
if n = 0
then result = this.getLastControllerComponent()
else result = this.getParentBlock().getParent(n - 1).getControllerComponent()
}
/**
* Gets the full controller targeted by this route.
*/
string getController() {
result =
concat(int n |
this.getControllerComponent(n) != ""
|
this.getControllerComponent(n), "/" order by n desc
)
}
/**
* Gets the action targeted by this route.
*/
abstract string getAction();
/**
* Gets the parent `RouteBlock` of this route.
*/
abstract RouteBlock getParentBlock();
/**
* Gets a component of the path. Components are numbered from 0 up, where 0
* is the last component, 1 is the second-last, etc.
* For example, in the following route:
*
* ```rb
* namespace path: "foo" do
* namespace path: "bar" do
* get "baz", to: "foo#bar
* end
* end
* ```
*
* the components are:
*
* | n | component
* |---|----------
* | 0 | baz
* | 1 | bar
* | 2 | foo
*/
string getPathComponent(int n) {
if n = 0
then result = this.getLastPathComponent()
else result = this.getParentBlock().getParent(n - 1).getPathComponent()
}
/**
* Gets the full path of the route.
*/
string getPath() {
result =
concat(int n |
this.getPathComponent(n) != ""
|
// Strip leading and trailing slashes from each path component before combining
stripSlashes(this.getPathComponent(n)), "/" order by n desc
)
}
/**
* Get a URL capture. This is a wildcard URL segment whose value is placed in `params`.
* For example, in
* ```ruby
* get "/foo/:bar/baz", to: "users#index"
* ```
* the capture is `:bar`.
* We don't currently make use of this, but it may be useful in future to more accurately
* model the contents of the `params` hash.
*/
string getACapture() { result = this.getPathComponent(_).regexpFind(":[^:/]+", _, _) }
}
/**
* A route generated by an explicit call to `get`, `post`, etc.
*
* ```ruby
* get "/photos", to: "photos#index"
* put "/photos/:id", to: "photos#update"
* ```
*/
private class ExplicitRoute extends RouteImpl, TExplicitRoute {
RouteBlock parentBlock;
ExplicitRoute() { this = TExplicitRoute(parentBlock, method) }
override string getAPrimaryQlClass() { result = "ExplicitRoute" }
override RouteBlock getParentBlock() { result = parentBlock }
override string getLastPathComponent() {
method.getArgument(0).getConstantValue().isStringlikeValue(result)
}
override string getLastControllerComponent() {
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result)
or
not exists(method.getKeywordArgument("controller")) and
(
result = extractController(this.getActionString())
or
// If controller is not specified, and we're in a `resources` route block, use the controller of that route.
// For example, in
//
// resources :posts do
// get "timestamp", to: :timestamp
// end
//
// The route is GET /posts/:post_id/timestamp => posts/timestamp
not exists(extractController(this.getActionString())) and
exists(ResourcesRoute r |
r.getDefiningMethodCall() = parentBlock.(ResourcesRouteBlock).getDefiningMethodCall()
|
result = r.getLastControllerComponent()
)
)
}
private string getActionString() {
method.getKeywordArgument("to").getConstantValue().isStringlikeValue(result)
or
method.getKeywordArgument("to").(MethodCall).getMethodName() = "redirect" and
result = "<redirect>#<redirect>"
}
override string getAction() {
// get "/photos", action: "index"
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result)
or
not exists(method.getKeywordArgument("action")) and
(
// get "/photos", to: "photos#index"
// get "/photos", to: redirect("some_url")
result = extractAction(this.getActionString())
or
// resources :photos, only: [] do
// get "/", to: "index"
// end
not exists(extractAction(this.getActionString())) and result = this.getActionString()
or
// get :some_action
not exists(this.getActionString()) and
method.getArgument(0).getConstantValue().isStringlikeValue(result)
)
}
override string getHttpMethod() { result = method.getMethodName().toString() }
}
/**
* A route generated by a call to `resources`.
*
* ```ruby
* resources :photos
* ```
* This creates eight routes, equivalent to the following code:
* ```ruby
* get "/photos", to: "photos#index"
* get "/photos/new", to: "photos#new"
* post "/photos", to: "photos#create"
* get "/photos/:id", to: "photos#show"
* get "/photos/:id/edit", to: "photos#edit"
* patch "/photos/:id", to: "photos#update"
* put "/photos/:id", to: "photos#update"
* delete "/photos/:id", to: "photos#delete"
* ```
*
* `resources` can take a block. Any routes defined inside the block will inherit a path component of
* `/<resource>/:<resource>_id`. For example:
*
* ```ruby
* resources :photos do
* get "/foo", to: "photos#foo"
* end
* ```
* This creates the eight default routes, plus one more, which is nested under "/photos/:photo_id", equivalent to:
* ```ruby
* get "/photos/:photo_id/foo", to: "photos#foo"
* ```
*/
private class ResourcesRoute extends RouteImpl, TResourcesRoute {
RouteBlock parent;
string action;
string httpMethod;
string pathComponent;
ResourcesRoute() {
exists(string resource |
this = TResourcesRoute(parent, method, action) and
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
isDefaultResourceRoute(resource, httpMethod, pathComponent, action)
)
}
override string getAPrimaryQlClass() { result = "ResourcesRoute" }
override RouteBlock getParentBlock() { result = parent }
override string getLastPathComponent() { result = pathComponent }
override string getLastControllerComponent() {
method.getArgument(0).getConstantValue().isStringlikeValue(result)
}
override string getAction() { result = action }
override string getHttpMethod() { result = httpMethod }
}
/**
* A route generated by a call to `resource`.
* This is like a `resources` route, but creates routes for a singular resource.
* This means there's no index route, no id parameter, and the resource name is expected to be singular.
* It will still be routed to a pluralised controller name.
* ```ruby
* resource :account
* ```
*/
private class SingularResourceRoute extends RouteImpl, TResourceRoute {
RouteBlock parent;
string action;
string httpMethod;
string pathComponent;
SingularResourceRoute() {
exists(string resource |
this = TResourceRoute(parent, method, action) and
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
isDefaultSingularResourceRoute(resource, httpMethod, pathComponent, action)
)
}
override string getAPrimaryQlClass() { result = "SingularResourceRoute" }
override RouteBlock getParentBlock() { result = parent }
override string getLastPathComponent() { result = pathComponent }
override string getLastControllerComponent() {
method.getArgument(0).getConstantValue().isStringlikeValue(result)
}
override string getAction() { result = action }
override string getHttpMethod() { result = httpMethod }
}
/**
* A route generated by a call to `match`.
* This is a lower level primitive that powers `get`, `post` etc.
* The first argument can be a path or a (path, controller-action) pair.
* The controller, action and HTTP method can be specified with the
* `controller:`, `action:` and `via:` keyword arguments, respectively.
* ```ruby
* match 'photos/:id' => 'photos#show', via: :get
* match 'photos/:id', to: 'photos#show', via: :get
* match 'photos/:id', to 'photos#show', via: [:get, :post]
* match 'photos/:id', controller: 'photos', action: 'show', via: :get
* ```
*/
private class MatchRoute extends RouteImpl, TMatchRoute {
private RouteBlock parent;
MatchRoute() { this = TMatchRoute(parent, method) }
override string getAPrimaryQlClass() { result = "MatchRoute" }
override RouteBlock getParentBlock() { result = parent }
override string getLastPathComponent() {
[method.getArgument(0), method.getArgument(0).(Pair).getKey()]
.getConstantValue()
.isStringlikeValue(result)
}
override string getLastControllerComponent() {
result =
extractController(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result) or
result =
extractController(method
.getArgument(0)
.(Pair)
.getValue()
.getConstantValue()
.getStringlikeValue())
}
override string getHttpMethod() {
exists(string via |
method.getKeywordArgument("via").getConstantValue().isStringlikeValue(via)
|
via = "all" and result = anyHttpMethod()
or
via != "all" and result = via
)
or
result =
method
.getKeywordArgument("via")
.(ArrayLiteral)
.getElement(_)
.getConstantValue()
.getStringlikeValue()
}
override string getAction() {
result =
extractAction(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result) or
result =
extractAction(method
.getArgument(0)
.(Pair)
.getValue()
.getConstantValue()
.getStringlikeValue())
}
}
private import Cached
/**
* This module contains the IPA types backing `RouteBlock` and `Route`, cached for performance.
*/
cached
private module Cached {
cached
newtype TRouteBlock =
TTopLevelRouteBlock(MethodCall routes, MethodCall draw, Block b) {
routes.getMethodName() = "routes" and
draw.getMethodName() = "draw" and
draw.getReceiver() = routes and
draw.getBlock() = b
} or
// constraints(foo: /some_regex/) do
// get "/posts/:foo", to "posts#something"
// end
TConstraintsRouteBlock(RouteBlock parent, MethodCall constraints, Block b) {
parent.getAStmt() = constraints and
constraints.getMethodName() = "constraints" and
constraints.getBlock() = b
} or
// scope(path: "/some_path", module: "some_module") do
// get "/posts/:foo", to "posts#something"
// end
TScopeRouteBlock(RouteBlock parent, MethodCall scope, Block b) {
parent.getAStmt() = scope and scope.getMethodName() = "scope" and scope.getBlock() = b
} or
// resources :articles do
// get "/comments", to "comments#index"
// end
TResourcesRouteBlock(RouteBlock parent, MethodCall resources, Block b) {
parent.getAStmt() = resources and
resources.getMethodName() = "resources" and
resources.getBlock() = b
} or
// A conditional statement guarding some routes.
// We ignore the condition and analyze both branches to obtain as
// much routing information as possible.
TConditionalRouteBlock(RouteBlock parent, ConditionalExpr e) { parent.getAStmt() = e } or
// namespace :admin do
// resources :posts
// end
TNamespaceRouteBlock(RouteBlock parent, MethodCall namespace, Block b) {
parent.getAStmt() = namespace and
namespace.getMethodName() = "namespace" and
namespace.getBlock() = b
}
/**
* A route configuration. See `Route` for more info
*/
cached
newtype TRoute =
/**
* See `ExplicitRoute`
*/
TExplicitRoute(RouteBlock b, MethodCall m) {
b.getAStmt() = m and m.getMethodName() = anyHttpMethod()
} or
/**
* See `ResourcesRoute`
*/
TResourcesRoute(RouteBlock b, MethodCall m, string action) {
b.getAStmt() = m and
m.getMethodName() = "resources" and
action in ["show", "index", "new", "edit", "create", "update", "destroy"] and
applyActionFilters(m, action)
} or
/**
* See `SingularResourceRoute`
*/
TResourceRoute(RouteBlock b, MethodCall m, string action) {
b.getAStmt() = m and
m.getMethodName() = "resource" and
action in ["show", "new", "edit", "create", "update", "destroy"] and
applyActionFilters(m, action)
} or
/**
* See `MatchRoute`
*/
TMatchRoute(RouteBlock b, MethodCall m) { b.getAStmt() = m and m.getMethodName() = "match" }
}
/**
* Several routing methods support the keyword arguments `only:` and `except:`.
* - `only:` restricts the set of actions to just those in the argument.
* - `except:` removes the given actions from the set.
*/
bindingset[action]
private predicate applyActionFilters(MethodCall m, string action) {
// Respect the `only` keyword argument, which restricts the set of actions.
(
not exists(m.getKeywordArgument("only"))
or
exists(Expr only | only = m.getKeywordArgument("only") |
[only.(ArrayLiteral).getElement(_), only].getConstantValue().isStringlikeValue(action)
)
) and
// Respect the `except` keyword argument, which removes actions from the default set.
(
not exists(m.getKeywordArgument("except"))
or
exists(Expr except | except = m.getKeywordArgument("except") |
[except.(ArrayLiteral).getElement(_), except].getConstantValue().getStringlikeValue() !=
action
)
)
}
/**
* Holds if the (resource, method, path, action) combination would be generated by a call to `resources :<resource>`.
*/
bindingset[resource]
private predicate isDefaultResourceRoute(
string resource, string method, string path, string action
) {
action = "create" and
(method = "post" and path = "/" + resource)
or
action = "index" and
(method = "get" and path = "/" + resource)
or
action = "new" and
(method = "get" and path = "/" + resource + "/new")
or
action = "edit" and
(method = "get" and path = "/" + resource + ":id/edit")
or
action = "show" and
(method = "get" and path = "/" + resource + "/:id")
or
action = "update" and
(method in ["put", "patch"] and path = "/" + resource + "/:id")
or
action = "destroy" and
(method = "delete" and path = "/" + resource + "/:id")
}
/**
* Holds if the (resource, method, path, action) combination would be generated by a call to `resource :<resource>`.
*/
bindingset[resource]
private predicate isDefaultSingularResourceRoute(
string resource, string method, string path, string action
) {
action = "create" and
(method = "post" and path = "/" + resource)
or
action = "new" and
(method = "get" and path = "/" + resource + "/new")
or
action = "edit" and
(method = "get" and path = "/" + resource + "/edit")
or
action = "show" and
(method = "get" and path = "/" + resource)
or
action = "update" and
(method in ["put", "patch"] and path = "/" + resource)
or
action = "destroy" and
(method = "delete" and path = "/" + resource)
}
/**
* Extract the controller from a Rails routing string
* ```
* extractController("posts#show") = "posts"
* ```
*/
bindingset[input]
private string extractController(string input) { result = input.regexpCapture("([^#]+)#.+", 1) }
/**
* Extract the action from a Rails routing string
* ```
* extractAction("posts#show") = "show"
*/
bindingset[input]
private string extractAction(string input) { result = input.regexpCapture("[^#]+#(.+)", 1) }
/**
* Returns the lowercase name of every HTTP method we support.
*/
private string anyHttpMethod() { result = ["get", "post", "put", "patch", "delete"] }
/**
* The inverse of `pluralize`. If `input` is a plural word, it returns the
* singular version.
*
* Examples:
*
* - photos -> photo
* - stories -> story
* - not_plural -> not_plural
*/
bindingset[input]
private string singularize(string input) {
exists(string prefix | prefix = input.regexpCapture("(.*)ies", 1) | result = prefix + "y")
or
not input.matches("%ies") and
exists(string prefix | prefix = input.regexpCapture("(.*)s", 1) | result = prefix)
or
not input.regexpMatch(".*(ies|s)") and result = input
}
/**
* Convert a camel-case string to underscore case. Converts `::` to `/`.
* This can be used to convert ActiveRecord controller names to a canonical form that matches the routes they handle.
* Note: All-uppercase words like `CONSTANT` are not handled correctly.
*/
bindingset[base]
string underscore(string base) {
base = "" and result = ""
or
result =
base.charAt(0).toLowerCase() +
// Walk along the string, keeping track of the previous character
// in order to determine if we've crossed a boundary.
// Boundaries are:
// - lower case to upper case: B in FooBar
// - entering a namespace: B in Foo::Bar
concat(int i, string prev, string char |
prev = base.charAt(i) and
char = base.charAt(i + 1)
|
any(string s |
char.regexpMatch("[A-Za-z0-9]") and
if prev = ":"
then s = "/" + char.toLowerCase()
else
if prev.isLowercase() and char.isUppercase()
then s = "_" + char.toLowerCase()
else s = char.toLowerCase()
)
order by
i
)
}
/**
* Strip leading and trailing forward slashes from the string.
*/
bindingset[input]
private string stripSlashes(string input) {
result = input.regexpReplaceAll("^/+(.+)$", "$1").regexpReplaceAll("^(.*[^/])/+$", "$1")
}
}

View File

@@ -583,7 +583,8 @@ module Array {
private class DeleteUnknownSummary extends DeleteSummary {
DeleteUnknownSummary() {
this = "delete" and
// Note: take care to avoid a name clash with the "delete" summary from String.qll
this = "delete-unknown-key" and
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
}
@@ -2066,7 +2067,11 @@ module Enumerable {
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
output = "Argument[block].Parameter[0]" and
preservesValue = true
or
input = "Argument[block].ReturnValue" and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
}

View File

@@ -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()

View File

@@ -199,11 +199,13 @@ module Hash {
}
}
private class AssocUnknownSummary extends AssocSummary {
AssocUnknownSummary() {
this = "assoc" and
mc.getNumberOfArguments() = 1 and
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
private class AssocUnknownSummary extends SummarizedCallable {
AssocUnknownSummary() { this = "assoc-unknown-arg" }
override MethodCall getACallSimple() {
result.getMethodName() = "assoc" and
result.getNumberOfArguments() = 1 and
not exists(DataFlow::Content::getKnownElementIndex(result.getArgument(0)))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {

View File

@@ -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
(

View File

@@ -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()
)
}

View File

@@ -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()
}
}

View File

@@ -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`. */

View 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()
}
}
}

View 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() }
}
}

View 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 }
}
}

View 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
}
}
}

View File

@@ -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

View File

@@ -21,6 +21,7 @@ private import codeql.ruby.typetracking.TypeTracker
private import codeql.ruby.ApiGraphs
private import codeql.ruby.Concepts
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
private import codeql.ruby.dataflow.internal.TaintTrackingPrivate as TaintTrackingPrivate
private import codeql.ruby.TaintTracking
private import codeql.ruby.frameworks.core.String
@@ -37,43 +38,6 @@ DataFlow::LocalSourceNode strStart() {
/** Gets a dataflow node for a regular expression literal. */
DataFlow::LocalSourceNode regStart() { result.asExpr().getExpr() instanceof Ast::RegExpLiteral }
/**
* Holds if the analysis should track flow from `nodeFrom` to `nodeTo` on top of the ordinary type-tracking steps.
* `nodeFrom` and `nodeTo` has type `fromType` and `toType` respectively.
* The types are either "string" or "regexp".
*/
predicate step(
DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo, string fromType, string toType
) {
fromType = toType and
fromType = "string" and
(
// include taint flow through `String` summaries
TaintTracking::localTaintStep(nodeFrom, nodeTo) and
nodeFrom.(DataFlowPrivate::SummaryNode).getSummarizedCallable() instanceof
String::SummarizedCallable
or
// string concatenations, and
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
)
or
// string interpolations
nodeFrom.asExpr() =
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
)
or
fromType = "string" and
toType = "reg" and
exists(DataFlow::CallNode call |
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
nodeFrom = call.getArgument(0) and
nodeTo = call
)
}
/** Gets a node where string values that flow to the node are interpreted as regular expressions. */
DataFlow::Node stringSink() {
result instanceof RE::RegExpInterpretation::Range and
@@ -91,28 +55,139 @@ DataFlow::Node stringSink() {
/** Gets a node where regular expressions that flow to the node are used. */
DataFlow::Node regSink() { result = any(RegexExecution exec).getRegex() }
/** Gets a node that is reachable by type-tracking from any string or regular expression. */
DataFlow::LocalSourceNode forward(TypeTracker t) {
t.start() and
result = [strStart(), regStart()]
or
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
or
exists(TypeTracker t2 | t2 = t.continue() | step(forward(t2).getALocalUse(), result, _, _))
private signature module TypeTrackInputSig {
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start);
predicate end(DataFlow::Node n);
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo);
}
/**
* Gets a node that is backwards reachable from any regular expression use,
* where that use is reachable by type-tracking from any string or regular expression.
* Provides a version of type tracking where we first prune for reachable nodes,
* before doing the type tracking computation.
*/
DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
t.start() and
result.flowsTo([stringSink(), regSink()]) and
result = forward(TypeTracker::end())
or
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
or
exists(TypeBackTracker t2 | t2 = t.continue() | step(result.getALocalUse(), backwards(t2), _, _))
private module TypeTrack<TypeTrackInputSig Input> {
private predicate additionalStep(
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
) {
Input::additionalStep(nodeFrom.getALocalUse(), nodeTo)
}
/** Gets a node that is forwards reachable by type-tracking. */
pragma[nomagic]
private DataFlow::LocalSourceNode forward(TypeTracker t) {
result = Input::start(t, _)
or
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
or
exists(TypeTracker t2 | t2 = t.continue() | additionalStep(forward(t2), result))
}
bindingset[result, tbt]
pragma[inline_late]
pragma[noopt]
private DataFlow::LocalSourceNode forwardLateInline(TypeBackTracker tbt) {
exists(TypeTracker tt |
result = forward(tt) and
tt = tbt.getACompatibleTypeTracker()
)
}
/** Gets a node that is backwards reachable by type-tracking. */
pragma[nomagic]
private DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
result = forwardLateInline(t) and
(
t.start() and
Input::end(result.getALocalUse())
or
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
or
exists(TypeBackTracker t2 | t2 = t.continue() | additionalStep(result, backwards(t2)))
)
}
bindingset[result, tt]
pragma[inline_late]
pragma[noopt]
private DataFlow::LocalSourceNode backwardsInlineLate(TypeTracker tt) {
exists(TypeBackTracker tbt |
result = backwards(tbt) and
tt = tbt.getACompatibleTypeTracker()
)
}
/** Holds if `n` is forwards and backwards reachable with type tracker `t`. */
pragma[nomagic]
private predicate reached(DataFlow::LocalSourceNode n, TypeTracker t) {
n = forward(t) and
n = backwardsInlineLate(t)
}
pragma[nomagic]
private TypeTracker stepReached(
TypeTracker t, DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
) {
exists(StepSummary summary |
StepSummary::step(nodeFrom, nodeTo, summary) and
reached(nodeFrom, t) and
reached(nodeTo, result) and
result = t.append(summary)
)
or
additionalStep(nodeFrom, nodeTo) and
reached(nodeFrom, pragma[only_bind_into](t)) and
reached(nodeTo, pragma[only_bind_into](t)) and
result = t.continue()
}
/** Gets a node that has been tracked from the start node `start`. */
DataFlow::LocalSourceNode track(DataFlow::Node start, TypeTracker t) {
t.start() and
result = Input::start(t, start) and
reached(result, t)
or
exists(TypeTracker t2 | t = stepReached(t2, track(start, t2), result))
}
}
/** Holds if `inputStr` is compiled to a regular expression that is returned at `call`. */
pragma[nomagic]
private predicate regFromString(DataFlow::LocalSourceNode inputStr, DataFlow::CallNode call) {
exists(DataFlow::Node mid |
inputStr.flowsTo(mid) and
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
mid = call.getArgument(0)
)
}
private module StringTypeTrackInput implements TypeTrackInputSig {
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
start = strStart() and t.start() and result = start
}
predicate end(DataFlow::Node n) {
n = stringSink() or
regFromString(n, _)
}
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) {
// include taint flow through `String` summaries
TaintTrackingPrivate::summaryThroughStepTaint(nodeFrom, nodeTo,
any(String::SummarizedCallable c))
or
// string concatenations, and
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and
op.getAnOperand() = nodeFrom.asExpr() and
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
)
or
// string interpolations
nodeFrom.asExpr() =
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
}
}
/**
@@ -120,41 +195,34 @@ DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
* This is used to figure out where `start` is evaluated as a regular expression against an input string,
* or where `start` is compiled into a regular expression.
*/
private DataFlow::LocalSourceNode trackStrings(DataFlow::Node start, TypeTracker t) {
result = backwards(_) and
(
private predicate trackStrings = TypeTrack<StringTypeTrackInput>::track/2;
/** Holds if `strConst` flows to a regex compilation (tracked by `t`), where the resulting regular expression is stored in `reg`. */
pragma[nomagic]
private predicate regFromStringStart(DataFlow::Node strConst, TypeTracker t, DataFlow::CallNode reg) {
regFromString(trackStrings(strConst, t), reg) and
exists(t.continue())
}
private module RegTypeTrackInput implements TypeTrackInputSig {
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
start = regStart() and
t.start() and
start = result and
result = strStart()
result = start
or
exists(TypeTracker t2 | result = trackStrings(start, t2).track(t2, t))
or
// an additional step from string to string
exists(TypeTracker t2 | t2 = t.continue() |
step(trackStrings(start, t2).getALocalUse(), result, "string", "string")
)
)
regFromStringStart(start, t, result)
}
predicate end(DataFlow::Node n) { n = regSink() }
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) { none() }
}
/**
* Gets a node that has been tracked from the regular expression `start` to some node.
* This is used to figure out where `start` is executed against an input string.
*/
private DataFlow::LocalSourceNode trackRegs(DataFlow::Node start, TypeTracker t) {
result = backwards(_) and
(
t.start() and
start = result and
result = regStart()
or
exists(TypeTracker t2 | result = trackRegs(start, t2).track(t2, t))
or
// an additional step where a string is converted to a regular expression
exists(TypeTracker t2 | t2 = t.continue() |
step(trackStrings(start, t2).getALocalUse(), result, "string", "reg")
)
)
}
private predicate trackRegs = TypeTrack<RegTypeTrackInput>::track/2;
/** Gets a node that references a regular expression. */
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {

View File

@@ -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%")
)
}
}

View File

@@ -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 + "%")
)
}
/**

View File

@@ -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>;

View File

@@ -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)
}

View File

@@ -26,18 +26,20 @@ module LdapInjection {
/**
* Additional taint steps for "LDAP Injection" vulnerabilities.
*/
predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
filterTaintStep(nodeFrom, nodeTo)
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
attributeArrayTaintStep(nodeFrom, nodeTo)
}
private predicate filterTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
/**
* Additional taint step to handle elements inside an array,
* specifically in the context of the following LDAP search function:
*
* ldap.search(base: "", filter: "", attributes: [name])
*/
private predicate attributeArrayTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(DataFlow::CallNode filterCall |
(
filterCall =
API::getTopLevelMember("Net").getMember("LDAP").getMember("Filter").getAMethodCall(["eq"]) or
filterCall.getMethodName() = ["[]"]
) and
n1 = filterCall.getArgument([0, 1]) and
filterCall.getMethodName() = "[]" and
n1 = filterCall.getArgument(_) and
n2 = filterCall
)
}
@@ -64,5 +66,16 @@ module LdapInjection {
* sanitizer-guard.
*/
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
StringConstArrayInclusionCallBarrier { }
StringConstArrayInclusionCallBarrier
{ }
/**
* A call to `Net::LDAP::Filter.escape`, considered as a sanitizer.
*/
class NetLdapFilterEscapeSanitization extends Sanitizer {
NetLdapFilterEscapeSanitization() {
this =
API::getTopLevelMember("Net").getMember("LDAP").getMember("Filter").getAMethodCall("escape")
}
}
}

View File

@@ -5,22 +5,25 @@
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
private import LdapInjectionCustomizations
private import LdapInjectionCustomizations::LdapInjection
/**
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "LdapInjection" }
/** Provides a taint-tracking configuration for detecting LDAP Injections vulnerabilities. */
module LdapInjection {
import LdapInjectionCustomizations::LdapInjection
override predicate isSource(DataFlow::Node source) { source instanceof Source }
/**
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
*/
private module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
LdapInjection::isAdditionalTaintStep(node1, node2)
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
LdapInjection::isAdditionalFlowStep(node1, node2)
}
}
import TaintTracking::Global<Config>
}

View File

@@ -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())
}

View File

@@ -37,6 +37,3 @@ module ReflectedXss {
}
}
}
/** DEPRECATED: Alias for ReflectedXss */
deprecated module ReflectedXSS = ReflectedXss;

Some files were not shown because too many files have changed in this diff Show More