mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Merge branch 'main' into ruby-more-flow
This commit is contained in:
@@ -3,7 +3,15 @@ description: Builds the Ruby CodeQL pack
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/cache@v3
|
||||
- name: Cache entire extractor
|
||||
id: cache-extractor
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ruby/extractor-pack
|
||||
key: ${{ runner.os }}-extractor-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}-${{ hashFiles('ruby/**/*.rs') }}-${{ hashFiles('ruby/codeql-extractor.yml', 'ruby/downgrades', 'ruby/tools', 'ruby/ql/lib/ruby.dbscheme', 'ruby/ql/lib/ruby.dbscheme.stats') }}
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -11,6 +19,7 @@ runs:
|
||||
ruby/target
|
||||
key: ${{ runner.os }}-ruby-qltest-cargo-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}
|
||||
- name: Build Extractor
|
||||
if: steps.cache-extractor.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: scripts/create-extractor-pack.sh
|
||||
working-directory: ruby
|
||||
|
||||
@@ -9,4 +9,4 @@ ruby_rational_def.rel: run ruby_rational_def.qlo
|
||||
ruby_call_method.rel: delete
|
||||
ruby_complex_def.rel: delete
|
||||
ruby_block_parameters_locals.rel: delete
|
||||
ruby_call_operator: delete
|
||||
ruby_call_operator.rel: delete
|
||||
|
||||
@@ -81,6 +81,14 @@ pub enum Storage {
|
||||
},
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub fn is_column(&self) -> bool {
|
||||
match self {
|
||||
Storage::Column { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn read_node_types(prefix: &str, node_types_path: &Path) -> std::io::Result<NodeTypeMap> {
|
||||
let file = fs::File::open(node_types_path)?;
|
||||
let node_types: Vec<NodeInfo> = serde_json::from_reader(file)?;
|
||||
@@ -245,10 +253,11 @@ fn add_field(
|
||||
}
|
||||
};
|
||||
let converted_types = convert_types(&field_info.types);
|
||||
let type_info = if field_info
|
||||
.types
|
||||
.iter()
|
||||
.all(|t| !t.named && token_kinds.contains(&convert_type(t)))
|
||||
let type_info = if storage.is_column()
|
||||
&& field_info
|
||||
.types
|
||||
.iter()
|
||||
.all(|t| !t.named && token_kinds.contains(&convert_type(t)))
|
||||
{
|
||||
// All possible types for this field are reserved words. The db
|
||||
// representation will be an `int` with a `case @foo.field = ...` to
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import codeql.ruby.dataflow.SSA
|
||||
import codeql.ruby.dataflow.internal.SsaImpl::Consistency as Consistency
|
||||
import codeql.ruby.dataflow.internal.SsaImpl
|
||||
import Consistency
|
||||
|
||||
class MyRelevantDefinition extends Consistency::RelevantDefinition, Ssa::Definition {
|
||||
class MyRelevantDefinition extends RelevantDefinition, Ssa::Definition {
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
@@ -9,10 +10,10 @@ class MyRelevantDefinition extends Consistency::RelevantDefinition, Ssa::Definit
|
||||
}
|
||||
}
|
||||
|
||||
query predicate nonUniqueDef = Consistency::nonUniqueDef/4;
|
||||
|
||||
query predicate readWithoutDef = Consistency::readWithoutDef/3;
|
||||
|
||||
query predicate deadDef = Consistency::deadDef/2;
|
||||
|
||||
query predicate notDominatedByDef = Consistency::notDominatedByDef/4;
|
||||
class MyRelevantDefinitionExt extends RelevantDefinitionExt, DefinitionExt {
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
## 0.4.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Data flow through the `ActiveSupport` extension `Enumerable#index_by` is now modeled.
|
||||
* The `codeql.ruby.Concepts` library now has a `SqlConstruction` class, in addition to the existing `SqlExecution` class.
|
||||
* Calls to `Arel.sql` are now modeled as instances of the new `SqlConstruction` concept.
|
||||
* Arguments to RPC endpoints (public methods) on subclasses of `ActionCable::Channel::Base` are now recognized as sources of remote user input.
|
||||
* Taint flow through the `ActiveSupport` extensions `Hash#reverse_merge` and `Hash:reverse_merge!`, and their aliases, is now modeled more generally, where previously it was only modeled in the context of `ActionController` parameters.
|
||||
* Calls to `logger` in `ActiveSupport` actions are now recognised as logger instances.
|
||||
* Calls to `send_data` in `ActiveSupport` actions are recognised as HTTP responses.
|
||||
* Calls to `body_stream` in `ActiveSupport` actions are recognised as HTTP request accesses.
|
||||
* The `ActiveSupport` extensions `Object#try` and `Object#try!` are now recognised as code executions.
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* String literals and arrays of string literals in case expression patterns are now recognised as barrier guards.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `ActiveSupport` extensions `Object#try` and `Object#try!` are now recognised as code executions.
|
||||
4
ruby/ql/lib/change-notes/2022-10-31-shared-redos-pack.md
Normal file
4
ruby/ql/lib/change-notes/2022-10-31-shared-redos-pack.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The ReDoS libraries in `codeql.ruby.security.regexp` has been moved to a shared pack inside the `shared/` folder, and the previous location has been deprecated.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Taint flow through the `ActiveSupport` extensions `Hash#reverse_merge` and `Hash:reverse_merge!`, and their aliases, is now modeled more generally, where previously it was only modeled in the context of `ActionController` parameters.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Arguments to RPC endpoints (public methods) on subclasses of `ActionCable::Channel::Base` are now recognized as sources of remote user input.
|
||||
4
ruby/ql/lib/change-notes/2022-11-24-json-taint-flow.md
Normal file
4
ruby/ql/lib/change-notes/2022-11-24-json-taint-flow.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Taint flow is now tracked through many common JSON parsing and generation methods.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Data flow through the `ActiveSupport` extensions `Enumerable#index_with`, `Enumerable#pick`, `Enumerable#pluck` and `Enumerable#sole` are now modeled.
|
||||
13
ruby/ql/lib/change-notes/released/0.4.4.md
Normal file
13
ruby/ql/lib/change-notes/released/0.4.4.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## 0.4.4
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Data flow through the `ActiveSupport` extension `Enumerable#index_by` is now modeled.
|
||||
* The `codeql.ruby.Concepts` library now has a `SqlConstruction` class, in addition to the existing `SqlExecution` class.
|
||||
* Calls to `Arel.sql` are now modeled as instances of the new `SqlConstruction` concept.
|
||||
* Arguments to RPC endpoints (public methods) on subclasses of `ActionCable::Channel::Base` are now recognized as sources of remote user input.
|
||||
* Taint flow through the `ActiveSupport` extensions `Hash#reverse_merge` and `Hash:reverse_merge!`, and their aliases, is now modeled more generally, where previously it was only modeled in the context of `ActionController` parameters.
|
||||
* Calls to `logger` in `ActiveSupport` actions are now recognised as logger instances.
|
||||
* Calls to `send_data` in `ActiveSupport` actions are recognised as HTTP responses.
|
||||
* Calls to `body_stream` in `ActiveSupport` actions are recognised as HTTP request accesses.
|
||||
* The `ActiveSupport` extensions `Object#try` and `Object#try!` are now recognised as code executions.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.3
|
||||
lastReleaseVersion: 0.4.4
|
||||
|
||||
@@ -6,7 +6,7 @@ import files.FileSystem
|
||||
* A location as given by a file, a start line, a start column,
|
||||
* an end line, and an end column.
|
||||
*
|
||||
* For more information about locations see [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
class Location extends @location {
|
||||
/** Gets the file for this location. */
|
||||
@@ -40,7 +40,7 @@ class Location extends @location {
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
* [Providing locations in CodeQL queries](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
|
||||
|
||||
@@ -11,9 +11,47 @@ private import codeql.ruby.Frameworks
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* A data-flow node that constructs a SQL statement.
|
||||
*
|
||||
* Often, it is worthy of an alert if a SQL statement is constructed such that
|
||||
* executing it would be a security risk.
|
||||
*
|
||||
* If it is important that the SQL statement is executed, use `SqlExecution`.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `SqlConstruction::Range` instead.
|
||||
*/
|
||||
class SqlConstruction extends DataFlow::Node instanceof SqlConstruction::Range {
|
||||
/** Gets the argument that specifies the SQL statements to be constructed. */
|
||||
DataFlow::Node getSql() { result = super.getSql() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new SQL execution APIs. */
|
||||
module SqlConstruction {
|
||||
/**
|
||||
* A data-flow node that constructs a SQL statement.
|
||||
*
|
||||
* Often, it is worthy of an alert if a SQL statement is constructed such that
|
||||
* executing it would be a security risk.
|
||||
*
|
||||
* If it is important that the SQL statement is executed, use `SqlExecution`.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `SqlConstruction` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the SQL statements to be constructed. */
|
||||
abstract DataFlow::Node getSql();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes SQL statements.
|
||||
*
|
||||
* If the context of interest is such that merely constructing a SQL statement
|
||||
* would be valuable to report, consider using `SqlConstruction`.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `SqlExecution::Range` instead.
|
||||
*/
|
||||
@@ -27,6 +65,9 @@ module SqlExecution {
|
||||
/**
|
||||
* A data-flow node that executes SQL statements.
|
||||
*
|
||||
* If the context of interest is such that merely constructing a SQL
|
||||
* statement would be valuable to report, consider using `SqlConstruction`.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `SqlExecution` instead.
|
||||
*/
|
||||
|
||||
@@ -23,3 +23,5 @@ private import codeql.ruby.frameworks.HttpClients
|
||||
private import codeql.ruby.frameworks.XmlParsing
|
||||
private import codeql.ruby.frameworks.ActionDispatch
|
||||
private import codeql.ruby.frameworks.PosixSpawn
|
||||
private import codeql.ruby.frameworks.StringFormatters
|
||||
private import codeql.ruby.frameworks.Json
|
||||
|
||||
@@ -234,7 +234,7 @@ module ExprNodes {
|
||||
override predicate relevantChild(AstNode n) { none() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `ArrayLiteral` AST expression. */
|
||||
/** A control-flow node that wraps a `Literal` AST expression. */
|
||||
class LiteralCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "LiteralCfgNode" }
|
||||
|
||||
@@ -432,8 +432,36 @@ module ExprNodes {
|
||||
final ExprCfgNode getBody() { e.hasCfgChild(e.getBody(), this, result) }
|
||||
}
|
||||
|
||||
private class WhenClauseChildMapping extends NonExprChildMapping, WhenClause {
|
||||
override predicate relevantChild(AstNode e) { e = [this.getBody(), this.getAPattern()] }
|
||||
// `when` clauses need special treatment, since they are neither pre-order
|
||||
// nor post-order
|
||||
private class WhenClauseChildMapping extends WhenClause {
|
||||
predicate patternReachesBasicBlock(int i, CfgNode cfnPattern, BasicBlock bb) {
|
||||
exists(Expr pattern |
|
||||
pattern = this.getPattern(i) and
|
||||
cfnPattern.getNode() = pattern and
|
||||
bb.getANode() = cfnPattern
|
||||
)
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
this.patternReachesBasicBlock(i, cfnPattern, mid) and
|
||||
bb = mid.getASuccessor() and
|
||||
not mid.getANode().getNode() = this
|
||||
)
|
||||
}
|
||||
|
||||
predicate bodyReachesBasicBlock(CfgNode cfnBody, BasicBlock bb) {
|
||||
exists(Stmt body |
|
||||
body = this.getBody() and
|
||||
cfnBody.getNode() = body and
|
||||
bb.getANode() = cfnBody
|
||||
)
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
this.bodyReachesBasicBlock(cfnBody, mid) and
|
||||
bb = mid.getAPredecessor() and
|
||||
not mid.getANode().getNode() = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `WhenClause` AST expression. */
|
||||
@@ -443,10 +471,16 @@ module ExprNodes {
|
||||
override WhenClauseChildMapping e;
|
||||
|
||||
/** Gets the body of this `when`-clause. */
|
||||
final ExprCfgNode getBody() { e.hasCfgChild(e.getBody(), this, result) }
|
||||
final ExprCfgNode getBody() {
|
||||
result.getNode() = desugar(e.getBody()) and
|
||||
e.bodyReachesBasicBlock(result, this.getBasicBlock())
|
||||
}
|
||||
|
||||
/** Gets the `i`th pattern this `when`-clause. */
|
||||
final ExprCfgNode getPattern(int i) { e.hasCfgChild(e.getPattern(i), this, result) }
|
||||
final ExprCfgNode getPattern(int i) {
|
||||
result.getNode() = desugar(e.getPattern(i)) and
|
||||
e.patternReachesBasicBlock(i, result, this.getBasicBlock())
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `CasePattern`. */
|
||||
@@ -866,6 +900,19 @@ module ExprNodes {
|
||||
final override RelationalOperation getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
private class SplatExprChildMapping extends OperationExprChildMapping, SplatExpr {
|
||||
override predicate relevantChild(AstNode n) { n = this.getOperand() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `SplatExpr` AST expression. */
|
||||
class SplatExprCfgNode extends UnaryOperationCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "SplatExprCfgNode" }
|
||||
|
||||
override SplatExprChildMapping e;
|
||||
|
||||
final override SplatExpr getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `ElementReference` AST expression. */
|
||||
class ElementReferenceCfgNode extends MethodCallCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "ElementReferenceCfgNode" }
|
||||
|
||||
@@ -211,8 +211,11 @@ private predicate inBooleanContext(AstNode n) {
|
||||
or
|
||||
exists(CaseExpr c, WhenClause w |
|
||||
not exists(c.getValue()) and
|
||||
c.getABranch() = w and
|
||||
c.getABranch() = w
|
||||
|
|
||||
w.getPattern(_) = n
|
||||
or
|
||||
w = n
|
||||
)
|
||||
}
|
||||
|
||||
@@ -233,8 +236,11 @@ private predicate inMatchingContext(AstNode n) {
|
||||
or
|
||||
exists(CaseExpr c, WhenClause w |
|
||||
exists(c.getValue()) and
|
||||
c.getABranch() = w and
|
||||
c.getABranch() = w
|
||||
|
|
||||
w.getPattern(_) = n
|
||||
or
|
||||
w = n
|
||||
)
|
||||
or
|
||||
n instanceof CasePattern
|
||||
|
||||
@@ -400,8 +400,9 @@ module Trees {
|
||||
c instanceof SimpleCompletion
|
||||
or
|
||||
exists(int i, WhenTree branch | branch = this.getBranch(i) |
|
||||
last(branch.getLastPattern(), pred, c) and
|
||||
pred = branch and
|
||||
first(this.getBranch(i + 1), succ) and
|
||||
c.isValidFor(branch) and
|
||||
c.(ConditionalCompletion).getValue() = false
|
||||
)
|
||||
or
|
||||
@@ -1397,8 +1398,10 @@ module Trees {
|
||||
final override ControlFlowTree getChildElement(int i) { result = this.getMethodName(i) }
|
||||
}
|
||||
|
||||
private class WhenTree extends PreOrderTree, WhenClause {
|
||||
final override predicate propagatesAbnormal(AstNode child) { child = this.getAPattern() }
|
||||
private class WhenTree extends ControlFlowTree, WhenClause {
|
||||
final override predicate propagatesAbnormal(AstNode child) {
|
||||
child = [this.getAPattern(), this.getBody()]
|
||||
}
|
||||
|
||||
final Expr getLastPattern() {
|
||||
exists(int i |
|
||||
@@ -1407,8 +1410,11 @@ module Trees {
|
||||
)
|
||||
}
|
||||
|
||||
final override predicate first(AstNode first) { first(this.getPattern(0), first) }
|
||||
|
||||
final override predicate last(AstNode last, Completion c) {
|
||||
last(this.getLastPattern(), last, c) and
|
||||
last = this and
|
||||
c.isValidFor(this) and
|
||||
c.(ConditionalCompletion).getValue() = false
|
||||
or
|
||||
last(this.getBody(), last, c)
|
||||
@@ -1416,8 +1422,9 @@ module Trees {
|
||||
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
pred = this and
|
||||
first(this.getPattern(0), succ) and
|
||||
c instanceof SimpleCompletion
|
||||
c.isValidFor(this) and
|
||||
c.(ConditionalCompletion).getValue() = true and
|
||||
first(this.getBody(), succ)
|
||||
or
|
||||
exists(int i, Expr p, boolean b |
|
||||
p = this.getPattern(i) and
|
||||
@@ -1425,10 +1432,13 @@ module Trees {
|
||||
b = c.(ConditionalCompletion).getValue()
|
||||
|
|
||||
b = true and
|
||||
first(this.getBody(), succ)
|
||||
succ = this
|
||||
or
|
||||
b = false and
|
||||
first(this.getPattern(i + 1), succ)
|
||||
or
|
||||
not exists(this.getPattern(i + 1)) and
|
||||
succ = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,9 +907,13 @@ module TestOutput {
|
||||
|
||||
query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) {
|
||||
attr = "semmle.label" and
|
||||
exists(SuccessorType t | succ = getASuccessor(pred, t) |
|
||||
if successorTypeIsSimple(t) then val = "" else val = t.toString()
|
||||
)
|
||||
val =
|
||||
strictconcat(SuccessorType t, string s |
|
||||
succ = getASuccessor(pred, t) and
|
||||
if successorTypeIsSimple(t) then s = "" else s = t.toString()
|
||||
|
|
||||
s, ", " order by s
|
||||
)
|
||||
or
|
||||
attr = "semmle.order" and
|
||||
val =
|
||||
|
||||
@@ -86,6 +86,10 @@ private module ConditionalCompletionSplitting {
|
||||
last(succ.(ConditionalExpr).getBranch(_), pred, c) and
|
||||
completion = c
|
||||
)
|
||||
or
|
||||
succ(pred, succ, c) and
|
||||
succ instanceof WhenClause and
|
||||
completion = c
|
||||
}
|
||||
|
||||
override predicate hasEntryScope(CfgScope scope, AstNode succ) { none() }
|
||||
|
||||
@@ -7,22 +7,53 @@ private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.ruby.ast.internal.Constant
|
||||
private import codeql.ruby.InclusionTests
|
||||
private import codeql.ruby.ast.internal.Literal
|
||||
|
||||
private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
|
||||
cached
|
||||
private predicate stringConstCompare(CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch) {
|
||||
exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c |
|
||||
c = g and
|
||||
c = guard and
|
||||
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
|
||||
c.getExpr() instanceof EqExpr and branch = true
|
||||
// Only consider strings without any interpolations
|
||||
not strLitNode.getExpr().getComponent(_) instanceof StringInterpolationComponent and
|
||||
c.getExpr() instanceof EqExpr and
|
||||
branch = true
|
||||
or
|
||||
c.getExpr() instanceof CaseEqExpr and branch = true
|
||||
or
|
||||
c.getExpr() instanceof NEExpr and branch = false
|
||||
|
|
||||
c.getLeftOperand() = strLitNode and c.getRightOperand() = e
|
||||
c.getLeftOperand() = strLitNode and c.getRightOperand() = testedNode
|
||||
or
|
||||
c.getLeftOperand() = e and c.getRightOperand() = strLitNode
|
||||
c.getLeftOperand() = testedNode and c.getRightOperand() = strLitNode
|
||||
)
|
||||
)
|
||||
or
|
||||
stringConstCaseCompare(guard, testedNode, branch)
|
||||
or
|
||||
exists(CfgNodes::ExprNodes::BinaryOperationCfgNode g |
|
||||
g = guard and
|
||||
stringConstCompareOr(guard, branch) and
|
||||
stringConstCompare(g.getLeftOperand(), testedNode, _)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `guard` is an `or` expression whose operands are string comparison guards.
|
||||
* For example:
|
||||
*
|
||||
* ```rb
|
||||
* x == "foo" or x == "bar"
|
||||
* ```
|
||||
*/
|
||||
private predicate stringConstCompareOr(
|
||||
CfgNodes::ExprNodes::BinaryOperationCfgNode guard, boolean branch
|
||||
) {
|
||||
guard.getExpr() instanceof LogicalOrExpr and
|
||||
branch = true and
|
||||
forall(CfgNode innerGuard | innerGuard = guard.getAnOperand() |
|
||||
stringConstCompare(innerGuard, any(Ssa::Definition def).getARead(), branch)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,10 +103,13 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
}
|
||||
}
|
||||
|
||||
private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
|
||||
cached
|
||||
private predicate stringConstArrayInclusionCall(
|
||||
CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch
|
||||
) {
|
||||
exists(InclusionTest t |
|
||||
t.asExpr() = g and
|
||||
e = t.getContainedNode().asExpr() and
|
||||
t.asExpr() = guard and
|
||||
testedNode = t.getContainedNode().asExpr() and
|
||||
branch = t.getPolarity()
|
||||
|
|
||||
exists(ExprNodes::ArrayLiteralCfgNode arr |
|
||||
@@ -132,3 +166,68 @@ deprecated class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
|
||||
|
||||
override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true }
|
||||
}
|
||||
|
||||
/**
|
||||
* A validation of a value by comparing with a constant string via a `case`
|
||||
* expression. For example:
|
||||
*
|
||||
* ```rb
|
||||
* name = params[:user_name]
|
||||
* case name
|
||||
* when "alice"
|
||||
* User.find_by("username = #{name}")
|
||||
* when *["bob", "charlie"]
|
||||
* User.find_by("username = #{name}")
|
||||
* when "dave", "eve" # this is not yet recognised as a barrier guard
|
||||
* User.find_by("username = #{name}")
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private predicate stringConstCaseCompare(
|
||||
CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch
|
||||
) {
|
||||
branch = true and
|
||||
exists(CfgNodes::ExprNodes::CaseExprCfgNode case |
|
||||
case.getValue() = testedNode and
|
||||
(
|
||||
guard =
|
||||
any(CfgNodes::ExprNodes::WhenClauseCfgNode branchNode |
|
||||
branchNode = case.getBranch(_) and
|
||||
// For simplicity, consider patterns that contain only string literals or arrays of string literals
|
||||
forall(ExprCfgNode pattern | pattern = branchNode.getPattern(_) |
|
||||
// when "foo"
|
||||
// when "foo", "bar"
|
||||
pattern instanceof ExprNodes::StringLiteralCfgNode
|
||||
or
|
||||
pattern =
|
||||
any(CfgNodes::ExprNodes::SplatExprCfgNode splat |
|
||||
// when *["foo", "bar"]
|
||||
forex(ExprCfgNode elem |
|
||||
elem = splat.getOperand().(ExprNodes::ArrayLiteralCfgNode).getAnArgument()
|
||||
|
|
||||
elem instanceof ExprNodes::StringLiteralCfgNode
|
||||
)
|
||||
or
|
||||
// when *some_var
|
||||
// when *SOME_CONST
|
||||
exists(ExprNodes::ArrayLiteralCfgNode arr |
|
||||
isArrayConstant(splat.getOperand(), arr) and
|
||||
forall(ExprCfgNode elem | elem = arr.getAnArgument() |
|
||||
elem instanceof ExprNodes::StringLiteralCfgNode
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
// in "foo"
|
||||
exists(
|
||||
CfgNodes::ExprNodes::InClauseCfgNode branchNode, ExprNodes::StringLiteralCfgNode pattern
|
||||
|
|
||||
branchNode = case.getBranch(_) and
|
||||
pattern = branchNode.getPattern() and
|
||||
guard = pattern
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
30
ruby/ql/lib/codeql/ruby/dataflow/FlowSteps.qll
Normal file
30
ruby/ql/lib/codeql/ruby/dataflow/FlowSteps.qll
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Provides classes representing various flow steps for taint tracking.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import internal.DataFlowPrivate as DFPrivate
|
||||
|
||||
private class Unit = DFPrivate::Unit;
|
||||
|
||||
/**
|
||||
* A module importing the frameworks that implement additional flow steps,
|
||||
* ensuring that they are visible to the taint tracking library.
|
||||
*/
|
||||
private module Frameworks {
|
||||
import codeql.ruby.frameworks.StringFormatters
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*
|
||||
* Extend this class to add additional taint steps that should apply to all
|
||||
* taint configurations.
|
||||
*/
|
||||
class AdditionalTaintStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `node1` to `node2` should be considered a taint
|
||||
* step for all configurations.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
|
||||
}
|
||||
@@ -494,6 +494,7 @@ private module Cached {
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
|
||||
} or
|
||||
THashSplatArgumentPosition() or
|
||||
TSplatAllArgumentPosition() or
|
||||
TAnyArgumentPosition() or
|
||||
TAnyKeywordArgumentPosition()
|
||||
|
||||
@@ -515,6 +516,7 @@ private module Cached {
|
||||
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
|
||||
} or
|
||||
THashSplatParameterPosition() or
|
||||
TSplatAllParameterPosition() or
|
||||
TAnyParameterPosition() or
|
||||
TAnyKeywordParameterPosition()
|
||||
}
|
||||
@@ -1146,6 +1148,8 @@ class ParameterPosition extends TParameterPosition {
|
||||
/** Holds if this position represents a hash-splat parameter. */
|
||||
predicate isHashSplat() { this = THashSplatParameterPosition() }
|
||||
|
||||
predicate isSplatAll() { this = TSplatAllParameterPosition() }
|
||||
|
||||
/**
|
||||
* Holds if this position represents any parameter, except `self` parameters. This
|
||||
* includes both positional, named, and block parameters.
|
||||
@@ -1169,6 +1173,8 @@ class ParameterPosition extends TParameterPosition {
|
||||
or
|
||||
this.isHashSplat() and result = "**"
|
||||
or
|
||||
this.isSplatAll() and result = "*"
|
||||
or
|
||||
this.isAny() and result = "any"
|
||||
or
|
||||
this.isAnyNamed() and result = "any-named"
|
||||
@@ -1204,6 +1210,8 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
*/
|
||||
predicate isHashSplat() { this = THashSplatArgumentPosition() }
|
||||
|
||||
predicate isSplatAll() { this = TSplatAllArgumentPosition() }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
@@ -1219,6 +1227,8 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
this.isAnyNamed() and result = "any-named"
|
||||
or
|
||||
this.isHashSplat() and result = "**"
|
||||
or
|
||||
this.isSplatAll() and result = "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1245,6 +1255,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
ppos.isHashSplat() and apos.isHashSplat()
|
||||
or
|
||||
ppos.isSplatAll() and apos.isSplatAll()
|
||||
or
|
||||
ppos.isAny() and argumentPositionIsNotSelf(apos)
|
||||
or
|
||||
apos.isAny() and parameterPositionIsNotSelf(ppos)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -915,6 +915,17 @@ private module Cached {
|
||||
TDataFlowCallNone() or
|
||||
TDataFlowCallSome(DataFlowCall call)
|
||||
|
||||
cached
|
||||
newtype TParameterPositionOption =
|
||||
TParameterPositionNone() or
|
||||
TParameterPositionSome(ParameterPosition pos)
|
||||
|
||||
cached
|
||||
newtype TReturnCtx =
|
||||
TReturnCtxNone() or
|
||||
TReturnCtxNoFlowThrough() or
|
||||
TReturnCtxMaybeFlowThrough(ReturnKindExt kind)
|
||||
|
||||
cached
|
||||
newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
|
||||
|
||||
@@ -1304,6 +1315,44 @@ class DataFlowCallOption extends TDataFlowCallOption {
|
||||
}
|
||||
}
|
||||
|
||||
/** An optional `ParameterPosition`. */
|
||||
class ParameterPositionOption extends TParameterPositionOption {
|
||||
string toString() {
|
||||
this = TParameterPositionNone() and
|
||||
result = "(none)"
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
this = TParameterPositionSome(pos) and
|
||||
result = pos.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A return context used to calculate flow summaries in reverse flow.
|
||||
*
|
||||
* The possible values are:
|
||||
*
|
||||
* - `TReturnCtxNone()`: no return flow.
|
||||
* - `TReturnCtxNoFlowThrough()`: return flow, but flow through is not possible.
|
||||
* - `TReturnCtxMaybeFlowThrough(ReturnKindExt kind)`: return flow, of kind `kind`, and
|
||||
* flow through may be possible.
|
||||
*/
|
||||
class ReturnCtx extends TReturnCtx {
|
||||
string toString() {
|
||||
this = TReturnCtxNone() and
|
||||
result = "(none)"
|
||||
or
|
||||
this = TReturnCtxNoFlowThrough() and
|
||||
result = "(no flow through)"
|
||||
or
|
||||
exists(ReturnKindExt kind |
|
||||
this = TReturnCtxMaybeFlowThrough(kind) and
|
||||
result = kind.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A `Content` tagged with the type of a containing object. */
|
||||
class TypedContent extends MkTypedContent {
|
||||
private Content c;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -241,6 +241,10 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
this = call.getAnArgument() and
|
||||
this.getExpr() instanceof HashSplatExpr and
|
||||
arg.isHashSplat()
|
||||
or
|
||||
this = call.getArgument(0) and
|
||||
this.getExpr() instanceof SplatExpr and
|
||||
arg.isSplatAll()
|
||||
}
|
||||
|
||||
/** Holds if this expression is the `i`th argument of `c`. */
|
||||
@@ -276,7 +280,8 @@ private module Cached {
|
||||
p instanceof SimpleParameter or
|
||||
p instanceof OptionalParameter or
|
||||
p instanceof KeywordParameter or
|
||||
p instanceof HashSplatParameter
|
||||
p instanceof HashSplatParameter or
|
||||
p instanceof SplatParameter
|
||||
} or
|
||||
TSelfParameterNode(MethodBase m) or
|
||||
TBlockParameterNode(MethodBase m) or
|
||||
@@ -474,7 +479,7 @@ private module Cached {
|
||||
// external model data. This, unfortunately, does not included any field names used
|
||||
// in models defined in QL code.
|
||||
exists(string input, string output |
|
||||
ModelOutput::relevantSummaryModel(_, _, _, input, output, _)
|
||||
ModelOutput::relevantSummaryModel(_, _, input, output, _)
|
||||
|
|
||||
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
|
||||
)
|
||||
@@ -616,6 +621,9 @@ private module ParameterNodes {
|
||||
or
|
||||
parameter = callable.getAParameter().(HashSplatParameter) and
|
||||
pos.isHashSplat()
|
||||
or
|
||||
parameter = callable.getParameter(0).(SplatParameter) and
|
||||
pos.isSplatAll()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -615,7 +615,7 @@ class ContentSet extends TContentSet {
|
||||
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
|
||||
* the argument `x`.
|
||||
*/
|
||||
signature predicate guardChecksSig(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch);
|
||||
signature predicate guardChecksSig(CfgNodes::AstCfgNode g, CfgNode e, boolean branch);
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates an expression.
|
||||
@@ -625,13 +625,13 @@ signature predicate guardChecksSig(CfgNodes::ExprCfgNode g, CfgNode e, boolean b
|
||||
*/
|
||||
module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
pragma[nomagic]
|
||||
private predicate guardChecksSsaDef(CfgNodes::ExprCfgNode g, boolean branch, Ssa::Definition def) {
|
||||
private predicate guardChecksSsaDef(CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def) {
|
||||
guardChecks(g, def.getARead(), branch)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate guardControlsSsaDef(
|
||||
CfgNodes::ExprCfgNode g, boolean branch, Ssa::Definition def, Node n
|
||||
CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def, Node n
|
||||
) {
|
||||
def.getARead() = n.asExpr() and
|
||||
guardControlsBlock(g, n.asExpr().getBasicBlock(), branch)
|
||||
@@ -639,7 +639,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
|
||||
/** Gets a node that is safely guarded by the given guard check. */
|
||||
Node getABarrierNode() {
|
||||
exists(CfgNodes::ExprCfgNode g, boolean branch, Ssa::Definition def |
|
||||
exists(CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def |
|
||||
guardChecksSsaDef(g, branch, def) and
|
||||
guardControlsSsaDef(g, branch, def, result)
|
||||
)
|
||||
@@ -669,8 +669,8 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
}
|
||||
|
||||
/** Holds if the guard `guard` controls block `bb` upon evaluating to `branch`. */
|
||||
private predicate guardControlsBlock(CfgNodes::ExprCfgNode guard, BasicBlock bb, boolean branch) {
|
||||
exists(ConditionBlock conditionBlock, SuccessorTypes::BooleanSuccessor s |
|
||||
private predicate guardControlsBlock(CfgNodes::AstCfgNode guard, BasicBlock bb, boolean branch) {
|
||||
exists(ConditionBlock conditionBlock, SuccessorTypes::ConditionalSuccessor s |
|
||||
guard = conditionBlock.getLastNode() and
|
||||
s.getValue() = branch and
|
||||
conditionBlock.controls(bb, s)
|
||||
|
||||
@@ -2,6 +2,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.dataflow.SSA
|
||||
private import codeql.ruby.ast.Variable
|
||||
private import Cfg::CfgNodes::ExprNodes
|
||||
|
||||
@@ -61,7 +62,23 @@ private module SsaInput implements SsaImplCommon::InputSig {
|
||||
}
|
||||
}
|
||||
|
||||
import SsaImplCommon::Make<SsaInput>
|
||||
private import SsaImplCommon::Make<SsaInput> as Impl
|
||||
|
||||
class Definition = Impl::Definition;
|
||||
|
||||
class WriteDefinition = Impl::WriteDefinition;
|
||||
|
||||
class UncertainWriteDefinition = Impl::UncertainWriteDefinition;
|
||||
|
||||
class PhiNode = Impl::PhiNode;
|
||||
|
||||
module Consistency = Impl::Consistency;
|
||||
|
||||
module ExposedForTestingOnly {
|
||||
predicate ssaDefReachesReadExt = Impl::ssaDefReachesReadExt/4;
|
||||
|
||||
predicate phiHasInputFromBlockExt = Impl::phiHasInputFromBlockExt/3;
|
||||
}
|
||||
|
||||
/** Holds if `v` is uninitialized at index `i` in entry block `bb`. */
|
||||
predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, LocalVariable v) {
|
||||
@@ -204,7 +221,7 @@ private predicate adjacentDefRead(
|
||||
Definition def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2,
|
||||
SsaInput::SourceVariable v
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
Impl::adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
v = def.getSourceVariable()
|
||||
}
|
||||
|
||||
@@ -220,7 +237,7 @@ private predicate adjacentDefReachesRead(
|
||||
exists(SsaInput::BasicBlock bb3, int i3 |
|
||||
adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
|
||||
SsaInput::variableRead(bb3, i3, _, false) and
|
||||
adjacentDefRead(def, bb3, i3, bb2, i2)
|
||||
Impl::adjacentDefRead(def, bb3, i3, bb2, i2)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,11 +260,11 @@ private predicate adjacentDefReachesUncertainRead(
|
||||
/** Same as `lastRefRedef`, but skips uncertain reads. */
|
||||
pragma[nomagic]
|
||||
private predicate lastRefSkipUncertainReads(Definition def, SsaInput::BasicBlock bb, int i) {
|
||||
lastRef(def, bb, i) and
|
||||
Impl::lastRef(def, bb, i) and
|
||||
not SsaInput::variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(SsaInput::BasicBlock bb0, int i0 |
|
||||
lastRef(def, bb0, i0) and
|
||||
Impl::lastRef(def, bb0, i0) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
@@ -304,7 +321,7 @@ private module Cached {
|
||||
cached
|
||||
VariableReadAccessCfgNode getARead(Definition def) {
|
||||
exists(LocalVariable v, Cfg::BasicBlock bb, int i |
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
Impl::ssaDefReachesRead(v, def, bb, i) and
|
||||
variableReadActual(bb, i, v) and
|
||||
result = bb.getNode(i)
|
||||
)
|
||||
@@ -315,7 +332,7 @@ private module Cached {
|
||||
Definition def, CallCfgNode call, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
Impl::ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedCallRead(call, bb, i, v) and
|
||||
scope.getOuterCfgScope() = bb.getScope()
|
||||
)
|
||||
@@ -360,7 +377,7 @@ private module Cached {
|
||||
Definition def, LocalVariable v, Cfg::CfgScope scope
|
||||
) {
|
||||
exists(Cfg::BasicBlock bb, int i |
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
Impl::ssaDefReachesRead(v, def, bb, i) and
|
||||
capturedExitRead(bb, i, v) and
|
||||
scope = bb.getScope().getOuterCfgScope*()
|
||||
)
|
||||
@@ -403,7 +420,7 @@ private module Cached {
|
||||
|
||||
cached
|
||||
Definition phiHasInputFromBlock(PhiNode phi, Cfg::BasicBlock bb) {
|
||||
phiHasInputFromBlock(phi, result, bb)
|
||||
Impl::phiHasInputFromBlock(phi, result, bb)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,19 +476,45 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate lastRefBeforeRedef(Definition def, Cfg::BasicBlock bb, int i, Definition next) {
|
||||
lastRefRedef(def, bb, i, next) and
|
||||
Impl::lastRefRedef(def, bb, i, next) and
|
||||
not SsaInput::variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(SsaInput::BasicBlock bb0, int i0 |
|
||||
lastRefRedef(def, bb0, i0, next) and
|
||||
Impl::lastRefRedef(def, bb0, i0, next) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
Definition uncertainWriteDefinitionInput(UncertainWriteDefinition def) {
|
||||
uncertainWriteDefinitionInput(def, result)
|
||||
Impl::uncertainWriteDefinitionInput(def, result)
|
||||
}
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
/**
|
||||
* An extended static single assignment (SSA) definition.
|
||||
*
|
||||
* This is either a normal SSA definition (`Definition`) or a
|
||||
* phi-read node (`PhiReadNode`).
|
||||
*
|
||||
* Only intended for internal use.
|
||||
*/
|
||||
class DefinitionExt extends Impl::DefinitionExt {
|
||||
override string toString() { result = this.(Ssa::Definition).toString() }
|
||||
|
||||
/** Gets the location of this definition. */
|
||||
Location getLocation() { result = this.(Ssa::Definition).getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A phi-read node.
|
||||
*
|
||||
* Only intended for internal use.
|
||||
*/
|
||||
class PhiReadNode extends DefinitionExt, Impl::PhiReadNode {
|
||||
override string toString() { result = "SSA phi read(" + this.getSourceVariable() + ")" }
|
||||
|
||||
override Location getLocation() { result = this.getBasicBlock().getLocation() }
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ private CfgNodes::ExprNodes::VariableWriteAccessCfgNode variablesInPattern(
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
private import codeql.ruby.dataflow.FlowSteps as FlowSteps
|
||||
|
||||
cached
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
|
||||
@@ -93,12 +95,10 @@ private module Cached {
|
||||
)
|
||||
)
|
||||
or
|
||||
// string interpolation of `nodeFrom` into `nodeTo`
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
|
||||
or
|
||||
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo)
|
||||
or
|
||||
// Although flow through collections is modeled precisely using stores/reads, we still
|
||||
// allow flow out of a _tainted_ collection. This is needed in order to support taint-
|
||||
// tracking configurations where the source is a collection.
|
||||
|
||||
@@ -12,6 +12,7 @@ private import codeql.ruby.frameworks.ActionDispatch
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.Rails
|
||||
private import codeql.ruby.frameworks.internal.Rails
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Rails` and use `Rails::ParamsCall` instead.
|
||||
@@ -295,7 +296,7 @@ private module Request {
|
||||
|
||||
/** A method call on `request` which returns the request body. */
|
||||
private class BodyCall extends RequestInputAccess {
|
||||
BodyCall() { this.getMethodName() = ["body", "raw_post"] }
|
||||
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
|
||||
}
|
||||
@@ -538,12 +539,34 @@ private class ActionControllerProtectFromForgeryCall extends CsrfProtectionSetti
|
||||
/**
|
||||
* A call to `send_file`, which sends the file at the given path to the client.
|
||||
*/
|
||||
private class SendFile extends FileSystemAccess::Range, DataFlow::CallNode {
|
||||
private class SendFile extends FileSystemAccess::Range, Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallNode {
|
||||
SendFile() {
|
||||
this = [actionControllerInstance(), Response::response()].getAMethodCall("send_file")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getBody() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() { result = "application/octet-stream" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `send_data`, which sends the given data to the client.
|
||||
*/
|
||||
class SendDataCall extends DataFlow::CallNode, Http::Server::HttpResponse::Range {
|
||||
SendDataCall() {
|
||||
this = [actionControllerInstance(), Response::response()].getAMethodCall("send_data")
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() { result = "application/octet-stream" }
|
||||
}
|
||||
|
||||
private module ParamsSummaries {
|
||||
@@ -733,3 +756,28 @@ private module Response {
|
||||
override DataFlow::Node getValue() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
|
||||
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"]
|
||||
}
|
||||
|
||||
// Note: this is identical to the definition `stdlib.Logger.LoggerInfoStyleCall`.
|
||||
override DataFlow::Node getAnInput() {
|
||||
// `msg` from `Logger#info(msg)`,
|
||||
// or `progname` from `Logger#info(progname) <block>`
|
||||
result = this.getArgument(0)
|
||||
or
|
||||
// a return value from the block in `Logger#info(progname) <block>`
|
||||
exprNodeReturnedFrom(result, this.getBlock().asExpr().getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,23 +21,21 @@ module ActionDispatch {
|
||||
*/
|
||||
private class MimeTypeTypeSummary extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1;type1;package2;type2;path
|
||||
// type1;type2;path
|
||||
row =
|
||||
[
|
||||
// Mime[type] : Mime::Type (omitted)
|
||||
// Method names with brackets like [] cannot be represented in MaD.
|
||||
// Mime.fetch(type) : Mime::Type
|
||||
"actiondispatch;Mime::Type;;;Member[Mime].Method[fetch].ReturnValue",
|
||||
// Mime::Type.new(str) : Mime::Type
|
||||
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Instance",
|
||||
"Mime::Type;Mime!;Method[fetch].ReturnValue",
|
||||
// Mime::Type.lookup(str) : Mime::Type
|
||||
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[lookup].ReturnValue",
|
||||
"Mime::Type;Mime::Type!;Method[lookup].ReturnValue",
|
||||
// Mime::Type.lookup_by_extension(str) : Mime::Type
|
||||
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[lookup_by_extension].ReturnValue",
|
||||
"Mime::Type;Mime::Type!;Method[lookup_by_extension].ReturnValue",
|
||||
// Mime::Type.register(str) : Mime::Type
|
||||
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[register].ReturnValue",
|
||||
"Mime::Type;Mime::Type!;Method[register].ReturnValue",
|
||||
// Mime::Type.register_alias(str) : Mime::Type
|
||||
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[register_alias].ReturnValue",
|
||||
"Mime::Type;Mime::Type!;Method[register_alias].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -48,10 +46,7 @@ module ActionDispatch {
|
||||
*/
|
||||
class MimeTypeMatchRegExpInterpretation extends RE::RegExpInterpretation::Range {
|
||||
MimeTypeMatchRegExpInterpretation() {
|
||||
this =
|
||||
ModelOutput::getATypeNode("actiondispatch", "Mime::Type")
|
||||
.getAMethodCall(["match?", "=~"])
|
||||
.getArgument(0)
|
||||
this = ModelOutput::getATypeNode("Mime::Type").getAMethodCall(["match?", "=~"]).getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ module ActiveStorage {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"activestorage;;Member[ActiveStorage].Member[Filename].Method[new];Argument[0];ReturnValue;taint",
|
||||
"activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized];Argument[self];ReturnValue;taint",
|
||||
"ActiveStorage::Filename!;Method[new];Argument[0];ReturnValue;taint",
|
||||
"ActiveStorage::Filename;Method[sanitized];Argument[self];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -45,25 +45,23 @@ module ActiveStorage {
|
||||
// package1;type1;package2;type2;path
|
||||
row =
|
||||
[
|
||||
// ActiveStorage::Blob.new : Blob
|
||||
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Instance",
|
||||
// ActiveStorage::Blob.create_and_upload! : Blob
|
||||
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[create_and_upload!].ReturnValue",
|
||||
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[create_and_upload!].ReturnValue",
|
||||
// ActiveStorage::Blob.create_before_direct_upload! : Blob
|
||||
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[create_before_direct_upload!].ReturnValue",
|
||||
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[create_before_direct_upload!].ReturnValue",
|
||||
// ActiveStorage::Blob.compose(blobs : [Blob]) : Blob
|
||||
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].ReturnValue",
|
||||
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[compose].ReturnValue",
|
||||
// gives error: Invalid name 'Element' in access path
|
||||
// "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].Argument[0].Element[any]",
|
||||
// "ActiveStorage::Blob;ActiveStorage::Blob!;Method[compose].Argument[0].Element[any]",
|
||||
// ActiveStorage::Blob.find_signed(!) : Blob
|
||||
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[find_signed,find_signed!].ReturnValue",
|
||||
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[find_signed,find_signed!].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private class BlobInstance extends DataFlow::Node {
|
||||
BlobInstance() {
|
||||
this = ModelOutput::getATypeNode("activestorage", "Blob").getAValueReachableFromSource()
|
||||
this = ModelOutput::getATypeNode("ActiveStorage::Blob").getAValueReachableFromSource()
|
||||
or
|
||||
// ActiveStorage::Attachment#blob : Blob
|
||||
exists(DataFlow::CallNode call |
|
||||
@@ -168,7 +166,7 @@ module ActiveStorage {
|
||||
* A call on an ActiveStorage object that results in an image transformation.
|
||||
* Arguments to these calls may be executed as system commands.
|
||||
*/
|
||||
private class ImageProcessingCall extends DataFlow::CallNode, SystemCommandExecution::Range {
|
||||
private class ImageProcessingCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
ImageProcessingCall() {
|
||||
this.getReceiver() instanceof BlobInstance and
|
||||
this.getMethodName() = ["variant", "preview", "representation"]
|
||||
@@ -209,7 +207,7 @@ module ActiveStorage {
|
||||
this = API::getTopLevelMember("ActiveStorage").getAMethodCall("video_preview_arguments=")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument(0) }
|
||||
override DataFlow::Node getAnArgument() { result = super.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,6 +104,17 @@ module ActiveSupport {
|
||||
|
||||
override predicate runsArbitraryCode() { none() }
|
||||
}
|
||||
|
||||
/** Flow summary for `Object#to_json`, which serializes the receiver as a JSON string. */
|
||||
private class ToJsonSummary extends SimpleSummarizedCallable {
|
||||
ToJsonSummary() { this = "to_json" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = ["Argument[self]", "Argument[self].Element[any]"] and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,7 +295,153 @@ module ActiveSupport {
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
// TODO: index_by, index_with, pick, pluck (they require Hash dataflow)
|
||||
|
||||
private class IndexBySummary extends SimpleSummarizedCallable {
|
||||
IndexBySummary() { this = "index_by" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class IndexWithSummary extends SimpleSummarizedCallable {
|
||||
IndexWithSummary() { this = "index_with" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any]" and
|
||||
output = "Argument[block].Parameter[0]" and
|
||||
preservesValue = true
|
||||
or
|
||||
input = ["Argument[0]", "Argument[block].ReturnValue"] and
|
||||
output = "ReturnValue.Element[?]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private string getKeyArgument(MethodCall mc, int i) {
|
||||
mc.getMethodName() = ["pick", "pluck"] and
|
||||
result = DataFlow::Content::getKnownElementIndex(mc.getArgument(i)).serialize()
|
||||
}
|
||||
|
||||
private class PickSingleSummary extends SummarizedCallable {
|
||||
private MethodCall mc;
|
||||
private string key;
|
||||
|
||||
PickSingleSummary() {
|
||||
key = getKeyArgument(mc, 0) and
|
||||
this = "Enumerable.pick(" + key + ")" and
|
||||
mc.getMethodName() = "pick" and
|
||||
mc.getNumberOfArguments() = 1
|
||||
}
|
||||
|
||||
override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[0].Element[" + key + "]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class PickMultipleSummary extends SummarizedCallable {
|
||||
private MethodCall mc;
|
||||
|
||||
PickMultipleSummary() {
|
||||
mc.getMethodName() = "pick" and
|
||||
mc.getNumberOfArguments() > 1 and
|
||||
exists(int maxKey |
|
||||
maxKey = max(int j | exists(getKeyArgument(mc, j))) and
|
||||
this =
|
||||
"Enumerable.pick(" +
|
||||
concat(int i, string key |
|
||||
key = getKeyArgument(mc, i)
|
||||
or
|
||||
key = "_" and
|
||||
not exists(getKeyArgument(mc, i)) and
|
||||
i in [0 .. maxKey]
|
||||
|
|
||||
key, "," order by i
|
||||
) + ")"
|
||||
)
|
||||
}
|
||||
|
||||
override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
exists(string s, int i |
|
||||
s = getKeyArgument(mc, i) and
|
||||
input = "Argument[self].Element[0].Element[" + s + "]" and
|
||||
output = "ReturnValue.Element[" + i + "]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class PluckSingleSummary extends SummarizedCallable {
|
||||
private MethodCall mc;
|
||||
private string key;
|
||||
|
||||
PluckSingleSummary() {
|
||||
key = getKeyArgument(mc, 0) and
|
||||
this = "Enumerable.pluck(" + key + ")" and
|
||||
mc.getMethodName() = "pluck" and
|
||||
mc.getNumberOfArguments() = 1
|
||||
}
|
||||
|
||||
override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[any].Element[" + key + "]" and
|
||||
output = "ReturnValue.Element[any]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class PluckMultipleSummary extends SummarizedCallable {
|
||||
private MethodCall mc;
|
||||
|
||||
PluckMultipleSummary() {
|
||||
mc.getMethodName() = "pluck" and
|
||||
mc.getNumberOfArguments() > 1 and
|
||||
exists(int maxKey |
|
||||
maxKey = max(int j | exists(getKeyArgument(mc, j))) and
|
||||
this =
|
||||
"Enumerable.pluck(" +
|
||||
concat(int i, string key |
|
||||
key = getKeyArgument(mc, i)
|
||||
or
|
||||
key = "_" and
|
||||
not exists(getKeyArgument(mc, i)) and
|
||||
i in [0 .. maxKey]
|
||||
|
|
||||
key, "," order by i
|
||||
) + ")"
|
||||
)
|
||||
}
|
||||
|
||||
override MethodCall getACall() { result = mc }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
exists(string s, int i |
|
||||
s = getKeyArgument(mc, i) and
|
||||
input = "Argument[self].Element[any].Element[" + s + "]" and
|
||||
output = "ReturnValue.Element[?].Element[" + i + "]"
|
||||
) and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class SoleSummary extends SimpleSummarizedCallable {
|
||||
SoleSummary() { this = "sole" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[self].Element[0]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,14 +459,34 @@ module ActiveSupport {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `ActiveSupport::ERB`
|
||||
*/
|
||||
module Erb {
|
||||
/**
|
||||
* `ActiveSupport::ERB::Util`
|
||||
*/
|
||||
module Util {
|
||||
private class JsonEscapeSummary extends SimpleSummarizedCallable {
|
||||
JsonEscapeSummary() { this = "json_escape" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type summaries for extensions to the `Pathname` module.
|
||||
*/
|
||||
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1;type1;package2;type2;path
|
||||
// type1;type2;path
|
||||
// Pathname#existence : Pathname
|
||||
row = ";Pathname;;Pathname;Method[existence].ReturnValue"
|
||||
row = "Pathname;Pathname;Method[existence].ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +494,7 @@ module ActiveSupport {
|
||||
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
// Pathname#existence
|
||||
row = ";Pathname;Method[existence];Argument[self];ReturnValue;taint"
|
||||
row = "Pathname;Method[existence];Argument[self];ReturnValue;taint"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,13 +512,26 @@ module ActiveSupport {
|
||||
row =
|
||||
[
|
||||
// SafeBuffer.new(x) does not sanitize x
|
||||
"activesupport;;Member[ActionView].Member[SafeBuffer].Method[new];Argument[0];ReturnValue;taint",
|
||||
"ActionView::SafeBuffer!;Method[new];Argument[0];ReturnValue;taint",
|
||||
// SafeBuffer#safe_concat(x) does not sanitize x
|
||||
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat];Argument[0];ReturnValue;taint",
|
||||
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat];Argument[0];Argument[self];taint",
|
||||
"ActionView::SafeBuffer;Method[safe_concat];Argument[0];ReturnValue;taint",
|
||||
"ActionView::SafeBuffer;Method[safe_concat];Argument[0];Argument[self];taint",
|
||||
// These methods preserve taint in self
|
||||
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[concat,insert,prepend,to_s,to_param];Argument[self];ReturnValue;taint",
|
||||
"ActionView::SafeBuffer;Method[concat,insert,prepend,to_s,to_param];Argument[self];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** `ActiveSupport::JSON` */
|
||||
module Json {
|
||||
private class JsonSummary extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"ActiveSupport::JSON!;Method[encode,dump];Argument[0];ReturnValue;taint",
|
||||
"ActiveSupport::JSON!;Method[decode,load];Argument[0];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
|
||||
@@ -28,4 +29,14 @@ module Arel {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `Arel.sql`, considered as a SQL construction. */
|
||||
private class ArelSqlConstruction extends SqlConstruction::Range, DataFlow::CallNode {
|
||||
ArelSqlConstruction() {
|
||||
this = DataFlow::getConstant("Arel").getAMethodCall() and
|
||||
this.getMethodName() = "sql"
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
|
||||
22
ruby/ql/lib/codeql/ruby/frameworks/Json.qll
Normal file
22
ruby/ql/lib/codeql/ruby/frameworks/Json.qll
Normal file
@@ -0,0 +1,22 @@
|
||||
/** Provides modeling for the `json` gem. */
|
||||
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
|
||||
/** Provides modeling for the `json` gem. */
|
||||
module Json {
|
||||
/**
|
||||
* Flow summaries for common `JSON` methods.
|
||||
* Not all of these methods are strictly defined in the `json` gem.
|
||||
* The `JSON` namespace is heavily overloaded by other JSON parsing gems such as `oj`, `json_pure`, `multi_json` etc.
|
||||
* This summary covers common methods we've seen called on `JSON` in the wild.
|
||||
*/
|
||||
private class JsonSummary extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"JSON!;Method[parse,parse!,load,restore];Argument[0];ReturnValue;taint",
|
||||
"JSON!;Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse];Argument[0];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ module PosixSpawn {
|
||||
/**
|
||||
* A call to `POSIX::Spawn::Child.new` or `POSIX::Spawn::Child.build`.
|
||||
*/
|
||||
class ChildCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
class ChildCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
ChildCall() {
|
||||
this =
|
||||
[
|
||||
@@ -30,7 +30,7 @@ module PosixSpawn {
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() {
|
||||
result = this.getArgument(_) and not result.asExpr() instanceof ExprNodes::PairCfgNode
|
||||
result = super.getArgument(_) and not result.asExpr() instanceof ExprNodes::PairCfgNode
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
||||
@@ -39,7 +39,7 @@ module PosixSpawn {
|
||||
/**
|
||||
* A call to `POSIX::Spawn.spawn` or a related method.
|
||||
*/
|
||||
class SystemCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
class SystemCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
SystemCall() {
|
||||
this =
|
||||
posixSpawnModule()
|
||||
@@ -71,7 +71,7 @@ module PosixSpawn {
|
||||
}
|
||||
|
||||
private predicate argument(DataFlow::Node arg) {
|
||||
arg = this.getArgument(_) and
|
||||
arg = super.getArgument(_) and
|
||||
not arg.asExpr() instanceof ExprNodes::HashLiteralCfgNode and
|
||||
not arg.asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
|
||||
not arg.asExpr() instanceof ExprNodes::PairCfgNode
|
||||
|
||||
@@ -27,12 +27,12 @@ module Railties {
|
||||
* A call to `Rails::Generators::Actions#execute_command`.
|
||||
* This method concatenates its first and second arguments and executes the result as a shell command.
|
||||
*/
|
||||
private class ExecuteCommandCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
private class ExecuteCommandCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
ExecuteCommandCall() {
|
||||
this = generatorsActionsClass().getAnInstanceSelf().getAMethodCall("execute_command")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument([0, 1]) }
|
||||
override DataFlow::Node getAnArgument() { result = super.getArgument([0, 1]) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
|
||||
}
|
||||
@@ -40,7 +40,7 @@ module Railties {
|
||||
/**
|
||||
* A call to a method in `Rails::Generators::Actions` which delegates to `execute_command`.
|
||||
*/
|
||||
private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
ExecuteCommandWrapperCall() {
|
||||
this =
|
||||
generatorsActionsClass()
|
||||
@@ -48,7 +48,7 @@ module Railties {
|
||||
.getAMethodCall(["rake", "rails_command", "git"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() { result = this.getArgument(0) }
|
||||
override DataFlow::Node getAnArgument() { result = super.getArgument(0) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
|
||||
}
|
||||
|
||||
135
ruby/ql/lib/codeql/ruby/frameworks/StringFormatters.qll
Normal file
135
ruby/ql/lib/codeql/ruby/frameworks/StringFormatters.qll
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Provides classes for modeling string formatting libraries.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST as Ast
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.core.IO
|
||||
|
||||
/**
|
||||
* A call to `printf` or `sprintf`.
|
||||
*/
|
||||
abstract class PrintfStyleCall extends DataFlow::CallNode {
|
||||
// We assume that most printf-like calls have the signature f(format_string, args...)
|
||||
/**
|
||||
* Gets the format string of this call.
|
||||
*/
|
||||
DataFlow::Node getFormatString() { result = this.getArgument(0) }
|
||||
|
||||
/**
|
||||
* Gets then `n`th formatted argument of this call.
|
||||
*/
|
||||
DataFlow::Node getFormatArgument(int n) { n >= 0 and result = this.getArgument(n + 1) }
|
||||
|
||||
/** Holds if this call returns the formatted string. */
|
||||
predicate returnsFormatted() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Kernel.printf`.
|
||||
*/
|
||||
class KernelPrintfCall extends PrintfStyleCall {
|
||||
KernelPrintfCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall("printf")
|
||||
or
|
||||
this.asExpr().getExpr() instanceof Ast::UnknownMethodCall and
|
||||
this.getMethodName() = "printf"
|
||||
}
|
||||
|
||||
// Kernel#printf supports two signatures:
|
||||
// printf(io, string, ...)
|
||||
// printf(string, ...)
|
||||
override DataFlow::Node getFormatString() {
|
||||
// Because `printf` has two different signatures, we can't be sure which
|
||||
// argument is the format string, so we use a heuristic:
|
||||
// If the first argument has a string value, then we assume it is the format string.
|
||||
// Otherwise we treat both the first and second args as the format string.
|
||||
if this.getArgument(0).getExprNode().getConstantValue().isString(_)
|
||||
then result = this.getArgument(0)
|
||||
else result = this.getArgument([0, 1])
|
||||
}
|
||||
|
||||
override predicate returnsFormatted() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Kernel.sprintf`.
|
||||
*/
|
||||
class KernelSprintfCall extends PrintfStyleCall {
|
||||
KernelSprintfCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall("sprintf")
|
||||
or
|
||||
this.asExpr().getExpr() instanceof Ast::UnknownMethodCall and
|
||||
this.getMethodName() = "sprintf"
|
||||
}
|
||||
|
||||
override predicate returnsFormatted() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `IO#printf`.
|
||||
*/
|
||||
class IOPrintfCall extends PrintfStyleCall {
|
||||
IOPrintfCall() {
|
||||
this.getReceiver() instanceof IO::IOInstance and this.getMethodName() = "printf"
|
||||
}
|
||||
|
||||
override predicate returnsFormatted() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `String#%`.
|
||||
*/
|
||||
class StringPercentCall extends PrintfStyleCall {
|
||||
StringPercentCall() { this.getMethodName() = "%" }
|
||||
|
||||
override DataFlow::Node getFormatString() { result = this.getReceiver() }
|
||||
|
||||
override DataFlow::Node getFormatArgument(int n) {
|
||||
exists(CfgNodes::ExprNodes::ArrayLiteralCfgNode arrLit | arrLit = this.getArgument(0).asExpr() |
|
||||
result.asExpr() = arrLit.getArgument(n)
|
||||
)
|
||||
or
|
||||
exists(CfgNodes::ExprNodes::HashLiteralCfgNode hashLit |
|
||||
hashLit = this.getArgument(0).asExpr()
|
||||
|
|
||||
n = -2 and // -2 is indicates that the index does not make sense in this context
|
||||
result.asExpr() = hashLit.getAKeyValuePair().getValue()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate returnsFormatted() { any() }
|
||||
}
|
||||
|
||||
private import codeql.ruby.dataflow.FlowSteps
|
||||
private import codeql.ruby.CFG
|
||||
|
||||
/**
|
||||
* A step for string interpolation of `pred` into `succ`.
|
||||
* E.g.
|
||||
* ```rb
|
||||
* succ = "foo #{pred} bar"
|
||||
* ```
|
||||
*/
|
||||
private class StringLiteralFormatStep extends AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
pred.asExpr() = succ.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint propagating data flow edge arising from string formatting.
|
||||
*/
|
||||
private class StringFormattingTaintStep extends AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(PrintfStyleCall call |
|
||||
call.returnsFormatted() and
|
||||
succ = call
|
||||
|
|
||||
pred = call.getFormatString()
|
||||
or
|
||||
pred = call.getFormatArgument(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ module IO {
|
||||
* ```
|
||||
* Ruby documentation: https://docs.ruby-lang.org/en/3.1/IO.html#method-c-popen
|
||||
*/
|
||||
class POpenCall extends SystemCommandExecution::Range, DataFlow::CallNode {
|
||||
class POpenCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
POpenCall() { this = API::getTopLevelMember("IO").getAMethodCall("popen") }
|
||||
|
||||
override DataFlow::Node getAnArgument() { this.argument(result, _) }
|
||||
@@ -131,7 +131,7 @@ module IO {
|
||||
not n instanceof ExprNodes::ArrayLiteralCfgNode and
|
||||
(
|
||||
// IO.popen({var: "a"}, "cmd", {some: :opt})
|
||||
arg = this.getArgument([0, 1]) and
|
||||
arg = super.getArgument([0, 1]) and
|
||||
// We over-approximate by assuming a subshell if the argument isn't an array or "-".
|
||||
// This increases the sensitivity of the CommandInjection query at the risk of some FPs.
|
||||
if n.getConstantValue().getString() = "-" then shell = false else shell = true
|
||||
@@ -139,7 +139,7 @@ module IO {
|
||||
// IO.popen([{var: "b"}, "cmd", "arg1", "arg2", {some: :opt}])
|
||||
// IO.popen({var: "a"}, ["cmd", "arg1", "arg2", {some: :opt}])
|
||||
shell = false and
|
||||
exists(ExprNodes::ArrayLiteralCfgNode arr | this.getArgument([0, 1]).asExpr() = arr |
|
||||
exists(ExprNodes::ArrayLiteralCfgNode arr | super.getArgument([0, 1]).asExpr() = arr |
|
||||
n = arr.getAnArgument()
|
||||
or
|
||||
// IO.popen([{var: "b"}, ["cmd", "argv0"], "arg1", "arg2", {some: :opt}])
|
||||
|
||||
@@ -13,7 +13,7 @@ module Regexp {
|
||||
/** A flow summary for `Regexp.escape` and its alias, `Regexp.quote`. */
|
||||
class RegexpEscapeSummary extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row = ";;Member[Regexp].Method[escape,quote];Argument[0];ReturnValue;taint"
|
||||
row = "Regexp!;Method[escape,quote];Argument[0];ReturnValue;taint"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,26 +33,23 @@ private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range {
|
||||
}
|
||||
|
||||
private class SummarizedCallableFromModel extends SummarizedCallable {
|
||||
string package;
|
||||
string type;
|
||||
string path;
|
||||
|
||||
SummarizedCallableFromModel() {
|
||||
ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and
|
||||
this = package + ";" + type + ";" + path
|
||||
ModelOutput::relevantSummaryModel(type, path, _, _, _) and
|
||||
this = type + ";" + path
|
||||
}
|
||||
|
||||
override Call getACall() {
|
||||
exists(API::MethodAccessNode base |
|
||||
ModelOutput::resolvedSummaryBase(package, type, path, base) and
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
result = base.getCallNode().asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
exists(string kind |
|
||||
ModelOutput::relevantSummaryModel(package, type, path, input, output, kind)
|
||||
|
|
||||
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind) |
|
||||
kind = "value" and
|
||||
preservesValue = true
|
||||
or
|
||||
|
||||
@@ -5,23 +5,20 @@
|
||||
*
|
||||
* The CSV specification has the following columns:
|
||||
* - Sources:
|
||||
* `package; type; path; kind`
|
||||
* `type; path; kind`
|
||||
* - Sinks:
|
||||
* `package; type; path; kind`
|
||||
* `type; path; kind`
|
||||
* - Summaries:
|
||||
* `package; type; path; input; output; kind`
|
||||
* `type; path; input; output; kind`
|
||||
* - Types:
|
||||
* `package1; type1; package2; type2; path`
|
||||
* `type1; type2; path`
|
||||
*
|
||||
* The interpretation of a row is similar to API-graphs with a left-to-right
|
||||
* reading.
|
||||
* 1. The `package` column selects a package name, as it would be referenced in the source code,
|
||||
* such as an NPM package, PIP package, or Ruby gem. (See `ModelsAsData.qll` for language-specific details).
|
||||
* It may also be a synthetic package used for a type definition (see type definitions below).
|
||||
* 2. The `type` column selects all instances of a named type originating from that package,
|
||||
* or the empty string if referring to the package itself.
|
||||
* 1. The `type` column selects all instances of a named type. The syntax of this column is language-specific.
|
||||
* The language defines some type names that the analysis knows how to identify without models.
|
||||
* It can also be a synthetic type name defined by a type definition (see type definitions below).
|
||||
* 3. The `path` column is a `.`-separated list of "access path tokens" to resolve, starting at the node selected by `package` and `type`.
|
||||
* 2. The `path` column is a `.`-separated list of "access path tokens" to resolve, starting at the node selected by `type`.
|
||||
*
|
||||
* Every language supports the following tokens:
|
||||
* - Argument[n]: the n-th argument to a call. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
|
||||
@@ -42,10 +39,10 @@
|
||||
*
|
||||
* For the time being, please consult `ApiGraphModelsSpecific.qll` to see which language-specific tokens are currently supported.
|
||||
*
|
||||
* 4. The `input` and `output` columns specify how data enters and leaves the element selected by the
|
||||
* first `(package, type, path)` tuple. Both strings are `.`-separated access paths
|
||||
* 3. The `input` and `output` columns specify how data enters and leaves the element selected by the
|
||||
* first `(type, path)` tuple. Both strings are `.`-separated access paths
|
||||
* of the same syntax as the `path` column.
|
||||
* 5. The `kind` column is a tag that can be referenced from QL to determine to
|
||||
* 4. The `kind` column is a tag that can be referenced from QL to determine to
|
||||
* which classes the interpreted elements should be added. For example, for
|
||||
* sources `"remote"` indicates a default remote flow source, and for summaries
|
||||
* `"taint"` indicates a default additional taint step and `"value"` indicates a
|
||||
@@ -53,17 +50,17 @@
|
||||
*
|
||||
* ### Types
|
||||
*
|
||||
* A type row of form `package1; type1; package2; type2; path` indicates that `package2; type2; path`
|
||||
* should be seen as an instance of the type `package1; type1`.
|
||||
* A type row of form `type1; type2; path` indicates that `type2; path`
|
||||
* should be seen as an instance of the type `type1`.
|
||||
*
|
||||
* A `(package,type)` pair may refer to a static type or a synthetic type name used internally in the model.
|
||||
* A type may refer to a static type or a synthetic type name used internally in the model.
|
||||
* Synthetic type names can be used to reuse intermediate sub-paths, when there are multiple ways to access the same
|
||||
* element.
|
||||
* See `ModelsAsData.qll` for the language-specific interpretation of packages and static type names.
|
||||
* See `ModelsAsData.qll` for the language-specific interpretation of type names.
|
||||
*
|
||||
* By convention, if one wants to avoid clashes with static types from the package, the type name
|
||||
* should be prefixed with a tilde character (`~`). For example, `(foo, ~Bar)` can be used to indicate that
|
||||
* the type is related to the `foo` package but is not intended to match a static type.
|
||||
* By convention, if one wants to avoid clashes with static types, the type name
|
||||
* should be prefixed with a tilde character (`~`). For example, `~Bar` can be used to indicate that
|
||||
* the type is not intended to match a static type.
|
||||
*/
|
||||
|
||||
private import ApiGraphModelsSpecific as Specific
|
||||
@@ -89,9 +86,9 @@ module ModelInput {
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;kind
|
||||
* type;path;kind
|
||||
* ```
|
||||
* indicates that the value at `(package, type, path)` should be seen as a flow
|
||||
* indicates that the value at `(type, path)` should be seen as a flow
|
||||
* source of the given `kind`.
|
||||
*
|
||||
* The kind `remote` represents a general remote flow source.
|
||||
@@ -110,9 +107,9 @@ module ModelInput {
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;kind
|
||||
* type;path;kind
|
||||
* ```
|
||||
* indicates that the value at `(package, type, path)` should be seen as a sink
|
||||
* indicates that the value at `(type, path)` should be seen as a sink
|
||||
* of the given `kind`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
@@ -129,9 +126,9 @@ module ModelInput {
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;input;output;kind
|
||||
* type;path;input;output;kind
|
||||
* ```
|
||||
* indicates that for each call to `(package, type, path)`, the value referred to by `input`
|
||||
* indicates that for each call to `(type, path)`, the value referred to by `input`
|
||||
* can flow to the value referred to by `output`.
|
||||
*
|
||||
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
|
||||
@@ -151,9 +148,9 @@ module ModelInput {
|
||||
*
|
||||
* A row of form,
|
||||
* ```
|
||||
* package1;type1;package2;type2;path
|
||||
* type1;type2;path
|
||||
* ```
|
||||
* indicates that `(package2, type2, path)` should be seen as an instance of `(package1, type1)`.
|
||||
* indicates that `(type2, path)` should be seen as an instance of `type1`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
@@ -163,28 +160,28 @@ module ModelInput {
|
||||
*/
|
||||
class TypeModel extends Unit {
|
||||
/**
|
||||
* Gets a data-flow node that is a source of the type `package;type`.
|
||||
* Gets a data-flow node that is a source of the given `type`.
|
||||
*
|
||||
* This must not depend on API graphs, but ensures that an API node is generated for
|
||||
* the source.
|
||||
*/
|
||||
DataFlow::Node getASource(string package, string type) { none() }
|
||||
DataFlow::Node getASource(string type) { none() }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that is a sink of the type `package;type`,
|
||||
* Gets a data-flow node that is a sink of the given `type`,
|
||||
* usually because it is an argument passed to a parameter of that type.
|
||||
*
|
||||
* This must not depend on API graphs, but ensures that an API node is generated for
|
||||
* the sink.
|
||||
*/
|
||||
DataFlow::Node getASink(string package, string type) { none() }
|
||||
DataFlow::Node getASink(string type) { none() }
|
||||
|
||||
/**
|
||||
* Gets an API node that is a source or sink of the type `package;type`.
|
||||
* Gets an API node that is a source or sink of the given `type`.
|
||||
*
|
||||
* Unlike `getASource` and `getASink`, this may depend on API graphs.
|
||||
*/
|
||||
API::Node getAnApiNode(string package, string type) { none() }
|
||||
API::Node getAnApiNode(string type) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +206,7 @@ private import ModelInput
|
||||
/**
|
||||
* An empty class, except in specific tests.
|
||||
*
|
||||
* If this is non-empty, all models are parsed even if the package is not
|
||||
* If this is non-empty, all models are parsed even if the type name is not
|
||||
* considered relevant for the current database.
|
||||
*/
|
||||
abstract class TestAllModels extends Unit { }
|
||||
@@ -232,53 +229,44 @@ private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row
|
||||
private predicate typeVariableModel(string row) { any(TypeVariableModelCsv s).row(inversePad(row)) }
|
||||
|
||||
/** Holds if a source model exists for the given parameters. */
|
||||
predicate sourceModel(string package, string type, string path, string kind) {
|
||||
predicate sourceModel(string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sourceModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
row.splitAt(";", 2) = path and
|
||||
row.splitAt(";", 3) = kind
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a sink model exists for the given parameters. */
|
||||
private predicate sinkModel(string package, string type, string path, string kind) {
|
||||
private predicate sinkModel(string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sinkModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
row.splitAt(";", 2) = path and
|
||||
row.splitAt(";", 3) = kind
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a summary model `row` exists for the given parameters. */
|
||||
private predicate summaryModel(
|
||||
string package, string type, string path, string input, string output, string kind
|
||||
) {
|
||||
private predicate summaryModel(string type, string path, string input, string output, string kind) {
|
||||
exists(string row |
|
||||
summaryModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
row.splitAt(";", 2) = path and
|
||||
row.splitAt(";", 3) = input and
|
||||
row.splitAt(";", 4) = output and
|
||||
row.splitAt(";", 5) = kind
|
||||
row.splitAt(";", 0) = type and
|
||||
row.splitAt(";", 1) = path and
|
||||
row.splitAt(";", 2) = input and
|
||||
row.splitAt(";", 3) = output and
|
||||
row.splitAt(";", 4) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a type model exists for the given parameters. */
|
||||
private predicate typeModel(
|
||||
string package1, string type1, string package2, string type2, string path
|
||||
) {
|
||||
private predicate typeModel(string type1, string type2, string path) {
|
||||
exists(string row |
|
||||
typeModel(row) and
|
||||
row.splitAt(";", 0) = package1 and
|
||||
row.splitAt(";", 1) = type1 and
|
||||
row.splitAt(";", 2) = package2 and
|
||||
row.splitAt(";", 3) = type2 and
|
||||
row.splitAt(";", 4) = path
|
||||
row.splitAt(";", 0) = type1 and
|
||||
row.splitAt(";", 1) = type2 and
|
||||
row.splitAt(";", 2) = path
|
||||
)
|
||||
}
|
||||
|
||||
@@ -292,61 +280,50 @@ private predicate typeVariableModel(string name, string path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a package that should be seen as an alias for the given other `package`,
|
||||
* or the `package` itself.
|
||||
* Holds if CSV rows involving `type` might be relevant for the analysis of this database.
|
||||
*/
|
||||
bindingset[package]
|
||||
bindingset[result]
|
||||
string getAPackageAlias(string package) {
|
||||
typeModel(package, "", result, "", "")
|
||||
or
|
||||
result = package
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if CSV rows involving `package` might be relevant for the analysis of this database.
|
||||
*/
|
||||
private predicate isRelevantPackage(string package) {
|
||||
predicate isRelevantType(string type) {
|
||||
(
|
||||
sourceModel(package, _, _, _) or
|
||||
sinkModel(package, _, _, _) or
|
||||
summaryModel(package, _, _, _, _, _) or
|
||||
typeModel(_, _, package, _, _)
|
||||
sourceModel(type, _, _) or
|
||||
sinkModel(type, _, _) or
|
||||
summaryModel(type, _, _, _, _) or
|
||||
typeModel(_, type, _)
|
||||
) and
|
||||
(
|
||||
Specific::isPackageUsed(package)
|
||||
Specific::isTypeUsed(type)
|
||||
or
|
||||
exists(TestAllModels t)
|
||||
)
|
||||
or
|
||||
exists(string other |
|
||||
isRelevantPackage(other) and
|
||||
typeModel(package, _, other, _, _)
|
||||
exists(string other | isRelevantType(other) |
|
||||
typeModel(type, other, _)
|
||||
or
|
||||
Specific::hasImplicitTypeModel(type, other)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `package,type,path` is used in some CSV row.
|
||||
* Holds if `type,path` is used in some CSV row.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate isRelevantFullPath(string package, string type, string path) {
|
||||
isRelevantPackage(package) and
|
||||
predicate isRelevantFullPath(string type, string path) {
|
||||
isRelevantType(type) and
|
||||
(
|
||||
sourceModel(package, type, path, _) or
|
||||
sinkModel(package, type, path, _) or
|
||||
summaryModel(package, type, path, _, _, _) or
|
||||
typeModel(_, _, package, type, path)
|
||||
sourceModel(type, path, _) or
|
||||
sinkModel(type, path, _) or
|
||||
summaryModel(type, path, _, _, _) or
|
||||
typeModel(_, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/** A string from a CSV row that should be parsed as an access path. */
|
||||
private class AccessPathRange extends AccessPath::Range {
|
||||
AccessPathRange() {
|
||||
isRelevantFullPath(_, _, this)
|
||||
isRelevantFullPath(_, this)
|
||||
or
|
||||
exists(string package | isRelevantPackage(package) |
|
||||
summaryModel(package, _, _, this, _, _) or
|
||||
summaryModel(package, _, _, _, this, _)
|
||||
exists(string type | isRelevantType(type) |
|
||||
summaryModel(type, _, this, _, _) or
|
||||
summaryModel(type, _, _, this, _)
|
||||
)
|
||||
or
|
||||
typeVariableModel(_, this)
|
||||
@@ -400,83 +377,73 @@ private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, A
|
||||
}
|
||||
|
||||
private class TypeModelUseEntry extends API::EntryPoint {
|
||||
private string package;
|
||||
private string type;
|
||||
|
||||
TypeModelUseEntry() {
|
||||
exists(any(TypeModel tm).getASource(package, type)) and
|
||||
this = "TypeModelUseEntry;" + package + ";" + type
|
||||
exists(any(TypeModel tm).getASource(type)) and
|
||||
this = "TypeModelUseEntry;" + type
|
||||
}
|
||||
|
||||
override DataFlow::LocalSourceNode getASource() {
|
||||
result = any(TypeModel tm).getASource(package, type)
|
||||
}
|
||||
override DataFlow::LocalSourceNode getASource() { result = any(TypeModel tm).getASource(type) }
|
||||
|
||||
API::Node getNodeForType(string package_, string type_) {
|
||||
package = package_ and type = type_ and result = this.getANode()
|
||||
}
|
||||
API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() }
|
||||
}
|
||||
|
||||
private class TypeModelDefEntry extends API::EntryPoint {
|
||||
private string package;
|
||||
private string type;
|
||||
|
||||
TypeModelDefEntry() {
|
||||
exists(any(TypeModel tm).getASink(package, type)) and
|
||||
this = "TypeModelDefEntry;" + package + ";" + type
|
||||
exists(any(TypeModel tm).getASink(type)) and
|
||||
this = "TypeModelDefEntry;" + type
|
||||
}
|
||||
|
||||
override DataFlow::Node getASink() { result = any(TypeModel tm).getASink(package, type) }
|
||||
override DataFlow::Node getASink() { result = any(TypeModel tm).getASink(type) }
|
||||
|
||||
API::Node getNodeForType(string package_, string type_) {
|
||||
package = package_ and type = type_ and result = this.getANode()
|
||||
}
|
||||
API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node identified by the given `(package,type)` pair.
|
||||
* Gets an API node identified by the given `type`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromType(string package, string type) {
|
||||
exists(string package2, string type2, AccessPath path2 |
|
||||
typeModel(package, type, package2, type2, path2) and
|
||||
result = getNodeFromPath(package2, type2, path2)
|
||||
private API::Node getNodeFromType(string type) {
|
||||
exists(string type2, AccessPath path2 |
|
||||
typeModel(type, type2, path2) and
|
||||
result = getNodeFromPath(type2, path2)
|
||||
)
|
||||
or
|
||||
result = any(TypeModelUseEntry e).getNodeForType(package, type)
|
||||
result = any(TypeModelUseEntry e).getNodeForType(type)
|
||||
or
|
||||
result = any(TypeModelDefEntry e).getNodeForType(package, type)
|
||||
result = any(TypeModelDefEntry e).getNodeForType(type)
|
||||
or
|
||||
result = any(TypeModel t).getAnApiNode(package, type)
|
||||
result = any(TypeModel t).getAnApiNode(type)
|
||||
or
|
||||
result = Specific::getExtraNodeFromType(package, type)
|
||||
result = Specific::getExtraNodeFromType(type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple.
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(type, path)` tuple.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
isRelevantFullPath(package, type, path) and
|
||||
private API::Node getNodeFromPath(string type, AccessPath path, int n) {
|
||||
isRelevantFullPath(type, path) and
|
||||
(
|
||||
n = 0 and
|
||||
result = getNodeFromType(package, type)
|
||||
result = getNodeFromType(type)
|
||||
or
|
||||
result = Specific::getExtraNodeFromPath(package, type, path, n)
|
||||
result = Specific::getExtraNodeFromPath(type, path, n)
|
||||
)
|
||||
or
|
||||
result = getSuccessorFromNode(getNodeFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
result = getSuccessorFromNode(getNodeFromPath(type, path, n - 1), path.getToken(n - 1))
|
||||
or
|
||||
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
|
||||
result =
|
||||
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
result = getSuccessorFromInvoke(getInvocationFromPath(type, path, n - 1), path.getToken(n - 1))
|
||||
or
|
||||
// Apply a subpath
|
||||
result =
|
||||
getNodeFromSubPath(getNodeFromPath(package, type, path, n - 1), getSubPathAt(path, n - 1))
|
||||
result = getNodeFromSubPath(getNodeFromPath(type, path, n - 1), getSubPathAt(path, n - 1))
|
||||
or
|
||||
// Apply a type step
|
||||
typeStep(getNodeFromPath(package, type, path, n), result)
|
||||
typeStep(getNodeFromPath(type, path, n), result)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -496,15 +463,15 @@ private AccessPath getSubPathAt(AccessPath path, int n) {
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath, int n) {
|
||||
exists(AccessPath path, int k |
|
||||
base = [getNodeFromPath(_, _, path, k), getNodeFromSubPath(_, path, k)] and
|
||||
base = [getNodeFromPath(_, path, k), getNodeFromSubPath(_, path, k)] and
|
||||
subPath = getSubPathAt(path, k) and
|
||||
result = base and
|
||||
n = 0
|
||||
)
|
||||
or
|
||||
exists(string package, string type, AccessPath basePath |
|
||||
typeStepModel(package, type, basePath, subPath) and
|
||||
base = getNodeFromPath(package, type, basePath) and
|
||||
exists(string type, AccessPath basePath |
|
||||
typeStepModel(type, basePath, subPath) and
|
||||
base = getNodeFromPath(type, basePath) and
|
||||
result = base and
|
||||
n = 0
|
||||
)
|
||||
@@ -543,42 +510,40 @@ private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath) {
|
||||
result = getNodeFromSubPath(base, subPath, subPath.getNumToken())
|
||||
}
|
||||
|
||||
/** Gets the node identified by the given `(package, type, path)` tuple. */
|
||||
private API::Node getNodeFromPath(string package, string type, AccessPath path) {
|
||||
result = getNodeFromPath(package, type, path, path.getNumToken())
|
||||
/** Gets the node identified by the given `(type, path)` tuple. */
|
||||
private API::Node getNodeFromPath(string type, AccessPath path) {
|
||||
result = getNodeFromPath(type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStepModel(string package, string type, AccessPath basePath, AccessPath output) {
|
||||
summaryModel(package, type, basePath, "", output, "type")
|
||||
private predicate typeStepModel(string type, AccessPath basePath, AccessPath output) {
|
||||
summaryModel(type, basePath, "", output, "type")
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStep(API::Node pred, API::Node succ) {
|
||||
exists(string package, string type, AccessPath basePath, AccessPath output |
|
||||
typeStepModel(package, type, basePath, output) and
|
||||
pred = getNodeFromPath(package, type, basePath) and
|
||||
exists(string type, AccessPath basePath, AccessPath output |
|
||||
typeStepModel(type, basePath, output) and
|
||||
pred = getNodeFromPath(type, basePath) and
|
||||
succ = getNodeFromSubPath(pred, output)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an invocation identified by the given `(package, type, path)` tuple.
|
||||
* Gets an invocation identified by the given `(type, path)` tuple.
|
||||
*
|
||||
* Unlike `getNodeFromPath`, the `path` may end with one or more call-site filters.
|
||||
*/
|
||||
private Specific::InvokeNode getInvocationFromPath(
|
||||
string package, string type, AccessPath path, int n
|
||||
) {
|
||||
result = Specific::getAnInvocationOf(getNodeFromPath(package, type, path, n))
|
||||
private Specific::InvokeNode getInvocationFromPath(string type, AccessPath path, int n) {
|
||||
result = Specific::getAnInvocationOf(getNodeFromPath(type, path, n))
|
||||
or
|
||||
result = getInvocationFromPath(package, type, path, n - 1) and
|
||||
result = getInvocationFromPath(type, path, n - 1) and
|
||||
invocationMatchesCallSiteFilter(result, path.getToken(n - 1))
|
||||
}
|
||||
|
||||
/** Gets an invocation identified by the given `(package, type, path)` tuple. */
|
||||
private Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPath path) {
|
||||
result = getInvocationFromPath(package, type, path, path.getNumToken())
|
||||
/** Gets an invocation identified by the given `(type, path)` tuple. */
|
||||
private Specific::InvokeNode getInvocationFromPath(string type, AccessPath path) {
|
||||
result = getInvocationFromPath(type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -631,9 +596,9 @@ module ModelOutput {
|
||||
*/
|
||||
cached
|
||||
API::Node getASourceNode(string kind) {
|
||||
exists(string package, string type, string path |
|
||||
sourceModel(package, type, path, kind) and
|
||||
result = getNodeFromPath(package, type, path)
|
||||
exists(string type, string path |
|
||||
sourceModel(type, path, kind) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -642,9 +607,9 @@ module ModelOutput {
|
||||
*/
|
||||
cached
|
||||
API::Node getASinkNode(string kind) {
|
||||
exists(string package, string type, string path |
|
||||
sinkModel(package, type, path, kind) and
|
||||
result = getNodeFromPath(package, type, path)
|
||||
exists(string type, string path |
|
||||
sinkModel(type, path, kind) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -653,32 +618,31 @@ module ModelOutput {
|
||||
*/
|
||||
cached
|
||||
predicate relevantSummaryModel(
|
||||
string package, string type, string path, string input, string output, string kind
|
||||
string type, string path, string input, string output, string kind
|
||||
) {
|
||||
isRelevantPackage(package) and
|
||||
summaryModel(package, type, path, input, output, kind)
|
||||
isRelevantType(type) and
|
||||
summaryModel(type, path, input, output, kind)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a `baseNode` is an invocation identified by the `package,type,path` part of a summary row.
|
||||
* Holds if a `baseNode` is an invocation identified by the `type,path` part of a summary row.
|
||||
*/
|
||||
cached
|
||||
predicate resolvedSummaryBase(
|
||||
string package, string type, string path, Specific::InvokeNode baseNode
|
||||
) {
|
||||
summaryModel(package, type, path, _, _, _) and
|
||||
baseNode = getInvocationFromPath(package, type, path)
|
||||
predicate resolvedSummaryBase(string type, string path, Specific::InvokeNode baseNode) {
|
||||
summaryModel(type, path, _, _, _) and
|
||||
baseNode = getInvocationFromPath(type, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is seen as an instance of `(package,type)` due to a type definition
|
||||
* Holds if `node` is seen as an instance of `type` due to a type definition
|
||||
* contributed by a CSV model.
|
||||
*/
|
||||
cached
|
||||
API::Node getATypeNode(string package, string type) { result = getNodeFromType(package, type) }
|
||||
API::Node getATypeNode(string type) { result = getNodeFromType(type) }
|
||||
}
|
||||
|
||||
import Cached
|
||||
import Specific::ModelOutputSpecific
|
||||
|
||||
/**
|
||||
* Gets an error message relating to an invalid CSV row in a model.
|
||||
@@ -686,13 +650,13 @@ module ModelOutput {
|
||||
string getAWarning() {
|
||||
// Check number of columns
|
||||
exists(string row, string kind, int expectedArity, int actualArity |
|
||||
any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 4
|
||||
any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 3
|
||||
or
|
||||
any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 4
|
||||
any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 3
|
||||
or
|
||||
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 6
|
||||
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 5
|
||||
or
|
||||
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 5
|
||||
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 3
|
||||
or
|
||||
any(TypeVariableModelCsv csv).row(row) and kind = "type-variable" and expectedArity = 2
|
||||
|
|
||||
@@ -705,7 +669,7 @@ module ModelOutput {
|
||||
or
|
||||
// Check names and arguments of access path tokens
|
||||
exists(AccessPath path, AccessPathToken token |
|
||||
(isRelevantFullPath(_, _, path) or typeVariableModel(_, path)) and
|
||||
(isRelevantFullPath(_, path) or typeVariableModel(_, path)) and
|
||||
token = path.getToken(_)
|
||||
|
|
||||
not isValidTokenNameInIdentifyingAccessPath(token.getName()) and
|
||||
|
||||
@@ -34,30 +34,73 @@ private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSumm
|
||||
private import codeql.ruby.dataflow.internal.FlowSummaryImpl::Public
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
|
||||
|
||||
/**
|
||||
* Holds if models describing `package` may be relevant for the analysis of this database.
|
||||
*
|
||||
* In the context of Ruby, this is the name of a Ruby gem.
|
||||
*/
|
||||
bindingset[package]
|
||||
predicate isPackageUsed(string package) {
|
||||
// For now everything is modeled as an access path starting at any top-level, so the package name has no effect.
|
||||
//
|
||||
// We allow an arbitrary package name so that the model can record the name of the package in case it's needed in the future.
|
||||
//
|
||||
// In principle we should consider a package to be "used" if there is a transitive dependency on it, but we can only
|
||||
// reliably see the direct dependencies.
|
||||
//
|
||||
// In practice, packages try to use unique top-level module names, which mitigates the precision loss of not checking
|
||||
// the package name.
|
||||
any()
|
||||
pragma[nomagic]
|
||||
private predicate isUsedTopLevelConstant(string name) {
|
||||
exists(ConstantAccess access |
|
||||
access.getName() = name and
|
||||
not exists(access.getScopeExpr())
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a Ruby-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */
|
||||
bindingset[package, type, path]
|
||||
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
// A row of form `;any;Method[foo]` should match any method named `foo`.
|
||||
exists(package) and
|
||||
bindingset[rawType]
|
||||
predicate isTypeUsed(string rawType) {
|
||||
exists(string consts |
|
||||
parseType(rawType, consts, _) and
|
||||
isUsedTopLevelConstant(consts.splitAt("::", 0))
|
||||
)
|
||||
or
|
||||
rawType = ["", "any"]
|
||||
}
|
||||
|
||||
bindingset[rawType]
|
||||
private predicate parseType(string rawType, string consts, string suffix) {
|
||||
exists(string regexp |
|
||||
regexp = "([^!]+)(!|)" and
|
||||
consts = rawType.regexpCapture(regexp, 1) and
|
||||
suffix = rawType.regexpCapture(regexp, 2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `type` can be obtained from an instance of `otherType` due to
|
||||
* language semantics modeled by `getExtraNodeFromType`.
|
||||
*/
|
||||
bindingset[otherType]
|
||||
predicate hasImplicitTypeModel(string type, string otherType) {
|
||||
// A::B! can be used to obtain A::B
|
||||
parseType(otherType, type, _)
|
||||
}
|
||||
|
||||
private predicate parseRelevantType(string rawType, string consts, string suffix) {
|
||||
isRelevantType(rawType) and
|
||||
parseType(rawType, consts, suffix)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private string getConstComponent(string consts, int n) {
|
||||
parseRelevantType(_, consts, _) and
|
||||
result = consts.splitAt("::", n)
|
||||
}
|
||||
|
||||
private int getNumConstComponents(string consts) {
|
||||
result = strictcount(int n | exists(getConstComponent(consts, n)))
|
||||
}
|
||||
|
||||
private DataFlow::ConstRef getConstantFromConstPath(string consts, int n) {
|
||||
n = 1 and
|
||||
result = DataFlow::getConstant(getConstComponent(consts, 0))
|
||||
or
|
||||
result = getConstantFromConstPath(consts, n - 1).getConstant(getConstComponent(consts, n - 1))
|
||||
}
|
||||
|
||||
private DataFlow::ConstRef getConstantFromConstPath(string consts) {
|
||||
result = getConstantFromConstPath(consts, getNumConstComponents(consts))
|
||||
}
|
||||
|
||||
/** Gets a Ruby-specific interpretation of the `(type, path)` tuple after resolving the first `n` access path tokens. */
|
||||
bindingset[type, path]
|
||||
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 |
|
||||
@@ -66,9 +109,27 @@ API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a Ruby-specific interpretation of the `(package, type)` tuple. */
|
||||
API::Node getExtraNodeFromType(string package, string type) {
|
||||
isRelevantFullPath(package, type, _) and // Allow any package name, see `isPackageUsed`.
|
||||
/** Gets a Ruby-specific interpretation of the given `type`. */
|
||||
API::Node getExtraNodeFromType(string type) {
|
||||
exists(string consts, string suffix, DataFlow::ConstRef constRef |
|
||||
parseRelevantType(type, consts, suffix) and
|
||||
constRef = getConstantFromConstPath(consts)
|
||||
|
|
||||
suffix = "!" and
|
||||
(
|
||||
result.asSource() = constRef
|
||||
or
|
||||
result.asSource() = constRef.getADescendentModule().getAnOwnModuleSelf()
|
||||
)
|
||||
or
|
||||
suffix = "" and
|
||||
(
|
||||
result.asSource() = constRef.getAMethodCall("new")
|
||||
or
|
||||
result.asSource() = constRef.getADescendentModule().getAnInstanceSelf()
|
||||
)
|
||||
)
|
||||
or
|
||||
type = "" and
|
||||
result = API::root()
|
||||
}
|
||||
@@ -78,7 +139,7 @@ API::Node getExtraNodeFromType(string package, string type) {
|
||||
* matching anywhere, and the path begins with `Method[methodName]`.
|
||||
*/
|
||||
private predicate methodMatchedByName(AccessPath path, string methodName) {
|
||||
isRelevantFullPath(_, "any", path) and
|
||||
isRelevantFullPath("any", path) and
|
||||
exists(AccessPathToken token |
|
||||
token = path.getToken(0) and
|
||||
token.getName() = "Method" and
|
||||
@@ -190,3 +251,5 @@ predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string a
|
||||
argument.regexpMatch("\\w+:") // keyword argument
|
||||
)
|
||||
}
|
||||
|
||||
module ModelOutputSpecific { }
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
* Provides modeling for the `Open3` library.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.Stdlib
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
@@ -17,23 +15,19 @@ module Open3 {
|
||||
* These methods take the same argument forms as `Kernel.system`.
|
||||
* See `KernelSystemCall` for details.
|
||||
*/
|
||||
class Open3Call extends SystemCommandExecution::Range {
|
||||
MethodCall methodCall;
|
||||
|
||||
class Open3Call extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
Open3Call() {
|
||||
this.asExpr().getExpr() = methodCall and
|
||||
this =
|
||||
API::getTopLevelMember("Open3")
|
||||
.getAMethodCall(["popen3", "popen2", "popen2e", "capture3", "capture2", "capture2e"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() {
|
||||
result.asExpr().getExpr() = methodCall.getAnArgument()
|
||||
}
|
||||
override DataFlow::Node getAnArgument() { result = super.getArgument(_) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
// These Open3 methods invoke a subshell if you provide a single string as argument
|
||||
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
|
||||
super.getNumberOfArguments() = 1 and
|
||||
arg = this.getAnArgument()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,11 +41,8 @@ module Open3 {
|
||||
* Open3.pipeline([{}, "cat", "foo.txt"], "tail")
|
||||
* Open3.pipeline([["cat", "cat"], "foo.txt"], "tail")
|
||||
*/
|
||||
class Open3PipelineCall extends SystemCommandExecution::Range {
|
||||
MethodCall methodCall;
|
||||
|
||||
class Open3PipelineCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
|
||||
Open3PipelineCall() {
|
||||
this.asExpr().getExpr() = methodCall and
|
||||
this =
|
||||
API::getTopLevelMember("Open3")
|
||||
.getAMethodCall([
|
||||
@@ -59,14 +50,12 @@ module Open3 {
|
||||
])
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnArgument() {
|
||||
result.asExpr().getExpr() = methodCall.getAnArgument()
|
||||
}
|
||||
override DataFlow::Node getAnArgument() { result = super.getArgument(_) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
// A command in the pipeline is executed in a subshell if it is given as a single string argument.
|
||||
arg.asExpr().getExpr() instanceof StringlikeLiteral and
|
||||
arg.asExpr().getExpr() = methodCall.getAnArgument()
|
||||
arg.asExpr().getExpr() instanceof Ast::StringlikeLiteral and
|
||||
arg = this.getAnArgument()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,33 +123,31 @@ module Pathname {
|
||||
*/
|
||||
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1;type1;package2;type2;path
|
||||
// type1;type2;path
|
||||
row =
|
||||
[
|
||||
// Pathname.new : Pathname
|
||||
";Pathname;;;Member[Pathname].Instance",
|
||||
// Pathname#+(path) : Pathname
|
||||
";Pathname;;Pathname;Method[+].ReturnValue",
|
||||
"Pathname;Pathname;Method[+].ReturnValue",
|
||||
// Pathname#/(path) : Pathname
|
||||
";Pathname;;Pathname;Method[/].ReturnValue",
|
||||
"Pathname;Pathname;Method[/].ReturnValue",
|
||||
// Pathname#basename(path) : Pathname
|
||||
";Pathname;;Pathname;Method[basename].ReturnValue",
|
||||
"Pathname;Pathname;Method[basename].ReturnValue",
|
||||
// Pathname#cleanpath(path) : Pathname
|
||||
";Pathname;;Pathname;Method[cleanpath].ReturnValue",
|
||||
"Pathname;Pathname;Method[cleanpath].ReturnValue",
|
||||
// Pathname#expand_path(path) : Pathname
|
||||
";Pathname;;Pathname;Method[expand_path].ReturnValue",
|
||||
"Pathname;Pathname;Method[expand_path].ReturnValue",
|
||||
// Pathname#join(path) : Pathname
|
||||
";Pathname;;Pathname;Method[join].ReturnValue",
|
||||
"Pathname;Pathname;Method[join].ReturnValue",
|
||||
// Pathname#realpath(path) : Pathname
|
||||
";Pathname;;Pathname;Method[realpath].ReturnValue",
|
||||
"Pathname;Pathname;Method[realpath].ReturnValue",
|
||||
// Pathname#relative_path_from(path) : Pathname
|
||||
";Pathname;;Pathname;Method[relative_path_from].ReturnValue",
|
||||
"Pathname;Pathname;Method[relative_path_from].ReturnValue",
|
||||
// Pathname#sub(path) : Pathname
|
||||
";Pathname;;Pathname;Method[sub].ReturnValue",
|
||||
"Pathname;Pathname;Method[sub].ReturnValue",
|
||||
// Pathname#sub_ext(path) : Pathname
|
||||
";Pathname;;Pathname;Method[sub_ext].ReturnValue",
|
||||
"Pathname;Pathname;Method[sub_ext].ReturnValue",
|
||||
// Pathname#to_path(path) : Pathname
|
||||
";Pathname;;Pathname;Method[to_path].ReturnValue",
|
||||
"Pathname;Pathname;Method[to_path].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -160,31 +158,31 @@ module Pathname {
|
||||
row =
|
||||
[
|
||||
// Pathname.new(path)
|
||||
";;Member[Pathname].Method[new];Argument[0];ReturnValue;taint",
|
||||
"Pathname!;Method[new];Argument[0];ReturnValue;taint",
|
||||
// Pathname#dirname
|
||||
";Pathname;Method[dirname];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[dirname];Argument[self];ReturnValue;taint",
|
||||
// Pathname#each_filename
|
||||
";Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
|
||||
"Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
|
||||
// Pathname#expand_path
|
||||
";Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
|
||||
// Pathname#join
|
||||
";Pathname;Method[join];Argument[self,any];ReturnValue;taint",
|
||||
"Pathname;Method[join];Argument[self,any];ReturnValue;taint",
|
||||
// Pathname#parent
|
||||
";Pathname;Method[parent];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[parent];Argument[self];ReturnValue;taint",
|
||||
// Pathname#realpath
|
||||
";Pathname;Method[realpath];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[realpath];Argument[self];ReturnValue;taint",
|
||||
// Pathname#relative_path_from
|
||||
";Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
|
||||
// Pathname#to_path
|
||||
";Pathname;Method[to_path];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[to_path];Argument[self];ReturnValue;taint",
|
||||
// Pathname#basename
|
||||
";Pathname;Method[basename];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[basename];Argument[self];ReturnValue;taint",
|
||||
// Pathname#cleanpath
|
||||
";Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
|
||||
// Pathname#sub
|
||||
";Pathname;Method[sub];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[sub];Argument[self];ReturnValue;taint",
|
||||
// Pathname#sub_ext
|
||||
";Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
|
||||
"Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class PrintAstNode extends TPrintNode {
|
||||
* Holds if this node 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
|
||||
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
* [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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,155 +2,7 @@
|
||||
* Provides predicates for reasoning about bad tag filter vulnerabilities.
|
||||
*/
|
||||
|
||||
import regexp.RegexpMatching
|
||||
|
||||
/**
|
||||
* Holds if the regexp `root` should be tested against `str`.
|
||||
* Implements the `isRegexpMatchingCandidateSig` signature from `RegexpMatching`.
|
||||
* `ignorePrefix` toggles whether the regular expression should be treated as accepting any prefix if it's unanchored.
|
||||
* `testWithGroups` toggles whether it's tested which groups are filled by a given input string.
|
||||
*/
|
||||
private predicate isBadTagFilterCandidate(
|
||||
RootTerm root, string str, boolean ignorePrefix, boolean testWithGroups
|
||||
) {
|
||||
// the regexp must mention "<" and ">" explicitly.
|
||||
forall(string angleBracket | angleBracket = ["<", ">"] |
|
||||
any(RegExpConstant term | term.getValue().matches("%" + angleBracket + "%")).getRootTerm() =
|
||||
root
|
||||
) and
|
||||
ignorePrefix = true and
|
||||
(
|
||||
str = ["<!-- foo -->", "<!-- foo --!>", "<!- foo ->", "<foo>", "<script>"] and
|
||||
testWithGroups = true
|
||||
or
|
||||
str =
|
||||
[
|
||||
"<!-- foo -->", "<!- foo ->", "<!-- foo --!>", "<!-- foo\n -->", "<script>foo</script>",
|
||||
"<script \n>foo</script>", "<script >foo\n</script>", "<foo ></foo>", "<foo>",
|
||||
"<foo src=\"foo\"></foo>", "<script>", "<script src=\"foo\"></script>",
|
||||
"<script src='foo'></script>", "<SCRIPT>foo</SCRIPT>", "<script\tsrc=\"foo\"/>",
|
||||
"<script\tsrc='foo'></script>", "<sCrIpT>foo</ScRiPt>", "<script src=\"foo\">foo</script >",
|
||||
"<script src=\"foo\">foo</script foo=\"bar\">", "<script src=\"foo\">foo</script\t\n bar>"
|
||||
] and
|
||||
testWithGroups = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A regexp that matches some string from the `isBadTagFilterCandidate` predicate.
|
||||
*/
|
||||
class HtmlMatchingRegExp extends RootTerm {
|
||||
HtmlMatchingRegExp() { RegexpMatching<isBadTagFilterCandidate/4>::matches(this, _) }
|
||||
|
||||
/** Holds if this regexp matched `str`, where `str` is one of the string from `isBadTagFilterCandidate`. */
|
||||
predicate matches(string str) { RegexpMatching<isBadTagFilterCandidate/4>::matches(this, str) }
|
||||
|
||||
/** Holds if this regexp fills capture group `g' when matching `str', where `str` is one of the string from `isBadTagFilterCandidate`. */
|
||||
predicate fillsCaptureGroup(string str, int g) {
|
||||
RegexpMatching<isBadTagFilterCandidate/4>::fillsCaptureGroup(this, str, g)
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for HtmlMatchingRegExp */
|
||||
deprecated class HTMLMatchingRegExp = HtmlMatchingRegExp;
|
||||
|
||||
/**
|
||||
* Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
|
||||
*
|
||||
* When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
|
||||
*/
|
||||
predicate isBadRegexpFilter(HtmlMatchingRegExp regexp, string msg) {
|
||||
// CVE-2021-33829 - matching both "<!-- foo -->" and "<!-- foo --!>", but in different capture groups
|
||||
regexp.matches("<!-- foo -->") and
|
||||
regexp.matches("<!-- foo --!>") and
|
||||
exists(int a, int b | a != b |
|
||||
regexp.fillsCaptureGroup("<!-- foo -->", a) and
|
||||
// <!-- foo --> might be ambiguously parsed (matching both capture groups), and that is ok here.
|
||||
regexp.fillsCaptureGroup("<!-- foo --!>", b) and
|
||||
not regexp.fillsCaptureGroup("<!-- foo --!>", a) and
|
||||
msg =
|
||||
"Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group "
|
||||
+ a + " and comments ending with --!> are matched with capture group " +
|
||||
strictconcat(int i | regexp.fillsCaptureGroup("<!-- foo --!>", i) | i.toString(), ", ") +
|
||||
"."
|
||||
)
|
||||
or
|
||||
// CVE-2020-17480 - matching "<!-- foo -->" and other tags, but not "<!-- foo --!>".
|
||||
exists(int group, int other |
|
||||
group != other and
|
||||
regexp.fillsCaptureGroup("<!-- foo -->", group) and
|
||||
regexp.fillsCaptureGroup("<foo>", other) and
|
||||
not regexp.matches("<!-- foo --!>") and
|
||||
not regexp.fillsCaptureGroup("<!-- foo -->", any(int i | i != group)) and
|
||||
not regexp.fillsCaptureGroup("<!- foo ->", group) and
|
||||
not regexp.fillsCaptureGroup("<foo>", group) and
|
||||
not regexp.fillsCaptureGroup("<script>", group) and
|
||||
msg =
|
||||
"This regular expression only parses --> (capture group " + group +
|
||||
") and not --!> as an HTML comment end tag."
|
||||
)
|
||||
or
|
||||
regexp.matches("<!-- foo -->") and
|
||||
not regexp.matches("<!-- foo\n -->") and
|
||||
not regexp.matches("<!- foo ->") and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<script>") and
|
||||
msg = "This regular expression does not match comments containing newlines."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<foo ></foo>") and
|
||||
(
|
||||
not regexp.matches("<script \n>foo</script>") and
|
||||
msg = "This regular expression matches <script></script>, but not <script \\n></script>"
|
||||
or
|
||||
not regexp.matches("<script >foo\n</script>") and
|
||||
msg = "This regular expression matches <script>...</script>, but not <script >...\\n</script>"
|
||||
)
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<script src='foo'></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
msg = "This regular expression does not match script tags where the attribute uses single-quotes."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src='foo'></script>") and
|
||||
not regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
msg = "This regular expression does not match script tags where the attribute uses double-quotes."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src='foo'></script>") and
|
||||
not regexp.matches("<script\tsrc='foo'></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<foo src=\"foo\"></foo>") and
|
||||
msg = "This regular expression does not match script tags where tabs are used between attributes."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
not RegExpFlags::isIgnoreCase(regexp) and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<foo ></foo>") and
|
||||
(
|
||||
not regexp.matches("<SCRIPT>foo</SCRIPT>") and
|
||||
msg = "This regular expression does not match upper case <SCRIPT> tags."
|
||||
or
|
||||
not regexp.matches("<sCrIpT>foo</ScRiPt>") and
|
||||
regexp.matches("<SCRIPT>foo</SCRIPT>") and
|
||||
msg = "This regular expression does not match mixed case <sCrIpT> tags."
|
||||
)
|
||||
or
|
||||
regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<foo ></foo>") and
|
||||
(
|
||||
not regexp.matches("<script src=\"foo\">foo</script >") and
|
||||
msg = "This regular expression does not match script end tags like </script >."
|
||||
or
|
||||
not regexp.matches("<script src=\"foo\">foo</script foo=\"bar\">") and
|
||||
msg = "This regular expression does not match script end tags like </script foo=\"bar\">."
|
||||
or
|
||||
not regexp.matches("<script src=\"foo\">foo</script\t\n bar>") and
|
||||
msg = "This regular expression does not match script end tags like </script\\t\\n bar>."
|
||||
)
|
||||
}
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
// BadTagFilterQuery should be used directly from the shared pack, and not from this file.
|
||||
deprecated import codeql.regex.nfa.BadTagFilterQuery::Make<TreeView> as Dep
|
||||
import Dep
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
*/
|
||||
|
||||
import codeql.ruby.frameworks.core.String
|
||||
import codeql.ruby.regexp.RegExpTreeView
|
||||
import codeql.ruby.security.regexp.NfaUtils as NfaUtils
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
import TreeView
|
||||
import codeql.regex.nfa.NfaUtils::Make<TreeView> as NfaUtils
|
||||
|
||||
/**
|
||||
* A regexp term that matches substrings that should be replaced with the empty string.
|
||||
|
||||
@@ -60,6 +60,14 @@ class StringReplaceSanitizer extends Sanitizer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Object#inspect`, considered as a sanitizer.
|
||||
* This is because `inspect` will replace newlines in strings with `\n`.
|
||||
*/
|
||||
class InspectSanitizer extends Sanitizer {
|
||||
InspectSanitizer() { this.(DataFlow::CallNode).getMethodName() = "inspect" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to an HTML escape method is considered to sanitize its input.
|
||||
*/
|
||||
|
||||
@@ -2,288 +2,7 @@
|
||||
* Classes and predicates for working with suspicious character ranges.
|
||||
*/
|
||||
|
||||
// We don't need the NFA utils, just the regexp tree.
|
||||
// but the below is a nice shared library that exposes the API we need.
|
||||
import regexp.NfaUtils
|
||||
|
||||
/**
|
||||
* Gets a rank for `range` that is unique for ranges in the same file.
|
||||
* Prioritizes ranges that match more characters.
|
||||
*/
|
||||
int rankRange(RegExpCharacterRange range) {
|
||||
range =
|
||||
rank[result](RegExpCharacterRange r, Location l, int low, int high |
|
||||
r.getLocation() = l and
|
||||
isRange(r, low, high)
|
||||
|
|
||||
r order by (high - low) desc, l.getStartLine(), l.getStartColumn()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `range` spans from the unicode code points `low` to `high` (both inclusive). */
|
||||
predicate isRange(RegExpCharacterRange range, int low, int high) {
|
||||
exists(string lowc, string highc |
|
||||
range.isRange(lowc, highc) and
|
||||
low.toUnicode() = lowc and
|
||||
high.toUnicode() = highc
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `char` is an alpha-numeric character. */
|
||||
predicate isAlphanumeric(string char) {
|
||||
// written like this to avoid having a bindingset for the predicate
|
||||
char = [[48 .. 57], [65 .. 90], [97 .. 122]].toUnicode() // 0-9, A-Z, a-z
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given ranges are from the same character class
|
||||
* and there exists at least one character matched by both ranges.
|
||||
*/
|
||||
predicate overlap(RegExpCharacterRange a, RegExpCharacterRange b) {
|
||||
exists(RegExpCharacterClass clz |
|
||||
a = clz.getAChild() and
|
||||
b = clz.getAChild() and
|
||||
a != b
|
||||
|
|
||||
exists(int alow, int ahigh, int blow, int bhigh |
|
||||
isRange(a, alow, ahigh) and
|
||||
isRange(b, blow, bhigh) and
|
||||
alow <= bhigh and
|
||||
blow <= ahigh
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `range` overlaps with the char class `escape` from the same character class.
|
||||
*/
|
||||
predicate overlapsWithCharEscape(RegExpCharacterRange range, RegExpCharacterClassEscape escape) {
|
||||
exists(RegExpCharacterClass clz, string low, string high |
|
||||
range = clz.getAChild() and
|
||||
escape = clz.getAChild() and
|
||||
range.isRange(low, high)
|
||||
|
|
||||
escape.getValue() = "w" and
|
||||
getInRange(low, high).regexpMatch("\\w")
|
||||
or
|
||||
escape.getValue() = "d" and
|
||||
getInRange(low, high).regexpMatch("\\d")
|
||||
or
|
||||
escape.getValue() = "s" and
|
||||
getInRange(low, high).regexpMatch("\\s")
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the unicode code point for a `char`. */
|
||||
bindingset[char]
|
||||
int toCodePoint(string char) { result.toUnicode() = char }
|
||||
|
||||
/** A character range that appears to be overly wide. */
|
||||
class OverlyWideRange extends RegExpCharacterRange {
|
||||
OverlyWideRange() {
|
||||
exists(int low, int high, int numChars |
|
||||
isRange(this, low, high) and
|
||||
numChars = (1 + high - low) and
|
||||
this.getRootTerm().isUsedAsRegExp() and
|
||||
numChars >= 10
|
||||
|
|
||||
// across the Z-a range (which includes backticks)
|
||||
toCodePoint("Z") >= low and
|
||||
toCodePoint("a") <= high
|
||||
or
|
||||
// across the 9-A range (which includes e.g. ; and ?)
|
||||
toCodePoint("9") >= low and
|
||||
toCodePoint("A") <= high
|
||||
or
|
||||
// a non-alphanumeric char as part of the range boundaries
|
||||
exists(int bound | bound = [low, high] | not isAlphanumeric(bound.toUnicode())) and
|
||||
// while still being ascii
|
||||
low < 128 and
|
||||
high < 128
|
||||
) and
|
||||
// allowlist for known ranges
|
||||
not this = allowedWideRanges()
|
||||
}
|
||||
|
||||
/** Gets a string representation of a character class that matches the same chars as this range. */
|
||||
string printEquivalent() { result = RangePrinter::printEquivalentCharClass(this) }
|
||||
}
|
||||
|
||||
/** Gets a range that should not be reported as an overly wide range. */
|
||||
RegExpCharacterRange allowedWideRanges() {
|
||||
// ~ is the last printable ASCII character, it's used right in various wide ranges.
|
||||
result.isRange(_, "~")
|
||||
or
|
||||
// the same with " " and "!". " " is the first printable character, and "!" is the first non-white-space printable character.
|
||||
result.isRange([" ", "!"], _)
|
||||
or
|
||||
// the `[@-_]` range is intentional
|
||||
result.isRange("@", "_")
|
||||
or
|
||||
// starting from the zero byte is a good indication that it's purposely matching a large range.
|
||||
result.isRange(0.toUnicode(), _)
|
||||
}
|
||||
|
||||
/** Gets a char between (and including) `low` and `high`. */
|
||||
bindingset[low, high]
|
||||
private string getInRange(string low, string high) {
|
||||
result = [toCodePoint(low) .. toCodePoint(high)].toUnicode()
|
||||
}
|
||||
|
||||
/** A module computing an equivalent character class for an overly wide range. */
|
||||
module RangePrinter {
|
||||
bindingset[char]
|
||||
bindingset[result]
|
||||
private string next(string char) {
|
||||
exists(int prev, int next |
|
||||
prev.toUnicode() = char and
|
||||
next.toUnicode() = result and
|
||||
next = prev + 1
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the points where the parts of the pretty printed range should be cut off. */
|
||||
private string cutoffs() { result = ["A", "Z", "a", "z", "0", "9"] }
|
||||
|
||||
/** Gets the char to use in the low end of a range for a given `cut` */
|
||||
private string lowCut(string cut) {
|
||||
cut = ["A", "a", "0"] and
|
||||
result = cut
|
||||
or
|
||||
cut = ["Z", "z", "9"] and
|
||||
result = next(cut)
|
||||
}
|
||||
|
||||
/** Gets the char to use in the high end of a range for a given `cut` */
|
||||
private string highCut(string cut) {
|
||||
cut = ["Z", "z", "9"] and
|
||||
result = cut
|
||||
or
|
||||
cut = ["A", "a", "0"] and
|
||||
next(result) = cut
|
||||
}
|
||||
|
||||
/** Gets the cutoff char used for a given `part` of a range when pretty-printing it. */
|
||||
private string cutoff(OverlyWideRange range, int part) {
|
||||
exists(int low, int high | isRange(range, low, high) |
|
||||
result =
|
||||
rank[part + 1](string cut |
|
||||
cut = cutoffs() and low < toCodePoint(cut) and toCodePoint(cut) < high
|
||||
|
|
||||
cut order by toCodePoint(cut)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of parts we should print for a given `range`. */
|
||||
private int parts(OverlyWideRange range) { result = 1 + count(cutoff(range, _)) }
|
||||
|
||||
/** Holds if the given part of a range should span from `low` to `high`. */
|
||||
private predicate part(OverlyWideRange range, int part, string low, string high) {
|
||||
// first part.
|
||||
part = 0 and
|
||||
(
|
||||
range.isRange(low, high) and
|
||||
parts(range) = 1
|
||||
or
|
||||
parts(range) >= 2 and
|
||||
range.isRange(low, _) and
|
||||
high = highCut(cutoff(range, part))
|
||||
)
|
||||
or
|
||||
// middle
|
||||
part >= 1 and
|
||||
part < parts(range) - 1 and
|
||||
low = lowCut(cutoff(range, part - 1)) and
|
||||
high = highCut(cutoff(range, part))
|
||||
or
|
||||
// last.
|
||||
part = parts(range) - 1 and
|
||||
low = lowCut(cutoff(range, part - 1)) and
|
||||
range.isRange(_, high)
|
||||
}
|
||||
|
||||
/** Gets an escaped `char` for use in a character class. */
|
||||
bindingset[char]
|
||||
private string escape(string char) {
|
||||
exists(string reg | reg = "(\\[|\\]|\\\\|-|/)" |
|
||||
if char.regexpMatch(reg) then result = "\\" + char else result = char
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a part of the equivalent range. */
|
||||
private string printEquivalentCharClass(OverlyWideRange range, int part) {
|
||||
exists(string low, string high | part(range, part, low, high) |
|
||||
if
|
||||
isAlphanumeric(low) and
|
||||
isAlphanumeric(high)
|
||||
then result = low + "-" + high
|
||||
else
|
||||
result =
|
||||
strictconcat(string char | char = getInRange(low, high) | escape(char) order by char)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the entire pretty printed equivalent range. */
|
||||
string printEquivalentCharClass(OverlyWideRange range) {
|
||||
result =
|
||||
strictconcat(string r, int part |
|
||||
r = "[" and part = -1 and exists(range)
|
||||
or
|
||||
r = printEquivalentCharClass(range, part)
|
||||
or
|
||||
r = "]" and part = parts(range)
|
||||
|
|
||||
r order by part
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a char range that is overly large because of `reason`. */
|
||||
RegExpCharacterRange getABadRange(string reason, int priority) {
|
||||
result instanceof OverlyWideRange and
|
||||
priority = 0 and
|
||||
exists(string equiv | equiv = result.(OverlyWideRange).printEquivalent() |
|
||||
if equiv.length() <= 50
|
||||
then reason = "is equivalent to " + equiv
|
||||
else reason = "is equivalent to " + equiv.substring(0, 50) + "..."
|
||||
)
|
||||
or
|
||||
priority = 1 and
|
||||
exists(RegExpCharacterRange other |
|
||||
reason = "overlaps with " + other + " in the same character class" and
|
||||
rankRange(result) < rankRange(other) and
|
||||
overlap(result, other)
|
||||
)
|
||||
or
|
||||
priority = 2 and
|
||||
exists(RegExpCharacterClassEscape escape |
|
||||
reason = "overlaps with " + escape + " in the same character class" and
|
||||
overlapsWithCharEscape(result, escape)
|
||||
)
|
||||
or
|
||||
reason = "is empty" and
|
||||
priority = 3 and
|
||||
exists(int low, int high |
|
||||
isRange(result, low, high) and
|
||||
low > high
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `range` matches suspiciously many characters. */
|
||||
predicate problem(RegExpCharacterRange range, string reason) {
|
||||
reason =
|
||||
strictconcat(string m, int priority |
|
||||
range = getABadRange(m, priority)
|
||||
|
|
||||
m, ", and " order by priority desc
|
||||
) and
|
||||
// specifying a range using an escape is usually OK.
|
||||
not range.getAChild() instanceof RegExpEscape and
|
||||
// Unicode escapes in strings are interpreted before it turns into a regexp,
|
||||
// so e.g. [\u0001-\uFFFF] will just turn up as a range between two constants.
|
||||
// We therefore exclude these ranges.
|
||||
range.getRootTerm().getParent() instanceof RegExpLiteral and
|
||||
// is used as regexp (mostly for JS where regular expressions are parsed eagerly)
|
||||
range.getRootTerm().isUsedAsRegExp()
|
||||
}
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
// OverlyLargeRangeQuery should be used directly from the shared pack, and not from this file.
|
||||
deprecated import codeql.regex.OverlyLargeRangeQuery::Make<TreeView> as Dep
|
||||
import Dep
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting SQL injection
|
||||
* vulnerabilities, 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 SQL injection
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module SqlInjection {
|
||||
/** A data flow source for SQL injection vulnerabilities. */
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/** A data flow sink for SQL injection vulnerabilities. */
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/** A sanitizer for SQL 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 SQL statement of a SQL execution, considered as a flow sink.
|
||||
*/
|
||||
private class SqlExecutionAsSink extends Sink {
|
||||
SqlExecutionAsSink() { this = any(SqlExecution e).getSql() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An SQL statement of a SQL construction, considered as a flow sink.
|
||||
*/
|
||||
private class SqlConstructionAsSink extends Sink {
|
||||
SqlConstructionAsSink() { this = any(SqlConstruction e).getSql() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier { }
|
||||
}
|
||||
21
ruby/ql/lib/codeql/ruby/security/SqlInjectionQuery.qll
Normal file
21
ruby/ql/lib/codeql/ruby/security/SqlInjectionQuery.qll
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting SQL injection
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
import SqlInjectionCustomizations::SqlInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting SQL injection vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SqlInjectionConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting stack trace
|
||||
* exposure vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.frameworks.core.Kernel
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting stack trace
|
||||
* exposure vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module StackTraceExposure {
|
||||
/** A data flow source for stack trace exposure vulnerabilities. */
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/** A data flow sink for stack trace exposure vulnerabilities. */
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/** A data flow sanitizer for stack trace exposure vulnerabilities. */
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A call to `backtrace` or `backtrace_locations` on a `rescue` variable,
|
||||
* considered as a flow source.
|
||||
*/
|
||||
class BacktraceCall extends Source, DataFlow::CallNode {
|
||||
BacktraceCall() {
|
||||
exists(DataFlow::LocalSourceNode varAccess |
|
||||
varAccess.asExpr().(ExprNodes::VariableReadAccessCfgNode).getExpr().getVariable() =
|
||||
any(RescueClause rc).getVariableExpr().(VariableAccess).getVariable() and
|
||||
varAccess.flowsTo(this.getReceiver())
|
||||
) and
|
||||
this.getMethodName() = ["backtrace", "backtrace_locations"]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Kernel#caller`, considered as a flow source.
|
||||
*/
|
||||
class KernelCallerCall extends Source, Kernel::KernelMethodCall {
|
||||
KernelCallerCall() { this.getMethodName() = "caller" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The body of an HTTP response that will be returned from a server,
|
||||
* considered as a flow sink.
|
||||
*/
|
||||
class ServerHttpResponseBodyAsSink extends Sink {
|
||||
ServerHttpResponseBodyAsSink() { this = any(Http::Server::HttpResponse response).getBody() }
|
||||
}
|
||||
}
|
||||
25
ruby/ql/lib/codeql/ruby/security/StackTraceExposureQuery.qll
Normal file
25
ruby/ql/lib/codeql/ruby/security/StackTraceExposureQuery.qll
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting stack-trace exposure
|
||||
* vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `StackTraceExposure::Configuration` is needed; otherwise,
|
||||
* `StackTraceExposureCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import StackTraceExposureCustomizations::StackTraceExposure
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "stack trace exposure" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "StackTraceExposure" }
|
||||
|
||||
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 }
|
||||
}
|
||||
@@ -2,73 +2,8 @@
|
||||
* Provides Ruby-specific imports and classes needed for `TaintedFormatStringQuery` and `TaintedFormatStringCustomizations`.
|
||||
*/
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.frameworks.StringFormatters
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.ApiGraphs
|
||||
import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.frameworks.core.IO
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
|
||||
/**
|
||||
* A call to `printf` or `sprintf`.
|
||||
*/
|
||||
abstract class PrintfStyleCall extends DataFlow::CallNode {
|
||||
// We assume that most printf-like calls have the signature f(format_string, args...)
|
||||
/**
|
||||
* Gets the format string of this call.
|
||||
*/
|
||||
DataFlow::Node getFormatString() { result = this.getArgument(0) }
|
||||
|
||||
/**
|
||||
* Gets then `n`th formatted argument of this call.
|
||||
*/
|
||||
DataFlow::Node getFormatArgument(int n) { n >= 0 and result = this.getArgument(n + 1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Kernel.printf`.
|
||||
*/
|
||||
class KernelPrintfCall extends PrintfStyleCall {
|
||||
KernelPrintfCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall("printf")
|
||||
or
|
||||
this.asExpr().getExpr() instanceof UnknownMethodCall and
|
||||
this.getMethodName() = "printf"
|
||||
}
|
||||
|
||||
// Kernel#printf supports two signatures:
|
||||
// printf(io, string, ...)
|
||||
// printf(string, ...)
|
||||
override DataFlow::Node getFormatString() {
|
||||
// Because `printf` has two different signatures, we can't be sure which
|
||||
// argument is the format string, so we use a heuristic:
|
||||
// If the first argument has a string value, then we assume it is the format string.
|
||||
// Otherwise we treat both the first and second args as the format string.
|
||||
if this.getArgument(0).getExprNode().getConstantValue().isString(_)
|
||||
then result = this.getArgument(0)
|
||||
else result = this.getArgument([0, 1])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Kernel.sprintf`.
|
||||
*/
|
||||
class KernelSprintfCall extends PrintfStyleCall {
|
||||
KernelSprintfCall() {
|
||||
this = API::getTopLevelMember("Kernel").getAMethodCall("sprintf")
|
||||
or
|
||||
this.asExpr().getExpr() instanceof UnknownMethodCall and
|
||||
this.getMethodName() = "sprintf"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `IO#printf`.
|
||||
*/
|
||||
class IOPrintfCall extends PrintfStyleCall {
|
||||
IOPrintfCall() {
|
||||
this.getReceiver() instanceof IO::IOInstance and this.getMethodName() = "printf"
|
||||
}
|
||||
}
|
||||
import codeql.ruby.DataFlow
|
||||
|
||||
@@ -62,284 +62,7 @@
|
||||
* a suffix `x` (possible empty) that is most likely __not__ accepted.
|
||||
*/
|
||||
|
||||
import NfaUtils
|
||||
|
||||
/**
|
||||
* Holds if state `s` might be inside a backtracking repetition.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate stateInsideBacktracking(State s) {
|
||||
s.getRepr().getParent*() instanceof MaybeBacktrackingRepetition
|
||||
}
|
||||
|
||||
/**
|
||||
* A infinitely repeating quantifier that might backtrack.
|
||||
*/
|
||||
private class MaybeBacktrackingRepetition extends InfiniteRepetitionQuantifier {
|
||||
MaybeBacktrackingRepetition() {
|
||||
exists(RegExpTerm child |
|
||||
child instanceof RegExpAlt or
|
||||
child instanceof RegExpQuantifier
|
||||
|
|
||||
child.getParent+() = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A state in the product automaton.
|
||||
*/
|
||||
private newtype TStatePair =
|
||||
/**
|
||||
* We lazily only construct those states that we are actually
|
||||
* going to need: `(q, q)` for every fork state `q`, and any
|
||||
* pair of states that can be reached from a pair that we have
|
||||
* already constructed. To cut down on the number of states,
|
||||
* we only represent states `(q1, q2)` where `q1` is lexicographically
|
||||
* no bigger than `q2`.
|
||||
*
|
||||
* States are only constructed if both states in the pair are
|
||||
* inside a repetition that might backtrack.
|
||||
*/
|
||||
MkStatePair(State q1, State q2) {
|
||||
isFork(q1, _, _, _, _) and q2 = q1
|
||||
or
|
||||
(step(_, _, _, q1, q2) or step(_, _, _, q2, q1)) and
|
||||
rankState(q1) <= rankState(q2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique number for a `state`.
|
||||
* Is used to create an ordering of states, where states with the same `toString()` will be ordered differently.
|
||||
*/
|
||||
private int rankState(State state) {
|
||||
state =
|
||||
rank[result](State s, Location l |
|
||||
stateInsideBacktracking(s) and
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s order by l.getStartLine(), l.getStartColumn(), s.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A state in the product automaton.
|
||||
*/
|
||||
private class StatePair extends TStatePair {
|
||||
State q1;
|
||||
State q2;
|
||||
|
||||
StatePair() { this = MkStatePair(q1, q2) }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "(" + q1 + ", " + q2 + ")" }
|
||||
|
||||
/** Gets the first component of the state pair. */
|
||||
State getLeft() { result = q1 }
|
||||
|
||||
/** Gets the second component of the state pair. */
|
||||
State getRight() { result = q2 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds for `(fork, fork)` state pairs when `isFork(fork, _, _, _, _)` holds.
|
||||
*
|
||||
* Used in `statePairDistToFork`
|
||||
*/
|
||||
private predicate isStatePairFork(StatePair p) {
|
||||
exists(State fork | p = MkStatePair(fork, fork) and isFork(fork, _, _, _, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r`.
|
||||
*
|
||||
* Used in `statePairDistToFork`
|
||||
*/
|
||||
private predicate reverseStep(StatePair r, StatePair q) { step(q, _, _, r) }
|
||||
|
||||
/**
|
||||
* Gets the minimum length of a path from `q` to `r` in the
|
||||
* product automaton.
|
||||
*/
|
||||
private int statePairDistToFork(StatePair q, StatePair r) =
|
||||
shortestDistances(isStatePairFork/1, reverseStep/2)(r, q, result)
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from `q` to `r1` and from `q` to `r2`
|
||||
* labelled with `s1` and `s2`, respectively, where `s1` and `s2` do not
|
||||
* trivially have an empty intersection.
|
||||
*
|
||||
* This predicate only holds for states associated with regular expressions
|
||||
* that have at least one repetition quantifier in them (otherwise the
|
||||
* expression cannot be vulnerable to ReDoS attacks anyway).
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
|
||||
stateInsideBacktracking(q) and
|
||||
exists(State q1, State q2 |
|
||||
q1 = epsilonSucc*(q) and
|
||||
delta(q1, s1, r1) and
|
||||
q2 = epsilonSucc*(q) and
|
||||
delta(q2, s2, r2) and
|
||||
// Use pragma[noopt] to prevent intersect(s1,s2) from being the starting point of the join.
|
||||
// From (s1,s2) it would find a huge number of intermediate state pairs (q1,q2) originating from different literals,
|
||||
// and discover at the end that no `q` can reach both `q1` and `q2` by epsilon transitions.
|
||||
exists(intersect(s1, s2))
|
||||
|
|
||||
s1 != s2
|
||||
or
|
||||
r1 != r2
|
||||
or
|
||||
r1 = r2 and q1 != q2
|
||||
or
|
||||
// If q can reach itself by epsilon transitions, then there are two distinct paths to the q1/q2 state:
|
||||
// one that uses the loop and one that doesn't. The engine will separately attempt to match with each path,
|
||||
// despite ending in the same state. The "fork" thus arises from the choice of whether to use the loop or not.
|
||||
// To avoid every state in the loop becoming a fork state,
|
||||
// we arbitrarily pick the InfiniteRepetitionQuantifier state as the canonical fork state for the loop
|
||||
// (every epsilon-loop must contain such a state).
|
||||
//
|
||||
// We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself.
|
||||
// This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`.
|
||||
// The below code is therefore a heuristic, that only flags regular expressions such as `/(a*)*b/`,
|
||||
// and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently.
|
||||
r1 = r2 and
|
||||
q1 = q2 and
|
||||
epsilonSucc+(q) = q and
|
||||
exists(RegExpTerm term | term = q.getRepr() | term instanceof InfiniteRepetitionQuantifier) and
|
||||
// One of the mid states is an infinite quantifier itself
|
||||
exists(State mid, RegExpTerm term |
|
||||
mid = epsilonSucc+(q) and
|
||||
term = mid.getRepr() and
|
||||
term instanceof InfiniteRepetitionQuantifier and
|
||||
q = epsilonSucc+(mid) and
|
||||
not mid = q
|
||||
)
|
||||
) and
|
||||
stateInsideBacktracking(r1) and
|
||||
stateInsideBacktracking(r2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state pair `(q1, q2)` or `(q2, q1)`; note that only
|
||||
* one or the other is defined.
|
||||
*/
|
||||
private StatePair mkStatePair(State q1, State q2) {
|
||||
result = MkStatePair(q1, q2) or result = MkStatePair(q2, q1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r` labelled with `s1` and `s2`, respectively.
|
||||
*/
|
||||
private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, StatePair r) {
|
||||
exists(State r1, State r2 | step(q, s1, s2, r1, r2) and r = mkStatePair(r1, r2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to `r1` and `r2`
|
||||
* labelled with `s1` and `s2`, respectively.
|
||||
*
|
||||
* We only consider transitions where the resulting states `(r1, r2)` are both
|
||||
* inside a repetition that might backtrack.
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
|
||||
exists(State q1, State q2 | q.getLeft() = q1 and q.getRight() = q2 |
|
||||
deltaClosed(q1, s1, r1) and
|
||||
deltaClosed(q2, s2, r2) and
|
||||
// use noopt to force the join on `intersect` to happen last.
|
||||
exists(intersect(s1, s2))
|
||||
) and
|
||||
stateInsideBacktracking(r1) and
|
||||
stateInsideBacktracking(r2)
|
||||
}
|
||||
|
||||
private newtype TTrace =
|
||||
Nil() or
|
||||
Step(InputSymbol s1, InputSymbol s2, TTrace t) { isReachableFromFork(_, _, s1, s2, t, _) }
|
||||
|
||||
/**
|
||||
* A list of pairs of input symbols that describe a path in the product automaton
|
||||
* starting from some fork state.
|
||||
*/
|
||||
private class Trace extends TTrace {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
this = Nil() and result = "Nil()"
|
||||
or
|
||||
exists(InputSymbol s1, InputSymbol s2, Trace t | this = Step(s1, s2, t) |
|
||||
result = "Step(" + s1 + ", " + s2 + ", " + t + ")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `r` is reachable from `(fork, fork)` under input `w`, and there is
|
||||
* a path from `r` back to `(fork, fork)` with `rem` steps.
|
||||
*/
|
||||
private predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) {
|
||||
exists(InputSymbol s1, InputSymbol s2, Trace v |
|
||||
isReachableFromFork(fork, r, s1, s2, v, rem) and
|
||||
w = Step(s1, s2, v)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isReachableFromFork(
|
||||
State fork, StatePair r, InputSymbol s1, InputSymbol s2, Trace v, int rem
|
||||
) {
|
||||
// base case
|
||||
exists(State q1, State q2 |
|
||||
isFork(fork, s1, s2, q1, q2) and
|
||||
r = MkStatePair(q1, q2) and
|
||||
v = Nil() and
|
||||
rem = statePairDistToFork(r, MkStatePair(fork, fork))
|
||||
)
|
||||
or
|
||||
// recursive case
|
||||
exists(StatePair p |
|
||||
isReachableFromFork(fork, p, v, rem + 1) and
|
||||
step(p, s1, s2, r) and
|
||||
rem = statePairDistToFork(r, MkStatePair(fork, fork))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a state in the product automaton from which `(fork, fork)` is
|
||||
* reachable in zero or more epsilon transitions.
|
||||
*/
|
||||
private StatePair getAForkPair(State fork) {
|
||||
isFork(fork, _, _, _, _) and
|
||||
result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
|
||||
}
|
||||
|
||||
/** An implementation of a chain containing chars for use by `Concretizer`. */
|
||||
private module CharTreeImpl implements CharTree {
|
||||
class CharNode = Trace;
|
||||
|
||||
CharNode getPrev(CharNode t) { t = Step(_, _, result) }
|
||||
|
||||
/** Holds if `n` is a trace that is used by `concretize` in `isPumpable`. */
|
||||
predicate isARelevantEnd(CharNode n) {
|
||||
exists(State f | isReachableFromFork(f, getAForkPair(f), n, _))
|
||||
}
|
||||
|
||||
string getChar(CharNode t) {
|
||||
exists(InputSymbol s1, InputSymbol s2 | t = Step(s1, s2, _) | result = intersect(s1, s2))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fork` is a pumpable fork with word `w`.
|
||||
*/
|
||||
private predicate isPumpable(State fork, string w) {
|
||||
exists(StatePair q, Trace t |
|
||||
isReachableFromFork(fork, q, t, _) and
|
||||
q = getAForkPair(fork) and
|
||||
w = Concretizer<CharTreeImpl>::concretize(t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `state` has exponential ReDoS */
|
||||
predicate hasReDoSResult = ReDoSPruning<isPumpable/2>::hasReDoSResult/4;
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
// ExponentialBackTracking should be used directly from the shared pack, and not from this file.
|
||||
deprecated private import codeql.regex.nfa.ExponentialBackTracking::Make<TreeView> as Dep
|
||||
import Dep
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Provides Ruby-specific definitions for use in the NfaUtils module.
|
||||
*/
|
||||
|
||||
import codeql.ruby.Regexp
|
||||
import codeql.Locations
|
||||
private import codeql.ruby.ast.Literal as Ast
|
||||
|
||||
/**
|
||||
* Holds if `term` is an escape class representing e.g. `\d`.
|
||||
* `clazz` is which character class it represents, e.g. "d" for `\d`.
|
||||
*/
|
||||
predicate isEscapeClass(RegExpTerm term, string clazz) {
|
||||
exists(RegExpCharacterClassEscape escape | term = escape | escape.getValue() = clazz)
|
||||
or
|
||||
// TODO: expand to cover more properties
|
||||
exists(RegExpNamedCharacterProperty escape | term = escape |
|
||||
escape.getName().toLowerCase() = "digit" and
|
||||
if escape.isInverted() then clazz = "D" else clazz = "d"
|
||||
or
|
||||
escape.getName().toLowerCase() = "space" and
|
||||
if escape.isInverted() then clazz = "S" else clazz = "s"
|
||||
or
|
||||
escape.getName().toLowerCase() = "word" and
|
||||
if escape.isInverted() then clazz = "W" else clazz = "w"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the regular expression should not be considered.
|
||||
*/
|
||||
predicate isExcluded(RegExpParent parent) {
|
||||
parent.(RegExpTerm).getRegExp().(Ast::RegExpLiteral).hasFreeSpacingFlag() // exclude free-spacing mode regexes
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` is a possessive quantifier.
|
||||
* Not currently implemented, but is used by the shared library.
|
||||
*/
|
||||
predicate isPossessive(RegExpQuantifier term) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the regex that `term` is part of is used in a way that ignores any leading prefix of the input it's matched against.
|
||||
* Not yet implemented for Ruby.
|
||||
*/
|
||||
predicate matchesAnyPrefix(RegExpTerm term) { any() }
|
||||
|
||||
/**
|
||||
* Holds if the regex that `term` is part of is used in a way that ignores any trailing suffix of the input it's matched against.
|
||||
* Not yet implemented for Ruby.
|
||||
*/
|
||||
predicate matchesAnySuffix(RegExpTerm term) { any() }
|
||||
|
||||
/**
|
||||
* A module containing predicates for determining which flags a regular expression have.
|
||||
*/
|
||||
module RegExpFlags {
|
||||
/**
|
||||
* Holds if `root` has the `i` flag for case-insensitive matching.
|
||||
*/
|
||||
predicate isIgnoreCase(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
root.getLiteral().isIgnoreCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `root` has the `s` flag for multi-line matching.
|
||||
*/
|
||||
predicate isDotAll(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
root.getLiteral().isDotAll()
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@ private import codeql.ruby.AST as Ast
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.Regexp
|
||||
private import codeql.ruby.security.regexp.SuperlinearBackTracking
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
@@ -17,6 +16,9 @@ private import codeql.ruby.security.regexp.SuperlinearBackTracking
|
||||
* as extension points for adding your own.
|
||||
*/
|
||||
module PolynomialReDoS {
|
||||
private import TreeView
|
||||
import codeql.regex.nfa.SuperlinearBackTracking::Make<TreeView>
|
||||
|
||||
/**
|
||||
* A data flow source node for polynomial regular expression denial-of-service vulnerabilities.
|
||||
*/
|
||||
@@ -127,7 +129,7 @@ module PolynomialReDoS {
|
||||
override DataFlow::Node getHighlight() { result = matchNode }
|
||||
}
|
||||
|
||||
private predicate lengthGuard(CfgNodes::ExprCfgNode g, CfgNode node, boolean branch) {
|
||||
private predicate lengthGuard(CfgNodes::AstCfgNode g, CfgNode node, boolean branch) {
|
||||
exists(DataFlow::Node input, DataFlow::CallNode length, DataFlow::ExprNode operand |
|
||||
length.asExpr().getExpr().(Ast::MethodCall).getMethodName() = "length" and
|
||||
length.getReceiver() = input and
|
||||
|
||||
@@ -3,155 +3,7 @@
|
||||
* and for testing which capture groups are filled when a particular regexp matches a string.
|
||||
*/
|
||||
|
||||
import NfaUtils
|
||||
|
||||
/** A root term */
|
||||
class RootTerm extends RegExpTerm {
|
||||
RootTerm() { this.isRootTerm() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if it should be tested whether `root` matches `str`.
|
||||
*
|
||||
* If `ignorePrefix` is true, then a regexp without a start anchor will be treated as if it had a start anchor.
|
||||
* E.g. a regular expression `/foo$/` will match any string that ends with "foo",
|
||||
* but if `ignorePrefix` is true, it will only match "foo".
|
||||
*
|
||||
* If `testWithGroups` is true, then the `RegexpMatching::fillsCaptureGroup` predicate can be used to determine which capture
|
||||
* groups are filled by a string.
|
||||
*/
|
||||
signature predicate isRegexpMatchingCandidateSig(
|
||||
RootTerm root, string str, boolean ignorePrefix, boolean testWithGroups
|
||||
);
|
||||
|
||||
/**
|
||||
* A module for determining if a regexp matches a given string,
|
||||
* and reasoning about which capture groups are filled by a given string.
|
||||
*
|
||||
* The module parameter `isCandidate` determines which strings should be tested,
|
||||
* and the results can be read from the `matches` and `fillsCaptureGroup` predicates.
|
||||
*/
|
||||
module RegexpMatching<isRegexpMatchingCandidateSig/4 isCandidate> {
|
||||
/**
|
||||
* Gets a state the regular expression `reg` can be in after matching the `i`th char in `str`.
|
||||
* The regular expression is modeled as a non-determistic finite automaton,
|
||||
* the regular expression can therefore be in multiple states after matching a character.
|
||||
*
|
||||
* It's a forward search to all possible states, and there is thus no guarantee that the state is on a path to an accepting state.
|
||||
*/
|
||||
private State getAState(RootTerm reg, int i, string str, boolean ignorePrefix) {
|
||||
// start state, the -1 position before any chars have been matched
|
||||
i = -1 and
|
||||
isCandidate(reg, str, ignorePrefix, _) and
|
||||
result.getRepr().getRootTerm() = reg and
|
||||
isStartState(result)
|
||||
or
|
||||
// recursive case
|
||||
result = getAStateAfterMatching(reg, _, str, i, _, ignorePrefix)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next state after the `prev` state from `reg`.
|
||||
* `prev` is the state after matching `fromIndex` chars in `str`,
|
||||
* and the result is the state after matching `toIndex` chars in `str`.
|
||||
*
|
||||
* This predicate is used as a step relation in the forwards search (`getAState`),
|
||||
* and also as a step relation in the later backwards search (`getAStateThatReachesAccept`).
|
||||
*/
|
||||
private State getAStateAfterMatching(
|
||||
RootTerm reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
|
||||
) {
|
||||
// the basic recursive case - outlined into a noopt helper to make performance work out.
|
||||
result = getAStateAfterMatchingAux(reg, prev, str, toIndex, fromIndex, ignorePrefix)
|
||||
or
|
||||
// we can skip past word boundaries if the next char is a non-word char.
|
||||
fromIndex = toIndex and
|
||||
prev.getRepr() instanceof RegExpWordBoundary and
|
||||
prev = getAState(reg, toIndex, str, ignorePrefix) and
|
||||
after(prev.getRepr()) = result and
|
||||
str.charAt(toIndex + 1).regexpMatch("\\W") // \W matches any non-word char.
|
||||
}
|
||||
|
||||
pragma[noopt]
|
||||
private State getAStateAfterMatchingAux(
|
||||
RootTerm reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
|
||||
) {
|
||||
prev = getAState(reg, fromIndex, str, ignorePrefix) and
|
||||
fromIndex = toIndex - 1 and
|
||||
exists(string char | char = str.charAt(toIndex) | specializedDeltaClosed(prev, char, result)) and
|
||||
not discardedPrefixStep(prev, result, ignorePrefix)
|
||||
}
|
||||
|
||||
/** Holds if a step from `prev` to `next` should be discarded when the `ignorePrefix` flag is set. */
|
||||
private predicate discardedPrefixStep(State prev, State next, boolean ignorePrefix) {
|
||||
prev = mkMatch(any(RegExpRoot r)) and
|
||||
ignorePrefix = true and
|
||||
next = prev
|
||||
}
|
||||
|
||||
// The `deltaClosed` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
|
||||
private predicate specializedDeltaClosed(State prev, string char, State next) {
|
||||
deltaClosed(prev, specializedGetAnInputSymbolMatching(char), next)
|
||||
}
|
||||
|
||||
// The `getAnInputSymbolMatching` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
|
||||
pragma[noinline]
|
||||
private InputSymbol specializedGetAnInputSymbolMatching(string char) {
|
||||
exists(string s, RootTerm r | isCandidate(r, s, _, _) | char = s.charAt(_)) and
|
||||
result = getAnInputSymbolMatching(char)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th state on a path to the accepting state when `reg` matches `str`.
|
||||
* Starts with an accepting state as found by `getAState` and searches backwards
|
||||
* to the start state through the reachable states (as found by `getAState`).
|
||||
*
|
||||
* This predicate satisfies the invariant that the result state can be reached with `i` steps from a start state,
|
||||
* and an accepting state can be found after (`str.length() - 1 - i`) steps from the result.
|
||||
* The result state is therefore always on a valid path where `reg` accepts `str`.
|
||||
*
|
||||
* This predicate is only used to find which capture groups a regular expression has filled,
|
||||
* and thus the search is only performed for the strings in the `testWithGroups(..)` predicate.
|
||||
*/
|
||||
private State getAStateThatReachesAccept(RootTerm reg, int i, string str, boolean ignorePrefix) {
|
||||
// base case, reaches an accepting state from the last state in `getAState(..)`
|
||||
isCandidate(reg, str, ignorePrefix, true) and
|
||||
i = str.length() - 1 and
|
||||
result = getAState(reg, i, str, ignorePrefix) and
|
||||
epsilonSucc*(result) = Accept(_)
|
||||
or
|
||||
// recursive case. `next` is the next state to be matched after matching `prev`.
|
||||
// this predicate is doing a backwards search, so `prev` is the result we are looking for.
|
||||
exists(State next, State prev, int fromIndex, int toIndex |
|
||||
next = getAStateThatReachesAccept(reg, toIndex, str, ignorePrefix) and
|
||||
next = getAStateAfterMatching(reg, prev, str, toIndex, fromIndex, ignorePrefix) and
|
||||
i = fromIndex and
|
||||
result = prev
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the capture group number that `term` belongs to. */
|
||||
private int group(RegExpTerm term) {
|
||||
exists(RegExpGroup grp | grp.getNumber() = result | term.getParent*() = grp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `reg` matches `str`, where `str` is in the `isCandidate` predicate.
|
||||
*/
|
||||
predicate matches(RootTerm reg, string str) {
|
||||
exists(State state | state = getAState(reg, str.length() - 1, str, _) |
|
||||
epsilonSucc*(state) = Accept(_)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if matching `str` against `reg` may fill capture group number `g`.
|
||||
* Only holds if `str` is in the `testWithGroups` predicate.
|
||||
*/
|
||||
predicate fillsCaptureGroup(RootTerm reg, string str, int g) {
|
||||
exists(State s |
|
||||
s = getAStateThatReachesAccept(reg, _, str, _) and
|
||||
g = group(s.getRepr())
|
||||
)
|
||||
}
|
||||
}
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
// RegexpMatching should be used directly from the shared pack, and not from this file.
|
||||
deprecated import codeql.regex.nfa.RegexpMatching::Make<TreeView> as Dep
|
||||
import Dep
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
/**
|
||||
* Provides classes for working with regular expressions that can
|
||||
* perform backtracking in superlinear time.
|
||||
*/
|
||||
|
||||
import NfaUtils
|
||||
|
||||
/*
|
||||
* This module implements the analysis described in the paper:
|
||||
* Valentin Wustholz, Oswaldo Olivo, Marijn J. H. Heule, and Isil Dillig:
|
||||
* Static Detection of DoS Vulnerabilities in
|
||||
@@ -42,377 +35,7 @@ import NfaUtils
|
||||
* It also doesn't find all transitions in the product automaton, which can cause false negatives.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets any root (start) state of a regular expression.
|
||||
*/
|
||||
private State getRootState() { result = mkMatch(any(RegExpRoot r)) }
|
||||
|
||||
private newtype TStateTuple =
|
||||
MkStateTuple(State q1, State q2, State q3) {
|
||||
// starts at (pivot, pivot, succ)
|
||||
isStartLoops(q1, q3) and q1 = q2
|
||||
or
|
||||
step(_, _, _, _, q1, q2, q3) and FeasibleTuple::isFeasibleTuple(q1, q2, q3)
|
||||
}
|
||||
|
||||
/**
|
||||
* A state in the product automaton.
|
||||
* The product automaton contains 3-tuples of states.
|
||||
*
|
||||
* We lazily only construct those states that we are actually
|
||||
* going to need.
|
||||
* Either a start state `(pivot, pivot, succ)`, or a state
|
||||
* where there exists a transition from an already existing state.
|
||||
*
|
||||
* The exponential variant of this query (`js/redos`) uses an optimization
|
||||
* trick where `q1 <= q2`. This trick cannot be used here as the order
|
||||
* of the elements matter.
|
||||
*/
|
||||
class StateTuple extends TStateTuple {
|
||||
State q1;
|
||||
State q2;
|
||||
State q3;
|
||||
|
||||
StateTuple() { this = MkStateTuple(q1, q2, q3) }
|
||||
|
||||
/**
|
||||
* Gest a string representation of this tuple.
|
||||
*/
|
||||
string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" }
|
||||
|
||||
/**
|
||||
* Holds if this tuple is `(r1, r2, r3)`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate isTuple(State r1, State r2, State r3) { r1 = q1 and r2 = q2 and r3 = q3 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for determining feasible tuples for the product automaton.
|
||||
*
|
||||
* The implementation is split into many predicates for performance reasons.
|
||||
*/
|
||||
private module FeasibleTuple {
|
||||
/**
|
||||
* Holds if the tuple `(r1, r2, r3)` might be on path from a start-state to an end-state in the product automaton.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate isFeasibleTuple(State r1, State r2, State r3) {
|
||||
// The first element is either inside a repetition (or the start state itself)
|
||||
isRepetitionOrStart(r1) and
|
||||
// The last element is inside a repetition
|
||||
stateInsideRepetition(r3) and
|
||||
// The states are reachable in the NFA in the order r1 -> r2 -> r3
|
||||
delta+(r1) = r2 and
|
||||
delta+(r2) = r3 and
|
||||
// The first element can reach a beginning (the "pivot" state in a `(pivot, succ)` pair).
|
||||
canReachABeginning(r1) and
|
||||
// The last element can reach a target (the "succ" state in a `(pivot, succ)` pair).
|
||||
canReachATarget(r3)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `s` is either inside a repetition, or is the start state (which is a repetition).
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate isRepetitionOrStart(State s) { stateInsideRepetition(s) or s = getRootState() }
|
||||
|
||||
/**
|
||||
* Holds if state `s` might be inside a backtracking repetition.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate stateInsideRepetition(State s) {
|
||||
s.getRepr().getParent*() instanceof InfiniteRepetitionQuantifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a path in the NFA from `s` to a "pivot" state
|
||||
* (from a `(pivot, succ)` pair that starts the search).
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate canReachABeginning(State s) {
|
||||
delta+(s) = any(State pivot | isStartLoops(pivot, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a path in the NFA from `s` to a "succ" state
|
||||
* (from a `(pivot, succ)` pair that starts the search).
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate canReachATarget(State s) { delta+(s) = any(State succ | isStartLoops(_, succ)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pivot` and `succ` are a pair of loops that could be the beginning of a quadratic blowup.
|
||||
*
|
||||
* There is a slight implementation difference compared to the paper: this predicate requires that `pivot != succ`.
|
||||
* The case where `pivot = succ` causes exponential backtracking and is handled by the `js/redos` query.
|
||||
*/
|
||||
predicate isStartLoops(State pivot, State succ) {
|
||||
pivot != succ and
|
||||
succ.getRepr() instanceof InfiniteRepetitionQuantifier and
|
||||
delta+(pivot) = succ and
|
||||
(
|
||||
pivot.getRepr() instanceof InfiniteRepetitionQuantifier
|
||||
or
|
||||
pivot = mkMatch(any(RegExpRoot root))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a state for which there exists a transition in the NFA from `s'.
|
||||
*/
|
||||
State delta(State s) { delta(s, _, result) }
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r` labelled with `s1`, `s2`, and `s3`, respectively.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate step(StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, StateTuple r) {
|
||||
exists(State r1, State r2, State r3 |
|
||||
step(q, s1, s2, s3, r1, r2, r3) and r = MkStateTuple(r1, r2, r3)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to `r1`, `r2`, and `r3
|
||||
* labelled with `s1`, `s2`, and `s3`, respectively.
|
||||
*/
|
||||
pragma[noopt]
|
||||
predicate step(
|
||||
StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, State r1, State r2, State r3
|
||||
) {
|
||||
exists(State q1, State q2, State q3 | q.isTuple(q1, q2, q3) |
|
||||
deltaClosed(q1, s1, r1) and
|
||||
deltaClosed(q2, s2, r2) and
|
||||
deltaClosed(q3, s3, r3) and
|
||||
// use noopt to force the join on `getAThreewayIntersect` to happen last.
|
||||
exists(getAThreewayIntersect(s1, s2, s3))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a char that is matched by all the edges `s1`, `s2`, and `s3`.
|
||||
*
|
||||
* The result is not complete, and might miss some combination of edges that share some character.
|
||||
*/
|
||||
pragma[noinline]
|
||||
string getAThreewayIntersect(InputSymbol s1, InputSymbol s2, InputSymbol s3) {
|
||||
result = minAndMaxIntersect(s1, s2) and result = [intersect(s2, s3), intersect(s1, s3)]
|
||||
or
|
||||
result = minAndMaxIntersect(s1, s3) and result = [intersect(s2, s3), intersect(s1, s2)]
|
||||
or
|
||||
result = minAndMaxIntersect(s2, s3) and result = [intersect(s1, s2), intersect(s1, s3)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum and maximum characters that intersect between `a` and `b`.
|
||||
* This predicate is used to limit the size of `getAThreewayIntersect`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
string minAndMaxIntersect(InputSymbol a, InputSymbol b) {
|
||||
result = [min(intersect(a, b)), max(intersect(a, b))]
|
||||
}
|
||||
|
||||
private newtype TTrace =
|
||||
Nil() or
|
||||
Step(InputSymbol s1, InputSymbol s2, InputSymbol s3, TTrace t) {
|
||||
isReachableFromStartTuple(_, _, t, s1, s2, s3, _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of tuples of input symbols that describe a path in the product automaton
|
||||
* starting from some start state.
|
||||
*/
|
||||
class Trace extends TTrace {
|
||||
/**
|
||||
* Gets a string representation of this Trace that can be used for debug purposes.
|
||||
*/
|
||||
string toString() {
|
||||
this = Nil() and result = "Nil()"
|
||||
or
|
||||
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t | this = Step(s1, s2, s3, t) |
|
||||
result = "Step(" + s1 + ", " + s2 + ", " + s3 + ", " + t + ")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a transition from `r` to `q` in the product automaton.
|
||||
* Notice that the arguments are flipped, and thus the direction is backwards.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate tupleDeltaBackwards(StateTuple q, StateTuple r) { step(r, _, _, _, q) }
|
||||
|
||||
/**
|
||||
* Holds if `tuple` is an end state in our search.
|
||||
* That means there exists a pair of loops `(pivot, succ)` such that `tuple = (pivot, succ, succ)`.
|
||||
*/
|
||||
predicate isEndTuple(StateTuple tuple) { tuple = getAnEndTuple(_, _) }
|
||||
|
||||
/**
|
||||
* Gets the minimum length of a path from `r` to some an end state `end`.
|
||||
*
|
||||
* The implementation searches backwards from the end-tuple.
|
||||
* This approach was chosen because it is way more efficient if the first predicate given to `shortestDistances` is small.
|
||||
* The `end` argument must always be an end state.
|
||||
*/
|
||||
int distBackFromEnd(StateTuple r, StateTuple end) =
|
||||
shortestDistances(isEndTuple/1, tupleDeltaBackwards/2)(end, r, result)
|
||||
|
||||
/**
|
||||
* Holds if there exists a pair of repetitions `(pivot, succ)` in the regular expression such that:
|
||||
* `tuple` is reachable from `(pivot, pivot, succ)` in the product automaton,
|
||||
* and there is a distance of `dist` from `tuple` to the nearest end-tuple `(pivot, succ, succ)`,
|
||||
* and a path from a start-state to `tuple` follows the transitions in `trace`.
|
||||
*/
|
||||
private predicate isReachableFromStartTuple(
|
||||
State pivot, State succ, StateTuple tuple, Trace trace, int dist
|
||||
) {
|
||||
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace v |
|
||||
isReachableFromStartTuple(pivot, succ, v, s1, s2, s3, tuple, dist) and
|
||||
trace = Step(s1, s2, s3, v)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isReachableFromStartTuple(
|
||||
State pivot, State succ, Trace trace, InputSymbol s1, InputSymbol s2, InputSymbol s3,
|
||||
StateTuple tuple, int dist
|
||||
) {
|
||||
// base case.
|
||||
exists(State q1, State q2, State q3 |
|
||||
isStartLoops(pivot, succ) and
|
||||
step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, tuple) and
|
||||
tuple = MkStateTuple(q1, q2, q3) and
|
||||
trace = Nil() and
|
||||
dist = distBackFromEnd(tuple, MkStateTuple(pivot, succ, succ))
|
||||
)
|
||||
or
|
||||
// recursive case
|
||||
exists(StateTuple p |
|
||||
isReachableFromStartTuple(pivot, succ, p, trace, dist + 1) and
|
||||
dist = distBackFromEnd(tuple, MkStateTuple(pivot, succ, succ)) and
|
||||
step(p, s1, s2, s3, tuple)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tuple `(pivot, succ, succ)` from the product automaton.
|
||||
*/
|
||||
StateTuple getAnEndTuple(State pivot, State succ) {
|
||||
isStartLoops(pivot, succ) and
|
||||
result = MkStateTuple(pivot, succ, succ)
|
||||
}
|
||||
|
||||
/** An implementation of a chain containing chars for use by `Concretizer`. */
|
||||
private module CharTreeImpl implements CharTree {
|
||||
class CharNode = Trace;
|
||||
|
||||
CharNode getPrev(CharNode t) { t = Step(_, _, _, result) }
|
||||
|
||||
/** Holds if `n` is used in `isPumpable`. */
|
||||
predicate isARelevantEnd(CharNode n) {
|
||||
exists(State pivot, State succ |
|
||||
isReachableFromStartTuple(pivot, succ, getAnEndTuple(pivot, succ), n, _)
|
||||
)
|
||||
}
|
||||
|
||||
string getChar(CharNode t) {
|
||||
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3 | t = Step(s1, s2, s3, _) |
|
||||
result = getAThreewayIntersect(s1, s2, s3)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if matching repetitions of `pump` can:
|
||||
* 1) Transition from `pivot` back to `pivot`.
|
||||
* 2) Transition from `pivot` to `succ`.
|
||||
* 3) Transition from `succ` to `succ`.
|
||||
*
|
||||
* From theorem 3 in the paper linked in the top of this file we can therefore conclude that
|
||||
* the regular expression has polynomial backtracking - if a rejecting suffix exists.
|
||||
*
|
||||
* This predicate is used by `SuperLinearReDoSConfiguration`, and the final results are
|
||||
* available in the `hasReDoSResult` predicate.
|
||||
*/
|
||||
predicate isPumpable(State pivot, State succ, string pump) {
|
||||
exists(StateTuple q, Trace t |
|
||||
isReachableFromStartTuple(pivot, succ, q, t, _) and
|
||||
q = getAnEndTuple(pivot, succ) and
|
||||
pump = Concretizer<CharTreeImpl>::concretize(t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if states starting in `state` can have polynomial backtracking with the string `pump`.
|
||||
*/
|
||||
predicate isReDoSCandidate(State state, string pump) { isPumpable(_, state, pump) }
|
||||
|
||||
/**
|
||||
* Holds if repetitions of `pump` at `t` will cause polynomial backtracking.
|
||||
*/
|
||||
predicate polynomialReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
|
||||
exists(State s, State pivot |
|
||||
ReDoSPruning<isReDoSCandidate/2>::hasReDoSResult(t, pump, s, prefixMsg) and
|
||||
isPumpable(pivot, s, _) and
|
||||
prev = pivot.getRepr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a message for why `term` can cause polynomial backtracking.
|
||||
*/
|
||||
string getReasonString(RegExpTerm term, string pump, string prefixMsg, RegExpTerm prev) {
|
||||
polynomialReDoS(term, pump, prefixMsg, prev) and
|
||||
result =
|
||||
"Strings " + prefixMsg + "with many repetitions of '" + pump +
|
||||
"' can start matching anywhere after the start of the preceeding " + prev
|
||||
}
|
||||
|
||||
/**
|
||||
* A term that may cause a regular expression engine to perform a
|
||||
* polynomial number of match attempts, relative to the input length.
|
||||
*/
|
||||
class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier {
|
||||
string reason;
|
||||
string pump;
|
||||
string prefixMsg;
|
||||
RegExpTerm prev;
|
||||
|
||||
PolynomialBackTrackingTerm() {
|
||||
reason = getReasonString(this, pump, prefixMsg, prev) and
|
||||
// there might be many reasons for this term to have polynomial backtracking - we pick the shortest one.
|
||||
reason = min(string msg | msg = getReasonString(this, _, _, _) | msg order by msg.length(), msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if all non-empty successors to the polynomial backtracking term matches the end of the line.
|
||||
*/
|
||||
predicate isAtEndLine() {
|
||||
forall(RegExpTerm succ | this.getSuccessor+() = succ and not matchesEpsilon(succ) |
|
||||
succ instanceof RegExpDollar
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string that should be repeated to cause this regular expression to perform polynomially.
|
||||
*/
|
||||
string getPumpString() { result = pump }
|
||||
|
||||
/**
|
||||
* Gets a message for which prefix a matching string must start with for this term to cause polynomial backtracking.
|
||||
*/
|
||||
string getPrefixMessage() { result = prefixMsg }
|
||||
|
||||
/**
|
||||
* Gets a predecessor to `this`, which also loops on the pump string, and thereby causes polynomial backtracking.
|
||||
*/
|
||||
RegExpTerm getPreviousLoop() { result = prev }
|
||||
|
||||
/**
|
||||
* Gets the reason for the number of match attempts.
|
||||
*/
|
||||
string getReason() { result = reason }
|
||||
}
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
// SuperlinearBackTracking should be used directly from the shared pack, and not from this file.
|
||||
deprecated private import codeql.regex.nfa.SuperlinearBackTracking::Make<TreeView> as Dep
|
||||
import Dep
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-all
|
||||
version: 0.4.4-dev
|
||||
version: 0.4.5-dev
|
||||
groups: ruby
|
||||
extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
@@ -7,4 +7,4 @@ upgrades: upgrades
|
||||
library: true
|
||||
dependencies:
|
||||
codeql/ssa: ${workspace}
|
||||
|
||||
codeql/regex: ${workspace}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 0.4.4
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `rb/shell-command-constructed-from-input`, to detect libraries that unsafely construct shell commands from their inputs.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `rb/sql-injection` query now considers consider SQL constructions, such as calls to `Arel.sql`, as sinks.
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rb/shell-command-constructed-from-input`, to detect libraries that unsafely construct shell commands from their inputs.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rb/stack-trace-exposure`, to detect exposure of stack-traces to users via HTTP responses.
|
||||
9
ruby/ql/src/change-notes/released/0.4.4.md
Normal file
9
ruby/ql/src/change-notes/released/0.4.4.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 0.4.4
|
||||
|
||||
### New Queries
|
||||
|
||||
* Added a new query, `rb/shell-command-constructed-from-input`, to detect libraries that unsafely construct shell commands from their inputs.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `rb/sql-injection` query now considers consider SQL constructions, such as calls to `Arel.sql`, as sinks.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.3
|
||||
lastReleaseVersion: 0.4.4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-queries
|
||||
version: 0.4.4-dev
|
||||
version: 0.4.5-dev
|
||||
groups:
|
||||
- ruby
|
||||
- queries
|
||||
|
||||
@@ -12,17 +12,18 @@
|
||||
* external/cwe/cwe-020
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.OverlyLargeRangeQuery
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
import codeql.regex.OverlyLargeRangeQuery::Make<TreeView>
|
||||
|
||||
RegExpCharacterClass potentialMisparsedCharClass() {
|
||||
TreeView::RegExpCharacterClass potentialMisparsedCharClass() {
|
||||
// some escapes, e.g. [\000-\037] are currently misparsed.
|
||||
result.getAChild().(RegExpNormalChar).getValue() = "\\"
|
||||
result.getAChild().(TreeView::RegExpNormalChar).getValue() = "\\"
|
||||
or
|
||||
// nested char classes are currently misparsed
|
||||
result.getAChild().(RegExpNormalChar).getValue() = "["
|
||||
result.getAChild().(TreeView::RegExpNormalChar).getValue() = "["
|
||||
}
|
||||
|
||||
from RegExpCharacterRange range, string reason
|
||||
from TreeView::RegExpCharacterRange range, string reason
|
||||
where
|
||||
problem(range, reason) and
|
||||
not range.getParent() = potentialMisparsedCharClass()
|
||||
|
||||
@@ -11,28 +11,11 @@
|
||||
* external/cwe/cwe-089
|
||||
*/
|
||||
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.Concepts
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.dataflow.BarrierGuards
|
||||
import codeql.ruby.dataflow.RemoteFlowSources
|
||||
import codeql.ruby.TaintTracking
|
||||
import codeql.ruby.security.SqlInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class SqlInjectionConfiguration extends TaintTracking::Configuration {
|
||||
SqlInjectionConfiguration() { this = "SQLInjectionConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof SqlExecution }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof StringConstCompareBarrier or
|
||||
node instanceof StringConstArrayInclusionCallBarrier
|
||||
}
|
||||
}
|
||||
|
||||
from SqlInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This SQL query depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* external/cwe/cwe-186
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.BadTagFilterQuery
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
import codeql.regex.nfa.BadTagFilterQuery::Make<TreeView>
|
||||
|
||||
from HtmlMatchingRegExp regexp, string msg
|
||||
where msg = min(string m | isBadRegexpFilter(regexp, m) | m order by m.length(), m) // there might be multiple, we arbitrarily pick the shortest one
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
import DataFlow::PathGraph
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.regexp.PolynomialReDoSQuery
|
||||
import codeql.ruby.security.regexp.SuperlinearBackTracking
|
||||
|
||||
from
|
||||
PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
PolynomialReDoS::Sink sinkNode, PolynomialBackTrackingTerm regexp
|
||||
PolynomialReDoS::Sink sinkNode, PolynomialReDoS::PolynomialBackTrackingTerm regexp
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
sinkNode = sink.getNode() and
|
||||
|
||||
@@ -14,11 +14,10 @@
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import codeql.ruby.security.regexp.ExponentialBackTracking
|
||||
import codeql.ruby.security.regexp.NfaUtils
|
||||
import codeql.ruby.Regexp
|
||||
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
|
||||
import codeql.regex.nfa.ExponentialBackTracking::Make<TreeView>
|
||||
|
||||
from RegExpTerm t, string pump, State s, string prefixMsg
|
||||
from TreeView::RegExpTerm t, string pump, State s, string prefixMsg
|
||||
where hasReDoSResult(t, pump, s, prefixMsg)
|
||||
select t,
|
||||
"This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
Software developers often add stack traces to error messages, as a debugging
|
||||
aid. Whenever that error message occurs for an end user, the developer can use
|
||||
the stack trace to help identify how to fix the problem. In particular, stack
|
||||
traces can tell the developer more about the sequence of events that led to a
|
||||
failure, as opposed to merely the final state of the software when the error
|
||||
occurred.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Unfortunately, the same information can be useful to an attacker. The sequence
|
||||
of class or method names in a stack trace can reveal the structure of the
|
||||
application as well as any internal components it relies on. Furthermore, the
|
||||
error message at the top of a stack trace can include information such as
|
||||
server-side file names and SQL code that the application relies on, allowing an
|
||||
attacker to fine-tune a subsequent injection attack.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Send the user a more generic error message that reveals less information.
|
||||
Either suppress the stack trace entirely, or log it only on the server.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the following example, an exception is handled in two different ways. In the
|
||||
first version, labeled BAD, the exception is exposed to the remote user by
|
||||
rendering it as an HTTP response. As such, the user is able to see a detailed
|
||||
stack trace, which may contain sensitive information. In the second version, the
|
||||
error message is logged only on the server, and a generic error message is
|
||||
displayed to the user. That way, the developers can still access and use the
|
||||
error log, but remote users will not see the information. </p>
|
||||
|
||||
<sample src="examples/StackTraceExposure.rb" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://owasp.org/www-community/Improper_Error_Handling">Improper Error Handling</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
23
ruby/ql/src/queries/security/cwe-209/StackTraceExposure.ql
Normal file
23
ruby/ql/src/queries/security/cwe-209/StackTraceExposure.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name Information exposure through an exception
|
||||
* @description Leaking information about an exception, such as messages and stack traces, to an
|
||||
* external user can expose implementation details that are useful to an attacker for
|
||||
* developing a subsequent exploit.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.4
|
||||
* @precision high
|
||||
* @id rb/stack-trace-exposure
|
||||
* @tags security
|
||||
* external/cwe/cwe-209
|
||||
* external/cwe/cwe-497
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.security.StackTraceExposureQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ can be exposed to an external user.", source.getNode(),
|
||||
"Error information"
|
||||
@@ -0,0 +1,18 @@
|
||||
class UsersController < ApplicationController
|
||||
|
||||
def update_bad(id)
|
||||
do_computation()
|
||||
rescue => e
|
||||
# BAD
|
||||
render body: e.backtrace, content_type: "text/plain"
|
||||
end
|
||||
|
||||
def update_good(id)
|
||||
do_computation()
|
||||
rescue => e
|
||||
# GOOD
|
||||
logger.error e.backtrace
|
||||
render body: "Computation failed", content_type: "text/plain"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -3,7 +3,6 @@
|
||||
*
|
||||
* Example for a test.ql:
|
||||
* ```ql
|
||||
* *import codeql.ruby.AST
|
||||
* import TestUtilities.InlineFlowTest
|
||||
* import PathGraph
|
||||
*
|
||||
|
||||
@@ -526,17 +526,20 @@ case.rb:
|
||||
#-----| -> exit if_in_case (normal)
|
||||
|
||||
# 2| call to x1
|
||||
#-----| -> when ...
|
||||
#-----| -> 1
|
||||
|
||||
# 2| self
|
||||
#-----| -> call to x1
|
||||
|
||||
# 3| when ...
|
||||
#-----| -> 1
|
||||
# 3| [match] when ...
|
||||
#-----| match -> self
|
||||
|
||||
# 3| [no-match] when ...
|
||||
#-----| no-match -> 2
|
||||
|
||||
# 3| 1
|
||||
#-----| match -> self
|
||||
#-----| no-match -> when ...
|
||||
#-----| match -> [match] when ...
|
||||
#-----| no-match -> [no-match] when ...
|
||||
|
||||
# 3| then ...
|
||||
#-----| -> case ...
|
||||
@@ -569,12 +572,15 @@ case.rb:
|
||||
# 3| x2
|
||||
#-----| -> "x2"
|
||||
|
||||
# 4| when ...
|
||||
#-----| -> 2
|
||||
# 4| [match] when ...
|
||||
#-----| match -> self
|
||||
|
||||
# 4| [no-match] when ...
|
||||
#-----| no-match -> case ...
|
||||
|
||||
# 4| 2
|
||||
#-----| no-match -> case ...
|
||||
#-----| match -> self
|
||||
#-----| match -> [match] when ...
|
||||
#-----| no-match -> [no-match] when ...
|
||||
|
||||
# 4| then ...
|
||||
#-----| -> case ...
|
||||
@@ -811,7 +817,7 @@ case.rb:
|
||||
#-----| -> [ ..., * ]
|
||||
|
||||
# 35| [ ..., * ]
|
||||
#-----| match -> x
|
||||
#-----| false, match, true -> x
|
||||
#-----| no-match -> in ... then ...
|
||||
|
||||
# 35| x
|
||||
@@ -821,7 +827,7 @@ case.rb:
|
||||
#-----| -> [ ..., * ]
|
||||
|
||||
# 36| [ ..., * ]
|
||||
#-----| match -> x
|
||||
#-----| false, match, true -> x
|
||||
#-----| no-match -> in ... then ...
|
||||
|
||||
# 36| x
|
||||
@@ -836,7 +842,7 @@ case.rb:
|
||||
|
||||
# 37| [ ..., * ]
|
||||
#-----| raise -> exit case_match_array (abnormal)
|
||||
#-----| match -> a
|
||||
#-----| false, match, true -> a
|
||||
|
||||
# 37| a
|
||||
#-----| match -> b
|
||||
@@ -881,7 +887,7 @@ case.rb:
|
||||
|
||||
# 43| [ *,...,* ]
|
||||
#-----| raise -> exit case_match_find (abnormal)
|
||||
#-----| match -> x
|
||||
#-----| false, match, true -> x
|
||||
|
||||
# 43| x
|
||||
#-----| -> 1
|
||||
@@ -931,7 +937,7 @@ case.rb:
|
||||
#-----| no-match -> in ... then ...
|
||||
|
||||
# 49| { ..., ** }
|
||||
#-----| match -> 1
|
||||
#-----| false, match, true -> 1
|
||||
#-----| no-match -> in ... then ...
|
||||
|
||||
# 49| 1
|
||||
@@ -952,7 +958,7 @@ case.rb:
|
||||
#-----| no-match -> in ... then ...
|
||||
|
||||
# 50| { ..., ** }
|
||||
#-----| match -> 1
|
||||
#-----| false, match, true -> 1
|
||||
#-----| no-match -> in ... then ...
|
||||
|
||||
# 50| 1
|
||||
@@ -968,7 +974,7 @@ case.rb:
|
||||
|
||||
# 51| { ..., ** }
|
||||
#-----| raise -> exit case_match_hash (abnormal)
|
||||
#-----| match -> case ...
|
||||
#-----| false, match, true -> case ...
|
||||
|
||||
# 55| case_match_variable
|
||||
#-----| -> case_match_underscore
|
||||
@@ -1372,7 +1378,7 @@ case.rb:
|
||||
|
||||
# 91| { ..., ** }
|
||||
#-----| raise -> exit case_match_various (abnormal)
|
||||
#-----| match -> case ...
|
||||
#-----| false, match, true -> case ...
|
||||
|
||||
# 95| case_match_guard_no_else
|
||||
#-----| -> exit case.rb (normal)
|
||||
@@ -1826,17 +1832,20 @@ cfg.rb:
|
||||
#-----| -> call to puts
|
||||
|
||||
# 41| case ...
|
||||
#-----| -> when ...
|
||||
#-----| -> b
|
||||
|
||||
# 41| 10
|
||||
#-----| -> when ...
|
||||
|
||||
# 42| when ...
|
||||
#-----| -> 1
|
||||
|
||||
# 42| 1
|
||||
# 42| [match] when ...
|
||||
#-----| match -> self
|
||||
#-----| no-match -> when ...
|
||||
|
||||
# 42| [no-match] when ...
|
||||
#-----| no-match -> 2
|
||||
|
||||
# 42| 1
|
||||
#-----| match -> [match] when ...
|
||||
#-----| no-match -> [no-match] when ...
|
||||
|
||||
# 42| then ...
|
||||
#-----| -> case ...
|
||||
@@ -1853,20 +1862,23 @@ cfg.rb:
|
||||
# 42| one
|
||||
#-----| -> "one"
|
||||
|
||||
# 43| when ...
|
||||
#-----| -> 2
|
||||
# 43| [match] when ...
|
||||
#-----| match -> self
|
||||
|
||||
# 43| [no-match] when ...
|
||||
#-----| no-match -> self
|
||||
|
||||
# 43| 2
|
||||
#-----| match -> [match] when ...
|
||||
#-----| no-match -> 3
|
||||
#-----| match -> self
|
||||
|
||||
# 43| 3
|
||||
#-----| match -> [match] when ...
|
||||
#-----| no-match -> 4
|
||||
#-----| match -> self
|
||||
|
||||
# 43| 4
|
||||
#-----| match -> self
|
||||
#-----| no-match -> self
|
||||
#-----| match -> [match] when ...
|
||||
#-----| no-match -> [no-match] when ...
|
||||
|
||||
# 43| then ...
|
||||
#-----| -> case ...
|
||||
@@ -1901,15 +1913,18 @@ cfg.rb:
|
||||
# 47| case ...
|
||||
#-----| -> chained
|
||||
|
||||
# 48| when ...
|
||||
#-----| -> b
|
||||
# 48| [false] when ...
|
||||
#-----| false -> b
|
||||
|
||||
# 48| [true] when ...
|
||||
#-----| true -> self
|
||||
|
||||
# 48| b
|
||||
#-----| -> 1
|
||||
|
||||
# 48| ... == ...
|
||||
#-----| true -> self
|
||||
#-----| false -> when ...
|
||||
#-----| false -> [false] when ...
|
||||
#-----| true -> [true] when ...
|
||||
|
||||
# 48| 1
|
||||
#-----| -> ... == ...
|
||||
@@ -1929,15 +1944,18 @@ cfg.rb:
|
||||
# 48| one
|
||||
#-----| -> "one"
|
||||
|
||||
# 49| when ...
|
||||
#-----| -> b
|
||||
# 49| [false] when ...
|
||||
#-----| false -> case ...
|
||||
|
||||
# 49| [true] when ...
|
||||
#-----| true -> self
|
||||
|
||||
# 49| b
|
||||
#-----| -> 0
|
||||
|
||||
# 49| ... == ...
|
||||
#-----| true -> [true] when ...
|
||||
#-----| false -> b
|
||||
#-----| true -> self
|
||||
|
||||
# 49| 0
|
||||
#-----| -> ... == ...
|
||||
@@ -1946,8 +1964,8 @@ cfg.rb:
|
||||
#-----| -> 1
|
||||
|
||||
# 49| ... > ...
|
||||
#-----| false -> case ...
|
||||
#-----| true -> self
|
||||
#-----| false -> [false] when ...
|
||||
#-----| true -> [true] when ...
|
||||
|
||||
# 49| 1
|
||||
#-----| -> ... > ...
|
||||
@@ -2559,7 +2577,7 @@ cfg.rb:
|
||||
#-----| -> type
|
||||
|
||||
# 101| value
|
||||
#-----| no-match -> 42
|
||||
#-----| false, no-match, true -> 42
|
||||
#-----| match -> key
|
||||
|
||||
# 101| 42
|
||||
@@ -6535,7 +6553,7 @@ raise.rb:
|
||||
#-----| -> m11
|
||||
|
||||
# 121| p
|
||||
#-----| no-match -> self
|
||||
#-----| false, no-match, true -> self
|
||||
#-----| match -> self
|
||||
|
||||
# 121| call to raise
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
WARNING: Type BarrierGuard has been deprecated and may be removed in future (barrier-guards.ql:9,3-15)
|
||||
WARNING: Type BarrierGuard has been deprecated and may be removed in future (barrier-guards.ql:10,3-15)
|
||||
failures
|
||||
oldStyleBarrierGuards
|
||||
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | barrier-guards.rb:3:4:3:6 | foo | true |
|
||||
| barrier-guards.rb:9:4:9:24 | call to include? | barrier-guards.rb:10:5:10:7 | foo | barrier-guards.rb:9:21:9:23 | foo | true |
|
||||
@@ -9,6 +10,15 @@ oldStyleBarrierGuards
|
||||
| barrier-guards.rb:43:4:43:15 | ... == ... | barrier-guards.rb:45:9:45:11 | foo | barrier-guards.rb:43:4:43:6 | foo | true |
|
||||
| barrier-guards.rb:70:4:70:21 | call to include? | barrier-guards.rb:71:5:71:7 | foo | barrier-guards.rb:70:18:70:20 | foo | true |
|
||||
| barrier-guards.rb:82:4:82:25 | ... != ... | barrier-guards.rb:83:5:83:7 | foo | barrier-guards.rb:82:15:82:17 | foo | true |
|
||||
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:208:5:208:7 | foo | barrier-guards.rb:207:4:207:6 | foo | true |
|
||||
| barrier-guards.rb:211:10:211:21 | ... == ... | barrier-guards.rb:212:5:212:7 | foo | barrier-guards.rb:211:10:211:12 | foo | true |
|
||||
| barrier-guards.rb:215:16:215:27 | ... == ... | barrier-guards.rb:216:5:216:7 | foo | barrier-guards.rb:215:16:215:18 | foo | true |
|
||||
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:219:21:219:23 | foo | barrier-guards.rb:219:4:219:6 | foo | true |
|
||||
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | barrier-guards.rb:219:4:219:6 | foo | true |
|
||||
| barrier-guards.rb:219:21:219:32 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | barrier-guards.rb:219:21:219:23 | foo | true |
|
||||
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:233:5:233:7 | foo | barrier-guards.rb:232:6:232:8 | foo | true |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:24:237:26 | foo | barrier-guards.rb:237:6:237:8 | foo | true |
|
||||
| barrier-guards.rb:268:1:268:12 | ... == ... | barrier-guards.rb:268:17:268:19 | foo | barrier-guards.rb:268:1:268:3 | foo | true |
|
||||
newStyleBarrierGuards
|
||||
| barrier-guards.rb:4:5:4:7 | foo |
|
||||
| barrier-guards.rb:10:5:10:7 | foo |
|
||||
@@ -20,6 +30,26 @@ newStyleBarrierGuards
|
||||
| barrier-guards.rb:71:5:71:7 | foo |
|
||||
| barrier-guards.rb:83:5:83:7 | foo |
|
||||
| barrier-guards.rb:91:5:91:7 | foo |
|
||||
| barrier-guards.rb:126:5:126:7 | foo |
|
||||
| barrier-guards.rb:133:5:133:7 | foo |
|
||||
| barrier-guards.rb:135:5:135:7 | foo |
|
||||
| barrier-guards.rb:140:5:140:7 | foo |
|
||||
| barrier-guards.rb:142:5:142:7 | foo |
|
||||
| barrier-guards.rb:149:5:149:7 | foo |
|
||||
| barrier-guards.rb:154:5:154:7 | foo |
|
||||
| barrier-guards.rb:159:5:159:7 | foo |
|
||||
| barrier-guards.rb:164:5:164:7 | foo |
|
||||
| barrier-guards.rb:192:5:192:7 | foo |
|
||||
| barrier-guards.rb:196:5:196:7 | foo |
|
||||
| barrier-guards.rb:208:5:208:7 | foo |
|
||||
| barrier-guards.rb:212:5:212:7 | foo |
|
||||
| barrier-guards.rb:216:5:216:7 | foo |
|
||||
| barrier-guards.rb:219:21:219:23 | foo |
|
||||
| barrier-guards.rb:220:5:220:7 | foo |
|
||||
| barrier-guards.rb:233:5:233:7 | foo |
|
||||
| barrier-guards.rb:237:24:237:26 | foo |
|
||||
| barrier-guards.rb:244:5:244:7 | foo |
|
||||
| barrier-guards.rb:268:17:268:19 | foo |
|
||||
controls
|
||||
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | true |
|
||||
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:6:5:6:7 | foo | false |
|
||||
@@ -67,3 +97,228 @@ controls
|
||||
| barrier-guards.rb:118:8:118:8 | call to x | barrier-guards.rb:118:4:118:8 | [true] not ... | false |
|
||||
| barrier-guards.rb:118:8:118:8 | call to x | barrier-guards.rb:119:5:119:7 | foo | false |
|
||||
| barrier-guards.rb:118:8:118:8 | call to x | barrier-guards.rb:121:5:121:8 | bars | true |
|
||||
| barrier-guards.rb:125:1:126:19 | [match] when ... | barrier-guards.rb:126:5:126:7 | foo | match |
|
||||
| barrier-guards.rb:125:1:126:19 | [no-match] when ... | barrier-guards.rb:128:5:128:7 | foo | no-match |
|
||||
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:125:1:126:19 | [match] when ... | match |
|
||||
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:125:1:126:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:126:5:126:7 | foo | match |
|
||||
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:128:5:128:7 | foo | no-match |
|
||||
| barrier-guards.rb:132:1:133:19 | [match] when ... | barrier-guards.rb:133:5:133:7 | foo | match |
|
||||
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:134:1:135:19 | [match] when ... | no-match |
|
||||
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:134:1:135:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:134:7:134:9 | bar | no-match |
|
||||
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:135:5:135:7 | foo | no-match |
|
||||
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:132:1:133:19 | [match] when ... | match |
|
||||
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:132:1:133:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:133:5:133:7 | foo | match |
|
||||
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:134:1:135:19 | [match] when ... | no-match |
|
||||
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:134:1:135:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:134:7:134:9 | bar | no-match |
|
||||
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:135:5:135:7 | foo | no-match |
|
||||
| barrier-guards.rb:134:1:135:19 | [match] when ... | barrier-guards.rb:135:5:135:7 | foo | match |
|
||||
| barrier-guards.rb:134:6:134:10 | "bar" | barrier-guards.rb:134:1:135:19 | [match] when ... | match |
|
||||
| barrier-guards.rb:134:6:134:10 | "bar" | barrier-guards.rb:134:1:135:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:134:6:134:10 | "bar" | barrier-guards.rb:135:5:135:7 | foo | match |
|
||||
| barrier-guards.rb:139:1:140:19 | [match] when ... | barrier-guards.rb:140:5:140:7 | foo | match |
|
||||
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:1:142:19 | [match] when ... | no-match |
|
||||
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:7:141:9 | baz | no-match |
|
||||
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:14:141:17 | quux | no-match |
|
||||
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:142:5:142:7 | foo | no-match |
|
||||
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:144:5:144:7 | foo | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:139:1:140:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:139:14:139:16 | bar | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:1:142:19 | [match] when ... | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:7:141:9 | baz | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:14:141:17 | quux | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:142:5:142:7 | foo | no-match |
|
||||
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:144:5:144:7 | foo | no-match |
|
||||
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:139:1:140:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:1:142:19 | [match] when ... | no-match |
|
||||
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:7:141:9 | baz | no-match |
|
||||
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:14:141:17 | quux | no-match |
|
||||
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:142:5:142:7 | foo | no-match |
|
||||
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:144:5:144:7 | foo | no-match |
|
||||
| barrier-guards.rb:141:1:142:19 | [match] when ... | barrier-guards.rb:142:5:142:7 | foo | match |
|
||||
| barrier-guards.rb:141:1:142:19 | [no-match] when ... | barrier-guards.rb:144:5:144:7 | foo | no-match |
|
||||
| barrier-guards.rb:141:6:141:10 | "baz" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:141:6:141:10 | "baz" | barrier-guards.rb:141:14:141:17 | quux | no-match |
|
||||
| barrier-guards.rb:141:6:141:10 | "baz" | barrier-guards.rb:144:5:144:7 | foo | no-match |
|
||||
| barrier-guards.rb:141:13:141:18 | "quux" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:141:13:141:18 | "quux" | barrier-guards.rb:144:5:144:7 | foo | no-match |
|
||||
| barrier-guards.rb:148:1:149:19 | [match] when ... | barrier-guards.rb:149:5:149:7 | foo | match |
|
||||
| barrier-guards.rb:148:6:148:20 | * ... | barrier-guards.rb:148:1:149:19 | [match] when ... | match |
|
||||
| barrier-guards.rb:148:6:148:20 | * ... | barrier-guards.rb:148:1:149:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:148:6:148:20 | * ... | barrier-guards.rb:149:5:149:7 | foo | match |
|
||||
| barrier-guards.rb:153:1:154:19 | [match] when ... | barrier-guards.rb:154:5:154:7 | foo | match |
|
||||
| barrier-guards.rb:153:6:153:17 | * ... | barrier-guards.rb:153:1:154:19 | [match] when ... | match |
|
||||
| barrier-guards.rb:153:6:153:17 | * ... | barrier-guards.rb:153:1:154:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:153:6:153:17 | * ... | barrier-guards.rb:154:5:154:7 | foo | match |
|
||||
| barrier-guards.rb:158:1:159:19 | [match] when ... | barrier-guards.rb:159:5:159:7 | foo | match |
|
||||
| barrier-guards.rb:158:6:158:9 | * ... | barrier-guards.rb:158:1:159:19 | [match] when ... | match |
|
||||
| barrier-guards.rb:158:6:158:9 | * ... | barrier-guards.rb:158:1:159:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:158:6:158:9 | * ... | barrier-guards.rb:159:5:159:7 | foo | match |
|
||||
| barrier-guards.rb:163:1:164:19 | [match] when ... | barrier-guards.rb:164:5:164:7 | foo | match |
|
||||
| barrier-guards.rb:163:6:163:10 | * ... | barrier-guards.rb:163:1:164:19 | [match] when ... | match |
|
||||
| barrier-guards.rb:163:6:163:10 | * ... | barrier-guards.rb:163:1:164:19 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:163:6:163:10 | * ... | barrier-guards.rb:164:5:164:7 | foo | match |
|
||||
| barrier-guards.rb:168:1:169:7 | [match] when ... | barrier-guards.rb:169:5:169:7 | foo | match |
|
||||
| barrier-guards.rb:168:6:168:16 | * ... | barrier-guards.rb:168:1:169:7 | [match] when ... | match |
|
||||
| barrier-guards.rb:168:6:168:16 | * ... | barrier-guards.rb:168:1:169:7 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:168:6:168:16 | * ... | barrier-guards.rb:169:5:169:7 | foo | match |
|
||||
| barrier-guards.rb:173:1:174:7 | [match] when ... | barrier-guards.rb:174:5:174:7 | foo | match |
|
||||
| barrier-guards.rb:173:6:173:10 | "foo" | barrier-guards.rb:173:1:174:7 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:173:6:173:10 | "foo" | barrier-guards.rb:173:13:173:13 | self | no-match |
|
||||
| barrier-guards.rb:173:13:173:13 | call to x | barrier-guards.rb:173:1:174:7 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:180:1:181:7 | [match] when ... | barrier-guards.rb:181:5:181:7 | foo | match |
|
||||
| barrier-guards.rb:180:6:180:15 | * ... | barrier-guards.rb:180:1:181:7 | [match] when ... | match |
|
||||
| barrier-guards.rb:180:6:180:15 | * ... | barrier-guards.rb:180:1:181:7 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:180:6:180:15 | * ... | barrier-guards.rb:181:5:181:7 | foo | match |
|
||||
| barrier-guards.rb:187:1:188:7 | [match] when ... | barrier-guards.rb:188:5:188:7 | foo | match |
|
||||
| barrier-guards.rb:187:6:187:15 | * ... | barrier-guards.rb:187:1:188:7 | [match] when ... | match |
|
||||
| barrier-guards.rb:187:6:187:15 | * ... | barrier-guards.rb:187:1:188:7 | [no-match] when ... | no-match |
|
||||
| barrier-guards.rb:187:6:187:15 | * ... | barrier-guards.rb:188:5:188:7 | foo | match |
|
||||
| barrier-guards.rb:191:4:191:15 | ... == ... | barrier-guards.rb:191:4:191:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:191:4:191:15 | ... == ... | barrier-guards.rb:191:20:191:22 | foo | false |
|
||||
| barrier-guards.rb:191:4:191:31 | [true] ... or ... | barrier-guards.rb:192:5:192:7 | foo | true |
|
||||
| barrier-guards.rb:191:20:191:31 | ... == ... | barrier-guards.rb:191:4:191:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:4:195:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:20:195:22 | foo | false |
|
||||
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:36:195:38 | foo | false |
|
||||
| barrier-guards.rb:195:4:195:31 | [false] ... or ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:195:4:195:31 | [false] ... or ... | barrier-guards.rb:195:36:195:38 | foo | false |
|
||||
| barrier-guards.rb:195:4:195:47 | [true] ... or ... | barrier-guards.rb:196:5:196:7 | foo | true |
|
||||
| barrier-guards.rb:195:20:195:31 | ... == ... | barrier-guards.rb:195:4:195:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:195:20:195:31 | ... == ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:195:20:195:31 | ... == ... | barrier-guards.rb:195:36:195:38 | foo | false |
|
||||
| barrier-guards.rb:195:36:195:47 | ... == ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:4:199:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:20:199:22 | foo | false |
|
||||
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:36:199:38 | foo | false |
|
||||
| barrier-guards.rb:199:4:199:31 | [false] ... or ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:199:4:199:31 | [false] ... or ... | barrier-guards.rb:199:36:199:38 | foo | false |
|
||||
| barrier-guards.rb:199:4:199:43 | [true] ... or ... | barrier-guards.rb:200:5:200:7 | foo | true |
|
||||
| barrier-guards.rb:199:20:199:31 | ... == ... | barrier-guards.rb:199:4:199:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:199:20:199:31 | ... == ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:199:20:199:31 | ... == ... | barrier-guards.rb:199:36:199:38 | foo | false |
|
||||
| barrier-guards.rb:199:36:199:43 | ... == ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:4:203:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:20:203:22 | self | false |
|
||||
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:36:203:38 | foo | false |
|
||||
| barrier-guards.rb:203:4:203:31 | [false] ... or ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:203:4:203:31 | [false] ... or ... | barrier-guards.rb:203:36:203:38 | foo | false |
|
||||
| barrier-guards.rb:203:4:203:47 | [true] ... or ... | barrier-guards.rb:204:5:204:7 | foo | true |
|
||||
| barrier-guards.rb:203:20:203:31 | ... == ... | barrier-guards.rb:203:4:203:31 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:203:20:203:31 | ... == ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:203:20:203:31 | ... == ... | barrier-guards.rb:203:36:203:38 | foo | false |
|
||||
| barrier-guards.rb:203:36:203:47 | ... == ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
|
||||
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:207:4:207:21 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:207:21:207:21 | self | true |
|
||||
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:208:5:208:7 | foo | true |
|
||||
| barrier-guards.rb:207:4:207:21 | [true] ... and ... | barrier-guards.rb:208:5:208:7 | foo | true |
|
||||
| barrier-guards.rb:207:21:207:21 | call to x | barrier-guards.rb:207:4:207:21 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:207:21:207:21 | call to x | barrier-guards.rb:208:5:208:7 | foo | true |
|
||||
| barrier-guards.rb:211:4:211:4 | call to x | barrier-guards.rb:211:4:211:21 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:211:4:211:4 | call to x | barrier-guards.rb:211:10:211:12 | foo | true |
|
||||
| barrier-guards.rb:211:4:211:4 | call to x | barrier-guards.rb:212:5:212:7 | foo | true |
|
||||
| barrier-guards.rb:211:4:211:21 | [true] ... and ... | barrier-guards.rb:212:5:212:7 | foo | true |
|
||||
| barrier-guards.rb:211:10:211:21 | ... == ... | barrier-guards.rb:211:4:211:21 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:211:10:211:21 | ... == ... | barrier-guards.rb:212:5:212:7 | foo | true |
|
||||
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:4:215:10 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:10:215:10 | self | true |
|
||||
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:16:215:18 | foo | true |
|
||||
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:216:5:216:7 | foo | true |
|
||||
| barrier-guards.rb:215:4:215:10 | [true] ... and ... | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:215:4:215:10 | [true] ... and ... | barrier-guards.rb:215:16:215:18 | foo | true |
|
||||
| barrier-guards.rb:215:4:215:10 | [true] ... and ... | barrier-guards.rb:216:5:216:7 | foo | true |
|
||||
| barrier-guards.rb:215:4:215:27 | [true] ... and ... | barrier-guards.rb:216:5:216:7 | foo | true |
|
||||
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:215:4:215:10 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:215:16:215:18 | foo | true |
|
||||
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:216:5:216:7 | foo | true |
|
||||
| barrier-guards.rb:215:16:215:27 | ... == ... | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:215:16:215:27 | ... == ... | barrier-guards.rb:216:5:216:7 | foo | true |
|
||||
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:219:4:219:32 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:219:21:219:23 | foo | true |
|
||||
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | true |
|
||||
| barrier-guards.rb:219:4:219:32 | [true] ... and ... | barrier-guards.rb:220:5:220:7 | foo | true |
|
||||
| barrier-guards.rb:219:21:219:32 | ... == ... | barrier-guards.rb:219:4:219:32 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:219:21:219:32 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | true |
|
||||
| barrier-guards.rb:223:4:223:4 | call to x | barrier-guards.rb:223:4:223:10 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:223:4:223:4 | call to x | barrier-guards.rb:223:10:223:10 | self | true |
|
||||
| barrier-guards.rb:223:4:223:4 | call to x | barrier-guards.rb:224:5:224:7 | foo | true |
|
||||
| barrier-guards.rb:223:4:223:10 | [true] ... and ... | barrier-guards.rb:224:5:224:7 | foo | true |
|
||||
| barrier-guards.rb:223:10:223:10 | call to y | barrier-guards.rb:223:4:223:10 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:223:10:223:10 | call to y | barrier-guards.rb:224:5:224:7 | foo | true |
|
||||
| barrier-guards.rb:227:4:227:15 | ... == ... | barrier-guards.rb:227:4:227:21 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:227:4:227:15 | ... == ... | barrier-guards.rb:227:21:227:21 | self | true |
|
||||
| barrier-guards.rb:227:4:227:15 | ... == ... | barrier-guards.rb:228:5:228:7 | self | true |
|
||||
| barrier-guards.rb:227:4:227:21 | [true] ... and ... | barrier-guards.rb:228:5:228:7 | self | true |
|
||||
| barrier-guards.rb:227:21:227:21 | call to y | barrier-guards.rb:227:4:227:21 | [true] ... and ... | true |
|
||||
| barrier-guards.rb:227:21:227:21 | call to y | barrier-guards.rb:228:5:228:7 | self | true |
|
||||
| barrier-guards.rb:232:1:233:19 | [true] when ... | barrier-guards.rb:233:5:233:7 | foo | true |
|
||||
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:232:1:233:19 | [false] when ... | false |
|
||||
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:232:1:233:19 | [true] when ... | true |
|
||||
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:233:5:233:7 | foo | true |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:1:238:26 | [false] when ... | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:1:238:26 | [true] when ... | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:6:238:8 | self | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:24:238:26 | foo | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:6:239:8 | foo | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:20:239:22 | foo | false |
|
||||
| barrier-guards.rb:237:1:237:38 | [true] when ... | barrier-guards.rb:237:24:237:26 | foo | true |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:1:237:38 | [false] when ... | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:1:237:38 | [true] when ... | true |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:24:237:26 | foo | true |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [false] when ... | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [true] when ... | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:6:238:8 | self | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:24:238:26 | foo | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:6:239:8 | foo | false |
|
||||
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:20:239:22 | foo | false |
|
||||
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
|
||||
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
|
||||
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:6:239:8 | foo | false |
|
||||
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:20:239:22 | foo | false |
|
||||
| barrier-guards.rb:238:1:238:26 | [true] when ... | barrier-guards.rb:238:24:238:26 | foo | true |
|
||||
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [false] when ... | false |
|
||||
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [true] when ... | true |
|
||||
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:238:24:238:26 | foo | true |
|
||||
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
|
||||
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
|
||||
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:6:239:8 | foo | false |
|
||||
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:20:239:22 | foo | false |
|
||||
| barrier-guards.rb:239:1:239:22 | [true] when ... | barrier-guards.rb:239:20:239:22 | foo | true |
|
||||
| barrier-guards.rb:239:6:239:13 | ... == ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
|
||||
| barrier-guards.rb:239:6:239:13 | ... == ... | barrier-guards.rb:239:1:239:22 | [true] when ... | true |
|
||||
| barrier-guards.rb:239:6:239:13 | ... == ... | barrier-guards.rb:239:20:239:22 | foo | true |
|
||||
| barrier-guards.rb:243:4:243:8 | "foo" | barrier-guards.rb:244:5:244:7 | foo | match |
|
||||
| barrier-guards.rb:243:4:243:8 | "foo" | barrier-guards.rb:245:1:246:7 | in ... then ... | no-match |
|
||||
| barrier-guards.rb:243:4:243:8 | "foo" | barrier-guards.rb:246:5:246:7 | foo | no-match |
|
||||
| barrier-guards.rb:245:4:245:4 | x | barrier-guards.rb:246:5:246:7 | foo | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:251:5:251:7 | foo | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:254:1:256:3 | if ... | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:255:5:255:7 | foo | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:259:1:261:3 | if ... | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:260:5:260:7 | foo | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:264:1:266:3 | if ... | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:265:5:265:7 | foo | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:268:1:268:19 | ... && ... | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:268:17:268:19 | foo | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:269:1:269:19 | ... && ... | match |
|
||||
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:269:8:269:10 | foo | match |
|
||||
| barrier-guards.rb:254:4:254:28 | ... == ... | barrier-guards.rb:255:5:255:7 | foo | true |
|
||||
| barrier-guards.rb:259:4:259:16 | ... == ... | barrier-guards.rb:260:5:260:7 | foo | true |
|
||||
| barrier-guards.rb:264:4:264:16 | ... == ... | barrier-guards.rb:265:5:265:7 | foo | true |
|
||||
| barrier-guards.rb:268:1:268:12 | ... == ... | barrier-guards.rb:268:17:268:19 | foo | true |
|
||||
| barrier-guards.rb:269:1:269:3 | foo | barrier-guards.rb:269:8:269:10 | foo | true |
|
||||
|
||||
@@ -4,6 +4,7 @@ import codeql.ruby.controlflow.CfgNodes
|
||||
import codeql.ruby.controlflow.ControlFlowGraph
|
||||
import codeql.ruby.controlflow.BasicBlocks
|
||||
import codeql.ruby.DataFlow
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
query predicate oldStyleBarrierGuards(
|
||||
BarrierGuard g, DataFlow::Node guardedNode, ExprCfgNode expr, boolean branch
|
||||
@@ -22,3 +23,19 @@ query predicate controls(CfgNode condition, BasicBlock bb, SuccessorTypes::Condi
|
||||
condition = cb.getLastNode()
|
||||
)
|
||||
}
|
||||
|
||||
class BarrierGuardTest extends InlineExpectationsTest {
|
||||
BarrierGuardTest() { this = "BarrierGuardTest" }
|
||||
|
||||
override string getARelevantTag() { result = "guarded" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "guarded" and
|
||||
exists(DataFlow::Node n |
|
||||
newStyleBarrierGuards(n) and
|
||||
location = n.getLocation() and
|
||||
element = n.toString() and
|
||||
value = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
foo = "foo"
|
||||
|
||||
if foo == "foo"
|
||||
foo
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if ["foo"].include?(foo)
|
||||
foo
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
@@ -15,17 +15,17 @@ end
|
||||
if foo != "foo"
|
||||
foo
|
||||
else
|
||||
foo
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
unless foo == "foo"
|
||||
foo
|
||||
else
|
||||
foo
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
unless foo != "foo"
|
||||
foo
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
@@ -35,14 +35,14 @@ foo
|
||||
FOO = ["foo"]
|
||||
|
||||
if FOO.include?(foo)
|
||||
foo
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
if foo == "foo"
|
||||
capture {
|
||||
foo # guarded
|
||||
foo # $ guarded
|
||||
}
|
||||
end
|
||||
|
||||
@@ -68,7 +68,7 @@ foos = ["foo"]
|
||||
bars = NotAnArray.new
|
||||
|
||||
if foos.include?(foo)
|
||||
foo
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
@@ -80,7 +80,7 @@ else
|
||||
end
|
||||
|
||||
if foos.index(foo) != nil
|
||||
foo
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
@@ -88,7 +88,7 @@ end
|
||||
if foos.index(foo) == nil
|
||||
foo
|
||||
else
|
||||
foo
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
bars = ["bar"]
|
||||
@@ -120,3 +120,150 @@ if not x then
|
||||
else
|
||||
bars
|
||||
end
|
||||
|
||||
case foo
|
||||
when "foo"
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
case foo
|
||||
when "foo"
|
||||
foo # $ guarded
|
||||
when "bar"
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
case foo
|
||||
when "foo", "bar"
|
||||
foo # $ guarded
|
||||
when "baz", "quux"
|
||||
foo # $ guarded
|
||||
else
|
||||
foo
|
||||
end
|
||||
|
||||
case foo
|
||||
when *["foo", "bar"]
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
case foo
|
||||
when *%w[foo bar]
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
case foo
|
||||
when *FOO
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
case foo
|
||||
when *foos
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
case foo
|
||||
when *["foo", x] # not a guard - includes non-constant element `x`
|
||||
foo
|
||||
end
|
||||
|
||||
case foo
|
||||
when "foo", x # not a guard - includes non-constant element `x`
|
||||
foo
|
||||
end
|
||||
|
||||
foo_and_x = ["foo", x]
|
||||
|
||||
case foo
|
||||
when *foo_and_x # not a guard - includes non-constant element `x`
|
||||
foo
|
||||
end
|
||||
|
||||
FOO_AND_X = ["foo", x]
|
||||
|
||||
case foo
|
||||
when *FOO_AND_X # not a guard - includes non-constant element `x`
|
||||
foo
|
||||
end
|
||||
|
||||
if foo == "foo" or foo == "bar"
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
if foo == "foo" or foo == "bar" or foo == "baz"
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
if foo == "foo" or foo == "bar" or foo == x
|
||||
foo
|
||||
end
|
||||
|
||||
if foo == "foo" or bar == "bar" or foo == "baz"
|
||||
foo
|
||||
end
|
||||
|
||||
if foo == "foo" and x
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
if x and foo == "foo"
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
if x and y and foo == "foo"
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
if foo == "foo" and foo == "bar" # $ guarded (second `foo` is guarded by the first comparison)
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
if x and y
|
||||
foo
|
||||
end
|
||||
|
||||
if foo == "foo" and y
|
||||
bar
|
||||
end
|
||||
|
||||
case
|
||||
when foo == "foo"
|
||||
foo # $ guarded
|
||||
end
|
||||
|
||||
case
|
||||
when foo == "foo" then foo # $ guarded
|
||||
when bar == "bar" then foo
|
||||
when foo == x then foo
|
||||
end
|
||||
|
||||
case foo
|
||||
in "foo"
|
||||
foo # $ guarded
|
||||
in x
|
||||
foo
|
||||
end
|
||||
|
||||
case bar
|
||||
in "foo"
|
||||
foo
|
||||
end
|
||||
|
||||
if foo == "#{some_method()}"
|
||||
foo
|
||||
end
|
||||
|
||||
F = "foo"
|
||||
if foo == "#{F}"
|
||||
foo # $ MISSING: guarded
|
||||
end
|
||||
|
||||
f = "foo"
|
||||
if foo == "#{f}"
|
||||
foo # $ MISSING: guarded
|
||||
end
|
||||
|
||||
foo == "foo" && foo # $ guarded
|
||||
foo && foo == "foo"
|
||||
@@ -1,6 +1,6 @@
|
||||
| local_dataflow.rb:1:1:7:3 | self (foo) | local_dataflow.rb:3:8:3:10 | self |
|
||||
| local_dataflow.rb:1:1:7:3 | self in foo | local_dataflow.rb:1:1:7:3 | self (foo) |
|
||||
| local_dataflow.rb:1:1:124:4 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
|
||||
| local_dataflow.rb:1:1:150:3 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
|
||||
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:1:9:1:9 | a |
|
||||
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:2:7:2:7 | a |
|
||||
| local_dataflow.rb:2:3:2:7 | ... = ... | local_dataflow.rb:3:13:3:13 | b |
|
||||
@@ -279,3 +279,110 @@
|
||||
| local_dataflow.rb:123:8:123:20 | call to dup | local_dataflow.rb:123:8:123:45 | call to tap |
|
||||
| local_dataflow.rb:123:8:123:45 | call to tap | local_dataflow.rb:123:8:123:49 | call to dup |
|
||||
| local_dataflow.rb:123:26:123:45 | <captured> self | local_dataflow.rb:123:32:123:43 | self |
|
||||
| local_dataflow.rb:126:1:128:3 | self (use) | local_dataflow.rb:127:3:127:8 | self |
|
||||
| local_dataflow.rb:126:1:128:3 | self in use | local_dataflow.rb:126:1:128:3 | self (use) |
|
||||
| local_dataflow.rb:130:1:150:3 | self (use_use_madness) | local_dataflow.rb:132:6:132:11 | self |
|
||||
| local_dataflow.rb:130:1:150:3 | self in use_use_madness | local_dataflow.rb:130:1:150:3 | self (use_use_madness) |
|
||||
| local_dataflow.rb:131:3:131:8 | ... = ... | local_dataflow.rb:132:10:132:10 | x |
|
||||
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
|
||||
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
|
||||
| local_dataflow.rb:132:6:132:11 | [post] self | local_dataflow.rb:133:8:133:13 | self |
|
||||
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:133:8:133:13 | self |
|
||||
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:133:12:133:12 | x |
|
||||
| local_dataflow.rb:132:12:148:10 | then ... | local_dataflow.rb:132:3:149:5 | if ... |
|
||||
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:133:18:133:23 | self |
|
||||
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:18:133:23 | self |
|
||||
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:22:133:22 | x |
|
||||
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:134:11:134:11 | x |
|
||||
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:136:7:136:12 | self |
|
||||
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:136:7:136:12 | self |
|
||||
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:134:11:134:11 | x |
|
||||
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:136:11:136:11 | x |
|
||||
| local_dataflow.rb:133:24:134:12 | then ... | local_dataflow.rb:133:5:139:7 | if ... |
|
||||
| local_dataflow.rb:134:7:134:12 | [post] self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:134:7:134:12 | call to use | local_dataflow.rb:133:24:134:12 | then ... |
|
||||
| local_dataflow.rb:134:7:134:12 | self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:134:11:134:11 | x | local_dataflow.rb:141:13:141:13 | x |
|
||||
| local_dataflow.rb:135:5:138:9 | else ... | local_dataflow.rb:133:5:139:7 | if ... |
|
||||
| local_dataflow.rb:136:7:136:12 | [post] self | local_dataflow.rb:137:10:137:15 | self |
|
||||
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:137:10:137:15 | self |
|
||||
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:137:14:137:14 | x |
|
||||
| local_dataflow.rb:137:7:138:9 | if ... | local_dataflow.rb:135:5:138:9 | else ... |
|
||||
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:137:21:137:26 | self |
|
||||
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
|
||||
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
|
||||
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:21:137:26 | self |
|
||||
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:25:137:25 | x |
|
||||
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:141:13:141:13 | x |
|
||||
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
|
||||
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
|
||||
| local_dataflow.rb:137:21:137:26 | [post] self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:21:137:26 | self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:25:137:25 | x | local_dataflow.rb:141:13:141:13 | x |
|
||||
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:141:8:141:14 | [true] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:141:20:141:25 | self |
|
||||
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:20:141:25 | self |
|
||||
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:24:141:24 | x |
|
||||
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:141:31:141:36 | self |
|
||||
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
|
||||
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
|
||||
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:141:31:141:36 | self |
|
||||
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:20:141:36 | [false] ... && ... | local_dataflow.rb:141:19:141:37 | [false] ( ... ) |
|
||||
| local_dataflow.rb:141:20:141:36 | [true] ... && ... | local_dataflow.rb:141:19:141:37 | [true] ( ... ) |
|
||||
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:35:141:35 | x |
|
||||
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:143:15:143:15 | x |
|
||||
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
|
||||
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
|
||||
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:143:15:143:15 | x |
|
||||
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:141:38:142:9 | then ... | local_dataflow.rb:141:5:145:7 | if ... |
|
||||
| local_dataflow.rb:142:7:142:9 | nil | local_dataflow.rb:141:38:142:9 | then ... |
|
||||
| local_dataflow.rb:143:5:144:16 | elsif ... | local_dataflow.rb:141:5:145:7 | if ... |
|
||||
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:143:21:143:26 | self |
|
||||
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:21:143:26 | self |
|
||||
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:25:143:25 | x |
|
||||
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:144:15:144:15 | x |
|
||||
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:144:15:144:15 | x |
|
||||
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:143:27:144:16 | then ... | local_dataflow.rb:143:5:144:16 | elsif ... |
|
||||
| local_dataflow.rb:144:11:144:16 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:144:11:144:16 | call to use | local_dataflow.rb:143:27:144:16 | then ... |
|
||||
| local_dataflow.rb:144:11:144:16 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:144:15:144:15 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:147:5:147:10 | [post] self | local_dataflow.rb:148:5:148:10 | self |
|
||||
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:148:5:148:10 | self |
|
||||
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:148:9:148:9 | x |
|
||||
| local_dataflow.rb:148:5:148:10 | call to use | local_dataflow.rb:132:12:148:10 | then ... |
|
||||
|
||||
@@ -19,6 +19,8 @@ ret
|
||||
| local_dataflow.rb:119:3:119:31 | call to sink |
|
||||
| local_dataflow.rb:123:3:123:50 | call to sink |
|
||||
| local_dataflow.rb:123:32:123:43 | call to puts |
|
||||
| local_dataflow.rb:127:3:127:8 | call to rand |
|
||||
| local_dataflow.rb:132:3:149:5 | if ... |
|
||||
arg
|
||||
| local_dataflow.rb:3:8:3:10 | self | local_dataflow.rb:3:8:3:10 | call to p | self |
|
||||
| local_dataflow.rb:3:10:3:10 | a | local_dataflow.rb:3:8:3:10 | call to p | position 0 |
|
||||
@@ -170,3 +172,61 @@ arg
|
||||
| local_dataflow.rb:123:26:123:45 | { ... } | local_dataflow.rb:123:8:123:45 | call to tap | block |
|
||||
| local_dataflow.rb:123:32:123:43 | self | local_dataflow.rb:123:32:123:43 | call to puts | self |
|
||||
| local_dataflow.rb:123:37:123:43 | "hello" | local_dataflow.rb:123:32:123:43 | call to puts | position 0 |
|
||||
| local_dataflow.rb:127:3:127:8 | self | local_dataflow.rb:127:3:127:8 | call to rand | self |
|
||||
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:132:6:132:11 | call to use | self |
|
||||
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:132:6:132:11 | call to use | position 0 |
|
||||
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... | self |
|
||||
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... | self |
|
||||
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:8:133:13 | call to use | self |
|
||||
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:8:133:13 | call to use | position 0 |
|
||||
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... | position 0 |
|
||||
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... | position 0 |
|
||||
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:133:18:133:23 | call to use | self |
|
||||
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:133:18:133:23 | call to use | position 0 |
|
||||
| local_dataflow.rb:134:7:134:12 | self | local_dataflow.rb:134:7:134:12 | call to use | self |
|
||||
| local_dataflow.rb:134:11:134:11 | x | local_dataflow.rb:134:7:134:12 | call to use | position 0 |
|
||||
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:136:7:136:12 | call to use | self |
|
||||
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:136:7:136:12 | call to use | position 0 |
|
||||
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [false] ... && ... | self |
|
||||
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [true] ... && ... | self |
|
||||
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:10:137:15 | call to use | self |
|
||||
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:10:137:15 | call to use | position 0 |
|
||||
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... | position 0 |
|
||||
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... | position 0 |
|
||||
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [false] ! ... | self |
|
||||
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [true] ! ... | self |
|
||||
| local_dataflow.rb:137:21:137:26 | self | local_dataflow.rb:137:21:137:26 | call to use | self |
|
||||
| local_dataflow.rb:137:25:137:25 | x | local_dataflow.rb:137:21:137:26 | call to use | position 0 |
|
||||
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... | self |
|
||||
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... | self |
|
||||
| local_dataflow.rb:141:8:141:14 | [true] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... | self |
|
||||
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [false] ! ... | self |
|
||||
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [true] ! ... | self |
|
||||
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:9:141:14 | call to use | self |
|
||||
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:9:141:14 | call to use | position 0 |
|
||||
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... | position 0 |
|
||||
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... | position 0 |
|
||||
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [false] ... && ... | self |
|
||||
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [true] ... && ... | self |
|
||||
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:141:20:141:25 | call to use | self |
|
||||
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:20:141:25 | call to use | position 0 |
|
||||
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... | position 0 |
|
||||
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... | position 0 |
|
||||
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [false] ! ... | self |
|
||||
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [true] ! ... | self |
|
||||
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:141:31:141:36 | call to use | self |
|
||||
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:141:31:141:36 | call to use | position 0 |
|
||||
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... | self |
|
||||
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... | self |
|
||||
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:11:143:16 | call to use | self |
|
||||
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:11:143:16 | call to use | position 0 |
|
||||
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... | position 0 |
|
||||
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... | position 0 |
|
||||
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:143:21:143:26 | call to use | self |
|
||||
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:143:21:143:26 | call to use | position 0 |
|
||||
| local_dataflow.rb:144:11:144:16 | self | local_dataflow.rb:144:11:144:16 | call to use | self |
|
||||
| local_dataflow.rb:144:15:144:15 | x | local_dataflow.rb:144:11:144:16 | call to use | position 0 |
|
||||
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:147:5:147:10 | call to use | self |
|
||||
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:147:5:147:10 | call to use | position 0 |
|
||||
| local_dataflow.rb:148:5:148:10 | self | local_dataflow.rb:148:5:148:10 | call to use | self |
|
||||
| local_dataflow.rb:148:9:148:9 | x | local_dataflow.rb:148:5:148:10 | call to use | position 0 |
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
| file://:0:0:0:0 | [summary] read: argument position 0.any element in Hash[] | file://:0:0:0:0 | [summary] read: argument position 0.any element.element 1 or unknown in Hash[] |
|
||||
| file://:0:0:0:0 | parameter any of ;Pathname;Method[join] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[join] |
|
||||
| file://:0:0:0:0 | parameter position 0 of & | file://:0:0:0:0 | [summary] read: argument position 0.any element in & |
|
||||
| file://:0:0:0:0 | parameter position 0 of + | file://:0:0:0:0 | [summary] read: argument position 0.any element in + |
|
||||
| file://:0:0:0:0 | parameter position 0 of ;;Member[Pathname].Method[new] | file://:0:0:0:0 | [summary] to write: return (return) in ;;Member[Pathname].Method[new] |
|
||||
| file://:0:0:0:0 | parameter position 0 of ;;Member[Regexp].Method[escape,quote] | file://:0:0:0:0 | [summary] to write: return (return) in ;;Member[Regexp].Method[escape,quote] |
|
||||
| file://:0:0:0:0 | parameter position 0 of ActionController::Parameters#merge | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#merge |
|
||||
| file://:0:0:0:0 | parameter position 0 of ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: argument self in ActionController::Parameters#merge! |
|
||||
| file://:0:0:0:0 | parameter position 0 of ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#merge! |
|
||||
@@ -17,27 +14,10 @@
|
||||
| file://:0:0:0:0 | parameter position 0 of Hash[] | file://:0:0:0:0 | [summary] read: argument position 0.any element in Hash[] |
|
||||
| file://:0:0:0:0 | parameter position 0 of String.try_convert | file://:0:0:0:0 | [summary] to write: return (return) in String.try_convert |
|
||||
| file://:0:0:0:0 | parameter position 0 of \| | file://:0:0:0:0 | [summary] read: argument position 0.any element in \| |
|
||||
| file://:0:0:0:0 | parameter position 0 of activestorage;;Member[ActiveStorage].Member[Filename].Method[new] | file://:0:0:0:0 | [summary] to write: return (return) in activestorage;;Member[ActiveStorage].Member[Filename].Method[new] |
|
||||
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] | file://:0:0:0:0 | [summary] to write: argument self in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] |
|
||||
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] |
|
||||
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Method[new] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Method[new] |
|
||||
| file://:0:0:0:0 | parameter position 0.. of File.join | file://:0:0:0:0 | [summary] to write: return (return) in File.join |
|
||||
| file://:0:0:0:0 | parameter self of & | file://:0:0:0:0 | [summary] read: argument self.any element in & |
|
||||
| file://:0:0:0:0 | parameter self of * | file://:0:0:0:0 | [summary] read: argument self.any element in * |
|
||||
| file://:0:0:0:0 | parameter self of - | file://:0:0:0:0 | [summary] read: argument self.any element in - |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[basename] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[basename] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[cleanpath] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[cleanpath] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[dirname] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[dirname] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[each_filename] | file://:0:0:0:0 | [summary] to write: argument block.parameter position 0 in ;Pathname;Method[each_filename] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[existence] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[existence] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[expand_path] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[expand_path] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[join] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[join] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[parent] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[parent] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[realpath] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[realpath] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[relative_path_from] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[relative_path_from] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[sub] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[sub] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[sub_ext] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[sub_ext] |
|
||||
| file://:0:0:0:0 | parameter self of ;Pathname;Method[to_path] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[to_path] |
|
||||
| file://:0:0:0:0 | parameter self of ActionController::Parameters#<various> | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#<various> |
|
||||
| file://:0:0:0:0 | parameter self of ActionController::Parameters#merge | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#merge |
|
||||
| file://:0:0:0:0 | parameter self of ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: argument self in ActionController::Parameters#merge! |
|
||||
@@ -45,12 +25,10 @@
|
||||
| file://:0:0:0:0 | parameter self of ActiveSupportStringTransform | file://:0:0:0:0 | [summary] to write: return (return) in ActiveSupportStringTransform |
|
||||
| file://:0:0:0:0 | parameter self of [] | file://:0:0:0:0 | [summary] to write: return (return) in [] |
|
||||
| file://:0:0:0:0 | parameter self of \| | file://:0:0:0:0 | [summary] read: argument self.any element in \| |
|
||||
| file://:0:0:0:0 | parameter self of activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized] | file://:0:0:0:0 | [summary] to write: return (return) in activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized] |
|
||||
| file://:0:0:0:0 | parameter self of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[concat,insert,prepend,to_s,to_param] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[concat,insert,prepend,to_s,to_param] |
|
||||
| file://:0:0:0:0 | parameter self of each(0) | file://:0:0:0:0 | [summary] read: argument self.any element in each(0) |
|
||||
| local_dataflow.rb:1:1:7:3 | self (foo) | local_dataflow.rb:3:8:3:10 | self |
|
||||
| local_dataflow.rb:1:1:7:3 | self in foo | local_dataflow.rb:1:1:7:3 | self (foo) |
|
||||
| local_dataflow.rb:1:1:124:4 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
|
||||
| local_dataflow.rb:1:1:150:3 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
|
||||
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:1:9:1:9 | a |
|
||||
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:2:7:2:7 | a |
|
||||
| local_dataflow.rb:2:3:2:7 | ... = ... | local_dataflow.rb:3:13:3:13 | b |
|
||||
@@ -354,3 +332,116 @@
|
||||
| local_dataflow.rb:123:8:123:20 | call to dup | local_dataflow.rb:123:8:123:45 | call to tap |
|
||||
| local_dataflow.rb:123:8:123:45 | call to tap | local_dataflow.rb:123:8:123:49 | call to dup |
|
||||
| local_dataflow.rb:123:26:123:45 | <captured> self | local_dataflow.rb:123:32:123:43 | self |
|
||||
| local_dataflow.rb:126:1:128:3 | self (use) | local_dataflow.rb:127:3:127:8 | self |
|
||||
| local_dataflow.rb:126:1:128:3 | self in use | local_dataflow.rb:126:1:128:3 | self (use) |
|
||||
| local_dataflow.rb:130:1:150:3 | self (use_use_madness) | local_dataflow.rb:132:6:132:11 | self |
|
||||
| local_dataflow.rb:130:1:150:3 | self in use_use_madness | local_dataflow.rb:130:1:150:3 | self (use_use_madness) |
|
||||
| local_dataflow.rb:131:3:131:8 | ... = ... | local_dataflow.rb:132:10:132:10 | x |
|
||||
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
|
||||
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
|
||||
| local_dataflow.rb:132:6:132:11 | [post] self | local_dataflow.rb:133:8:133:13 | self |
|
||||
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:133:8:133:13 | self |
|
||||
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:133:12:133:12 | x |
|
||||
| local_dataflow.rb:132:12:148:10 | then ... | local_dataflow.rb:132:3:149:5 | if ... |
|
||||
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:133:18:133:23 | self |
|
||||
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:18:133:23 | self |
|
||||
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:22:133:22 | x |
|
||||
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:134:11:134:11 | x |
|
||||
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:136:7:136:12 | self |
|
||||
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:134:7:134:12 | self |
|
||||
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:136:7:136:12 | self |
|
||||
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:134:11:134:11 | x |
|
||||
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:136:11:136:11 | x |
|
||||
| local_dataflow.rb:133:24:134:12 | then ... | local_dataflow.rb:133:5:139:7 | if ... |
|
||||
| local_dataflow.rb:134:7:134:12 | [post] self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:134:7:134:12 | call to use | local_dataflow.rb:133:24:134:12 | then ... |
|
||||
| local_dataflow.rb:134:7:134:12 | self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:134:11:134:11 | x | local_dataflow.rb:141:13:141:13 | x |
|
||||
| local_dataflow.rb:135:5:138:9 | else ... | local_dataflow.rb:133:5:139:7 | if ... |
|
||||
| local_dataflow.rb:136:7:136:12 | [post] self | local_dataflow.rb:137:10:137:15 | self |
|
||||
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:137:10:137:15 | self |
|
||||
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:137:14:137:14 | x |
|
||||
| local_dataflow.rb:137:7:138:9 | if ... | local_dataflow.rb:135:5:138:9 | else ... |
|
||||
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:137:21:137:26 | self |
|
||||
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
|
||||
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
|
||||
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:21:137:26 | self |
|
||||
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:25:137:25 | x |
|
||||
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:141:13:141:13 | x |
|
||||
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
|
||||
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
|
||||
| local_dataflow.rb:137:21:137:26 | [post] self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [false] ! ... |
|
||||
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [true] ! ... |
|
||||
| local_dataflow.rb:137:21:137:26 | self | local_dataflow.rb:141:9:141:14 | self |
|
||||
| local_dataflow.rb:137:25:137:25 | x | local_dataflow.rb:141:13:141:13 | x |
|
||||
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:141:8:141:14 | [true] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:141:20:141:25 | self |
|
||||
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [false] ! ... |
|
||||
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [true] ! ... |
|
||||
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:20:141:25 | self |
|
||||
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:24:141:24 | x |
|
||||
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:141:31:141:36 | self |
|
||||
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
|
||||
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
|
||||
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:141:31:141:36 | self |
|
||||
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:20:141:36 | [false] ... && ... | local_dataflow.rb:141:19:141:37 | [false] ( ... ) |
|
||||
| local_dataflow.rb:141:20:141:36 | [true] ... && ... | local_dataflow.rb:141:19:141:37 | [true] ( ... ) |
|
||||
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:35:141:35 | x |
|
||||
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:143:15:143:15 | x |
|
||||
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
|
||||
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
|
||||
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [false] ! ... |
|
||||
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [true] ! ... |
|
||||
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:143:11:143:16 | self |
|
||||
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:143:15:143:15 | x |
|
||||
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:141:38:142:9 | then ... | local_dataflow.rb:141:5:145:7 | if ... |
|
||||
| local_dataflow.rb:142:7:142:9 | nil | local_dataflow.rb:141:38:142:9 | then ... |
|
||||
| local_dataflow.rb:143:5:144:16 | elsif ... | local_dataflow.rb:141:5:145:7 | if ... |
|
||||
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:143:21:143:26 | self |
|
||||
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:21:143:26 | self |
|
||||
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:25:143:25 | x |
|
||||
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:144:15:144:15 | x |
|
||||
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
|
||||
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
|
||||
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:144:11:144:16 | self |
|
||||
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:144:15:144:15 | x |
|
||||
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:143:27:144:16 | then ... | local_dataflow.rb:143:5:144:16 | elsif ... |
|
||||
| local_dataflow.rb:144:11:144:16 | [post] self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:144:11:144:16 | call to use | local_dataflow.rb:143:27:144:16 | then ... |
|
||||
| local_dataflow.rb:144:11:144:16 | self | local_dataflow.rb:147:5:147:10 | self |
|
||||
| local_dataflow.rb:144:15:144:15 | x | local_dataflow.rb:147:9:147:9 | x |
|
||||
| local_dataflow.rb:147:5:147:10 | [post] self | local_dataflow.rb:148:5:148:10 | self |
|
||||
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:148:5:148:10 | self |
|
||||
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:148:9:148:9 | x |
|
||||
| local_dataflow.rb:148:5:148:10 | call to use | local_dataflow.rb:132:12:148:10 | then ... |
|
||||
|
||||
@@ -122,3 +122,29 @@ end
|
||||
def dup_tap
|
||||
sink(source(1).dup.tap { |x| puts "hello" }.dup) # $ hasValueFlow=1
|
||||
end
|
||||
|
||||
def use x
|
||||
rand()
|
||||
end
|
||||
|
||||
def use_use_madness
|
||||
x = ""
|
||||
if use(x)
|
||||
if use(x) || use(x)
|
||||
use(x)
|
||||
else
|
||||
use(x)
|
||||
if use(x) && !use(x)
|
||||
end
|
||||
end
|
||||
|
||||
if !use(x) || (use(x) && !use(x))
|
||||
nil
|
||||
elsif use(x) || use(x)
|
||||
use(x)
|
||||
end
|
||||
|
||||
use(x)
|
||||
use(x)
|
||||
end
|
||||
end
|
||||
@@ -40,6 +40,11 @@ edges
|
||||
| params_flow.rb:49:13:49:14 | p1 : | params_flow.rb:50:10:50:11 | p1 |
|
||||
| params_flow.rb:54:9:54:17 | call to taint : | params_flow.rb:49:13:49:14 | p1 : |
|
||||
| params_flow.rb:57:9:57:17 | call to taint : | params_flow.rb:49:13:49:14 | p1 : |
|
||||
| params_flow.rb:62:8:62:16 | call to taint : | params_flow.rb:66:13:66:16 | args : |
|
||||
| params_flow.rb:63:16:63:17 | *x [element 0] : | params_flow.rb:64:10:64:10 | x [element 0] : |
|
||||
| params_flow.rb:64:10:64:10 | x [element 0] : | params_flow.rb:64:10:64:13 | ...[...] |
|
||||
| params_flow.rb:66:12:66:16 | * ... [element 0] : | params_flow.rb:63:16:63:17 | *x [element 0] : |
|
||||
| params_flow.rb:66:13:66:16 | args : | params_flow.rb:66:12:66:16 | * ... [element 0] : |
|
||||
nodes
|
||||
| params_flow.rb:9:16:9:17 | p1 : | semmle.label | p1 : |
|
||||
| params_flow.rb:9:20:9:21 | p2 : | semmle.label | p2 : |
|
||||
@@ -89,6 +94,12 @@ nodes
|
||||
| params_flow.rb:50:10:50:11 | p1 | semmle.label | p1 |
|
||||
| params_flow.rb:54:9:54:17 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:57:9:57:17 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:62:8:62:16 | call to taint : | semmle.label | call to taint : |
|
||||
| params_flow.rb:63:16:63:17 | *x [element 0] : | semmle.label | *x [element 0] : |
|
||||
| params_flow.rb:64:10:64:10 | x [element 0] : | semmle.label | x [element 0] : |
|
||||
| params_flow.rb:64:10:64:13 | ...[...] | semmle.label | ...[...] |
|
||||
| params_flow.rb:66:12:66:16 | * ... [element 0] : | semmle.label | * ... [element 0] : |
|
||||
| params_flow.rb:66:13:66:16 | args : | semmle.label | args : |
|
||||
subpaths
|
||||
#select
|
||||
| params_flow.rb:10:10:10:11 | p1 | params_flow.rb:14:12:14:19 | call to taint : | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:14:12:14:19 | call to taint : | call to taint : |
|
||||
@@ -111,3 +122,4 @@ subpaths
|
||||
| params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:34:14:34:22 | call to taint : | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:34:14:34:22 | call to taint : | call to taint : |
|
||||
| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:54:9:54:17 | call to taint : | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:54:9:54:17 | call to taint : | call to taint : |
|
||||
| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:57:9:57:17 | call to taint : | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:57:9:57:17 | call to taint : | call to taint : |
|
||||
| params_flow.rb:64:10:64:13 | ...[...] | params_flow.rb:62:8:62:16 | call to taint : | params_flow.rb:64:10:64:13 | ...[...] | $@ | params_flow.rb:62:8:62:16 | call to taint : | call to taint : |
|
||||
|
||||
@@ -57,4 +57,10 @@ args = [taint(22)]
|
||||
posargs(taint(23), *args)
|
||||
|
||||
args = [taint(24), taint(25)]
|
||||
posargs(*args)
|
||||
posargs(*args)
|
||||
|
||||
args = taint(26)
|
||||
def splatstuff(*x)
|
||||
sink x[0] # $ hasValueFlow=26
|
||||
end
|
||||
splatstuff(*args)
|
||||
@@ -12,6 +12,7 @@ edges
|
||||
| string_flow.rb:10:29:10:29 | b : | string_flow.rb:10:10:10:30 | call to try_convert |
|
||||
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:15:10:15:17 | ... % ... |
|
||||
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:15:17:15:17 | a : |
|
||||
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:16:10:16:29 | ... % ... |
|
||||
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:16:28:16:28 | a : |
|
||||
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:17:10:17:10 | a : |
|
||||
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:17:10:17:18 | ... % ... |
|
||||
|
||||
@@ -534,8 +534,8 @@ invalidSpecComponent
|
||||
| summaries.rb:150:39:150:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:150:39:150:45 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
| summaries.rb:150:39:150:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:150:39:150:45 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
|
||||
warning
|
||||
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
|
||||
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |
|
||||
| CSV type row should have 3 columns but has 1: TooFewColumns |
|
||||
| CSV type row should have 3 columns but has 6: TooManyColumns;;Member[Foo].Instance;too;many;columns |
|
||||
| Invalid argument '0-1' in token 'Argument[0-1]' in access path: Method[foo].Argument[0-1] |
|
||||
| Invalid argument '*' in token 'Argument[*]' in access path: Method[foo].Argument[*] |
|
||||
| Invalid token 'Argument' is missing its arguments, in access path: Method[foo].Argument |
|
||||
|
||||
@@ -67,32 +67,32 @@ private class StepsFromModel extends ModelInput::SummaryModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
";any;Method[set_value];Argument[0];Argument[self].Field[@value];value",
|
||||
";any;Method[get_value];Argument[self].Field[@value];ReturnValue;value",
|
||||
";;Member[Foo].Method[firstArg];Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[secondArg];Argument[1];ReturnValue;taint",
|
||||
";;Member[Foo].Method[onlyWithoutBlock].WithoutBlock;Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[onlyWithBlock].WithBlock;Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[blockArg].Argument[block].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[namedArg];Argument[foo:];ReturnValue;taint",
|
||||
";;Member[Foo].Method[anyArg];Argument[any];ReturnValue;taint",
|
||||
";;Member[Foo].Method[anyNamedArg];Argument[any-named];ReturnValue;taint",
|
||||
";;Member[Foo].Method[anyPositionFromOne];Argument[1..];ReturnValue;taint",
|
||||
";;Member[Foo].Method[intoNamedCallback];Argument[0];Argument[foo:].Parameter[0];taint",
|
||||
";;Member[Foo].Method[intoNamedParameter];Argument[0];Argument[0].Parameter[foo:];taint",
|
||||
";;Member[Foo].Method[startInNamedCallback].Argument[foo:].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Method[startInNamedParameter].Argument[0].Parameter[foo:].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
";;Member[Foo].Instance.Method[flowToAnyArg];Argument[0];Argument[any];taint",
|
||||
";;Member[Foo].Instance.Method[flowToSelf];Argument[0];Argument[self];taint",
|
||||
";any;Method[matchedByName];Argument[0];ReturnValue;taint",
|
||||
";any;Method[matchedByNameRcv];Argument[self];ReturnValue;taint",
|
||||
";any;Method[withElementOne];Argument[self].WithElement[1];ReturnValue;value",
|
||||
";any;Method[withExactlyElementOne];Argument[self].WithElement[1!];ReturnValue;value",
|
||||
";any;Method[withoutElementOne];Argument[self].WithoutElement[1];Argument[self];value",
|
||||
";any;Method[withoutExactlyElementOne];Argument[self].WithoutElement[1!];Argument[self];value",
|
||||
";any;Method[readElementOne];Argument[self].Element[1];ReturnValue;value",
|
||||
";any;Method[readExactlyElementOne];Argument[self].Element[1!];ReturnValue;value",
|
||||
";any;Method[withoutElementOneAndTwo];Argument[self].WithoutElement[1].WithoutElement[2].WithElement[any];Argument[self];value",
|
||||
"any;Method[set_value];Argument[0];Argument[self].Field[@value];value",
|
||||
"any;Method[get_value];Argument[self].Field[@value];ReturnValue;value",
|
||||
"Foo!;Method[firstArg];Argument[0];ReturnValue;taint",
|
||||
"Foo!;Method[secondArg];Argument[1];ReturnValue;taint",
|
||||
"Foo!;Method[onlyWithoutBlock].WithoutBlock;Argument[0];ReturnValue;taint",
|
||||
"Foo!;Method[onlyWithBlock].WithBlock;Argument[0];ReturnValue;taint",
|
||||
"Foo!;Method[blockArg].Argument[block].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
"Foo!;Method[namedArg];Argument[foo:];ReturnValue;taint",
|
||||
"Foo!;Method[anyArg];Argument[any];ReturnValue;taint",
|
||||
"Foo!;Method[anyNamedArg];Argument[any-named];ReturnValue;taint",
|
||||
"Foo!;Method[anyPositionFromOne];Argument[1..];ReturnValue;taint",
|
||||
"Foo!;Method[intoNamedCallback];Argument[0];Argument[foo:].Parameter[0];taint",
|
||||
"Foo!;Method[intoNamedParameter];Argument[0];Argument[0].Parameter[foo:];taint",
|
||||
"Foo!;Method[startInNamedCallback].Argument[foo:].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
"Foo!;Method[startInNamedParameter].Argument[0].Parameter[foo:].Method[preserveTaint];Argument[0];ReturnValue;taint",
|
||||
"Foo;Method[flowToAnyArg];Argument[0];Argument[any];taint",
|
||||
"Foo;Method[flowToSelf];Argument[0];Argument[self];taint",
|
||||
"any;Method[matchedByName];Argument[0];ReturnValue;taint",
|
||||
"any;Method[matchedByNameRcv];Argument[self];ReturnValue;taint",
|
||||
"any;Method[withElementOne];Argument[self].WithElement[1];ReturnValue;value",
|
||||
"any;Method[withExactlyElementOne];Argument[self].WithElement[1!];ReturnValue;value",
|
||||
"any;Method[withoutElementOne];Argument[self].WithoutElement[1];Argument[self];value",
|
||||
"any;Method[withoutExactlyElementOne];Argument[self].WithoutElement[1!];Argument[self];value",
|
||||
"any;Method[readElementOne];Argument[self].Element[1];ReturnValue;value",
|
||||
"any;Method[readExactlyElementOne];Argument[self].Element[1!];ReturnValue;value",
|
||||
"any;Method[withoutElementOneAndTwo];Argument[self].WithoutElement[1].WithoutElement[2].WithElement[any];Argument[self];value",
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -101,23 +101,21 @@ private class TypeFromModel extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"test;FooOrBar;;;Member[Foo].Instance", //
|
||||
"test;FooOrBar;;;Member[Bar].Instance", //
|
||||
"test;FooOrBar;test;FooOrBar;Method[next].ReturnValue",
|
||||
"~FooOrBar;Foo;", //
|
||||
"~FooOrBar;Bar;", //
|
||||
"~FooOrBar;~FooOrBar;Method[next].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeFromCodeQL extends ModelInput::TypeModel {
|
||||
override DataFlow::Node getASource(string package, string type) {
|
||||
package = "test" and
|
||||
type = "FooOrBar" and
|
||||
override DataFlow::Node getASource(string type) {
|
||||
type = "~FooOrBar" and
|
||||
result.getConstantValue().getString() = "magic_string"
|
||||
}
|
||||
|
||||
override API::Node getAnApiNode(string package, string type) {
|
||||
package = "test" and
|
||||
type = "FooOrBar" and
|
||||
override API::Node getAnApiNode(string type) {
|
||||
type = "~FooOrBar" and
|
||||
result = API::getTopLevelMember("Alias").getMember(["Foo", "Bar"])
|
||||
}
|
||||
}
|
||||
@@ -126,13 +124,13 @@ private class InvalidTypeModel extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"test;TooManyColumns;;;Member[Foo].Instance;too;many;columns", //
|
||||
"test;TooFewColumns", //
|
||||
"test;X;test;Y;Method[foo].Arg[0]", //
|
||||
"test;X;test;Y;Method[foo].Argument[0-1]", //
|
||||
"test;X;test;Y;Method[foo].Argument[*]", //
|
||||
"test;X;test;Y;Method[foo].Argument", //
|
||||
"test;X;test;Y;Method[foo].Member", //
|
||||
"TooManyColumns;;Member[Foo].Instance;too;many;columns", //
|
||||
"TooFewColumns", //
|
||||
"Foo;Foo;Method[foo].Arg[0]", //
|
||||
"Foo;Foo;Method[foo].Argument[0-1]", //
|
||||
"Foo;Foo;Method[foo].Argument[*]", //
|
||||
"Foo;Foo;Method[foo].Argument", //
|
||||
"Foo;Foo;Method[foo].Member", //
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -141,12 +139,12 @@ private class SinkFromModel extends ModelInput::SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
"test;FooOrBar;Method[method].Argument[0];test-sink", //
|
||||
";;Member[Foo].Method[sinkAnyArg].Argument[any];test-sink", //
|
||||
";;Member[Foo].Method[sinkAnyNamedArg].Argument[any-named];test-sink", //
|
||||
";;Member[Foo].Method[getSinks].ReturnValue.Element[any].Method[mySink].Argument[0];test-sink", //
|
||||
";;Member[Foo].Method[arraySink].Argument[0].Element[any];test-sink", //
|
||||
";;Member[Foo].Method[secondArrayElementIsSink].Argument[0].Element[1];test-sink", //
|
||||
"~FooOrBar;Method[method].Argument[0];test-sink", //
|
||||
"Foo!;Method[sinkAnyArg].Argument[any];test-sink", //
|
||||
"Foo!;Method[sinkAnyNamedArg].Argument[any-named];test-sink", //
|
||||
"Foo!;Method[getSinks].ReturnValue.Element[any].Method[mySink].Argument[0];test-sink", //
|
||||
"Foo!;Method[arraySink].Argument[0].Element[any];test-sink", //
|
||||
"Foo!;Method[secondArrayElementIsSink].Argument[0].Element[1];test-sink", //
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
actionControllerControllerClasses
|
||||
| action_controller/input_access.rb:1:1:50:3 | UsersController |
|
||||
| action_controller/params_flow.rb:1:1:162:3 | MyController |
|
||||
| action_controller/params_flow.rb:170:1:178:3 | Subclass |
|
||||
| active_record/ActiveRecord.rb:23:1:39:3 | FooController |
|
||||
| active_record/ActiveRecord.rb:41:1:64:3 | BarController |
|
||||
| active_record/ActiveRecord.rb:66:1:98:3 | BazController |
|
||||
| active_record/ActiveRecord.rb:100:1:108:3 | AnnotatedController |
|
||||
| active_storage/active_storage.rb:39:1:45:3 | PostsController2 |
|
||||
| app/controllers/comments_controller.rb:1:1:40:3 | CommentsController |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController |
|
||||
| app/controllers/photos_controller.rb:1:1:4:3 | PhotosController |
|
||||
| app/controllers/posts_controller.rb:1:1:10:3 | PostsController |
|
||||
| app/controllers/tags_controller.rb:1:1:2:3 | TagsController |
|
||||
| app/controllers/users/notifications_controller.rb:2:3:5:5 | Users::NotificationsController |
|
||||
actionControllerActionMethods
|
||||
| action_controller/input_access.rb:2:3:49:5 | index |
|
||||
| action_controller/params_flow.rb:2:3:4:5 | m1 |
|
||||
| action_controller/params_flow.rb:6:3:8:5 | m2 |
|
||||
| action_controller/params_flow.rb:10:3:12:5 | m2 |
|
||||
| action_controller/params_flow.rb:14:3:16:5 | m3 |
|
||||
| action_controller/params_flow.rb:18:3:20:5 | m4 |
|
||||
| action_controller/params_flow.rb:22:3:24:5 | m5 |
|
||||
| action_controller/params_flow.rb:26:3:28:5 | m6 |
|
||||
| action_controller/params_flow.rb:30:3:32:5 | m7 |
|
||||
| action_controller/params_flow.rb:34:3:36:5 | m8 |
|
||||
| action_controller/params_flow.rb:38:3:40:5 | m9 |
|
||||
| action_controller/params_flow.rb:42:3:44:5 | m10 |
|
||||
| action_controller/params_flow.rb:46:3:48:5 | m11 |
|
||||
| action_controller/params_flow.rb:50:3:52:5 | m12 |
|
||||
| action_controller/params_flow.rb:54:3:56:5 | m13 |
|
||||
| action_controller/params_flow.rb:58:3:60:5 | m14 |
|
||||
| action_controller/params_flow.rb:62:3:64:5 | m15 |
|
||||
| action_controller/params_flow.rb:66:3:68:5 | m16 |
|
||||
| action_controller/params_flow.rb:70:3:72:5 | m17 |
|
||||
| action_controller/params_flow.rb:74:3:76:5 | m18 |
|
||||
| action_controller/params_flow.rb:78:3:80:5 | m19 |
|
||||
| action_controller/params_flow.rb:82:3:84:5 | m20 |
|
||||
| action_controller/params_flow.rb:86:3:88:5 | m21 |
|
||||
| action_controller/params_flow.rb:90:3:92:5 | m22 |
|
||||
| action_controller/params_flow.rb:94:3:96:5 | m23 |
|
||||
| action_controller/params_flow.rb:98:3:100:5 | m24 |
|
||||
| action_controller/params_flow.rb:102:3:104:5 | m25 |
|
||||
| action_controller/params_flow.rb:106:3:108:5 | m26 |
|
||||
| action_controller/params_flow.rb:110:3:113:5 | m27 |
|
||||
| action_controller/params_flow.rb:115:3:118:5 | m28 |
|
||||
| action_controller/params_flow.rb:120:3:123:5 | m29 |
|
||||
| action_controller/params_flow.rb:125:3:132:5 | m30 |
|
||||
| action_controller/params_flow.rb:134:3:141:5 | m31 |
|
||||
| action_controller/params_flow.rb:143:3:150:5 | m32 |
|
||||
| action_controller/params_flow.rb:152:3:159:5 | m33 |
|
||||
| action_controller/params_flow.rb:165:3:167:5 | m34 |
|
||||
| action_controller/params_flow.rb:171:3:173:5 | m35 |
|
||||
| active_record/ActiveRecord.rb:27:3:38:5 | some_request_handler |
|
||||
| active_record/ActiveRecord.rb:42:3:47:5 | some_other_request_handler |
|
||||
| active_record/ActiveRecord.rb:49:3:63:5 | safe_paths |
|
||||
| active_record/ActiveRecord.rb:67:3:69:5 | yet_another_handler |
|
||||
| active_record/ActiveRecord.rb:71:3:73:5 | create1 |
|
||||
| active_record/ActiveRecord.rb:75:3:77:5 | create2 |
|
||||
| active_record/ActiveRecord.rb:79:3:81:5 | create3 |
|
||||
| active_record/ActiveRecord.rb:83:3:85:5 | create4 |
|
||||
| active_record/ActiveRecord.rb:87:3:89:5 | update1 |
|
||||
| active_record/ActiveRecord.rb:91:3:93:5 | update2 |
|
||||
| active_record/ActiveRecord.rb:95:3:97:5 | update3 |
|
||||
| active_record/ActiveRecord.rb:101:3:103:5 | index |
|
||||
| active_record/ActiveRecord.rb:105:3:107:5 | unsafe_action |
|
||||
| active_storage/active_storage.rb:40:3:44:5 | create |
|
||||
| app/controllers/comments_controller.rb:2:3:36:5 | index |
|
||||
| app/controllers/comments_controller.rb:38:3:39:5 | show |
|
||||
| app/controllers/foo/bars_controller.rb:5:3:7:5 | index |
|
||||
| app/controllers/foo/bars_controller.rb:9:3:18:5 | show_debug |
|
||||
| app/controllers/foo/bars_controller.rb:20:3:24:5 | show |
|
||||
| app/controllers/foo/bars_controller.rb:26:3:28:5 | go_back |
|
||||
| app/controllers/foo/bars_controller.rb:30:3:32:5 | go_back_2 |
|
||||
| app/controllers/foo/bars_controller.rb:34:3:39:5 | show_2 |
|
||||
| app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/controllers/posts_controller.rb:2:3:3:5 | index |
|
||||
| app/controllers/posts_controller.rb:5:3:6:5 | show |
|
||||
| app/controllers/posts_controller.rb:8:3:9:5 | upvote |
|
||||
| app/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
|
||||
paramsCalls
|
||||
| action_controller/params_flow.rb:3:10:3:15 | call to params |
|
||||
| action_controller/params_flow.rb:7:10:7:15 | call to params |
|
||||
| action_controller/params_flow.rb:11:10:11:15 | call to params |
|
||||
| action_controller/params_flow.rb:15:10:15:15 | call to params |
|
||||
| action_controller/params_flow.rb:19:10:19:15 | call to params |
|
||||
| action_controller/params_flow.rb:23:10:23:15 | call to params |
|
||||
| action_controller/params_flow.rb:27:10:27:15 | call to params |
|
||||
| action_controller/params_flow.rb:31:10:31:15 | call to params |
|
||||
| action_controller/params_flow.rb:35:10:35:15 | call to params |
|
||||
| action_controller/params_flow.rb:39:10:39:15 | call to params |
|
||||
| action_controller/params_flow.rb:43:10:43:15 | call to params |
|
||||
| action_controller/params_flow.rb:47:10:47:15 | call to params |
|
||||
| action_controller/params_flow.rb:51:10:51:15 | call to params |
|
||||
| action_controller/params_flow.rb:55:10:55:15 | call to params |
|
||||
| action_controller/params_flow.rb:59:10:59:15 | call to params |
|
||||
| action_controller/params_flow.rb:63:10:63:15 | call to params |
|
||||
| action_controller/params_flow.rb:67:10:67:15 | call to params |
|
||||
| action_controller/params_flow.rb:71:10:71:15 | call to params |
|
||||
| action_controller/params_flow.rb:75:10:75:15 | call to params |
|
||||
| action_controller/params_flow.rb:79:10:79:15 | call to params |
|
||||
| action_controller/params_flow.rb:83:10:83:15 | call to params |
|
||||
| action_controller/params_flow.rb:87:10:87:15 | call to params |
|
||||
| action_controller/params_flow.rb:91:10:91:15 | call to params |
|
||||
| action_controller/params_flow.rb:95:10:95:15 | call to params |
|
||||
| action_controller/params_flow.rb:99:10:99:15 | call to params |
|
||||
| action_controller/params_flow.rb:103:10:103:15 | call to params |
|
||||
| action_controller/params_flow.rb:107:10:107:15 | call to params |
|
||||
| action_controller/params_flow.rb:111:10:111:15 | call to params |
|
||||
| action_controller/params_flow.rb:112:23:112:28 | call to params |
|
||||
| action_controller/params_flow.rb:116:10:116:15 | call to params |
|
||||
| action_controller/params_flow.rb:117:31:117:36 | call to params |
|
||||
| action_controller/params_flow.rb:121:10:121:15 | call to params |
|
||||
| action_controller/params_flow.rb:122:31:122:36 | call to params |
|
||||
| action_controller/params_flow.rb:126:10:126:15 | call to params |
|
||||
| action_controller/params_flow.rb:127:24:127:29 | call to params |
|
||||
| action_controller/params_flow.rb:130:14:130:19 | call to params |
|
||||
| action_controller/params_flow.rb:135:10:135:15 | call to params |
|
||||
| action_controller/params_flow.rb:136:32:136:37 | call to params |
|
||||
| action_controller/params_flow.rb:139:22:139:27 | call to params |
|
||||
| action_controller/params_flow.rb:144:10:144:15 | call to params |
|
||||
| action_controller/params_flow.rb:145:32:145:37 | call to params |
|
||||
| action_controller/params_flow.rb:148:22:148:27 | call to params |
|
||||
| action_controller/params_flow.rb:153:10:153:15 | call to params |
|
||||
| action_controller/params_flow.rb:154:32:154:37 | call to params |
|
||||
| action_controller/params_flow.rb:157:22:157:27 | call to params |
|
||||
| action_controller/params_flow.rb:166:10:166:15 | call to params |
|
||||
| action_controller/params_flow.rb:172:10:172:15 | call to params |
|
||||
| action_controller/params_flow.rb:176:10:176:15 | call to params |
|
||||
| action_mailer/mailer.rb:3:10:3:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
|
||||
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
|
||||
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
|
||||
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
|
||||
| active_record/ActiveRecord.rb:88:21:88:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:27:92:32 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:52:92:57 | call to params |
|
||||
| active_record/ActiveRecord.rb:96:28:96:33 | call to params |
|
||||
| active_record/ActiveRecord.rb:96:53:96:58 | call to params |
|
||||
| active_record/ActiveRecord.rb:106:59:106:64 | call to params |
|
||||
| active_storage/active_storage.rb:41:21:41:26 | call to params |
|
||||
| active_storage/active_storage.rb:42:24:42:29 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params |
|
||||
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
|
||||
paramsSources
|
||||
| action_controller/params_flow.rb:3:10:3:15 | call to params |
|
||||
| action_controller/params_flow.rb:7:10:7:15 | call to params |
|
||||
| action_controller/params_flow.rb:11:10:11:15 | call to params |
|
||||
| action_controller/params_flow.rb:15:10:15:15 | call to params |
|
||||
| action_controller/params_flow.rb:19:10:19:15 | call to params |
|
||||
| action_controller/params_flow.rb:23:10:23:15 | call to params |
|
||||
| action_controller/params_flow.rb:27:10:27:15 | call to params |
|
||||
| action_controller/params_flow.rb:31:10:31:15 | call to params |
|
||||
| action_controller/params_flow.rb:35:10:35:15 | call to params |
|
||||
| action_controller/params_flow.rb:39:10:39:15 | call to params |
|
||||
| action_controller/params_flow.rb:43:10:43:15 | call to params |
|
||||
| action_controller/params_flow.rb:47:10:47:15 | call to params |
|
||||
| action_controller/params_flow.rb:51:10:51:15 | call to params |
|
||||
| action_controller/params_flow.rb:55:10:55:15 | call to params |
|
||||
| action_controller/params_flow.rb:59:10:59:15 | call to params |
|
||||
| action_controller/params_flow.rb:63:10:63:15 | call to params |
|
||||
| action_controller/params_flow.rb:67:10:67:15 | call to params |
|
||||
| action_controller/params_flow.rb:71:10:71:15 | call to params |
|
||||
| action_controller/params_flow.rb:75:10:75:15 | call to params |
|
||||
| action_controller/params_flow.rb:79:10:79:15 | call to params |
|
||||
| action_controller/params_flow.rb:83:10:83:15 | call to params |
|
||||
| action_controller/params_flow.rb:87:10:87:15 | call to params |
|
||||
| action_controller/params_flow.rb:91:10:91:15 | call to params |
|
||||
| action_controller/params_flow.rb:95:10:95:15 | call to params |
|
||||
| action_controller/params_flow.rb:99:10:99:15 | call to params |
|
||||
| action_controller/params_flow.rb:103:10:103:15 | call to params |
|
||||
| action_controller/params_flow.rb:107:10:107:15 | call to params |
|
||||
| action_controller/params_flow.rb:111:10:111:15 | call to params |
|
||||
| action_controller/params_flow.rb:112:23:112:28 | call to params |
|
||||
| action_controller/params_flow.rb:116:10:116:15 | call to params |
|
||||
| action_controller/params_flow.rb:117:31:117:36 | call to params |
|
||||
| action_controller/params_flow.rb:121:10:121:15 | call to params |
|
||||
| action_controller/params_flow.rb:122:31:122:36 | call to params |
|
||||
| action_controller/params_flow.rb:126:10:126:15 | call to params |
|
||||
| action_controller/params_flow.rb:127:24:127:29 | call to params |
|
||||
| action_controller/params_flow.rb:130:14:130:19 | call to params |
|
||||
| action_controller/params_flow.rb:135:10:135:15 | call to params |
|
||||
| action_controller/params_flow.rb:136:32:136:37 | call to params |
|
||||
| action_controller/params_flow.rb:139:22:139:27 | call to params |
|
||||
| action_controller/params_flow.rb:144:10:144:15 | call to params |
|
||||
| action_controller/params_flow.rb:145:32:145:37 | call to params |
|
||||
| action_controller/params_flow.rb:148:22:148:27 | call to params |
|
||||
| action_controller/params_flow.rb:153:10:153:15 | call to params |
|
||||
| action_controller/params_flow.rb:154:32:154:37 | call to params |
|
||||
| action_controller/params_flow.rb:157:22:157:27 | call to params |
|
||||
| action_controller/params_flow.rb:166:10:166:15 | call to params |
|
||||
| action_controller/params_flow.rb:172:10:172:15 | call to params |
|
||||
| action_controller/params_flow.rb:176:10:176:15 | call to params |
|
||||
| action_mailer/mailer.rb:3:10:3:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
|
||||
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
|
||||
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
|
||||
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
|
||||
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
|
||||
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
|
||||
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
|
||||
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
|
||||
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
|
||||
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
|
||||
| active_record/ActiveRecord.rb:88:21:88:26 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:27:92:32 | call to params |
|
||||
| active_record/ActiveRecord.rb:92:52:92:57 | call to params |
|
||||
| active_record/ActiveRecord.rb:96:28:96:33 | call to params |
|
||||
| active_record/ActiveRecord.rb:96:53:96:58 | call to params |
|
||||
| active_record/ActiveRecord.rb:106:59:106:64 | call to params |
|
||||
| active_storage/active_storage.rb:41:21:41:26 | call to params |
|
||||
| active_storage/active_storage.rb:42:24:42:29 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params |
|
||||
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
|
||||
httpInputAccesses
|
||||
| action_controller/input_access.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
|
||||
| action_controller/input_access.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
|
||||
| action_controller/input_access.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
|
||||
| action_controller/input_access.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
|
||||
| action_controller/input_access.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
|
||||
| action_controller/input_access.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
|
||||
| action_controller/input_access.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
|
||||
| action_controller/input_access.rb:11:5:11:25 | call to authorization | ActionDispatch::Request#authorization |
|
||||
| action_controller/input_access.rb:12:5:12:23 | call to script_name | ActionDispatch::Request#script_name |
|
||||
| action_controller/input_access.rb:13:5:13:21 | call to path_info | ActionDispatch::Request#path_info |
|
||||
| action_controller/input_access.rb:14:5:14:22 | call to user_agent | ActionDispatch::Request#user_agent |
|
||||
| action_controller/input_access.rb:15:5:15:19 | call to referer | ActionDispatch::Request#referer |
|
||||
| action_controller/input_access.rb:16:5:16:20 | call to referrer | ActionDispatch::Request#referrer |
|
||||
| action_controller/input_access.rb:17:5:17:26 | call to host_authority | ActionDispatch::Request#host_authority |
|
||||
| action_controller/input_access.rb:18:5:18:24 | call to content_type | ActionDispatch::Request#content_type |
|
||||
| action_controller/input_access.rb:19:5:19:16 | call to host | ActionDispatch::Request#host |
|
||||
| action_controller/input_access.rb:20:5:20:20 | call to hostname | ActionDispatch::Request#hostname |
|
||||
| action_controller/input_access.rb:21:5:21:27 | call to accept_encoding | ActionDispatch::Request#accept_encoding |
|
||||
| action_controller/input_access.rb:22:5:22:27 | call to accept_language | ActionDispatch::Request#accept_language |
|
||||
| action_controller/input_access.rb:23:5:23:25 | call to if_none_match | ActionDispatch::Request#if_none_match |
|
||||
| action_controller/input_access.rb:24:5:24:31 | call to if_none_match_etags | ActionDispatch::Request#if_none_match_etags |
|
||||
| action_controller/input_access.rb:25:5:25:29 | call to content_mime_type | ActionDispatch::Request#content_mime_type |
|
||||
| action_controller/input_access.rb:27:5:27:21 | call to authority | ActionDispatch::Request#authority |
|
||||
| action_controller/input_access.rb:28:5:28:16 | call to host | ActionDispatch::Request#host |
|
||||
| action_controller/input_access.rb:29:5:29:26 | call to host_authority | ActionDispatch::Request#host_authority |
|
||||
| action_controller/input_access.rb:30:5:30:26 | call to host_with_port | ActionDispatch::Request#host_with_port |
|
||||
| action_controller/input_access.rb:31:5:31:20 | call to hostname | ActionDispatch::Request#hostname |
|
||||
| action_controller/input_access.rb:32:5:32:25 | call to forwarded_for | ActionDispatch::Request#forwarded_for |
|
||||
| action_controller/input_access.rb:33:5:33:26 | call to forwarded_host | ActionDispatch::Request#forwarded_host |
|
||||
| action_controller/input_access.rb:34:5:34:16 | call to port | ActionDispatch::Request#port |
|
||||
| action_controller/input_access.rb:35:5:35:26 | call to forwarded_port | ActionDispatch::Request#forwarded_port |
|
||||
| action_controller/input_access.rb:37:5:37:22 | call to media_type | ActionDispatch::Request#media_type |
|
||||
| action_controller/input_access.rb:38:5:38:29 | call to media_type_params | ActionDispatch::Request#media_type_params |
|
||||
| action_controller/input_access.rb:39:5:39:27 | call to content_charset | ActionDispatch::Request#content_charset |
|
||||
| action_controller/input_access.rb:40:5:40:20 | call to base_url | ActionDispatch::Request#base_url |
|
||||
| action_controller/input_access.rb:42:5:42:16 | call to body | ActionDispatch::Request#body |
|
||||
| action_controller/input_access.rb:43:5:43:20 | call to raw_post | ActionDispatch::Request#raw_post |
|
||||
| action_controller/input_access.rb:45:5:45:30 | ...[...] | ActionDispatch::Request#env[] |
|
||||
| action_controller/input_access.rb:47:5:47:39 | ...[...] | ActionDispatch::Request#env[] |
|
||||
| action_controller/params_flow.rb:3:10:3:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:7:10:7:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:11:10:11:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:15:10:15:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:19:10:19:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:23:10:23:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:27:10:27:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:31:10:31:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:35:10:35:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:39:10:39:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:43:10:43:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:47:10:47:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:51:10:51:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:55:10:55:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:59:10:59:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:63:10:63:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:67:10:67:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:71:10:71:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:75:10:75:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:79:10:79:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:83:10:83:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:87:10:87:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:91:10:91:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:95:10:95:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:99:10:99:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:103:10:103:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:107:10:107:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:111:10:111:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:112:23:112:28 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:116:10:116:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:117:31:117:36 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:121:10:121:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:122:31:122:36 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:126:10:126:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:127:24:127:29 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:130:14:130:19 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:135:10:135:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:136:32:136:37 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:139:22:139:27 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:144:10:144:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:145:32:145:37 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:148:22:148:27 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:153:10:153:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:154:32:154:37 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:157:22:157:27 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:166:10:166:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:172:10:172:15 | call to params | ActionController::Metal#params |
|
||||
| action_controller/params_flow.rb:176:10:176:15 | call to params | ActionController::Metal#params |
|
||||
| action_mailer/mailer.rb:3:10:3:15 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:28:30:28:35 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:29:29:29:34 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:30:31:30:36 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:32:21:32:26 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:34:34:34:39 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:35:23:35:28 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:35:38:35:43 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:43:10:43:15 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:50:11:50:16 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:54:12:54:17 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:59:12:59:17 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:62:15:62:20 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:68:21:68:26 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:72:18:72:23 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:76:24:76:29 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:76:49:76:54 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:80:25:80:30 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:80:50:80:55 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:88:21:88:26 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:92:27:92:32 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:92:52:92:57 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:96:28:96:33 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:96:53:96:58 | call to params | ActionController::Metal#params |
|
||||
| active_record/ActiveRecord.rb:106:59:106:64 | call to params | ActionController::Metal#params |
|
||||
| active_storage/active_storage.rb:41:21:41:26 | call to params | ActionController::Metal#params |
|
||||
| active_storage/active_storage.rb:42:24:42:29 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/comments_controller.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
|
||||
| app/controllers/comments_controller.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
|
||||
| app/controllers/comments_controller.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
|
||||
| app/controllers/comments_controller.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
|
||||
| app/controllers/comments_controller.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
|
||||
| app/controllers/comments_controller.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
|
||||
| app/controllers/comments_controller.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
|
||||
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies | ActionController::Metal#cookies |
|
||||
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params | ActionController::Metal#params |
|
||||
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params | ActionController::Metal#params |
|
||||
| app/graphql/mutations/dummy.rb:5:24:5:25 | id | GraphQL RoutedParameter |
|
||||
| app/graphql/mutations/dummy.rb:9:17:9:25 | something | GraphQL RoutedParameter |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:6:24:6:25 | id | GraphQL RoutedParameter |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:10:17:10:25 | something | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:10:18:10:23 | number | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:18:23:18:33 | blah_number | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:27:20:27:25 | **args | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:36:34:36:37 | arg1 | GraphQL RoutedParameter |
|
||||
| app/graphql/types/query_type.rb:36:41:36:46 | **rest | GraphQL RoutedParameter |
|
||||
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params | ActionController::Metal#params |
|
||||
cookiesCalls
|
||||
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
|
||||
cookiesSources
|
||||
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
|
||||
redirectToCalls
|
||||
| app/controllers/foo/bars_controller.rb:17:5:17:30 | call to redirect_to |
|
||||
| app/controllers/foo/bars_controller.rb:27:5:27:39 | call to redirect_back_or_to |
|
||||
| app/controllers/foo/bars_controller.rb:31:5:31:56 | call to redirect_back |
|
||||
actionControllerHelperMethods
|
||||
getAssociatedControllerClasses
|
||||
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
|
||||
controllerTemplateFiles
|
||||
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
|
||||
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
|
||||
headerWriteAccesses
|
||||
| app/controllers/comments_controller.rb:15:5:15:35 | call to []= | content-type | app/controllers/comments_controller.rb:15:39:15:49 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:16:5:16:46 | call to set_header | content-length | app/controllers/comments_controller.rb:16:43:16:45 | 100 |
|
||||
| app/controllers/comments_controller.rb:17:5:17:39 | call to []= | x-custom-header | app/controllers/comments_controller.rb:17:43:17:46 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:18:5:18:39 | call to []= | x-another-custom-header | app/controllers/comments_controller.rb:18:43:18:47 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:19:5:19:49 | call to add_header | x-yet-another | app/controllers/comments_controller.rb:19:42:19:49 | "indeed" |
|
||||
| app/controllers/comments_controller.rb:25:5:25:21 | call to location= | location | app/controllers/comments_controller.rb:25:25:25:36 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:26:5:26:26 | call to cache_control= | cache-control | app/controllers/comments_controller.rb:26:30:26:36 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:27:5:27:27 | call to _cache_control= | cache-control | app/controllers/comments_controller.rb:27:31:27:37 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:28:5:28:17 | call to etag= | etag | app/controllers/comments_controller.rb:28:21:28:27 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:29:5:29:20 | call to charset= | content-type | app/controllers/comments_controller.rb:29:24:29:30 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:30:5:30:25 | call to content_type= | content-type | app/controllers/comments_controller.rb:30:29:30:35 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:32:5:32:17 | call to date= | date | app/controllers/comments_controller.rb:32:21:32:30 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:33:5:33:26 | call to last_modified= | last-modified | app/controllers/comments_controller.rb:33:30:33:43 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:34:5:34:22 | call to weak_etag= | etag | app/controllers/comments_controller.rb:34:26:34:32 | ... = ... |
|
||||
| app/controllers/comments_controller.rb:35:5:35:24 | call to strong_etag= | etag | app/controllers/comments_controller.rb:35:28:35:34 | ... = ... |
|
||||
@@ -34,15 +34,26 @@ actionDispatchRoutes
|
||||
| app/config/routes.rb:49:5:49:95 | call to delete | delete | users/:user/notifications | users/notifications | destroy |
|
||||
| app/config/routes.rb:50:5:50:94 | call to post | post | users/:user/notifications/:notification_id/mark_as_read | users/notifications | mark_as_read |
|
||||
actionDispatchControllerMethods
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | action_controller/controllers/posts_controller.rb:2:3:3:5 | index |
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | action_controller/controllers/posts_controller.rb:5:3:6:5 | show |
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:2:3:3:5 | index |
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:5:3:6:5 | show |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | action_controller/controllers/comments_controller.rb:2:3:36:5 | index |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | action_controller/controllers/comments_controller.rb:38:3:44:5 | show |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | action_controller/controllers/comments_controller.rb:50:3:52:5 | destroy |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:36:5 | index |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:38:3:39:5 | show |
|
||||
| app/config/routes.rb:7:5:7:37 | call to post | action_controller/controllers/posts_controller.rb:8:3:9:5 | upvote |
|
||||
| app/config/routes.rb:7:5:7:37 | call to post | app/controllers/posts_controller.rb:8:3:9:5 | upvote |
|
||||
| app/config/routes.rb:27:3:27:48 | call to match | action_controller/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:27:3:27:48 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:28:3:28:50 | call to match | action_controller/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:28:3:28:50 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:29:3:29:69 | call to match | action_controller/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:29:3:29:69 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:30:3:30:50 | call to match | action_controller/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:30:3:30:50 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:50:5:50:94 | call to post | action_controller/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
|
||||
| app/config/routes.rb:50:5:50:94 | call to post | app/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
|
||||
underscore
|
||||
| Foo | foo |
|
||||
|
||||
@@ -29,9 +29,7 @@ query predicate underscore(string input, string output) {
|
||||
]
|
||||
}
|
||||
|
||||
query predicate mimeTypeInstances(API::Node n) {
|
||||
n = ModelOutput::getATypeNode("actiondispatch", "Mime::Type")
|
||||
}
|
||||
query predicate mimeTypeInstances(API::Node n) { n = ModelOutput::getATypeNode("Mime::Type") }
|
||||
|
||||
query predicate mimeTypeMatchRegExpInterpretations(
|
||||
ActionDispatch::MimeTypeMatchRegExpInterpretation s
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user