Merge branch 'github:main' into amammad-ruby-bombs

This commit is contained in:
amammad
2023-09-06 20:17:49 +10:00
committed by GitHub
3603 changed files with 157648 additions and 124591 deletions

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

@@ -1,13 +1,16 @@
import codeql.ruby.AST
import codeql.ruby.CFG
import codeql.ruby.DataFlow::DataFlow
import codeql.ruby.dataflow.internal.DataFlowPrivate
import codeql.ruby.dataflow.internal.DataFlowImplConsistency::Consistency
import codeql.ruby.DataFlow::DataFlow as DataFlow
private import codeql.ruby.AST
private import codeql.ruby.CFG
private import codeql.ruby.dataflow.internal.DataFlowImplSpecific
private import codeql.ruby.dataflow.internal.TaintTrackingImplSpecific
private import codeql.dataflow.internal.DataFlowImplConsistency
private class MyConsistencyConfiguration extends ConsistencyConfiguration {
override predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
private module Input implements InputSig<RubyDataFlow> {
private import RubyDataFlow
override predicate argHasPostUpdateExclude(ArgumentNode n) {
predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
predicate argHasPostUpdateExclude(ArgumentNode n) {
n instanceof BlockArgumentNode
or
n instanceof FlowSummaryNode
@@ -17,7 +20,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
not isNonConstantExpr(getAPostUpdateNodeForArg(n.asExpr()))
}
override predicate postHasUniquePreExclude(PostUpdateNode n) {
predicate postHasUniquePreExclude(PostUpdateNode n) {
exists(CfgNodes::ExprCfgNode e, CfgNodes::ExprCfgNode arg |
e = getAPostUpdateNodeForArg(arg) and
e != arg and
@@ -25,7 +28,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
)
}
override predicate uniquePostUpdateExclude(Node n) {
predicate uniquePostUpdateExclude(Node n) {
exists(CfgNodes::ExprCfgNode e, CfgNodes::ExprCfgNode arg |
e = getAPostUpdateNodeForArg(arg) and
e != arg and
@@ -33,3 +36,10 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
)
}
}
import MakeConsistency<RubyDataFlow, RubyTaintTracking, Input>
query predicate multipleToString(DataFlow::Node n, string s) {
s = strictconcat(n.toString(), ",") and
strictcount(n.toString()) > 1
}

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

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Support for the `mysql2` gem has been added. Method calls that execute queries against an MySQL database that may be vulnerable to injection attacks will now be recognized.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Support for the `pg` gem has been added. Method calls that execute queries against a PostgreSQL database that may be vulnerable to injection attacks will now be recognized.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Support for the `sequel` gem has been added. Method calls that execute queries against a database that may be vulnerable to injection attacks will now be recognized.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Additional sinks for `rb/unsafe-deserialization` have been added. This includes various methods from the `yaml` and `plist` gems, which deserialize YAML and Property List data, respectively.

View File

@@ -1,7 +0,0 @@
---
category: minorAnalysis
---
* Deleted many deprecated predicates and classes with uppercase `URL`, `XSS`, etc. in their names. Use the PascalCased versions instead.
* Deleted the deprecated `getValueText` predicate from the `Expr`, `StringComponent`, and `ExprCfgNode` classes. Use `getConstantValue` instead.
* Deleted the deprecated `VariableReferencePattern` class, use `ReferencePattern` instead.
* Deleted all deprecated aliases in `StandardLibrary.qll`, use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.

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

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

View File

@@ -0,0 +1,6 @@
## 0.7.3
### Minor Analysis Improvements
* Flow between positional arguments and splat parameters (`*args`) is now tracked more precisely.
* Flow between splat arguments (`*args`) and positional parameters is now tracked more precisely.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.6.2
lastReleaseVersion: 0.7.3

View File

@@ -2,15 +2,6 @@
import files.FileSystem
bindingset[loc]
pragma[inline_late]
private string locationToString(Location loc) {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
loc.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}
/**
* A location as given by a file, a start line, a start column,
* an end line, and an end column.
@@ -37,8 +28,14 @@ class Location extends @location_default {
int getNumLines() { result = this.getEndLine() - this.getStartLine() + 1 }
/** Gets a textual representation of this element. */
pragma[inline]
string toString() { result = locationToString(this) }
bindingset[this]
pragma[inline_late]
string toString() {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
this.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}
/**
* Holds if this element is at the specified location.

File diff suppressed because it is too large Load Diff

View File

@@ -843,6 +843,58 @@ module XmlParserCall {
}
}
/**
* A data-flow node that constructs an XPath expression.
*
* If it is important that the XPath expression is indeed executed, then use `XPathExecution`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `XPathConstruction::Range` instead.
*/
class XPathConstruction extends DataFlow::Node instanceof XPathConstruction::Range {
/** Gets the argument that specifies the XPath expressions to be constructed. */
DataFlow::Node getXPath() { result = super.getXPath() }
}
/** Provides a class for modeling new XPath construction APIs. */
module XPathConstruction {
/**
* A data-flow node that constructs an XPath expression.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XPathConstruction` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the XPath expressions to be constructed. */
abstract DataFlow::Node getXPath();
}
}
/**
* A data-flow node that executes an XPath expression.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `XPathExecution::Range` instead.
*/
class XPathExecution extends DataFlow::Node instanceof XPathExecution::Range {
/** Gets the argument that specifies the XPath expressions to be executed. */
DataFlow::Node getXPath() { result = super.getXPath() }
}
/** Provides a class for modeling new XPath execution APIs. */
module XPathExecution {
/**
* A data-flow node that executes an XPath expression.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XPathExecution` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the XPath expressions to be executed. */
abstract DataFlow::Node getXPath();
}
}
/**
* A data-flow node that may represent a database object in an ORM system.
*
@@ -1132,3 +1184,106 @@ module TemplateRendering {
abstract DataFlow::Node getTemplate();
}
}
/**
* A data-flow node that constructs a LDAP query.
*
* Often, it is worthy of an alert if an LDAP query is constructed such that
* executing it would be a security risk.
*
* If it is important that the query is executed, use `LdapExecution`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LdapConstruction::Range` instead.
*/
class LdapConstruction extends DataFlow::Node instanceof LdapConstruction::Range {
/** Gets the argument that specifies the query to be constructed. */
DataFlow::Node getQuery() { result = super.getQuery() }
}
/** Provides a class for modeling new LDAP query construction APIs. */
module LdapConstruction {
/**
* A data-flow node that constructs a LDAP query.
*
* Often, it is worthy of an alert if an LDAP query is constructed such that
* executing it would be a security risk.
*
* If it is important that the query is executed, use `LdapExecution`.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `LdapConstruction` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the query to be constructed. */
abstract DataFlow::Node getQuery();
}
}
/**
* A data-flow node that executes LDAP queries.
*
* If the context of interest is such that merely constructing a LDAP query
* would be valuable to report, consider using `LdapConstruction`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LdapExecution::Range` instead.
*/
class LdapExecution extends DataFlow::Node instanceof LdapExecution::Range {
/** Gets the argument that specifies the query to be executed. */
DataFlow::Node getQuery() { result = super.getQuery() }
}
/** Provides a class for modeling new LDAP query execution APIs. */
module LdapExecution {
/**
* A data-flow node that executes LDAP queries.
*
* If the context of interest is such that merely constructing a LDAP query
* would be valuable to report, consider using `LdapConstruction`.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `LdapExecution` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the query to be executed. */
abstract DataFlow::Node getQuery();
}
}
/**
* A data-flow node that collects methods binding a LDAP connection.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LdapBind::Range` instead.
*/
class LdapBind extends DataFlow::Node instanceof LdapBind::Range {
/** Gets the argument containing the binding host */
DataFlow::Node getHost() { result = super.getHost() }
/** Gets the argument containing the binding expression. */
DataFlow::Node getPassword() { result = super.getPassword() }
/** Holds if the binding process use SSL. */
predicate usesSsl() { super.usesSsl() }
}
/** Provides classes for modeling LDAP bind-related APIs. */
module LdapBind {
/**
* A data-flow node that collects methods binding a LDAP connection.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `LdapBind` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument containing the binding host. */
abstract DataFlow::Node getHost();
/** Gets the argument containing the binding expression. */
abstract DataFlow::Node getPassword();
/** Holds if the binding process use SSL. */
abstract predicate usesSsl();
}
}

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

@@ -36,3 +36,4 @@ private import codeql.ruby.frameworks.Mysql2
private import codeql.ruby.frameworks.Pg
private import codeql.ruby.frameworks.Yaml
private import codeql.ruby.frameworks.Sequel
private import codeql.ruby.frameworks.Ldap

View File

@@ -3,6 +3,10 @@
* global (inter-procedural) taint-tracking analyses.
*/
module TaintTracking {
import codeql.ruby.dataflow.internal.tainttracking1.TaintTracking
import codeql.ruby.dataflow.internal.tainttracking1.TaintTrackingParameter::Public
private import codeql.ruby.dataflow.internal.DataFlowImplSpecific
private import codeql.ruby.dataflow.internal.TaintTrackingImplSpecific
private import codeql.dataflow.TaintTracking
import TaintFlowMake<RubyDataFlow, RubyTaintTracking>
import codeql.ruby.dataflow.internal.tainttracking1.TaintTrackingImpl
}

View File

@@ -48,7 +48,7 @@ class UnaryLogicalOperation extends UnaryOperation, TUnaryLogicalOperation { }
* not params.empty?
* ```
*/
class NotExpr extends UnaryLogicalOperation, TNotExpr {
class NotExpr extends UnaryLogicalOperation instanceof NotExprImpl {
final override string getAPrimaryQlClass() { result = "NotExpr" }
}
@@ -118,7 +118,7 @@ class ComplementExpr extends UnaryBitwiseOperation, TComplementExpr {
* defined? some_method
* ```
*/
class DefinedExpr extends UnaryOperation, TDefinedExpr {
class DefinedExpr extends UnaryOperation instanceof DefinedExprImpl {
final override string getAPrimaryQlClass() { result = "DefinedExpr" }
}

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

@@ -123,7 +123,8 @@ private module Cached {
TConstantWriteAccessSynth(Ast::AstNode parent, int i, string value) {
mkSynthChild(ConstantWriteAccessKind(value), parent, i)
} or
TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
TDefinedExprReal(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
TDefinedExprSynth(Ast::AstNode parent, int i) { mkSynthChild(DefinedExprKind(), parent, i) } or
TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or
TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) {
not strictcount(int i | exists(g.getParent().(Ruby::LeftAssignmentList).getChild(i))) = 1
@@ -228,7 +229,8 @@ private module Cached {
TNilLiteralReal(Ruby::Nil g) or
TNilLiteralSynth(Ast::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or
TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
TNotExprReal(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
TNotExprSynth(Ast::AstNode parent, int i) { mkSynthChild(NotExprKind(), parent, i) } or
TOptionalParameter(Ruby::OptionalParameter g) or
TPair(Ruby::Pair g) or
TParenthesizedExpr(Ruby::ParenthesizedStatements g) or
@@ -354,21 +356,21 @@ private module Cached {
TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or TBlockParameter or
TBraceBlockReal or TBreakStmt or TCaseEqExpr or TCaseExpr or TCaseMatchReal or
TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or
TDestructuredParameter or TDivExprReal or TDo or TDoBlock or TElementReference or
TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or TEnsure or TEqExpr or
TExponentExprReal or TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or
TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
TIfReal or TIfModifierExpr or TInClauseReal or TInstanceVariableAccessReal or
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TMatchPattern or
TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or
TNilLiteralReal or TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or
TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or
TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
TComplexLiteral or TDefinedExprReal or TDelimitedSymbolLiteral or
TDestructuredLeftAssignment or TDestructuredParameter or TDivExprReal or TDo or TDoBlock or
TElementReference or TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or
TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFile or TFindPattern or
TFloatLiteral or TForExpr or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashPattern or
THashSplatExpr or THashSplatNilParameter or THashSplatParameter or THereDoc or
TIdentifierMethodCall or TIfReal or TIfModifierExpr or TInClauseReal or
TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLine or
TLocalVariableAccessReal or TLogicalAndExprReal or TLogicalOrExprReal or TMethod or
TMatchPattern or TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or
TNextStmt or TNilLiteralReal or TNoRegExpMatchExpr or TNotExprReal or TOptionalParameter or
TPair or TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or
TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or
TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
@@ -438,7 +440,7 @@ private module Cached {
n = TClassVariableAccessReal(result, _) or
n = TComplementExpr(result) or
n = TComplexLiteral(result) or
n = TDefinedExpr(result) or
n = TDefinedExprReal(result) or
n = TDelimitedSymbolLiteral(result) or
n = TDestructuredLeftAssignment(result) or
n = TDivExprReal(result) or
@@ -495,7 +497,7 @@ private module Cached {
n = TNextStmt(result) or
n = TNilLiteralReal(result) or
n = TNoRegExpMatchExpr(result) or
n = TNotExpr(result) or
n = TNotExprReal(result) or
n = TOptionalParameter(result) or
n = TPair(result) or
n = TParenthesizedExpr(result) or
@@ -585,6 +587,8 @@ private module Cached {
or
result = TConstantWriteAccessSynth(parent, i, _)
or
result = TDefinedExprSynth(parent, i)
or
result = TDivExprSynth(parent, i)
or
result = TElseSynth(parent, i)
@@ -617,6 +621,8 @@ private module Cached {
or
result = TNilLiteralSynth(parent, i)
or
result = TNotExprSynth(parent, i)
or
result = TRangeLiteralSynth(parent, i, _)
or
result = TRShiftExprSynth(parent, i)
@@ -789,10 +795,14 @@ class TNamespace = TClassDeclaration or TModuleDeclaration;
class TOperation = TUnaryOperation or TBinaryOperation or TAssignment;
class TDefinedExpr = TDefinedExprReal or TDefinedExprSynth;
class TUnaryOperation =
TUnaryLogicalOperation or TUnaryArithmeticOperation or TUnaryBitwiseOperation or TDefinedExpr or
TSplatExpr or THashSplatExpr;
class TNotExpr = TNotExprReal or TNotExprSynth;
class TUnaryLogicalOperation = TNotExpr;
class TUnaryArithmeticOperation = TUnaryPlusExpr or TUnaryMinusExpr;

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

@@ -35,6 +35,16 @@ class UnaryOperationGenerated extends UnaryOperationImpl {
final override string getOperatorImpl() { result = g.getOperator() }
}
abstract class NotExprImpl extends UnaryOperationImpl, TNotExpr { }
class NotExprReal extends NotExprImpl, UnaryOperationGenerated, TNotExprReal { }
class NotExprSynth extends NotExprImpl, TNotExprSynth {
final override string getOperatorImpl() { result = "!" }
final override Expr getOperandImpl() { synthChild(this, 0, result) }
}
class SplatExprReal extends UnaryOperationImpl, TSplatExprReal {
private Ruby::SplatArgument g;
@@ -67,6 +77,16 @@ class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr {
final override string getOperatorImpl() { result = "**" }
}
abstract class DefinedExprImpl extends UnaryOperationImpl, TDefinedExpr { }
class DefinedExprReal extends DefinedExprImpl, UnaryOperationGenerated, TDefinedExprReal { }
class DefinedExprSynth extends DefinedExprImpl, TDefinedExprSynth {
final override string getOperatorImpl() { result = "defined?" }
final override Expr getOperandImpl() { synthChild(this, 0, result) }
}
abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinaryOperation {
abstract Stmt getLeftOperandImpl();

View File

@@ -21,6 +21,7 @@ newtype SynthKind =
BraceBlockKind() or
CaseMatchKind() or
ClassVariableAccessKind(ClassVariable v) or
DefinedExprKind() or
DivExprKind() or
ElseKind() or
ExponentExprKind() or
@@ -40,6 +41,7 @@ newtype SynthKind =
ModuloExprKind() or
MulExprKind() or
NilLiteralKind() or
NotExprKind() or
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
RShiftExprKind() or
SimpleParameterKind() or
@@ -1258,6 +1260,7 @@ private module HashLiteralDesugar {
* ```
* desugars to, roughly,
* ```rb
* if not defined? x then x = nil end
* xs.each { |__synth__0| x = __synth__0; <loop_body> }
* ```
*
@@ -1267,58 +1270,160 @@ private module HashLiteralDesugar {
* scoped to the synthesized block.
*/
private module ForLoopDesugar {
private Ruby::AstNode getForLoopPatternChild(Ruby::For for) {
result = for.getPattern()
or
result.getParent() = getForLoopPatternChild(for)
}
/** Holds if `n` is an access to variable `v` in the pattern of `for`. */
pragma[nomagic]
private predicate forLoopVariableAccess(Ruby::For for, Ruby::AstNode n, VariableReal v) {
n = getForLoopPatternChild(for) and
access(n, v)
}
/** Holds if `v` is the `i`th iteration variable of `for`. */
private predicate forLoopVariable(Ruby::For for, VariableReal v, int i) {
v =
rank[i + 1](VariableReal v0, Ruby::AstNode n, Location l |
forLoopVariableAccess(for, n, v0) and
l = n.getLocation()
|
v0 order by l.getStartLine(), l.getStartColumn()
)
}
/** Gets the number of iteration variables of `for`. */
private int forLoopVariableCount(Ruby::For for) {
result = count(int j | forLoopVariable(for, _, j))
}
private Ruby::For toTsFor(ForExpr for) { for = TForExpr(result) }
/**
* Synthesizes an assignment
* ```rb
* if not defined? v then v = nil end
* ```
* anchored at index `rootIndex` of `root`.
*/
bindingset[root, rootIndex, v]
private predicate nilAssignUndefined(
AstNode root, int rootIndex, AstNode parent, int i, Child child, VariableReal v
) {
parent = root and
i = rootIndex and
child = SynthChild(IfKind())
or
exists(AstNode if_ | if_ = TIfSynth(root, rootIndex) |
parent = if_ and
i = 0 and
child = SynthChild(NotExprKind())
or
exists(AstNode not_ | not_ = TNotExprSynth(if_, 0) |
parent = not_ and
i = 0 and
child = SynthChild(DefinedExprKind())
or
parent = TDefinedExprSynth(not_, 0) and
i = 0 and
child = SynthChild(LocalVariableAccessRealKind(v))
)
or
parent = if_ and
i = 1 and
child = SynthChild(AssignExprKind())
or
parent = TAssignExprSynth(if_, 1) and
(
i = 0 and
child = SynthChild(LocalVariableAccessRealKind(v))
or
i = 1 and
child = SynthChild(NilLiteralKind())
)
)
}
pragma[nomagic]
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
exists(ForExpr for |
// each call
parent = for and
i = -1 and
child = SynthChild(MethodCallKind("each", false, 0))
child = SynthChild(StmtSequenceKind())
or
exists(MethodCall eachCall | eachCall = TMethodCallSynth(for, -1, "each", false, 0) |
// receiver
parent = eachCall and
i = 0 and
child = childRef(for.getValue()) // value is the Enumerable
exists(AstNode seq | seq = TStmtSequenceSynth(for, -1) |
exists(VariableReal v, int j | forLoopVariable(toTsFor(for), v, j) |
nilAssignUndefined(seq, j, parent, i, child, v)
)
or
parent = eachCall and
i = 1 and
child = SynthChild(BraceBlockKind())
or
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
// block params
parent = block and
i = 0 and
child = SynthChild(SimpleParameterKind())
exists(int numberOfVars | numberOfVars = forLoopVariableCount(toTsFor(for)) |
// each call
parent = seq and
i = numberOfVars and
child = SynthChild(MethodCallKind("each", false, 0))
or
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
parent = param and
exists(MethodCall eachCall |
eachCall = TMethodCallSynth(seq, numberOfVars, "each", false, 0)
|
// receiver
parent = eachCall and
i = 0 and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
child = childRef(for.getValue()) // value is the Enumerable
or
// assignment to pattern from for loop to synth parameter
parent = block and
parent = eachCall and
i = 1 and
child = SynthChild(AssignExprKind())
child = SynthChild(BraceBlockKind())
or
parent = TAssignExprSynth(block, 1) and
(
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
// block params
parent = block and
i = 0 and
child = childRef(for.getPattern())
child = SynthChild(SimpleParameterKind())
or
i = 1 and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
parent = param and
i = 0 and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
or
// assignment to pattern from for loop to synth parameter
parent = block and
i = 1 and
child = SynthChild(AssignExprKind())
or
parent = TAssignExprSynth(block, 1) and
(
i = 0 and
child = childRef(for.getPattern())
or
i = 1 and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
)
)
or
// rest of block body
parent = block and
child = childRef(for.getBody().(Do).getStmt(i - 2))
)
)
or
// rest of block body
parent = block and
child = childRef(for.getBody().(Do).getStmt(i - 2))
)
)
)
}
pragma[nomagic]
private predicate isDesugaredInitNode(ForExpr for, Variable v, AstNode n) {
exists(StmtSequence seq, AssignExpr ae |
seq = for.getDesugared() and
n = seq.getStmt(_) and
ae = n.(IfExpr).getThen() and
v = ae.getLeftOperand().getAVariable()
)
or
isDesugaredInitNode(for, v, n.getParent())
}
private class ForLoopSynthesis extends Synthesis {
final override predicate child(AstNode parent, int i, Child child) {
forLoopSynthesis(parent, i, child)
@@ -1338,6 +1443,14 @@ private module ForLoopDesugar {
final override predicate excludeFromControlFlowTree(AstNode n) {
n = any(ForExpr for).getBody()
}
final override predicate location(AstNode n, Location l) {
exists(ForExpr for, Ruby::AstNode access, Variable v |
forLoopVariableAccess(toTsFor(for), access, v) and
isDesugaredInitNode(for, v, n) and
l = access.getLocation()
)
}
}
}

View File

@@ -1,5 +1,6 @@
private import TreeSitter
private import codeql.ruby.AST
private import codeql.ruby.CFG
private import codeql.ruby.ast.internal.AST
private import codeql.ruby.ast.internal.Parameter
private import codeql.ruby.ast.internal.Pattern
@@ -364,22 +365,11 @@ private module Cached {
cached
predicate isCapturedAccess(LocalVariableAccess access) {
exists(Scope scope1, Scope scope2 |
exists(Scope scope1, CfgScope scope2 |
scope1 = access.getVariable().getDeclaringScope() and
scope2 = access.getCfgScope() and
scope1 != scope2
|
if access instanceof SelfVariableAccess
then
// ```
// class C
// def self.m // not a captured access
// end
// end
// ```
not scope2 instanceof Toplevel or
not access = any(SingletonMethod m).getObject()
else any()
scope1 != scope2 and
not scope2 instanceof Toplevel
)
}

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,44 +35,18 @@ class ExitNode extends CfgNode, TExitNode {
* (dead) code or not important for control flow, and multiple when there are different
* splits for the AST node.
*/
class AstCfgNode extends CfgNode, TElementNode {
class AstCfgNode extends CfgNode, CfgImpl::AstCfgNode {
/** Gets the name of the primary QL class for this node. */
override string getAPrimaryQlClass() { result = "AstCfgNode" }
private Splits splits;
AstNode e;
AstCfgNode() { this = TElementNode(_, e, splits) }
final override AstNode getNode() { result = e }
override Location getLocation() { result = e.getLocation() }
final override string toString() {
exists(string s | s = e.toString() |
result = "[" + this.getSplitsString() + "] " + s
or
not exists(this.getSplitsString()) and result = s
)
}
/** Gets a comma-separated list of strings for each split in this node, if any. */
final string getSplitsString() {
result = splits.toString() and
result != ""
}
/** Gets a split for this control flow node, if any. */
final Split getASplit() { result = splits.getASplit() }
}
/** A control-flow node that wraps an AST expression. */
class ExprCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "ExprCfgNode" }
override Expr e;
Expr e;
ExprCfgNode() { e = this.getNode() }
ExprCfgNode() { e = this.getAstNode() }
/** Gets the underlying expression. */
Expr getExpr() { result = e }
@@ -123,12 +61,12 @@ class ReturningCfgNode extends AstCfgNode {
ReturningStmt s;
ReturningCfgNode() { s = this.getNode() }
ReturningCfgNode() { s = this.getAstNode() }
/** Gets the node of the returned value, if any. */
ExprCfgNode getReturnedValueNode() {
result = this.getAPredecessor() and
result.getNode() = s.getValue()
result.getAstNode() = s.getValue()
}
}
@@ -136,17 +74,19 @@ class ReturningCfgNode extends AstCfgNode {
class StringComponentCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "StringComponentCfgNode" }
StringComponentCfgNode() { this.getNode() instanceof StringComponent }
StringComponentCfgNode() { this.getAstNode() instanceof StringComponent }
/** Gets the constant value of this string component. */
ConstantValue getConstantValue() { result = this.getNode().(StringComponent).getConstantValue() }
ConstantValue getConstantValue() {
result = this.getAstNode().(StringComponent).getConstantValue()
}
}
/** A control-flow node that wraps a `RegExpComponent` AST expression. */
class RegExpComponentCfgNode extends StringComponentCfgNode {
override string getAPrimaryQlClass() { result = "RegExpComponentCfgNode" }
RegExpComponentCfgNode() { e instanceof RegExpComponent }
RegExpComponentCfgNode() { this.getAstNode() instanceof RegExpComponent }
}
private AstNode desugar(AstNode n) {
@@ -179,7 +119,7 @@ abstract private class ChildMapping extends AstNode {
cached
predicate hasCfgChild(AstNode child, CfgNode cfn, CfgNode cfnChild) {
this.reachesBasicBlock(child, cfn, cfnChild.getBasicBlock()) and
cfnChild.getNode() = desugar(child)
cfnChild.getAstNode() = desugar(child)
}
}
@@ -196,7 +136,7 @@ abstract private class ExprChildMapping extends Expr, ChildMapping {
exists(BasicBlock mid |
this.reachesBasicBlock(child, cfn, mid) and
bb = mid.getAPredecessor() and
not mid.getANode().getNode() = child
not mid.getANode().getAstNode() = child
)
}
}
@@ -210,13 +150,13 @@ abstract private class NonExprChildMapping extends ChildMapping {
pragma[nomagic]
override predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb) {
this.relevantChild(child) and
cfn.getNode() = this and
cfn.getAstNode() = this and
bb.getANode() = cfn
or
exists(BasicBlock mid |
this.reachesBasicBlock(child, cfn, mid) and
bb = mid.getASuccessor() and
not mid.getANode().getNode() = child
not mid.getANode().getAstNode() = child
)
}
}
@@ -440,9 +380,11 @@ module ExprNodes {
/** A control-flow node that wraps an `InClause` AST expression. */
class InClauseCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "InClauseCfgNode" }
private InClauseChildMapping e;
override InClauseChildMapping e;
InClauseCfgNode() { e = this.getAstNode() }
override string getAPrimaryQlClass() { result = "InClauseCfgNode" }
/** Gets the pattern in this `in`-clause. */
final AstCfgNode getPattern() { e.hasCfgChild(e.getPattern(), this, result) }
@@ -460,56 +402,60 @@ module ExprNodes {
predicate patternReachesBasicBlock(int i, CfgNode cfnPattern, BasicBlock bb) {
exists(Expr pattern |
pattern = this.getPattern(i) and
cfnPattern.getNode() = pattern and
cfnPattern.getAstNode() = pattern and
bb.getANode() = cfnPattern
)
or
exists(BasicBlock mid |
this.patternReachesBasicBlock(i, cfnPattern, mid) and
bb = mid.getASuccessor() and
not mid.getANode().getNode() = this
not mid.getANode().getAstNode() = this
)
}
predicate bodyReachesBasicBlock(CfgNode cfnBody, BasicBlock bb) {
exists(Stmt body |
body = this.getBody() and
cfnBody.getNode() = body and
cfnBody.getAstNode() = body and
bb.getANode() = cfnBody
)
or
exists(BasicBlock mid |
this.bodyReachesBasicBlock(cfnBody, mid) and
bb = mid.getAPredecessor() and
not mid.getANode().getNode() = this
not mid.getANode().getAstNode() = this
)
}
}
/** A control-flow node that wraps a `WhenClause` AST expression. */
class WhenClauseCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "WhenClauseCfgNode" }
private WhenClauseChildMapping e;
override WhenClauseChildMapping e;
WhenClauseCfgNode() { e = this.getAstNode() }
override string getAPrimaryQlClass() { result = "WhenClauseCfgNode" }
/** Gets the body of this `when`-clause. */
final ExprCfgNode getBody() {
result.getNode() = desugar(e.getBody()) and
result.getAstNode() = desugar(e.getBody()) and
e.bodyReachesBasicBlock(result, this.getBasicBlock())
}
/** Gets the `i`th pattern this `when`-clause. */
final ExprCfgNode getPattern(int i) {
result.getNode() = desugar(e.getPattern(i)) and
result.getAstNode() = desugar(e.getPattern(i)) and
e.patternReachesBasicBlock(i, result, this.getBasicBlock())
}
}
/** A control-flow node that wraps a `CasePattern`. */
class CasePatternCfgNode extends AstCfgNode {
override string getAPrimaryQlClass() { result = "CasePatternCfgNode" }
CasePattern e;
override CasePattern e;
CasePatternCfgNode() { e = this.getAstNode() }
override string getAPrimaryQlClass() { result = "CasePatternCfgNode" }
}
private class ArrayPatternChildMapping extends NonExprChildMapping, ArrayPattern {
@@ -871,7 +817,7 @@ module ExprNodes {
/** A control-flow node that wraps an `InstanceVariableReadAccess` AST expression. */
class InstanceVariableReadAccessCfgNode extends InstanceVariableAccessCfgNode {
InstanceVariableReadAccessCfgNode() { this.getNode() instanceof InstanceVariableReadAccess }
InstanceVariableReadAccessCfgNode() { this.getAstNode() instanceof InstanceVariableReadAccess }
override string getAPrimaryQlClass() { result = "InstanceVariableReadAccessCfgNode" }
@@ -880,7 +826,9 @@ module ExprNodes {
/** A control-flow node that wraps an `InstanceVariableWriteAccess` AST expression. */
class InstanceVariableWriteAccessCfgNode extends InstanceVariableAccessCfgNode {
InstanceVariableWriteAccessCfgNode() { this.getNode() instanceof InstanceVariableWriteAccess }
InstanceVariableWriteAccessCfgNode() {
this.getAstNode() instanceof InstanceVariableWriteAccess
}
override string getAPrimaryQlClass() { result = "InstanceVariableWriteAccessCfgNode" }
@@ -891,7 +839,9 @@ module ExprNodes {
class StringInterpolationComponentCfgNode extends StringComponentCfgNode, StmtSequenceCfgNode {
override string getAPrimaryQlClass() { result = "StringInterpolationComponentCfgNode" }
StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent }
StringInterpolationComponentCfgNode() {
this.getAstNode() instanceof StringInterpolationComponent
}
final override ConstantValue getConstantValue() {
result = StmtSequenceCfgNode.super.getConstantValue()
@@ -902,7 +852,9 @@ module ExprNodes {
class RegExpInterpolationComponentCfgNode extends RegExpComponentCfgNode, StmtSequenceCfgNode {
override string getAPrimaryQlClass() { result = "RegExpInterpolationComponentCfgNode" }
RegExpInterpolationComponentCfgNode() { this.getNode() instanceof RegExpInterpolationComponent }
RegExpInterpolationComponentCfgNode() {
this.getAstNode() instanceof RegExpInterpolationComponent
}
final override ConstantValue getConstantValue() {
result = StmtSequenceCfgNode.super.getConstantValue()

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,22 +68,20 @@ class SuccessorType extends TSuccessorType {
/** Provides different types of control flow successor types. */
module SuccessorTypes {
/** A normal control flow successor. */
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
class NormalSuccessor extends SuccessorType, CfgImpl::TSuccessorSuccessor {
final override string toString() { result = "successor" }
}
/**
* A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`),
* an emptiness successor (`EmptinessSuccessor`), or a matching successor
* (`MatchingSuccessor`)
* A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`)
* or a matching successor (`MatchingSuccessor`)
*/
class ConditionalSuccessor extends SuccessorType {
boolean value;
ConditionalSuccessor() {
this = TBooleanSuccessor(value) or
this = TEmptinessSuccessor(value) or
this = TMatchingSuccessor(value)
this = CfgImpl::TBooleanSuccessor(value) or
this = CfgImpl::TMatchingSuccessor(value)
}
/** Gets the Boolean value of this successor. */
@@ -125,42 +105,7 @@ module SuccessorTypes {
*
* `x >= 0` has both a `true` successor and a `false` successor.
*/
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { }
/**
* An emptiness control flow successor.
*
* For example, this program fragment:
*
* ```rb
* for arg in args do
* puts arg
* end
* puts "done";
* ```
*
* has a control flow graph containing emptiness successors:
*
* ```
* args
* |
* for------<-----
* / \ \
* / \ |
* / \ |
* / \ |
* empty non-empty |
* | \ |
* puts "done" \ |
* arg |
* | |
* puts arg |
* \___/
* ```
*/
class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor {
override string toString() { if value = true then result = "empty" else result = "non-empty" }
}
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor { }
/**
* A matching control flow successor.
@@ -189,7 +134,7 @@ module SuccessorTypes {
* puts "one" puts "not one"
* ```
*/
class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor {
class MatchingSuccessor extends ConditionalSuccessor, CfgImpl::TMatchingSuccessor {
override string toString() { if value = true then result = "match" else result = "no-match" }
}
@@ -207,7 +152,7 @@ module SuccessorTypes {
* The exit node of `sum` is a `return` successor of the `return x + y`
* statement.
*/
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
class ReturnSuccessor extends SuccessorType, CfgImpl::TReturnSuccessor {
final override string toString() { result = "return" }
}
@@ -230,7 +175,7 @@ module SuccessorTypes {
*
* The node `puts "done"` is `break` successor of the node `break`.
*/
class BreakSuccessor extends SuccessorType, TBreakSuccessor {
class BreakSuccessor extends SuccessorType, CfgImpl::TBreakSuccessor {
final override string toString() { result = "break" }
}
@@ -253,7 +198,7 @@ module SuccessorTypes {
*
* The node `x >= 0` is `next` successor of the node `next`.
*/
class NextSuccessor extends SuccessorType, TNextSuccessor {
class NextSuccessor extends SuccessorType, CfgImpl::TNextSuccessor {
final override string toString() { result = "next" }
}
@@ -278,7 +223,7 @@ module SuccessorTypes {
*
* The node `x -= 1` is `redo` successor of the node `redo`.
*/
class RedoSuccessor extends SuccessorType, TRedoSuccessor {
class RedoSuccessor extends SuccessorType, CfgImpl::TRedoSuccessor {
final override string toString() { result = "redo" }
}
@@ -302,7 +247,7 @@ module SuccessorTypes {
*
* The node `puts "Retry"` is `retry` successor of the node `retry`.
*/
class RetrySuccessor extends SuccessorType, TRetrySuccessor {
class RetrySuccessor extends SuccessorType, CfgImpl::TRetrySuccessor {
final override string toString() { result = "retry" }
}
@@ -323,7 +268,7 @@ module SuccessorTypes {
* The exit node of `m` is an exceptional successor of the node
* `raise "x > 2"`.
*/
class RaiseSuccessor extends SuccessorType, TRaiseSuccessor {
class RaiseSuccessor extends SuccessorType, CfgImpl::TRaiseSuccessor {
final override string toString() { result = "raise" }
}
@@ -344,7 +289,7 @@ module SuccessorTypes {
* The exit node of `m` is an exit successor of the node
* `exit 1`.
*/
class ExitSuccessor extends SuccessorType, TExitSuccessor {
class ExitSuccessor extends SuccessorType, CfgImpl::TExitSuccessor {
final override string toString() { result = "exit" }
}
}

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

@@ -264,7 +264,7 @@ private predicate selfInToplevel(SelfVariable self, Module m) {
private predicate asModulePattern(SsaDefinitionExtNode def, Module m) {
exists(AsPattern ap |
m = resolveConstantReadAccess(ap.getPattern()) and
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getNode() =
def.getDefinitionExt().(Ssa::WriteDefinition).getWriteAccess().getAstNode() =
ap.getVariableAccess()
)
}
@@ -438,7 +438,8 @@ private module Cached {
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
} or
THashSplatArgumentPosition() or
TSplatAllArgumentPosition() or
TSplatArgumentPosition(int pos) { exists(Call c | c.getArgument(pos) instanceof SplatExpr) } or
TSynthSplatArgumentPosition() or
TAnyArgumentPosition() or
TAnyKeywordArgumentPosition()
@@ -467,7 +468,11 @@ private module Cached {
// position for multiple parameter nodes in the same callable, we introduce this
// synthetic parameter position.
TSynthHashSplatParameterPosition() or
TSplatAllParameterPosition() or
TSplatParameterPosition(int pos) {
exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter)
} or
TSynthSplatParameterPosition() or
TSynthArgSplatParameterPosition() or
TAnyParameterPosition() or
TAnyKeywordParameterPosition()
}
@@ -1288,7 +1293,12 @@ class ParameterPosition extends TParameterPosition {
predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() }
predicate isSplatAll() { this = TSplatAllParameterPosition() }
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
// A fake position to indicate that this parameter node holds content from a synth arg splat node
predicate isSynthArgSplat() { this = TSynthArgSplatParameterPosition() }
predicate isSplat(int n) { this = TSplatParameterPosition(n) }
/**
* Holds if this position represents any parameter, except `self` parameters. This
@@ -1315,11 +1325,15 @@ class ParameterPosition extends TParameterPosition {
or
this.isSynthHashSplat() and result = "synthetic **"
or
this.isSplatAll() and result = "*"
or
this.isAny() and result = "any"
or
this.isAnyNamed() and result = "any-named"
or
this.isSynthSplat() and result = "synthetic *"
or
this.isSynthArgSplat() and result = "synthetic * (from *args)"
or
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
}
}
@@ -1352,7 +1366,9 @@ class ArgumentPosition extends TArgumentPosition {
*/
predicate isHashSplat() { this = THashSplatArgumentPosition() }
predicate isSplatAll() { this = TSplatAllArgumentPosition() }
predicate isSplat(int n) { this = TSplatArgumentPosition(n) }
predicate isSynthSplat() { this = TSynthSplatArgumentPosition() }
/** Gets a textual representation of this position. */
string toString() {
@@ -1370,7 +1386,9 @@ class ArgumentPosition extends TArgumentPosition {
or
this.isHashSplat() and result = "**"
or
this.isSplatAll() and result = "*"
this.isSynthSplat() and result = "synthetic *"
or
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
}
}
@@ -1399,7 +1417,14 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
or
ppos.isSynthHashSplat() and apos.isHashSplat()
or
ppos.isSplatAll() and apos.isSplatAll()
ppos.isSplat(0) and apos.isSynthSplat()
or
ppos.isSynthSplat() and apos.isSplat(0)
or
apos.isSynthSplat() and ppos.isSynthArgSplat()
or
// Exact splat match
exists(int n | apos.isSplat(n) and ppos.isSplat(n))
or
ppos.isAny() and argumentPositionIsNotSelf(apos)
or
@@ -1416,6 +1441,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
* This is a temporary hook to support technical debt in the Go language; do not use.
*/
pragma[inline]
predicate golangSpecificParamArgFilter(DataFlowCall call, DataFlow::Node p, ArgumentNode arg) {
predicate golangSpecificParamArgFilter(
DataFlowCall call, DataFlow::ParameterNode p, ArgumentNode arg
) {
any()
}

File diff suppressed because it is too large Load Diff

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -313,6 +315,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -313,6 +315,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -1,299 +0,0 @@
/**
* Provides consistency queries for checking invariants in the language-specific
* data-flow classes and predicates.
*/
private import DataFlowImplSpecific::Private
private import DataFlowImplSpecific::Public
private import tainttracking1.TaintTrackingParameter::Private
private import tainttracking1.TaintTrackingParameter::Public
module Consistency {
private newtype TConsistencyConfiguration = MkConsistencyConfiguration()
/** A class for configuring the consistency queries. */
class ConsistencyConfiguration extends TConsistencyConfiguration {
string toString() { none() }
/** Holds if `n` should be excluded from the consistency test `uniqueEnclosingCallable`. */
predicate uniqueEnclosingCallableExclude(Node n) { none() }
/** Holds if `call` should be excluded from the consistency test `uniqueCallEnclosingCallable`. */
predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) { none() }
/** Holds if `n` should be excluded from the consistency test `uniqueNodeLocation`. */
predicate uniqueNodeLocationExclude(Node n) { none() }
/** Holds if `n` should be excluded from the consistency test `missingLocation`. */
predicate missingLocationExclude(Node n) { none() }
/** Holds if `n` should be excluded from the consistency test `postWithInFlow`. */
predicate postWithInFlowExclude(Node n) { none() }
/** Holds if `n` should be excluded from the consistency test `argHasPostUpdate`. */
predicate argHasPostUpdateExclude(ArgumentNode n) { none() }
/** Holds if `n` should be excluded from the consistency test `reverseRead`. */
predicate reverseReadExclude(Node n) { none() }
/** Holds if `n` should be excluded from the consistency test `postHasUniquePre`. */
predicate postHasUniquePreExclude(PostUpdateNode n) { none() }
/** Holds if `n` should be excluded from the consistency test `uniquePostUpdate`. */
predicate uniquePostUpdateExclude(Node n) { none() }
/** Holds if `(call, ctx)` should be excluded from the consistency test `viableImplInCallContextTooLargeExclude`. */
predicate viableImplInCallContextTooLargeExclude(
DataFlowCall call, DataFlowCall ctx, DataFlowCallable callable
) {
none()
}
/** Holds if `(c, pos, p)` should be excluded from the consistency test `uniqueParameterNodeAtPosition`. */
predicate uniqueParameterNodeAtPositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
none()
}
/** Holds if `(c, pos, p)` should be excluded from the consistency test `uniqueParameterNodePosition`. */
predicate uniqueParameterNodePositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {
none()
}
/** Holds if `n` should be excluded from the consistency test `identityLocalStep`. */
predicate identityLocalStepExclude(Node n) { none() }
}
private class RelevantNode extends Node {
RelevantNode() {
this instanceof ArgumentNode or
this instanceof ParameterNode or
this instanceof ReturnNode or
this = getAnOutNode(_, _) or
simpleLocalFlowStep(this, _) or
simpleLocalFlowStep(_, this) or
jumpStep(this, _) or
jumpStep(_, this) or
storeStep(this, _, _) or
storeStep(_, _, this) or
readStep(this, _, _) or
readStep(_, _, this) or
defaultAdditionalTaintStep(this, _) or
defaultAdditionalTaintStep(_, this)
}
}
query predicate uniqueEnclosingCallable(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(nodeGetEnclosingCallable(n)) and
c != 1 and
not any(ConsistencyConfiguration conf).uniqueEnclosingCallableExclude(n) and
msg = "Node should have one enclosing callable but has " + c + "."
)
}
query predicate uniqueCallEnclosingCallable(DataFlowCall call, string msg) {
exists(int c |
c = count(call.getEnclosingCallable()) and
c != 1 and
not any(ConsistencyConfiguration conf).uniqueCallEnclosingCallableExclude(call) and
msg = "Call should have one enclosing callable but has " + c + "."
)
}
query predicate uniqueType(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(getNodeType(n)) and
c != 1 and
msg = "Node should have one type but has " + c + "."
)
}
query predicate uniqueNodeLocation(Node n, string msg) {
exists(int c |
c =
count(string filepath, int startline, int startcolumn, int endline, int endcolumn |
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
) and
c != 1 and
not any(ConsistencyConfiguration conf).uniqueNodeLocationExclude(n) and
msg = "Node should have one location but has " + c + "."
)
}
query predicate missingLocation(string msg) {
exists(int c |
c =
strictcount(Node n |
not n.hasLocationInfo(_, _, _, _, _) and
not any(ConsistencyConfiguration conf).missingLocationExclude(n)
) and
msg = "Nodes without location: " + c
)
}
query predicate uniqueNodeToString(Node n, string msg) {
exists(int c |
c = count(n.toString()) and
c != 1 and
msg = "Node should have one toString but has " + c + "."
)
}
query predicate missingToString(string msg) {
exists(int c |
c = strictcount(Node n | not exists(n.toString())) and
msg = "Nodes without toString: " + c
)
}
query predicate parameterCallable(ParameterNode p, string msg) {
exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
msg = "Callable mismatch for parameter."
}
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
simpleLocalFlowStep(n1, n2) and
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Local flow step does not preserve enclosing callable."
}
query predicate readStepIsLocal(Node n1, Node n2, string msg) {
readStep(n1, _, n2) and
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Read step does not preserve enclosing callable."
}
query predicate storeStepIsLocal(Node n1, Node n2, string msg) {
storeStep(n1, _, n2) and
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
msg = "Store step does not preserve enclosing callable."
}
private DataFlowType typeRepr() { result = getNodeType(_) }
query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
t = typeRepr() and
not compatibleTypes(t, t) and
msg = "Type compatibility predicate is not reflexive."
}
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
isUnreachableInCall(n, call) and
exists(DataFlowCallable c |
c = nodeGetEnclosingCallable(n) and
not viableCallable(call) = c
) and
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
}
query predicate localCallNodes(DataFlowCall call, Node n, string msg) {
(
n = getAnOutNode(call, _) and
msg = "OutNode and call does not share enclosing callable."
or
n.(ArgumentNode).argumentOf(call, _) and
msg = "ArgumentNode and call does not share enclosing callable."
) and
nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
}
// This predicate helps the compiler forget that in some languages
// it is impossible for a result of `getPreUpdateNode` to be an
// instance of `PostUpdateNode`.
private Node getPre(PostUpdateNode n) {
result = n.getPreUpdateNode()
or
none()
}
query predicate postIsNotPre(PostUpdateNode n, string msg) {
getPre(n) = n and
msg = "PostUpdateNode should not equal its pre-update node."
}
query predicate postHasUniquePre(PostUpdateNode n, string msg) {
not any(ConsistencyConfiguration conf).postHasUniquePreExclude(n) and
exists(int c |
c = count(n.getPreUpdateNode()) and
c != 1 and
msg = "PostUpdateNode should have one pre-update node but has " + c + "."
)
}
query predicate uniquePostUpdate(Node n, string msg) {
not any(ConsistencyConfiguration conf).uniquePostUpdateExclude(n) and
1 < strictcount(PostUpdateNode post | post.getPreUpdateNode() = n) and
msg = "Node has multiple PostUpdateNodes."
}
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
msg = "PostUpdateNode does not share callable with its pre-update node."
}
private predicate hasPost(Node n) { exists(PostUpdateNode post | post.getPreUpdateNode() = n) }
query predicate reverseRead(Node n, string msg) {
exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
not any(ConsistencyConfiguration conf).reverseReadExclude(n) and
msg = "Origin of readStep is missing a PostUpdateNode."
}
query predicate argHasPostUpdate(ArgumentNode n, string msg) {
not hasPost(n) and
not any(ConsistencyConfiguration c).argHasPostUpdateExclude(n) and
msg = "ArgumentNode is missing PostUpdateNode."
}
// This predicate helps the compiler forget that in some languages
// it is impossible for a `PostUpdateNode` to be the target of
// `simpleLocalFlowStep`.
private predicate isPostUpdateNode(Node n) { n instanceof PostUpdateNode or none() }
query predicate postWithInFlow(Node n, string msg) {
isPostUpdateNode(n) and
not clearsContent(n, _) and
simpleLocalFlowStep(_, n) and
not any(ConsistencyConfiguration c).postWithInFlowExclude(n) and
msg = "PostUpdateNode should not be the target of local flow."
}
query predicate viableImplInCallContextTooLarge(
DataFlowCall call, DataFlowCall ctx, DataFlowCallable callable
) {
callable = viableImplInCallContext(call, ctx) and
not callable = viableCallable(call) and
not any(ConsistencyConfiguration c).viableImplInCallContextTooLargeExclude(call, ctx, callable)
}
query predicate uniqueParameterNodeAtPosition(
DataFlowCallable c, ParameterPosition pos, Node p, string msg
) {
not any(ConsistencyConfiguration conf).uniqueParameterNodeAtPositionExclude(c, pos, p) and
isParameterNode(p, c, pos) and
not exists(unique(Node p0 | isParameterNode(p0, c, pos))) and
msg = "Parameters with overlapping positions."
}
query predicate uniqueParameterNodePosition(
DataFlowCallable c, ParameterPosition pos, Node p, string msg
) {
not any(ConsistencyConfiguration conf).uniqueParameterNodePositionExclude(c, pos, p) and
isParameterNode(p, c, pos) and
not exists(unique(ParameterPosition pos0 | isParameterNode(p, c, pos0))) and
msg = "Parameter node with multiple positions."
}
query predicate uniqueContentApprox(Content c, string msg) {
not exists(unique(ContentApprox approx | approx = getContentApprox(c))) and
msg = "Non-unique content approximation."
}
query predicate identityLocalStep(Node n, string msg) {
simpleLocalFlowStep(n, n) and
not any(ConsistencyConfiguration c).identityLocalStepExclude(n) and
msg = "Node steps to itself"
}
}

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -313,6 +315,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

@@ -276,6 +276,8 @@ private module Config implements FullStateConfigSig {
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
}
predicate isSink(Node sink) { none() }
predicate isSink(Node sink, FlowState state) {
getConfig(state).isSink(sink, getState(state))
or
@@ -313,6 +315,8 @@ private module Config implements FullStateConfigSig {
any(Configuration config).allowImplicitRead(node, c)
}
predicate neverSkip(Node node) { none() }
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }

View File

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

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, _)
)
}
@@ -137,22 +137,6 @@ module LocalFlow {
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
}
/**
* Holds if `nodeFrom -> nodeTo` is a step from a parameter to a capture entry node for
* that parameter.
*
* This is intended to recover from flow not currently recognised by ordinary capture flow.
*/
predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) {
exists(Ssa::CapturedEntryDefinition def |
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
or
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
)
}
/**
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
* involving SSA definition `def`.
@@ -203,8 +187,8 @@ module LocalFlow {
exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n |
nodeFrom = n and
exprTo = nodeTo.asExpr() and
n.getReturningNode().getNode() instanceof BreakStmt and
exprTo.getNode() instanceof Loop and
n.getReturningNode().getAstNode() instanceof BreakStmt and
exprTo.getAstNode() instanceof Loop and
nodeTo.asExpr().getAPredecessor(any(SuccessorTypes::BreakSuccessor s)) = n.getReturningNode()
)
or
@@ -245,6 +229,7 @@ private class Argument extends CfgNodes::ExprCfgNode {
not this.getExpr() instanceof BlockArgument and
not this.getExpr().(Pair).getKey().getConstantValue().isSymbol(_) and
not this.getExpr() instanceof HashSplatExpr and
not this.getExpr() instanceof SplatExpr and
arg.isPositional(i)
)
or
@@ -260,9 +245,11 @@ private class Argument extends CfgNodes::ExprCfgNode {
this.getExpr() instanceof HashSplatExpr and
arg.isHashSplat()
or
this = call.getArgument(0) and
this.getExpr() instanceof SplatExpr and
arg.isSplatAll()
exists(int pos |
this = call.getArgument(pos) and
this.getExpr() instanceof SplatExpr and
arg.isSplat(pos)
)
}
/** Holds if this expression is the `i`th argument of `c`. */
@@ -277,6 +264,53 @@ predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) {
not n.getExpr() instanceof ConstantAccess
}
/** Provides logic related to captured variables. */
module VariableCapture {
class CapturedVariable extends LocalVariable {
CapturedVariable() { this.isCaptured() }
CfgScope getCfgScope() {
exists(Scope scope | scope = this.getDeclaringScope() |
result = scope
or
result = scope.(ModuleBase).getCfgScope()
)
}
}
class CapturedSsaDefinitionExt extends SsaImpl::DefinitionExt {
CapturedSsaDefinitionExt() { this.getSourceVariable() instanceof CapturedVariable }
}
/**
* Holds if there is control-flow insensitive data-flow from `node1` to `node2`
* involving a captured variable. Only used in type tracking.
*/
predicate flowInsensitiveStep(Node node1, Node node2) {
exists(CapturedSsaDefinitionExt def, CapturedVariable v |
// From an assignment or implicit initialization of a captured variable to its flow-insensitive node
def = node1.(SsaDefinitionExtNode).getDefinitionExt() and
def.getSourceVariable() = v and
(
def instanceof Ssa::WriteDefinition
or
def instanceof Ssa::SelfDefinition
) and
node2.(CapturedVariableNode).getVariable() = v
or
// From a captured variable node to its flow-sensitive capture nodes
node1.(CapturedVariableNode).getVariable() = v and
def = node2.(SsaDefinitionExtNode).getDefinitionExt() and
def.getSourceVariable() = v and
(
def instanceof Ssa::CapturedCallDefinition
or
def instanceof Ssa::CapturedEntryDefinition
)
)
}
}
/** A collection of cached types and predicates to be evaluated in the same stage. */
cached
private module Cached {
@@ -288,6 +322,7 @@ private module Cached {
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
TReturningNode(CfgNodes::ReturningCfgNode n) or
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
TCapturedVariableNode(VariableCapture::CapturedVariable v) or
TNormalParameterNode(Parameter p) {
p instanceof SimpleParameter or
p instanceof OptionalParameter or
@@ -300,6 +335,19 @@ private module Cached {
TSynthHashSplatParameterNode(DataFlowCallable c) {
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
} or
TSynthSplatParameterNode(DataFlowCallable c) {
exists(c.asCallable()) and // exclude library callables
isParameterNode(_, c, any(ParameterPosition p | p.isPositional(_)))
} or
TSynthSplatArgParameterNode(DataFlowCallable c) {
exists(c.asCallable()) and // exclude library callables
isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_)))
} or
TSynthSplatParameterElementNode(DataFlowCallable c, int n) {
exists(c.asCallable()) and // exclude library callables
isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_))) and
n in [0 .. 10]
} or
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) {
// filter out nodes that clearly don't need post-update nodes
isNonConstantExpr(n) and
@@ -314,11 +362,15 @@ private module Cached {
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
or
c.getAnArgument() instanceof CfgNodes::ExprNodes::PairCfgNode
} or
TSynthSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
exists(Argument arg, ArgumentPosition pos | pos.isPositional(_) | arg.isArgumentOf(c, pos)) and
not exists(Argument arg, ArgumentPosition pos | pos.isSplat(_) | arg.isArgumentOf(c, pos))
}
class TSourceParameterNode =
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
TSynthHashSplatParameterNode;
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode;
cached
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
@@ -377,6 +429,8 @@ private module Cached {
LocalFlow::localFlowSsaInputFromRead(exprFrom, _, nodeTo) and
exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()]
)
or
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
}
private predicate entrySsaDefinition(SsaDefinitionExtNode n) {
@@ -418,8 +472,11 @@ private module Cached {
entrySsaDefinition(n) and
not LocalFlow::localFlowSsaParamInput(_, n)
or
// Needed for stores in type tracking
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
or
TypeTrackerSpecific::readStepIntoSourceNode(_, n, _)
or
TypeTrackerSpecific::readStoreStepIntoSourceNode(_, n, _, _)
}
cached
@@ -501,9 +558,7 @@ import Cached
/** Holds if `n` should be hidden from path explanations. */
predicate nodeIsHidden(Node n) {
exists(SsaImpl::DefinitionExt def | def = n.(SsaDefinitionExtNode).getDefinitionExt() |
not def instanceof Ssa::WriteDefinition
)
n.(SsaDefinitionExtNode).isHidden()
or
n = LocalFlow::getParameterDefNode(_)
or
@@ -514,6 +569,14 @@ predicate nodeIsHidden(Node n) {
n instanceof SynthHashSplatParameterNode
or
n instanceof SynthHashSplatArgumentNode
or
n instanceof SynthSplatParameterNode
or
n instanceof SynthSplatArgumentNode
or
n instanceof SynthSplatArgParameterNode
or
n instanceof SynthSplatParameterElementNode
}
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -528,6 +591,13 @@ class SsaDefinitionExtNode extends NodeImpl, TSsaDefinitionExtNode {
/** Gets the underlying variable. */
Variable getVariable() { result = def.getSourceVariable() }
/** Holds if this node should be hidden from path explanations. */
predicate isHidden() {
not def instanceof Ssa::WriteDefinition
or
isDesugarNode(def.(Ssa::WriteDefinition).getWriteAccess().getExpr())
}
override CfgScope getCfgScope() { result = def.getBasicBlock().getScope() }
override Location getLocationImpl() { result = def.getLocation() }
@@ -536,7 +606,7 @@ class SsaDefinitionExtNode extends NodeImpl, TSsaDefinitionExtNode {
}
/** An SSA definition for a `self` variable. */
class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode {
class SsaSelfDefinitionNode extends SsaDefinitionExtNode {
private SelfVariable self;
SsaSelfDefinitionNode() { self = def.getSourceVariable() }
@@ -545,6 +615,22 @@ class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode {
Scope getSelfScope() { result = self.getDeclaringScope() }
}
/** A data flow node representing a captured variable. Only used in type tracking. */
class CapturedVariableNode extends NodeImpl, TCapturedVariableNode {
private VariableCapture::CapturedVariable variable;
CapturedVariableNode() { this = TCapturedVariableNode(variable) }
/** Gets the captured variable represented by this node. */
VariableCapture::CapturedVariable getVariable() { result = variable }
override CfgScope getCfgScope() { result = variable.getCfgScope() }
override Location getLocationImpl() { result = variable.getLocation() }
override string toStringImpl() { result = "captured " + variable.getName() }
}
/**
* A value returning statement, viewed as a node in a data flow graph.
*
@@ -609,8 +695,12 @@ private module ParameterNodes {
parameter = callable.getAParameter().(HashSplatParameter) and
pos.isHashSplat()
or
parameter = callable.getParameter(0).(SplatParameter) and
pos.isSplatAll()
exists(int n |
parameter = callable.getParameter(n).(SplatParameter) and
pos.isSplat(n) and
// There are no positional parameters after the splat
not exists(SimpleParameter p, int m | m > n | p = callable.getParameter(m))
)
)
}
@@ -749,6 +839,129 @@ private module ParameterNodes {
final override string toStringImpl() { result = "**kwargs" }
}
/**
* A synthetic data-flow node to allow flow to positional parameters from a splat argument.
*
* For example, in the following code:
*
* ```rb
* def foo(x, y); end
*
* foo(*[a, b])
* ```
*
* We want `a` to flow to `x` and `b` to flow to `y`. We do this by constructing
* a `SynthSplatParameterNode` for the method `foo`, and matching the splat argument to this
* parameter node via `parameterMatch/2`. We then add read steps from this node to parameters
* `x` and `y`, for content at indices 0 and 1 respectively (see `readStep`).
*
* We don't yet correctly handle cases where the splat argument is not the first argument, e.g. in
* ```rb
* foo(a, *[b])
* ```
*/
class SynthSplatParameterNode extends ParameterNodeImpl, TSynthSplatParameterNode {
private DataFlowCallable callable;
SynthSplatParameterNode() { this = TSynthSplatParameterNode(callable) }
/**
* Gets a parameter which will contain the value given by `c`, assuming
* that the method was called with a single splat argument.
* For example, if the synth splat parameter is for the following method
*
* ```rb
* def foo(x, y, a:, *rest)
* end
* ```
*
* Then `getAParameter(element 0) = x` and `getAParameter(element 1) = y`.
*/
ParameterNode getAParameter(ContentSet c) {
exists(int n |
isParameterNode(result, callable, (any(ParameterPosition p | p.isPositional(n)))) and
(
c = getPositionalContent(n)
or
c.isSingleton(TUnknownElementContent())
)
)
}
final override Parameter getParameter() { none() }
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
c = callable and pos.isSynthSplat()
}
final override CfgScope getCfgScope() { result = callable.asCallable() }
final override DataFlowCallable getEnclosingCallable() { result = callable }
final override Location getLocationImpl() { result = callable.getLocation() }
final override string toStringImpl() { result = "synthetic *args" }
}
/**
* A node that holds all positional arguments passed in a call to `c`.
* This is a mirror of the `SynthSplatArgumentNode` on the callable side.
* See `SynthSplatArgumentNode` for more information.
*/
class SynthSplatArgParameterNode extends ParameterNodeImpl, TSynthSplatArgParameterNode {
private DataFlowCallable callable;
SynthSplatArgParameterNode() { this = TSynthSplatArgParameterNode(callable) }
final override Parameter getParameter() { none() }
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
c = callable and pos.isSynthArgSplat()
}
final override CfgScope getCfgScope() { result = callable.asCallable() }
final override DataFlowCallable getEnclosingCallable() { result = callable }
final override Location getLocationImpl() { result = callable.getLocation() }
final override string toStringImpl() { result = "synthetic *args" }
}
/**
* A node that holds the content of a specific positional argument.
* See `SynthSplatArgumentNode` for more information.
*/
class SynthSplatParameterElementNode extends NodeImpl, TSynthSplatParameterElementNode {
private DataFlowCallable callable;
private int pos;
SynthSplatParameterElementNode() { this = TSynthSplatParameterElementNode(callable, pos) }
pragma[nomagic]
NormalParameterNode getSplatParameterNode(int splatPos) {
result
.isParameterOf(this.getEnclosingCallable(), any(ParameterPosition p | p.isSplat(splatPos)))
}
int getStorePosition() { result = pos }
int getReadPosition() {
exists(int splatPos |
exists(this.getSplatParameterNode(splatPos)) and
result = pos + splatPos
)
}
final override CfgScope getCfgScope() { result = callable.asCallable() }
final override DataFlowCallable getEnclosingCallable() { result = callable }
final override Location getLocationImpl() { result = callable.getLocation() }
final override string toStringImpl() { result = "synthetic *args[" + pos + "]" }
}
/** A parameter for a library callable with a flow summary. */
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
private ParameterPosition pos_;
@@ -894,6 +1107,51 @@ private module ArgumentNodes {
override string toStringImpl() { result = "**" }
}
/**
* A data-flow node that represents all arguments passed to the call.
* We use this to model data flow via splat parameters.
* Consider this example:
*
* ```rb
* def foo(x, y, *z)
* end
*
* foo(1, 2, 3, 4)
* ```
*
* 1. We want `3` to flow to `z[0]` and `4` to flow to `z[1]`. We model this by first storing all arguments
* in a synthetic argument node `SynthSplatArgumentNode` (see `storeStepCommon`).
* 2. We match this to an analogous parameter node `SynthSplatArgParameterNode` on the callee side
* (see `parameterMatch`).
* 3. For each content element stored in the `SynthSplatArgParameterNode`, we add a read step to a separate
* `SynthSplatParameterElementNode`, which is parameterized by the element index (see `readStep`).
* 4. Finally, we add store steps from these `SynthSplatParameterElementNode`s to the real splat parameter node
* (see `storeStep`).
* We only add store steps for elements that will not flow to the earlier positional parameters.
* In practice that means we ignore elements at index `<= N`, where `N` is the index of the splat parameter.
* For the remaining elements we subtract `N` from their index and store them in the splat parameter.
*/
class SynthSplatArgumentNode extends ArgumentNode, NodeImpl, TSynthSplatArgumentNode {
CfgNodes::ExprNodes::CallCfgNode c;
SynthSplatArgumentNode() { this = TSynthSplatArgumentNode(c) }
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
this.sourceArgumentOf(call.asCall(), pos)
}
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
call = c and
pos.isSynthSplat()
}
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
override Location getLocationImpl() { result = c.getLocation() }
override string toStringImpl() { result = "*" }
}
}
import ArgumentNodes
@@ -926,7 +1184,7 @@ abstract class SourceReturnNode extends ReturnNode {
private module ReturnNodes {
private predicate isValid(CfgNodes::ReturningCfgNode node) {
exists(ReturningStmt stmt, Callable scope |
stmt = node.getNode() and
stmt = node.getAstNode() and
scope = node.getScope()
|
stmt instanceof ReturnStmt and
@@ -952,7 +1210,7 @@ private module ReturnNodes {
}
override ReturnKind getKindSource() {
if n.getNode() instanceof BreakStmt
if n.getAstNode() instanceof BreakStmt
then result instanceof BreakReturnKind
else
exists(CfgScope scope | scope = this.getCfgScope() |
@@ -1077,13 +1335,7 @@ private module OutNodes {
import OutNodes
predicate jumpStep(Node pred, Node succ) {
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
succ.(SsaDefinitionExtNode).getDefinitionExt())
or
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
succ.(SsaDefinitionExtNode).getDefinitionExt())
or
predicate jumpStepTypeTracker(Node pred, Node succ) {
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
or
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
@@ -1092,6 +1344,16 @@ predicate jumpStep(Node pred, Node succ) {
any(AdditionalJumpStep s).step(pred, succ)
}
predicate jumpStep(Node pred, Node succ) {
jumpStepTypeTracker(pred, succ)
or
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
succ.(SsaDefinitionExtNode).getDefinitionExt())
or
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
succ.(SsaDefinitionExtNode).getDefinitionExt())
}
private ContentSet getKeywordContent(string name) {
exists(ConstantValue::ConstantSymbolValue key |
result.isSingleton(TKnownElementContent(key)) and
@@ -1099,6 +1361,13 @@ private ContentSet getKeywordContent(string name) {
)
}
ContentSet getPositionalContent(int n) {
exists(ConstantValue::ConstantIntegerValue i |
result.isSingleton(TKnownElementContent(i)) and
i.isInt(n)
)
}
/**
* Subset of `storeStep` that should be shared with type-tracking.
*/
@@ -1122,6 +1391,14 @@ predicate storeStepCommon(Node node1, ContentSet c, Node node2) {
c.isSingleton(TKnownElementContent(cv))
)
)
or
// Wrap all positional arguments in a synthesized splat argument node
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos |
node2 = TSynthSplatArgumentNode(call) and
node1.asExpr().(Argument).isArgumentOf(call, pos)
|
exists(int n | pos.isPositional(n) and c = getPositionalContent(n))
)
}
/**
@@ -1155,9 +1432,24 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
or
node1 =
any(SynthSplatParameterElementNode elemNode |
node2 = elemNode.getSplatParameterNode(_) and
c = getPositionalContent(elemNode.getStorePosition())
)
or
storeStepCommon(node1, c, node2)
}
/**
* Subset of `readStep` that should be shared with type-tracking.
*/
predicate readStepCommon(Node node1, ContentSet c, Node node2) {
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
or
node2 = node1.(SynthSplatParameterNode).getAParameter(c)
}
/**
* Holds if there is a read step of content `c` from `node1` to `node2`.
*/
@@ -1185,10 +1477,17 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
))
)
or
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode())
or
// Read from SynthSplatArgParameterNode into SynthSplatParameterElementNode
node2 =
any(SynthSplatParameterElementNode e |
node1.(SynthSplatArgParameterNode).isParameterOf(e.getEnclosingCallable(), _) and
c = getPositionalContent(e.getReadPosition())
)
or
readStepCommon(node1, c, node2)
}
/**
@@ -1232,7 +1531,7 @@ class DataFlowType extends TDataFlowType {
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
/** Gets the type of `n` used for type pruning. */
DataFlowType getNodeType(NodeImpl n) { result = TTodoDataFlowType() and exists(n) }
DataFlowType getNodeType(Node n) { result = TTodoDataFlowType() and exists(n) }
/** Gets a string representation of a `DataFlowType`. */
string ppReprType(DataFlowType t) { none() }
@@ -1290,16 +1589,24 @@ private import PostUpdateNodes
/** A node that performs a type cast. */
class CastNode extends Node {
CastNode() {
// ensure that all variable assignments are included in the path graph
this.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition
}
CastNode() { none() }
}
/**
* Holds if `n` should never be skipped over in the `PathGraph` and in path
* explanations.
*/
predicate neverSkipInPathGraph(Node n) {
// ensure that all variable assignments are included in the path graph
n =
any(SsaDefinitionExtNode def |
def.getDefinitionExt() instanceof Ssa::WriteDefinition and
not def.isHidden()
)
}
class DataFlowExpr = CfgNodes::ExprCfgNode;
int accessPathLimit() { result = 5 }
/**
* Holds if access paths with `c` at their head always should be tracked at high
* precision. This disables adaptive access path precision for such access paths.
@@ -1327,11 +1634,20 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
creation.asExpr() =
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
c.asCallable() = mc.getBlock().getExpr() and
mc.getExpr().getMethodName() = ["lambda", "proc"]
isProcCreationCall(mc.getExpr())
)
)
}
/** Holds if `call` is a call to `lambda`, `proc`, or `Proc.new` */
pragma[nomagic]
private predicate isProcCreationCall(MethodCall call) {
call.getMethodName() = ["proc", "lambda"]
or
call.getMethodName() = "new" and
call.getReceiver().(ConstantReadAccess).getAQualifiedName() = "Proc"
}
/**
* Holds if `call` is a from-source lambda call of kind `kind` where `receiver`
* is the lambda expression.

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 }
}
/**
@@ -217,6 +239,10 @@ class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl {
class LocalSourceNode extends Node {
LocalSourceNode() { isLocalSourceNode(this) }
/** Starts tracking this node forward using API graphs. */
pragma[inline]
API::Node track() { result = API::Internal::getNodeForForwardTracking(this) }
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
pragma[inline]
predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) }
@@ -337,10 +363,9 @@ private module Cached {
source = sink and
source instanceof LocalSourceNode
or
exists(Node mid | hasLocalSource(mid, source) |
exists(Node mid |
hasLocalSource(mid, source) and
localFlowStepTypeTracker(mid, sink)
or
LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
)
}
@@ -359,6 +384,11 @@ private module Cached {
)
}
cached
predicate methodHasSuperCall(MethodNode method, CallNode call) {
call.isSuperCall() and method = call.getEnclosingMethod()
}
/**
* A place in which a named constant can be looked up during constant lookup.
*/
@@ -387,6 +417,39 @@ private module Cached {
result.asExpr().getExpr() = access
}
/**
* Gets a module for which `constRef` is the reference to an ancestor module.
*
* For example, `M` is the ancestry target of `C` in the following examples:
* ```rb
* class M < C {}
*
* module M
* include C
* end
*
* module M
* prepend C
* end
* ```
*/
private ModuleNode getAncestryTarget(ConstRef constRef) { result.getAnAncestorExpr() = constRef }
/**
* Gets a scope in which a constant lookup may access the contents of the module referenced by `constRef`.
*/
cached
TConstLookupScope getATargetScope(ConstRef constRef) {
result = MkAncestorLookup(getAncestryTarget(constRef).getAnImmediateDescendent*())
or
constRef.asConstantAccess() = any(ConstantAccess ac).getScopeExpr() and
result = MkQualifiedLookup(constRef.asConstantAccess())
or
result = MkNestedLookup(getAncestryTarget(constRef))
or
result = MkExactLookup(constRef.asConstantAccess().(Namespace).getModule())
}
cached
predicate forceCachingInSameStage() { any() }
@@ -1028,6 +1091,33 @@ class ModuleNode instanceof Module {
* this predicate.
*/
ModuleNode getNestedModule(string name) { result = super.getNestedModule(name) }
/**
* Starts tracking the module object using API graphs.
*
* Concretely, this tracks forward from the following starting points:
* - A constant access that resolves to this module.
* - `self` in the module scope or in a singleton method of the module.
* - A call to `self.class` in an instance method of this module or an ancestor module.
*/
bindingset[this]
pragma[inline]
API::Node trackModule() { result = API::Internal::getModuleNode(this) }
/**
* Starts tracking instances of this module forward using API graphs.
*
* Concretely, this tracks forward from the following starting points:
* - `self` in instance methods of this module and ancestor modules
* - Calls to `new` on the module object
*
* Note that this includes references to `self` in ancestor modules, but not in descendent modules.
* This is usually the desired behavior, particularly if this module was itself found using
* a call to `getADescendentModule()`.
*/
bindingset[this]
pragma[inline]
API::Node trackInstance() { result = API::Internal::getModuleInstance(this) }
}
/**
@@ -1216,6 +1306,9 @@ class MethodNode extends CallableNode {
/** Holds if this method is protected. */
predicate isProtected() { this.asCallableAstNode().isProtected() }
/** Gets a `super` call in this method. */
CallNode getASuperCall() { methodHasSuperCall(this, result) }
}
/**
@@ -1284,13 +1377,16 @@ class HashLiteralNode extends LocalSourceNode, ExprNode {
* into calls to `Array.[]`, so this includes both desugared calls as well as
* explicit calls.
*/
class ArrayLiteralNode extends LocalSourceNode, ExprNode {
class ArrayLiteralNode extends LocalSourceNode, CallNode {
ArrayLiteralNode() { super.getExprNode() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode }
/**
* Gets an element of the array.
*/
Node getAnElement() { result = this.(CallNode).getPositionalArgument(_) }
Node getAnElement() { result = this.getElement(_) }
/** Gets the `i`th element of the array. */
Node getElement(int i) { result = this.getPositionalArgument(i) }
}
/**
@@ -1331,24 +1427,6 @@ class ConstRef extends LocalSourceNode {
not exists(access.getScopeExpr())
}
/**
* Gets a module for which this constant is the reference to an ancestor module.
*
* For example, `M` is the ancestry target of `C` in the following examples:
* ```rb
* class M < C {}
*
* module M
* include C
* end
*
* module M
* prepend C
* end
* ```
*/
private ModuleNode getAncestryTarget() { result.getAnAncestorExpr() = this }
/**
* Gets the known target module.
*
@@ -1356,22 +1434,6 @@ class ConstRef extends LocalSourceNode {
*/
private Module getExactTarget() { result.getAnImmediateReference() = access }
/**
* Gets a scope in which a constant lookup may access the contents of the module referenced by this constant.
*/
cached
private TConstLookupScope getATargetScope() {
forceCachingInSameStage() and
result = MkAncestorLookup(this.getAncestryTarget().getAnImmediateDescendent*())
or
access = any(ConstantAccess ac).getScopeExpr() and
result = MkQualifiedLookup(access)
or
result = MkNestedLookup(this.getAncestryTarget())
or
result = MkExactLookup(access.(Namespace).getModule())
}
/**
* Gets the scope expression, or the immediately enclosing `Namespace` (skipping over singleton classes).
*
@@ -1433,7 +1495,7 @@ class ConstRef extends LocalSourceNode {
pragma[inline]
ConstRef getConstant(string name) {
exists(TConstLookupScope scope |
pragma[only_bind_into](scope) = pragma[only_bind_out](this).getATargetScope() and
pragma[only_bind_into](scope) = getATargetScope(pragma[only_bind_out](this)) and
result.accesses(pragma[only_bind_out](scope), name)
)
}
@@ -1455,7 +1517,9 @@ class ConstRef extends LocalSourceNode {
* end
* ```
*/
ModuleNode getADescendentModule() { MkAncestorLookup(result) = this.getATargetScope() }
bindingset[this]
pragma[inline_late]
ModuleNode getADescendentModule() { MkAncestorLookup(result) = getATargetScope(this) }
}
/**

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,42 +169,6 @@ module Public {
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
}
/** Gets a textual representation of this component used for flow summaries. */
private string getComponent(SummaryComponent sc) {
result = getComponentSpecific(sc)
or
exists(ArgumentPosition pos |
sc = TParameterSummaryComponent(pos) and
result = "Parameter[" + getArgumentPosition(pos) + "]"
)
or
exists(ParameterPosition pos |
sc = TArgumentSummaryComponent(pos) and
result = "Argument[" + getParameterPosition(pos) + "]"
)
or
exists(string synthetic |
sc = TSyntheticGlobalSummaryComponent(synthetic) and
result = "SyntheticGlobal[" + synthetic + "]"
)
or
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
}
/** Gets a textual representation of this stack used for flow summaries. */
string getComponentStack(SummaryComponentStack stack) {
exists(SummaryComponent head, SummaryComponentStack tail |
head = stack.head() and
tail = stack.tail() and
result = getComponentStack(tail) + "." + getComponent(head)
)
or
exists(SummaryComponent c |
stack = TSingletonSummaryComponentStack(c) and
result = getComponent(c)
)
}
/**
* A class that exists for QL technical reasons only (the IPA type used
* to represent component stacks needs to be bounded).
@@ -329,11 +296,21 @@ module Public {
predicate hasProvenance(Provenance provenance) { provenance = "manual" }
}
/** A callable where there is no flow via the callable. */
class NeutralCallable extends SummarizedCallableBase {
/**
* A callable where there is no flow via the callable.
*/
class NeutralSummaryCallable extends NeutralCallable {
NeutralSummaryCallable() { this.getKind() = "summary" }
}
/**
* A callable that has a neutral model.
*/
class NeutralCallable extends NeutralCallableBase {
private string kind;
private Provenance provenance;
NeutralCallable() { neutralSummaryElement(this, provenance) }
NeutralCallable() { neutralElement(this, kind, provenance) }
/**
* Holds if the neutral is auto generated.
@@ -349,6 +326,11 @@ module Public {
* Holds if the neutral has provenance `p`.
*/
predicate hasProvenance(Provenance p) { p = provenance }
/**
* Gets the kind of the neutral.
*/
string getKind() { result = kind }
}
}
@@ -1351,6 +1333,11 @@ module Private {
/** Gets the string representation of this callable used by `neutral/1`. */
abstract string getCallableCsv();
/**
* Gets the kind of the neutral.
*/
string getKind() { result = super.getKind() }
string toString() { result = super.toString() }
}
@@ -1382,8 +1369,8 @@ module Private {
c.relevantSummary(input, output, preservesValue) and
csv =
c.getCallableCsv() // Callable information
+ getComponentStack(input) + ";" // input
+ getComponentStack(output) + ";" // output
+ input.getMadRepresentation() + ";" // input
+ output.getMadRepresentation() + ";" // output
+ renderKind(preservesValue) + ";" // kind
+ renderProvenance(c) // provenance
)
@@ -1391,12 +1378,13 @@ module Private {
/**
* Holds if a neutral model `csv` exists (semi-colon separated format). Used for testing purposes.
* The syntax is: "namespace;type;name;signature;provenance"",
* The syntax is: "namespace;type;name;signature;kind;provenance"",
*/
query predicate neutral(string csv) {
exists(RelevantNeutralCallable c |
csv =
c.getCallableCsv() // Callable information
+ c.getKind() + ";" // kind
+ renderProvenanceNeutral(c) // provenance
)
}

View File

@@ -11,8 +11,16 @@ private import FlowSummaryImpl::Private
private import FlowSummaryImpl::Public
private import codeql.ruby.dataflow.FlowSummary as FlowSummary
/**
* A class of callables that are candidates for flow summary modeling.
*/
class SummarizedCallableBase = string;
/**
* A class of callables that are candidates for neutral modeling.
*/
class NeutralCallableBase = string;
DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
/** Gets the parameter position representing a callback itself, if any. */
@@ -62,11 +70,11 @@ predicate summaryElement(
}
/**
* Holds if a neutral summary model exists for `c` with provenance `provenance`,
* which means that there is no flow through `c`.
* Holds if a neutral model exists for `c` of kind `kind`
* and with provenance `provenance`.
* Note. Neutral models have not been implemented for Ruby.
*/
predicate neutralSummaryElement(FlowSummary::SummarizedCallable c, string provenance) { none() }
predicate neutralElement(NeutralCallableBase c, string kind, string provenance) { none() }
bindingset[arg]
private SummaryComponent interpretElementArg(string arg) {
@@ -139,8 +147,53 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
)
}
/** Gets the textual representation of a summary component in the format used for flow summaries. */
string getComponentSpecific(SummaryComponent sc) { none() }
private string getContentSpecific(Content c) {
exists(string name | c = TFieldContent(name) and result = "Field[" + name + "]")
or
exists(ConstantValue cv |
c = TKnownElementContent(cv) and result = "Element[" + cv.serialize() + "!]"
)
or
c = TUnknownElementContent() and result = "Element[?]"
}
private string getContentSetSpecific(ContentSet cs) {
exists(Content c | cs = TSingletonContent(c) and result = getContentSpecific(c))
or
cs = TAnyElementContent() and result = "Element[any]"
or
exists(Content::KnownElementContent kec |
cs = TKnownOrUnknownElementContent(kec) and
result = "Element[" + kec.getIndex().serialize() + "]"
)
or
exists(int lower, boolean includeUnknown, string unknown |
cs = TElementLowerBoundContent(lower, includeUnknown) and
(if includeUnknown = true then unknown = "" else unknown = "!") and
result = "Element[" + lower + ".." + unknown + "]"
)
}
/** Gets the textual representation of a summary component in the format used for MaD models. */
string getMadRepresentationSpecific(SummaryComponent sc) {
exists(ContentSet cs | sc = TContentSummaryComponent(cs) and result = getContentSetSpecific(cs))
or
exists(ContentSet cs |
sc = TWithoutContentSummaryComponent(cs) and
result = "WithoutElement[" + getContentSetSpecific(cs) + "]"
)
or
exists(ContentSet cs |
sc = TWithContentSummaryComponent(cs) and
result = "WithElement[" + getContentSetSpecific(cs) + "]"
)
or
exists(ReturnKind rk |
sc = TReturnSummaryComponent(rk) and
not rk = getReturnValueKind() and
result = "ReturnValue[" + rk + "]"
)
}
/** Gets the textual representation of a parameter position in the format used for flow summaries. */
string getParameterPosition(ParameterPosition pos) {
@@ -170,6 +223,9 @@ string getParameterPosition(ParameterPosition pos) {
or
pos.isAnyNamed() and
result = "any-named"
or
pos.isHashSplat() and
result = "hash-splat"
}
/** Gets the textual representation of an argument position in the format used for flow summaries. */

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

@@ -0,0 +1,10 @@
/**
* Provides Ruby-specific definitions for use in the taint tracking library.
*/
private import codeql.dataflow.TaintTracking
private import DataFlowImplSpecific
module RubyTaintTracking implements InputSig<RubyDataFlow> {
import TaintTrackingPrivate
}

View File

@@ -1,74 +0,0 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
import TaintTrackingParameter::Public
private import TaintTrackingParameter::Private
private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> implements
DataFlowInternal::FullStateConfigSig
{
import Config
predicate isBarrier(DataFlow::Node node) {
Config::isBarrier(node) or defaultTaintSanitizer(node)
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
Config::isAdditionalFlowStep(node1, node2) or
defaultAdditionalTaintStep(node1, node2)
}
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
Config::allowImplicitRead(node, c)
or
(
Config::isSink(node, _) or
Config::isAdditionalFlowStep(node, _) or
Config::isAdditionalFlowStep(node, _, _, _)
) and
defaultImplicitTaintRead(node, c)
}
}
/**
* Constructs a global taint tracking computation.
*/
module Global<DataFlow::ConfigSig Config> implements DataFlow::GlobalFlowSig {
private module Config0 implements DataFlowInternal::FullStateConfigSig {
import DataFlowInternal::DefaultState<Config>
import Config
}
private module C implements DataFlowInternal::FullStateConfigSig {
import AddTaintDefaults<Config0>
}
import DataFlowInternal::Impl<C>
}
/** DEPRECATED: Use `Global` instead. */
deprecated module Make<DataFlow::ConfigSig Config> implements DataFlow::GlobalFlowSig {
import Global<Config>
}
/**
* Constructs a global taint tracking computation using flow state.
*/
module GlobalWithState<DataFlow::StateConfigSig Config> implements DataFlow::GlobalFlowSig {
private module Config0 implements DataFlowInternal::FullStateConfigSig {
import Config
}
private module C implements DataFlowInternal::FullStateConfigSig {
import AddTaintDefaults<Config0>
}
import DataFlowInternal::Impl<C>
}
/** DEPRECATED: Use `GlobalWithState` instead. */
deprecated module MakeWithState<DataFlow::StateConfigSig Config> implements DataFlow::GlobalFlowSig {
import GlobalWithState<Config>
}

View File

@@ -91,19 +91,19 @@ class Configuration extends TaintTracking::Configuration {
// unicode_utils
exists(API::MethodAccessNode mac |
mac = API::getTopLevelMember("UnicodeUtils").getMethod(["nfkd", "nfc", "nfd", "nfkc"]) and
sink = mac.getParameter(0).asSink()
sink = mac.getArgument(0).asSink()
)
or
// eprun
exists(API::MethodAccessNode mac |
mac = API::getTopLevelMember("Eprun").getMethod("normalize") and
sink = mac.getParameter(0).asSink()
sink = mac.getArgument(0).asSink()
)
or
// unf
exists(API::MethodAccessNode mac |
mac = API::getTopLevelMember("UNF").getMember("Normalizer").getMethod("normalize") and
sink = mac.getParameter(0).asSink()
sink = mac.getArgument(0).asSink()
)
or
// ActiveSupport::Multibyte::Chars
@@ -113,7 +113,7 @@ class Configuration extends TaintTracking::Configuration {
.getMember("Multibyte")
.getMember("Chars")
.getMethod("new")
.getCallNode() and
.asCall() and
n = cn.getAMethodCall("normalize") and
sink = cn.getArgument(0)
)

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()
}
/**
@@ -222,19 +222,19 @@ private class ActionControllerRenderToCall extends RenderToCallImpl {
}
}
pragma[nomagic]
private DataFlow::CallNode renderCall() {
// ActionController#render is an alias for ActionController::Renderer#render
result =
[
any(ActionControllerClass c).trackModule().getAMethodCall("render"),
any(ActionControllerClass c).trackModule().getReturn("renderer").getAMethodCall("render")
]
}
/** A call to `ActionController::Renderer#render`. */
private class RendererRenderCall extends RenderCallImpl {
RendererRenderCall() {
this =
[
// ActionController#render is an alias for ActionController::Renderer#render
any(ActionControllerClass c).getAnImmediateReference().getAMethodCall("render"),
any(ActionControllerClass c)
.getAnImmediateReference()
.getAMethodCall("renderer")
.getAMethodCall("render")
].asExpr().getExpr()
}
RendererRenderCall() { this = renderCall().asExpr().getExpr() }
}
/** A call to `html_escape` from within a controller. */
@@ -260,6 +260,7 @@ class RedirectToCall extends MethodCall {
this =
controller
.getSelf()
.track()
.getAMethodCall(["redirect_to", "redirect_back", "redirect_back_or_to"])
.asExpr()
.getExpr()
@@ -600,9 +601,7 @@ private module ParamsSummaries {
* response.
*/
private module Response {
DataFlow::LocalSourceNode response() {
result = actionControllerInstance().getAMethodCall("response")
}
API::Node response() { result = actionControllerInstance().getReturn("response") }
class BodyWrite extends DataFlow::CallNode, Http::Server::HttpResponse::Range {
BodyWrite() { this = response().getAMethodCall("body=") }
@@ -628,7 +627,7 @@ private module Response {
HeaderWrite() {
// response.header[key] = val
// response.headers[key] = val
this = response().getAMethodCall(["header", "headers"]).getAMethodCall("[]=")
this = response().getReturn(["header", "headers"]).getAMethodCall("[]=")
or
// response.set_header(key) = val
// response[header] = val
@@ -673,18 +672,12 @@ private module Response {
}
}
private class ActionControllerLoggerInstance extends DataFlow::Node {
ActionControllerLoggerInstance() {
this = actionControllerInstance().getAMethodCall("logger")
or
any(ActionControllerLoggerInstance i).(DataFlow::LocalSourceNode).flowsTo(this)
}
}
private class ActionControllerLoggingCall extends DataFlow::CallNode, Logging::Range {
ActionControllerLoggingCall() {
this.getReceiver() instanceof ActionControllerLoggerInstance and
this.getMethodName() = ["debug", "error", "fatal", "info", "unknown", "warn"]
this =
actionControllerInstance()
.getReturn("logger")
.getAMethodCall(["debug", "error", "fatal", "info", "unknown", "warn"])
}
// Note: this is identical to the definition `stdlib.Logger.LoggerInfoStyleCall`.

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

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

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

@@ -0,0 +1,106 @@
/**
* Provides modeling for `net-ldap` a ruby library for LDAP.
*/
private import ruby
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.Concepts
/**
* Provides modeling for `net-ldap` a ruby library for LDAP.
*/
module NetLdap {
/**
* Flow summary for `Net::LDAP.new`. This method establishes a connection to a LDAP server.
*/
private class LdapConnSummary extends SummarizedCallable {
LdapConnSummary() { this = "Net::LDAP.new" }
override MethodCall getACall() { result = any(NetLdapConnection l).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
/**
* Flow summary for `Net::LDAP.Filter`.
*/
private class LdapFilterSummary extends SummarizedCallable {
LdapFilterSummary() { this = "Net::LDAP::Filter" }
override MethodCall getACall() { result = any(NetLdapFilter l).asExpr().getExpr() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = ["Argument[0]", "Argument[1]"] and output = "ReturnValue" and preservesValue = false
}
}
/** Net LDAP Api Node */
private API::Node ldap() { result = API::getTopLevelMember("Net").getMember("LDAP") }
/** A call that establishes a LDAP Connection */
private class NetLdapConnection extends DataFlow::CallNode {
NetLdapConnection() { this in [ldap().getAnInstantiation(), ldap().getAMethodCall("open")] }
predicate usesSsl() {
getValue(this, "encryption").getConstantValue().isStringlikeValue("simple_tls")
}
DataFlow::Node getAuthValue(string arg) {
result =
this.getKeywordArgument("auth")
.(DataFlow::HashLiteralNode)
.getElementFromKey(any(Ast::ConstantValue cv | cv.isStringlikeValue(arg)))
}
}
/** A call that constructs a LDAP query */
private class NetLdapFilter extends LdapConstruction::Range, DataFlow::CallNode {
NetLdapFilter() {
this =
any(ldap()
.getMember("Filter")
.getAMethodCall([
"begins", "bineq", "contains", "ends", "eq", "equals", "ex", "ge", "le", "ne",
"present"
])
)
}
override DataFlow::Node getQuery() { result = this.getArgument([0, 1]) }
}
/** A call considered as a LDAP execution. */
private class NetLdapExecution extends LdapExecution::Range, DataFlow::CallNode {
NetLdapExecution() { this = any(NetLdapConnection l).getAMethodCall("search") }
override DataFlow::Node getQuery() { result = this.getKeywordArgument(_) }
}
/** A call considered as a LDAP bind. */
private class NetLdapBind extends LdapBind::Range, DataFlow::CallNode {
private NetLdapConnection l;
NetLdapBind() { this = l.getAMethodCall("bind") }
override DataFlow::Node getHost() { result = getValue(l, "host") }
override DataFlow::Node getPassword() {
result = l.getAuthValue("password") or
result = l.getAMethodCall("auth").getArgument(1)
}
override predicate usesSsl() { l.usesSsl() }
}
/** LDAP Attribute value */
DataFlow::Node getValue(NetLdapConnection l, string attr) {
result =
[
l.getKeywordArgument(attr), l.getAMethodCall(attr).getArgument(0),
l.getAMethodCall(attr).getKeywordArgument(attr)
]
}
}

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.getAReturnNode() = trackRackResponse()
}
/**
* Gets the environment of the request, which is the lone parameter to the `call` method.
*/
DataFlow::ParameterNode getEnv() { result = call.getParameter(0) }
}
private predicate isRackResponse(DataFlow::Node r) {
// [status, headers, body]
r.asExpr().(ArrayLiteralCfgNode).getNumberOfArguments() = 3
}
private DataFlow::LocalSourceNode trackRackResponse(TypeTracker t) {
t.start() and
isRackResponse(result)
or
exists(TypeTracker t2 | result = trackRackResponse(t2).track(t2, t))
}
private DataFlow::Node trackRackResponse() {
trackRackResponse(TypeTracker::end()).flowsTo(result)
}
/** DEPRECATED: Alias for App::AppCandidate */
deprecated class AppCandidate = App::AppCandidate;
}

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

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

@@ -128,7 +128,7 @@ module Request {
private import codeql.ruby.frameworks.Rack
private class RackEnv extends Env {
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
RackEnv() { this = any(Rack::App::RequestHandler handler).getEnv().getALocalUse() }
}
/**

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

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

@@ -64,10 +64,8 @@ class ExconHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
exists(DataFlow::CallNode newCall | newCall = connectionNode.getAValueReachableFromSource() |
// Check for `ssl_verify_peer: false`
result = newCall.getKeywordArgumentIncludeHashArgument("ssl_verify_peer")
)
result =
connectionUse.(DataFlow::CallNode).getKeywordArgumentIncludeHashArgument("ssl_verify_peer")
}
cached

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

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

@@ -0,0 +1,49 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* improper LDAP authentication, as well as extension points for adding your own
*/
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.BarrierGuards
private import codeql.ruby.dataflow.RemoteFlowSources
/**
* Provides default sources, sinks and sanitizers for detecting
* improper LDAP authentication, as well as extension points for adding your own
*/
module ImproperLdapAuth {
/** A data flow source for improper LDAP authentication vulnerabilities */
abstract class Source extends DataFlow::Node { }
/** A data flow sink for improper LDAP authentication vulnerabilities */
abstract class Sink extends DataFlow::Node { }
/** A sanitizer for improper LDAP authentication vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of remote user input, considered as a flow source.
*/
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
/**
* An LDAP query execution considered as a flow sink.
*/
private class LdapBindAsSink extends Sink {
LdapBindAsSink() { this = any(LdapBind l).getPassword() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
/**
* An inclusion check against an array of constant strings, considered as a
* sanitizer-guard.
*/
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
StringConstArrayInclusionCallBarrier
{ }
}

View File

@@ -0,0 +1,21 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* improper LDAP authentication, as well as extension points for adding your own
*/
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
private import ImproperLdapAuthCustomizations::ImproperLdapAuth
/**
* A taint-tracking configuration for detecting improper LDAP authentication vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "ImproperLdapAuth" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}

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

@@ -0,0 +1,81 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* LDAP Injections, as well as extension points for adding your own
*/
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.BarrierGuards
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ApiGraphs
/**
* Provides default sources, sinks and sanitizers for detecting
* LDAP Injections, as well as extension points for adding your own
*/
module LdapInjection {
/** A data flow source for LDAP Injection vulnerabilities */
abstract class Source extends DataFlow::Node { }
/** A data flow sink for LDAP Injection vulnerabilities */
abstract class Sink extends DataFlow::Node { }
/** A sanitizer for LDAP Injection vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
/**
* Additional taint steps for "LDAP Injection" vulnerabilities.
*/
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
attributeArrayTaintStep(nodeFrom, nodeTo)
}
/**
* Additional taint step to handle elements inside an array,
* specifically in the context of the following LDAP search function:
*
* ldap.search(base: "", filter: "", attributes: [name])
*/
private predicate attributeArrayTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(DataFlow::CallNode filterCall |
filterCall.getMethodName() = "[]" and
n1 = filterCall.getArgument(_) and
n2 = filterCall
)
}
/**
* A source of remote user input, considered as a flow source.
*/
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
/**
* An LDAP query execution considered as a flow sink.
*/
private class LdapExecutionAsSink extends Sink {
LdapExecutionAsSink() { this = any(LdapExecution l).getQuery() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
/**
* An inclusion check against an array of constant strings, considered as a
* sanitizer-guard.
*/
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
StringConstArrayInclusionCallBarrier
{ }
/**
* A call to `Net::LDAP::Filter.escape`, considered as a sanitizer.
*/
class NetLdapFilterEscapeSanitization extends Sanitizer {
NetLdapFilterEscapeSanitization() {
this =
API::getTopLevelMember("Net").getMember("LDAP").getMember("Filter").getAMethodCall("escape")
}
}
}

View File

@@ -0,0 +1,29 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* LDAP Injections, as well as extension points for adding your own
*/
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
/** Provides a taint-tracking configuration for detecting LDAP Injections vulnerabilities. */
module LdapInjection {
import LdapInjectionCustomizations::LdapInjection
/**
* A taint-tracking configuration for detecting LDAP Injections vulnerabilities.
*/
private module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
LdapInjection::isAdditionalFlowStep(node1, node2)
}
}
import TaintTracking::Global<Config>
}

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

@@ -15,7 +15,7 @@ class Configuration extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}

View File

@@ -15,7 +15,7 @@ class Configuration extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}

View File

@@ -0,0 +1,55 @@
/**
* Provides class and predicates to track external data that
* may represent malicious xpath query objects.
*
* This module is intended to be imported into a taint-tracking query.
*/
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.BarrierGuards
private import codeql.ruby.dataflow.RemoteFlowSources
/** Models Xpath Injection related classes and functions */
module XpathInjection {
/** A data flow source for "XPath injection" vulnerabilities. */
abstract class Source extends DataFlow::Node { }
/** A data flow sink for "XPath injection" vulnerabilities */
abstract class Sink extends DataFlow::Node { }
/** A sanitizer for "XPath injection" vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of remote user input, considered as a flow source.
*/
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
/**
* An execution of an XPath expression, considered as a sink.
*/
private class XPathExecutionAsSink extends Sink {
XPathExecutionAsSink() { this = any(XPathExecution e).getXPath() }
}
/**
* A construction of an XPath expression, considered as a sink.
*/
private class XPathConstructionAsSink extends Sink {
XPathConstructionAsSink() { this = any(XPathConstruction c).getXPath() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
/**
* An inclusion check against an array of constant strings, considered as a
* sanitizer-guard.
*/
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
StringConstArrayInclusionCallBarrier
{ }
}

View File

@@ -0,0 +1,27 @@
/**
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
*
* Note, for performance reasons: only import this file if
* `XpathInjection::Configuration` is needed, otherwise
* `XpathInjectionCustomizations` should be imported instead.
*/
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
import XpathInjectionCustomizations::XpathInjection
/** Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities. */
module XpathInjection {
/**
* A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
*/
private module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof Source }
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
}
import TaintTracking::Global<Config>
}

View File

@@ -0,0 +1,328 @@
/**
* Parts of API graphs that can be shared with other dynamic languages.
*
* Depends on TypeTrackerSpecific for the corresponding language.
*/
private import codeql.Locations
private import codeql.ruby.typetracking.TypeTracker
private import TypeTrackerSpecific
/**
* The signature to use when instantiating `ApiGraphShared`.
*
* The implementor should define a newtype with at least three branches as follows:
* ```ql
* newtype TApiNode =
* MkForwardNode(LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
* MkBackwardNode(LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
* MkSinkNode(Node node) { ... } or
* ...
* ```
*
* The three branches should be exposed through `getForwardNode`, `getBackwardNode`, and `getSinkNode`, respectively.
*/
signature module ApiGraphSharedSig {
/** A node in the API graph. */
class ApiNode {
/** Gets a string representation of this API node. */
string toString();
/** Gets the location associated with this API node, if any. */
Location getLocation();
}
/**
* Gets the forward node with the given type-tracking state.
*
* This node will have outgoing epsilon edges to its type-tracking successors.
*/
ApiNode getForwardNode(TypeTrackingNode node, TypeTracker t);
/**
* Gets the backward node with the given type-tracking state.
*
* This node will have outgoing epsilon edges to its type-tracking predecessors.
*/
ApiNode getBackwardNode(TypeTrackingNode node, TypeTracker t);
/**
* Gets the sink node corresponding to `node`.
*
* Since sinks are not generally `LocalSourceNode`s, such nodes are materialised separately in order for
* the API graph to include representatives for sinks. Note that there is no corresponding case for "source"
* nodes as these are represented as forward nodes with initial-state type-trackers.
*
* Sink nodes have outgoing epsilon edges to the backward nodes corresponding to their local sources.
*/
ApiNode getSinkNode(Node node);
/**
* Holds if a language-specific epsilon edge `pred -> succ` should be generated.
*/
predicate specificEpsilonEdge(ApiNode pred, ApiNode succ);
}
/**
* Parts of API graphs that can be shared between language implementations.
*/
module ApiGraphShared<ApiGraphSharedSig S> {
private import S
/** Gets a local source of `node`. */
bindingset[node]
pragma[inline_late]
TypeTrackingNode getALocalSourceStrict(Node node) { result = node.getALocalSource() }
cached
private module Cached {
/**
* Holds if there is an epsilon edge `pred -> succ`.
*
* That relation is reflexive, so `fastTC` produces the equivalent of a reflexive, transitive closure.
*/
pragma[noopt]
cached
predicate epsilonEdge(ApiNode pred, ApiNode succ) {
exists(
StepSummary summary, TypeTrackingNode predNode, TypeTracker predState,
TypeTrackingNode succNode, TypeTracker succState
|
StepSummary::stepCall(predNode, succNode, summary)
or
StepSummary::stepNoCall(predNode, succNode, summary)
|
pred = getForwardNode(predNode, predState) and
succState = StepSummary::append(predState, summary) and
succ = getForwardNode(succNode, succState)
or
succ = getBackwardNode(predNode, predState) and // swap order for backward flow
succState = StepSummary::append(predState, summary) and
pred = getBackwardNode(succNode, succState) // swap order for backward flow
)
or
exists(Node sink, TypeTrackingNode localSource |
pred = getSinkNode(sink) and
localSource = getALocalSourceStrict(sink) and
succ = getBackwardStartNode(localSource)
)
or
specificEpsilonEdge(pred, succ)
or
succ instanceof ApiNode and
succ = pred
}
/**
* Holds if `pred` can reach `succ` by zero or more epsilon edges.
*/
cached
predicate epsilonStar(ApiNode pred, ApiNode succ) = fastTC(epsilonEdge/2)(pred, succ)
/** Gets the API node to use when starting forward flow from `source` */
cached
ApiNode forwardStartNode(TypeTrackingNode source) {
result = getForwardNode(source, TypeTracker::end(false))
}
/** Gets the API node to use when starting backward flow from `sink` */
cached
ApiNode backwardStartNode(TypeTrackingNode sink) {
// There is backward flow A->B iff there is forward flow B->A.
// The starting point of backward flow corresponds to the end of a forward flow, and vice versa.
result = getBackwardNode(sink, TypeTracker::end(_))
}
/** Gets `node` as a data flow source. */
cached
TypeTrackingNode asSourceCached(ApiNode node) { node = forwardEndNode(result) }
/** Gets `node` as a data flow sink. */
cached
Node asSinkCached(ApiNode node) { node = getSinkNode(result) }
}
private import Cached
/** Gets an API node corresponding to the end of forward-tracking to `localSource`. */
pragma[nomagic]
private ApiNode forwardEndNode(TypeTrackingNode localSource) {
result = getForwardNode(localSource, TypeTracker::end(_))
}
/** Gets an API node corresponding to the end of backtracking to `localSource`. */
pragma[nomagic]
private ApiNode backwardEndNode(TypeTrackingNode localSource) {
result = getBackwardNode(localSource, TypeTracker::end(false))
}
/** Gets a node reachable from `node` by zero or more epsilon edges, including `node` itself. */
bindingset[node]
pragma[inline_late]
ApiNode getAnEpsilonSuccessorInline(ApiNode node) { epsilonStar(node, result) }
/** Gets `node` as a data flow sink. */
bindingset[node]
pragma[inline_late]
Node asSinkInline(ApiNode node) { result = asSinkCached(node) }
/** Gets `node` as a data flow source. */
bindingset[node]
pragma[inline_late]
TypeTrackingNode asSourceInline(ApiNode node) { result = asSourceCached(node) }
/** Gets a value reachable from `source`. */
bindingset[source]
pragma[inline_late]
Node getAValueReachableFromSourceInline(ApiNode source) {
exists(TypeTrackingNode src |
src = asSourceInline(getAnEpsilonSuccessorInline(source)) and
src.flowsTo(pragma[only_bind_into](result))
)
}
/** Gets a value that can reach `sink`. */
bindingset[sink]
pragma[inline_late]
Node getAValueReachingSinkInline(ApiNode sink) {
result = asSinkInline(getAnEpsilonSuccessorInline(sink))
}
/**
* Gets the starting point for forward-tracking at `node`.
*
* Should be used to obtain the successor of an edge when constructing labelled edges.
*/
bindingset[node]
pragma[inline_late]
ApiNode getForwardStartNode(Node node) { result = forwardStartNode(node) }
/**
* Gets the starting point of backtracking from `node`.
*
* Should be used to obtain the successor of an edge when constructing labelled edges.
*/
bindingset[node]
pragma[inline_late]
ApiNode getBackwardStartNode(Node node) { result = backwardStartNode(node) }
/**
* Gets a possible ending point of forward-tracking at `node`.
*
* Should be used to obtain the predecessor of an edge when constructing labelled edges.
*
* This is not backed by a `cached` predicate, and should only be used for materialising `cached`
* predicates in the API graph implementation - it should not be called in later stages.
*/
bindingset[node]
pragma[inline_late]
ApiNode getForwardEndNode(Node node) { result = forwardEndNode(node) }
/**
* Gets a possible ending point backtracking to `node`.
*
* Should be used to obtain the predecessor of an edge when constructing labelled edges.
*
* This is not backed by a `cached` predicate, and should only be used for materialising `cached`
* predicates in the API graph implementation - it should not be called in later stages.
*/
bindingset[node]
pragma[inline_late]
ApiNode getBackwardEndNode(Node node) { result = backwardEndNode(node) }
/**
* Gets a possible eding point of forward or backward tracking at `node`.
*
* Should be used to obtain the predecessor of an edge generated from store or load edges.
*/
bindingset[node]
pragma[inline_late]
ApiNode getForwardOrBackwardEndNode(Node node) {
result = getForwardEndNode(node) or result = getBackwardEndNode(node)
}
/** Gets an API node for tracking forward starting at `node`. This is the implementation of `DataFlow::LocalSourceNode.track()` */
bindingset[node]
pragma[inline_late]
ApiNode getNodeForForwardTracking(Node node) { result = forwardStartNode(node) }
/** Gets an API node for backtracking starting at `node`. The implementation of `DataFlow::Node.backtrack()`. */
bindingset[node]
pragma[inline_late]
ApiNode getNodeForBacktracking(Node node) {
result = getBackwardStartNode(getALocalSourceStrict(node))
}
/** Parts of the shared module to be re-exported by the user-facing `API` module. */
module Public {
/**
* The signature to use when instantiating the `ExplainFlow` module.
*/
signature module ExplainFlowSig {
/** Holds if `node` should be a source. */
predicate isSource(ApiNode node);
/** Holds if `node` should be a sink. */
default predicate isSink(ApiNode node) { any() }
/** Holds if `node` should be skipped in the generated paths. */
default predicate isHidden(ApiNode node) { none() }
}
/**
* Module to help debug and visualize the data flows underlying API graphs.
*
* This module exports the query predicates for a path-problem query, and should be imported
* into the top-level of such a query.
*
* The module argument should specify source and sink API nodes, and the resulting query
* will show paths of epsilon edges that go from a source to a sink. Only epsilon edges are visualized.
*
* To condense the output a bit, paths in which the source and sink are the same node are omitted.
*/
module ExplainFlow<ExplainFlowSig T> {
private import T
private ApiNode relevantNode() {
isSink(result) and
result = getAnEpsilonSuccessorInline(any(ApiNode node | isSource(node)))
or
epsilonEdge(result, relevantNode())
}
/** Holds if `node` is part of the graph to visualize. */
query predicate nodes(ApiNode node) { node = relevantNode() and not isHidden(node) }
private predicate edgeToHiddenNode(ApiNode pred, ApiNode succ) {
epsilonEdge(pred, succ) and
isHidden(succ) and
pred = relevantNode() and
succ = relevantNode()
}
/** Holds if `pred -> succ` is an edge in the graph to visualize. */
query predicate edges(ApiNode pred, ApiNode succ) {
nodes(pred) and
nodes(succ) and
exists(ApiNode mid |
edgeToHiddenNode*(pred, mid) and
epsilonEdge(mid, succ)
)
}
/** Holds for each source/sink pair to visualize in the graph. */
query predicate problems(
ApiNode location, ApiNode sourceNode, ApiNode sinkNode, string message
) {
nodes(sourceNode) and
nodes(sinkNode) and
isSource(sourceNode) and
isSink(sinkNode) and
sinkNode = getAnEpsilonSuccessorInline(sourceNode) and
sourceNode != sinkNode and
location = sinkNode and
message = "Node flows here"
}
}
}
}

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